@@ -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( ) ,
@@ -209,6 +239,15 @@ pub fn split(
209
239
210
240
let target_entry = target_tree. get_path ( path) ?;
211
241
let temp_tree_oid = match ( parent_entry, target_entry, & split_mode) {
242
+ // added/modified & InsertBefore => add to extracted commit
243
+ ( None , Some ( commit_entry) , SplitMode :: InsertBefore )
244
+ | ( Some ( _) , Some ( commit_entry) , SplitMode :: InsertBefore ) => {
245
+ remainder_tree. add_or_replace ( & repo, path, & commit_entry) ?
246
+ }
247
+
248
+ // removed & InsertBefore => remove from remainder commit
249
+ ( Some ( _) , None , SplitMode :: InsertBefore ) => remainder_tree. remove ( & repo, path) ?,
250
+
212
251
// added => remove from remainder commit
213
252
( None , Some ( _) , SplitMode :: InsertAfter )
214
253
| ( None , Some ( _) , SplitMode :: DetachAfter )
@@ -242,7 +281,11 @@ pub fn split(
242
281
let message = {
243
282
let ( effects, _progress) =
244
283
effects. start_operation ( lib:: core:: effects:: OperationType :: CalculateDiff ) ;
245
- let ( old_tree, new_tree) = ( & remainder_tree, & target_tree) ;
284
+ let ( old_tree, new_tree) = if let SplitMode :: InsertBefore = & split_mode {
285
+ ( & parent_tree, & remainder_tree)
286
+ } else {
287
+ ( & remainder_tree, & target_tree)
288
+ } ;
246
289
let diff = repo. get_diff_between_trees (
247
290
& effects,
248
291
Some ( old_tree) ,
@@ -253,8 +296,23 @@ pub fn split(
253
296
summarize_diff_for_temporary_commit ( & diff) ?
254
297
} ;
255
298
256
- let remainder_commit_oid =
257
- target_commit. amend_commit ( None , None , None , None , Some ( & remainder_tree) ) ?;
299
+ // before => split commit is created on parent as "extracted", target is rebased onto split
300
+ // after => target is amended as "split", split is cherry picked onto split as "extracted"
301
+
302
+ // FIXME terminology if wrong here: remainder is correct for "After", but
303
+ // this is the "extracted" commit for InsertBefore
304
+ let remainder_commit_oid = if let SplitMode :: InsertBefore = split_mode {
305
+ repo. create_commit (
306
+ None ,
307
+ & target_commit. get_author ( ) ,
308
+ & target_commit. get_committer ( ) ,
309
+ format ! ( "temp(split): {message}" ) . as_str ( ) ,
310
+ & remainder_tree,
311
+ parent_commits. iter ( ) . collect ( ) ,
312
+ ) ?
313
+ } else {
314
+ target_commit. amend_commit ( None , None , None , None , Some ( & remainder_tree) ) ?
315
+ } ;
258
316
let remainder_commit = repo. find_commit_or_fail ( remainder_commit_oid) ?;
259
317
260
318
if remainder_commit. is_empty ( ) {
@@ -273,8 +331,11 @@ pub fn split(
273
331
new_commit_oid: MaybeZeroOid :: NonZero ( remainder_commit_oid) ,
274
332
} ] ) ?;
275
333
334
+ // FIXME terminology if wrong here: extracted is correct for "After" and
335
+ // Discard, but the extracted commit is not None for InsertBefore, it's just
336
+ // handled in a different way
276
337
let extracted_commit_oid = match split_mode {
277
- SplitMode :: Discard => None ,
338
+ SplitMode :: InsertBefore | SplitMode :: Discard => None ,
278
339
SplitMode :: InsertAfter | SplitMode :: DetachAfter => {
279
340
let extracted_tree = repo. cherry_pick_fast (
280
341
& target_commit,
@@ -289,7 +350,11 @@ pub fn split(
289
350
& target_commit. get_committer ( ) ,
290
351
format ! ( "temp(split): {message}" ) . as_str ( ) ,
291
352
& extracted_tree,
292
- vec ! [ & remainder_commit] ,
353
+ if let SplitMode :: InsertBefore = & split_mode {
354
+ parent_commits. iter ( ) . collect ( )
355
+ } else {
356
+ vec ! [ & remainder_commit]
357
+ } ,
293
358
) ?;
294
359
295
360
// see git-branchless/src/commands/amend.rs:172
@@ -321,42 +386,68 @@ pub fn split(
321
386
) ?;
322
387
323
388
let head_info = repo. get_head_info ( ) ?;
324
- let ( checkout_target, rewritten_oids) = match head_info {
325
- // branch @ split commit checked out: extend branch to include extracted
326
- // commit; branch will stay checked out w/o any explicit checkout
327
- ResolvedReferenceInfo {
328
- oid : Some ( oid) ,
329
- reference_name : Some ( _) ,
330
- } if oid == target_oid
331
- && split_mode != SplitMode :: DetachAfter
332
- && extracted_commit_oid. is_some ( ) =>
333
- {
334
- (
335
- None ,
336
- vec ! [ (
337
- target_oid,
338
- MaybeZeroOid :: NonZero ( extracted_commit_oid. unwrap( ) ) ,
339
- ) ] ,
340
- )
341
- }
389
+ let ( checkout_target, rewritten_oids, rebase_force_detach) = match ( head_info, & split_mode) {
390
+ // branch @ target commit checked out: extend branch to include extracted
391
+ // commit; branch will stay checked out w/o any explicit checkout ResolvedReferenceInfo {
392
+ (
393
+ ResolvedReferenceInfo {
394
+ oid : Some ( oid) ,
395
+ reference_name : Some ( _) ,
396
+ } ,
397
+ // not DetatchAfter
398
+ SplitMode :: InsertAfter | SplitMode :: Discard ,
399
+ ) if oid == target_oid && extracted_commit_oid. is_some ( ) => (
400
+ None ,
401
+ vec ! [ (
402
+ target_oid,
403
+ MaybeZeroOid :: NonZero ( extracted_commit_oid. unwrap( ) ) ,
404
+ ) ] ,
405
+ false ,
406
+ ) ,
407
+
408
+ // same as above, but InsertBefore; do not move branches
409
+ (
410
+ ResolvedReferenceInfo {
411
+ oid : Some ( oid) ,
412
+ reference_name : Some ( _) ,
413
+ } ,
414
+ SplitMode :: InsertBefore ,
415
+ ) if oid == target_oid => ( None , vec ! [ ] , false ) ,
342
416
343
- // commit to split checked out as detached HEAD, don't extend any
417
+ // target checked out as detached HEAD, don't extend any
344
418
// branches, but explicitly check out the newly split commit
345
- ResolvedReferenceInfo {
346
- oid : Some ( oid) ,
347
- reference_name : None ,
348
- } if oid == target_oid => (
419
+ (
420
+ ResolvedReferenceInfo {
421
+ oid : Some ( oid) ,
422
+ reference_name : None ,
423
+ } ,
424
+ SplitMode :: InsertAfter | SplitMode :: Discard | SplitMode :: DetachAfter ,
425
+ ) if oid == target_oid => (
349
426
Some ( CheckoutTarget :: Oid ( remainder_commit_oid) ) ,
350
427
vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
428
+ false ,
351
429
) ,
352
430
431
+ // same as above, but InsertBefore; do not move branches
432
+ (
433
+ ResolvedReferenceInfo {
434
+ oid : Some ( oid) ,
435
+ reference_name : None ,
436
+ } ,
437
+ SplitMode :: InsertBefore ,
438
+ ) if oid == target_oid => ( None , vec ! [ ] , true ) ,
439
+
353
440
// some other commit or branch was checked out, default behavior is fine
354
- ResolvedReferenceInfo {
355
- oid : _,
356
- reference_name : _,
357
- } => (
441
+ (
442
+ ResolvedReferenceInfo {
443
+ oid : _,
444
+ reference_name : _,
445
+ } ,
446
+ _,
447
+ ) => (
358
448
None ,
359
449
vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
450
+ false ,
360
451
) ,
361
452
} ;
362
453
@@ -386,21 +477,18 @@ pub fn split(
386
477
}
387
478
388
479
let mut builder = RebasePlanBuilder :: new ( & dag, permissions) ;
389
- let children = dag. query_children ( CommitSet :: from ( target_oid) ) ?;
390
- for child in dag. commit_set_to_vec ( & children) ? {
391
- match ( & split_mode, extracted_commit_oid) {
392
- ( _, None ) => builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?,
393
- ( _, Some ( extracted_commit_oid) ) => {
394
- builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
395
- }
396
- }
397
-
398
- match ( & split_mode, extracted_commit_oid) {
399
- ( _, None ) | ( SplitMode :: DetachAfter , Some ( _) ) => {
400
- builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?
401
- }
402
- ( _, Some ( extracted_commit_oid) ) => {
403
- builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
480
+ if let SplitMode :: InsertBefore = & split_mode {
481
+ builder. move_subtree ( target_oid, vec ! [ remainder_commit_oid] ) ?
482
+ } else {
483
+ let children = dag. query_children ( CommitSet :: from ( target_oid) ) ?;
484
+ for child in dag. commit_set_to_vec ( & children) ? {
485
+ match ( & split_mode, extracted_commit_oid) {
486
+ ( _, None ) | ( SplitMode :: DetachAfter , Some ( _) ) => {
487
+ builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?
488
+ }
489
+ ( _, Some ( extracted_commit_oid) ) => {
490
+ builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
491
+ }
404
492
}
405
493
}
406
494
}
@@ -421,7 +509,7 @@ pub fn split(
421
509
resolve_merge_conflicts,
422
510
check_out_commit_options : CheckOutCommitOptions {
423
511
additional_args : Default :: default ( ) ,
424
- force_detach : false ,
512
+ force_detach : rebase_force_detach ,
425
513
reset : false ,
426
514
render_smartlog : false ,
427
515
} ,
0 commit comments