@@ -40,6 +40,7 @@ pub enum SplitMode {
40
40
DetachAfter ,
41
41
Discard ,
42
42
InsertAfter ,
43
+ InsertBefore ,
43
44
}
44
45
45
46
/// Split a commit and restack its descendants.
@@ -123,6 +124,26 @@ pub fn split(
123
124
}
124
125
} ;
125
126
127
+ //
128
+ // a-t-b
129
+ //
130
+ // a-r-x-b (default)
131
+ // a-x-r-b (before)
132
+ // a-r-b (detach)
133
+ // \-x
134
+ // a-r-b (discard)
135
+ //
136
+ // default: x == t tree, x is t with changes removed
137
+ // before: r == t tree, e is a with changes added
138
+ // detach: (same as default, different rebase)
139
+ // discard: (same as default, w/o any rebase)
140
+ //
141
+ // below:
142
+ // a => parent
143
+ // t => target
144
+ // r => remainder
145
+ // x => extracted
146
+
126
147
let target_commit = repo. find_commit_or_fail ( target_oid) ?;
127
148
let target_tree = target_commit. get_tree ( ) ?;
128
149
let parent_commits = target_commit. get_parents ( ) ;
@@ -135,11 +156,20 @@ pub fn split(
135
156
( only_parent. get_tree ( ) ?, target_commit. get_tree ( ) ?)
136
157
}
137
158
159
+ // split the commit by adding the changed to a copy of the parent tree,
160
+ // then rebasing the orignal target onto the extracted commit
161
+ ( SplitMode :: InsertBefore , [ only_parent] ) => {
162
+ ( only_parent. get_tree ( ) ?, only_parent. get_tree ( ) ?)
163
+ }
164
+
138
165
// no parent: use an empty tree for comparison
139
166
( SplitMode :: InsertAfter , [ ] ) | ( SplitMode :: Discard , [ ] ) | ( SplitMode :: DetachAfter , [ ] ) => {
140
167
( make_empty_tree ( & repo) ?, target_commit. get_tree ( ) ?)
141
168
}
142
169
170
+ // no parent: add extracted changes to an empty tree
171
+ ( SplitMode :: InsertBefore , [ ] ) => ( make_empty_tree ( & repo) ?, make_empty_tree ( & repo) ?) ,
172
+
143
173
( _, [ ..] ) => {
144
174
writeln ! (
145
175
effects. get_error_stream( ) ,
@@ -220,6 +250,15 @@ pub fn split(
220
250
221
251
let target_entry = target_tree. get_path ( path) ?;
222
252
let temp_tree_oid = match ( parent_entry, target_entry, & split_mode) {
253
+ // added or modified & InsertBefore => add to extracted commit
254
+ ( None , Some ( commit_entry) , SplitMode :: InsertBefore )
255
+ | ( Some ( _) , Some ( commit_entry) , SplitMode :: InsertBefore ) => {
256
+ remainder_tree. add_or_replace ( & repo, path, & commit_entry) ?
257
+ }
258
+
259
+ // removed & InsertBefore => remove from remainder commit
260
+ ( Some ( _) , None , SplitMode :: InsertBefore ) => remainder_tree. remove ( & repo, path) ?,
261
+
223
262
// added => remove from remainder commit
224
263
( None , Some ( _) , SplitMode :: InsertAfter )
225
264
| ( None , Some ( _) , SplitMode :: DetachAfter )
@@ -253,7 +292,11 @@ pub fn split(
253
292
let message = {
254
293
let ( effects, _progress) =
255
294
effects. start_operation ( lib:: core:: effects:: OperationType :: CalculateDiff ) ;
256
- let ( old_tree, new_tree) = ( & remainder_tree, & target_tree) ;
295
+ let ( old_tree, new_tree) = if let SplitMode :: InsertBefore = & split_mode {
296
+ ( & parent_tree, & remainder_tree)
297
+ } else {
298
+ ( & remainder_tree, & target_tree)
299
+ } ;
257
300
let diff = repo. get_diff_between_trees (
258
301
& effects,
259
302
Some ( old_tree) ,
@@ -264,8 +307,23 @@ pub fn split(
264
307
summarize_diff_for_temporary_commit ( & diff) ?
265
308
} ;
266
309
267
- let remainder_commit_oid =
268
- target_commit. amend_commit ( None , None , None , None , Some ( & remainder_tree) ) ?;
310
+ // before => split commit is created on parent as "extracted", target is rebased onto split
311
+ // after => target is amended as "split", split is cherry picked onto split as "extracted"
312
+
313
+ // FIXME terminology is wrong here: remainder is correct for "After", but
314
+ // this is the "extracted" commit for InsertBefore
315
+ let remainder_commit_oid = if let SplitMode :: InsertBefore = split_mode {
316
+ repo. create_commit (
317
+ None ,
318
+ & target_commit. get_author ( ) ,
319
+ & target_commit. get_committer ( ) ,
320
+ format ! ( "temp(split): {message}" ) . as_str ( ) ,
321
+ & remainder_tree,
322
+ parent_commits. iter ( ) . collect ( ) ,
323
+ ) ?
324
+ } else {
325
+ target_commit. amend_commit ( None , None , None , None , Some ( & remainder_tree) ) ?
326
+ } ;
269
327
let remainder_commit = repo. find_commit_or_fail ( remainder_commit_oid) ?;
270
328
271
329
if remainder_commit. is_empty ( ) {
@@ -284,8 +342,11 @@ pub fn split(
284
342
new_commit_oid: MaybeZeroOid :: NonZero ( remainder_commit_oid) ,
285
343
} ] ) ?;
286
344
345
+ // FIXME terminology is wrong here: extracted is correct for After and
346
+ // Discard modes, but the extracted commit is not None for InsertBefore:
347
+ // it's just handled in a different way
287
348
let extracted_commit_oid = match split_mode {
288
- SplitMode :: Discard => None ,
349
+ SplitMode :: InsertBefore | SplitMode :: Discard => None ,
289
350
SplitMode :: InsertAfter | SplitMode :: DetachAfter => {
290
351
let extracted_tree = repo. cherry_pick_fast (
291
352
& target_commit,
@@ -300,7 +361,11 @@ pub fn split(
300
361
& target_commit. get_committer ( ) ,
301
362
format ! ( "temp(split): {message}" ) . as_str ( ) ,
302
363
& extracted_tree,
303
- vec ! [ & remainder_commit] ,
364
+ if let SplitMode :: InsertBefore = & split_mode {
365
+ parent_commits. iter ( ) . collect ( )
366
+ } else {
367
+ vec ! [ & remainder_commit]
368
+ } ,
304
369
) ?;
305
370
306
371
// see git-branchless/src/commands/amend.rs:172
@@ -360,45 +425,73 @@ pub fn split(
360
425
struct CleanUp {
361
426
checkout_target : Option < CheckoutTarget > ,
362
427
rewritten_oids : Vec < ( NonZeroOid , MaybeZeroOid ) > ,
428
+ rebase_force_detach : bool ,
363
429
reset_index : bool ,
364
430
}
365
431
366
432
let cleanup = match ( target_state, & split_mode, extracted_commit_oid) {
367
- // branch @ split commit checked out: extend branch to include extracted
368
- // commit; branch will stay checked out w/o any explicit checkout
433
+ // branch @ target commit checked out: extend branch to include
434
+ // extracted commit; branch will stay checked out w/o any explicit
435
+ // checkout
369
436
( TargetState :: CurrentBranch , SplitMode :: InsertAfter , Some ( extracted_commit_oid) ) => {
370
437
CleanUp {
371
438
checkout_target : None ,
372
439
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( extracted_commit_oid) ) ] ,
440
+ rebase_force_detach : false ,
373
441
reset_index : false ,
374
442
}
375
443
}
376
444
445
+ // same as above, but Discard; don't move branches, but do force reset
377
446
( TargetState :: CurrentBranch , SplitMode :: Discard , None ) => CleanUp {
378
447
checkout_target : None ,
379
448
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
449
+ rebase_force_detach : false ,
380
450
reset_index : true ,
381
451
} ,
382
452
383
- // commit to split checked out as detached HEAD, don't extend any
384
- // branches, but explicitly check out the newly split commit
385
- ( TargetState :: DetachedHead , _, _) => CleanUp {
453
+ // same as above, but InsertBefore; do not move branches
454
+ ( TargetState :: CurrentBranch , SplitMode :: InsertBefore , _) => CleanUp {
455
+ checkout_target : None ,
456
+ rewritten_oids : vec ! [ ] ,
457
+ rebase_force_detach : false ,
458
+ reset_index : false ,
459
+ } ,
460
+
461
+ // target checked out as detached HEAD, don't extend any branches, but
462
+ // explicitly check out the newly split commit
463
+ (
464
+ TargetState :: DetachedHead ,
465
+ SplitMode :: InsertAfter | SplitMode :: Discard | SplitMode :: DetachAfter ,
466
+ _,
467
+ ) => CleanUp {
386
468
checkout_target : Some ( CheckoutTarget :: Oid ( remainder_commit_oid) ) ,
387
469
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
470
+ rebase_force_detach : false ,
471
+ reset_index : false ,
472
+ } ,
473
+
474
+ // same as above, but InsertBefore; do not move branches
475
+ ( TargetState :: DetachedHead , SplitMode :: InsertBefore , _) => CleanUp {
476
+ checkout_target : None ,
477
+ rewritten_oids : vec ! [ ] ,
478
+ rebase_force_detach : true ,
388
479
reset_index : false ,
389
480
} ,
390
481
391
482
// some other commit or branch was checked out, default behavior is fine
392
483
( TargetState :: CurrentBranch | TargetState :: Other , _, _) => CleanUp {
393
484
checkout_target : None ,
394
485
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
486
+ rebase_force_detach : false ,
395
487
reset_index : false ,
396
488
} ,
397
489
} ;
398
490
399
491
let CleanUp {
400
492
checkout_target,
401
493
rewritten_oids,
494
+ rebase_force_detach,
402
495
reset_index,
403
496
} = cleanup;
404
497
@@ -444,21 +537,18 @@ pub fn split(
444
537
}
445
538
446
539
let mut builder = RebasePlanBuilder :: new ( & dag, permissions) ;
447
- let children = dag. query_children ( CommitSet :: from ( target_oid) ) ?;
448
- for child in dag. commit_set_to_vec ( & children) ? {
449
- match ( & split_mode, extracted_commit_oid) {
450
- ( _, None ) => builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?,
451
- ( _, Some ( extracted_commit_oid) ) => {
452
- builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
453
- }
454
- }
455
-
456
- match ( & split_mode, extracted_commit_oid) {
457
- ( _, None ) | ( SplitMode :: DetachAfter , Some ( _) ) => {
458
- builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?
459
- }
460
- ( _, Some ( extracted_commit_oid) ) => {
461
- builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
540
+ if let SplitMode :: InsertBefore = & split_mode {
541
+ builder. move_subtree ( target_oid, vec ! [ remainder_commit_oid] ) ?
542
+ } else {
543
+ let children = dag. query_children ( CommitSet :: from ( target_oid) ) ?;
544
+ for child in dag. commit_set_to_vec ( & children) ? {
545
+ match ( & split_mode, extracted_commit_oid) {
546
+ ( _, None ) | ( SplitMode :: DetachAfter , Some ( _) ) => {
547
+ builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?
548
+ }
549
+ ( _, Some ( extracted_commit_oid) ) => {
550
+ builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
551
+ }
462
552
}
463
553
}
464
554
}
@@ -479,7 +569,7 @@ pub fn split(
479
569
resolve_merge_conflicts,
480
570
check_out_commit_options : CheckOutCommitOptions {
481
571
additional_args : Default :: default ( ) ,
482
- force_detach : false ,
572
+ force_detach : rebase_force_detach ,
483
573
reset : false ,
484
574
render_smartlog : false ,
485
575
} ,
0 commit comments