@@ -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 )
@@ -251,7 +290,11 @@ pub fn split(
251
290
. expect ( "should have been found" ) ;
252
291
}
253
292
let message = {
254
- let ( old_tree, new_tree) = ( & remainder_tree, & target_tree) ;
293
+ let ( old_tree, new_tree) = if let SplitMode :: InsertBefore = & split_mode {
294
+ ( & parent_tree, & remainder_tree)
295
+ } else {
296
+ ( & remainder_tree, & target_tree)
297
+ } ;
255
298
let diff = repo. get_diff_between_trees (
256
299
effects,
257
300
Some ( old_tree) ,
@@ -262,8 +305,25 @@ pub fn split(
262
305
summarize_diff_for_temporary_commit ( & diff) ?
263
306
} ;
264
307
265
- let remainder_commit_oid =
266
- target_commit. amend_commit ( None , None , None , None , Some ( & remainder_tree) ) ?;
308
+ // before => split commit is created on parent as "extracted", target is
309
+ // rebased onto split
310
+ // after => target is amended as "split", split is cherry picked onto split
311
+ // as "extracted"
312
+
313
+ // FIXME terminology is wrong here: "remainder" is correct for `After` mode,
314
+ // but this is actually the "extracted" commit for `InsertBefore` mode
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
+ } ;
267
327
let remainder_commit = repo. find_commit_or_fail ( remainder_commit_oid) ?;
268
328
269
329
if remainder_commit. is_empty ( ) {
@@ -282,8 +342,11 @@ pub fn split(
282
342
new_commit_oid: MaybeZeroOid :: NonZero ( remainder_commit_oid) ,
283
343
} ] ) ?;
284
344
345
+ // FIXME terminology is also wrong here: "extracted" is correct for `After`
346
+ // and `Discard` modes, but the extracted commit is not actually None for
347
+ // `InsertBefore`; it's just handled in a different way
285
348
let extracted_commit_oid = match split_mode {
286
- SplitMode :: Discard => None ,
349
+ SplitMode :: InsertBefore | SplitMode :: Discard => None ,
287
350
SplitMode :: InsertAfter | SplitMode :: DetachAfter => {
288
351
let extracted_tree = repo. cherry_pick_fast (
289
352
& target_commit,
@@ -298,7 +361,11 @@ pub fn split(
298
361
& target_commit. get_committer ( ) ,
299
362
format ! ( "temp(split): {message}" ) . as_str ( ) ,
300
363
& extracted_tree,
301
- vec ! [ & remainder_commit] ,
364
+ if let SplitMode :: InsertBefore = & split_mode {
365
+ parent_commits. iter ( ) . collect ( )
366
+ } else {
367
+ vec ! [ & remainder_commit]
368
+ } ,
302
369
) ?;
303
370
304
371
// see git-branchless/src/commands/amend.rs:172
@@ -358,45 +425,73 @@ pub fn split(
358
425
struct CleanUp {
359
426
checkout_target : Option < CheckoutTarget > ,
360
427
rewritten_oids : Vec < ( NonZeroOid , MaybeZeroOid ) > ,
428
+ rebase_force_detach : bool ,
361
429
reset_index : bool ,
362
430
}
363
431
364
432
let cleanup = match ( target_state, & split_mode, extracted_commit_oid) {
365
- // branch @ split commit checked out: extend branch to include extracted
366
- // 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
367
436
( TargetState :: CurrentBranch , SplitMode :: InsertAfter , Some ( extracted_commit_oid) ) => {
368
437
CleanUp {
369
438
checkout_target : None ,
370
439
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( extracted_commit_oid) ) ] ,
440
+ rebase_force_detach : false ,
371
441
reset_index : false ,
372
442
}
373
443
}
374
444
445
+ // same as above, but Discard; don't move branches, but do force reset
375
446
( TargetState :: CurrentBranch , SplitMode :: Discard , None ) => CleanUp {
376
447
checkout_target : None ,
377
448
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
449
+ rebase_force_detach : false ,
378
450
reset_index : true ,
379
451
} ,
380
452
381
- // commit to split checked out as detached HEAD, don't extend any
382
- // branches, but explicitly check out the newly split commit
383
- ( 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 {
384
468
checkout_target : Some ( CheckoutTarget :: Oid ( remainder_commit_oid) ) ,
385
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 ,
386
479
reset_index : false ,
387
480
} ,
388
481
389
482
// some other commit or branch was checked out, default behavior is fine
390
483
( TargetState :: CurrentBranch | TargetState :: Other , _, _) => CleanUp {
391
484
checkout_target : None ,
392
485
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
486
+ rebase_force_detach : false ,
393
487
reset_index : false ,
394
488
} ,
395
489
} ;
396
490
397
491
let CleanUp {
398
492
checkout_target,
399
493
rewritten_oids,
494
+ rebase_force_detach,
400
495
reset_index,
401
496
} = cleanup;
402
497
@@ -431,21 +526,18 @@ pub fn split(
431
526
}
432
527
433
528
let mut builder = RebasePlanBuilder :: new ( & dag, permissions) ;
434
- let children = dag. query_children ( CommitSet :: from ( target_oid) ) ?;
435
- for child in dag. commit_set_to_vec ( & children) ? {
436
- match ( & split_mode, extracted_commit_oid) {
437
- ( _, None ) => builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?,
438
- ( _, Some ( extracted_commit_oid) ) => {
439
- builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
440
- }
441
- }
442
-
443
- match ( & split_mode, extracted_commit_oid) {
444
- ( _, None ) | ( SplitMode :: DetachAfter , Some ( _) ) => {
445
- builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?
446
- }
447
- ( _, Some ( extracted_commit_oid) ) => {
448
- builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
529
+ if let SplitMode :: InsertBefore = & split_mode {
530
+ builder. move_subtree ( target_oid, vec ! [ remainder_commit_oid] ) ?
531
+ } else {
532
+ let children = dag. query_children ( CommitSet :: from ( target_oid) ) ?;
533
+ for child in dag. commit_set_to_vec ( & children) ? {
534
+ match ( & split_mode, extracted_commit_oid) {
535
+ ( _, None ) | ( SplitMode :: DetachAfter , Some ( _) ) => {
536
+ builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?
537
+ }
538
+ ( _, Some ( extracted_commit_oid) ) => {
539
+ builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
540
+ }
449
541
}
450
542
}
451
543
}
@@ -466,7 +558,7 @@ pub fn split(
466
558
resolve_merge_conflicts,
467
559
check_out_commit_options : CheckOutCommitOptions {
468
560
additional_args : Default :: default ( ) ,
469
- force_detach : false ,
561
+ force_detach : rebase_force_detach ,
470
562
reset : false ,
471
563
render_smartlog : false ,
472
564
} ,
0 commit comments