@@ -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,23 @@ 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 rebased onto split
309
+ // after => target is amended as "split", split is cherry picked onto split as "extracted"
310
+
311
+ // FIXME terminology is wrong here: remainder is correct for "After", but
312
+ // this is the "extracted" commit for InsertBefore
313
+ let remainder_commit_oid = if let SplitMode :: InsertBefore = split_mode {
314
+ repo. create_commit (
315
+ None ,
316
+ & target_commit. get_author ( ) ,
317
+ & target_commit. get_committer ( ) ,
318
+ format ! ( "temp(split): {message}" ) . as_str ( ) ,
319
+ & remainder_tree,
320
+ parent_commits. iter ( ) . collect ( ) ,
321
+ ) ?
322
+ } else {
323
+ target_commit. amend_commit ( None , None , None , None , Some ( & remainder_tree) ) ?
324
+ } ;
267
325
let remainder_commit = repo. find_commit_or_fail ( remainder_commit_oid) ?;
268
326
269
327
if remainder_commit. is_empty ( ) {
@@ -282,8 +340,11 @@ pub fn split(
282
340
new_commit_oid: MaybeZeroOid :: NonZero ( remainder_commit_oid) ,
283
341
} ] ) ?;
284
342
343
+ // FIXME terminology is wrong here: extracted is correct for After and
344
+ // Discard modes, but the extracted commit is not None for InsertBefore:
345
+ // it's just handled in a different way
285
346
let extracted_commit_oid = match split_mode {
286
- SplitMode :: Discard => None ,
347
+ SplitMode :: InsertBefore | SplitMode :: Discard => None ,
287
348
SplitMode :: InsertAfter | SplitMode :: DetachAfter => {
288
349
let extracted_tree = repo. cherry_pick_fast (
289
350
& target_commit,
@@ -298,7 +359,11 @@ pub fn split(
298
359
& target_commit. get_committer ( ) ,
299
360
format ! ( "temp(split): {message}" ) . as_str ( ) ,
300
361
& extracted_tree,
301
- vec ! [ & remainder_commit] ,
362
+ if let SplitMode :: InsertBefore = & split_mode {
363
+ parent_commits. iter ( ) . collect ( )
364
+ } else {
365
+ vec ! [ & remainder_commit]
366
+ } ,
302
367
) ?;
303
368
304
369
// see git-branchless/src/commands/amend.rs:172
@@ -358,45 +423,73 @@ pub fn split(
358
423
struct CleanUp {
359
424
checkout_target : Option < CheckoutTarget > ,
360
425
rewritten_oids : Vec < ( NonZeroOid , MaybeZeroOid ) > ,
426
+ rebase_force_detach : bool ,
361
427
reset_index : bool ,
362
428
}
363
429
364
430
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
431
+ // branch @ target commit checked out: extend branch to include
432
+ // extracted commit; branch will stay checked out w/o any explicit
433
+ // checkout
367
434
( TargetState :: CurrentBranch , SplitMode :: InsertAfter , Some ( extracted_commit_oid) ) => {
368
435
CleanUp {
369
436
checkout_target : None ,
370
437
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( extracted_commit_oid) ) ] ,
438
+ rebase_force_detach : false ,
371
439
reset_index : false ,
372
440
}
373
441
}
374
442
443
+ // same as above, but Discard; don't move branches, but do force reset
375
444
( TargetState :: CurrentBranch , SplitMode :: Discard , None ) => CleanUp {
376
445
checkout_target : None ,
377
446
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
447
+ rebase_force_detach : false ,
378
448
reset_index : true ,
379
449
} ,
380
450
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 {
451
+ // same as above, but InsertBefore; do not move branches
452
+ ( TargetState :: CurrentBranch , SplitMode :: InsertBefore , _) => CleanUp {
453
+ checkout_target : None ,
454
+ rewritten_oids : vec ! [ ] ,
455
+ rebase_force_detach : false ,
456
+ reset_index : false ,
457
+ } ,
458
+
459
+ // target checked out as detached HEAD, don't extend any branches, but
460
+ // explicitly check out the newly split commit
461
+ (
462
+ TargetState :: DetachedHead ,
463
+ SplitMode :: InsertAfter | SplitMode :: Discard | SplitMode :: DetachAfter ,
464
+ _,
465
+ ) => CleanUp {
384
466
checkout_target : Some ( CheckoutTarget :: Oid ( remainder_commit_oid) ) ,
385
467
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
468
+ rebase_force_detach : false ,
469
+ reset_index : false ,
470
+ } ,
471
+
472
+ // same as above, but InsertBefore; do not move branches
473
+ ( TargetState :: DetachedHead , SplitMode :: InsertBefore , _) => CleanUp {
474
+ checkout_target : None ,
475
+ rewritten_oids : vec ! [ ] ,
476
+ rebase_force_detach : true ,
386
477
reset_index : false ,
387
478
} ,
388
479
389
480
// some other commit or branch was checked out, default behavior is fine
390
481
( TargetState :: CurrentBranch | TargetState :: Other , _, _) => CleanUp {
391
482
checkout_target : None ,
392
483
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
484
+ rebase_force_detach : false ,
393
485
reset_index : false ,
394
486
} ,
395
487
} ;
396
488
397
489
let CleanUp {
398
490
checkout_target,
399
491
rewritten_oids,
492
+ rebase_force_detach,
400
493
reset_index,
401
494
} = cleanup;
402
495
@@ -431,21 +524,18 @@ pub fn split(
431
524
}
432
525
433
526
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] ) ?
527
+ if let SplitMode :: InsertBefore = & split_mode {
528
+ builder. move_subtree ( target_oid, vec ! [ remainder_commit_oid] ) ?
529
+ } else {
530
+ let children = dag. query_children ( CommitSet :: from ( target_oid) ) ?;
531
+ for child in dag. commit_set_to_vec ( & children) ? {
532
+ match ( & split_mode, extracted_commit_oid) {
533
+ ( _, None ) | ( SplitMode :: DetachAfter , Some ( _) ) => {
534
+ builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?
535
+ }
536
+ ( _, Some ( extracted_commit_oid) ) => {
537
+ builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
538
+ }
449
539
}
450
540
}
451
541
}
@@ -466,7 +556,7 @@ pub fn split(
466
556
resolve_merge_conflicts,
467
557
check_out_commit_options : CheckOutCommitOptions {
468
558
additional_args : Default :: default ( ) ,
469
- force_detach : false ,
559
+ force_detach : rebase_force_detach ,
470
560
reset : false ,
471
561
render_smartlog : false ,
472
562
} ,
0 commit comments