@@ -34,13 +34,21 @@ use lib::{
34
34
} ;
35
35
use tracing:: instrument;
36
36
37
+ #[ derive( Debug , PartialEq ) ]
38
+ /// What should `split` do with the extracted changes?
39
+ pub enum SplitMode {
40
+ DetachAfter ,
41
+ InsertAfter ,
42
+ }
43
+
37
44
/// Split a commit and restack its descendants.
38
45
#[ instrument]
39
46
pub fn split (
40
47
effects : & Effects ,
41
48
revset : Revset ,
42
49
resolve_revset_options : & ResolveRevsetOptions ,
43
50
files_to_extract : Vec < String > ,
51
+ split_mode : SplitMode ,
44
52
move_options : & MoveOptions ,
45
53
git_run_info : & GitRunInfo ,
46
54
) -> EyreExitOr < ( ) > {
@@ -117,15 +125,19 @@ pub fn split(
117
125
let target_commit = repo. find_commit_or_fail ( target_oid) ?;
118
126
let target_tree = target_commit. get_tree ( ) ?;
119
127
let parent_commits = target_commit. get_parents ( ) ;
120
- let ( parent_tree, mut remainder_tree) = match parent_commits. as_slice ( ) {
128
+ let ( parent_tree, mut remainder_tree) = match ( & split_mode , parent_commits. as_slice ( ) ) {
121
129
// split the commit by removing the changes from the target, and then
122
130
// cherry picking the orignal target as the "extracted" commit
123
- [ only_parent] => ( only_parent. get_tree ( ) ?, target_commit. get_tree ( ) ?) ,
131
+ ( SplitMode :: InsertAfter , [ only_parent] ) | ( SplitMode :: DetachAfter , [ only_parent] ) => {
132
+ ( only_parent. get_tree ( ) ?, target_commit. get_tree ( ) ?)
133
+ }
124
134
125
135
// no parent: use an empty tree for comparison
126
- [ ] => ( make_empty_tree ( & repo) ?, target_commit. get_tree ( ) ?) ,
136
+ ( SplitMode :: InsertAfter , [ ] ) | ( SplitMode :: DetachAfter , [ ] ) => {
137
+ ( make_empty_tree ( & repo) ?, target_commit. get_tree ( ) ?)
138
+ }
127
139
128
- [ ..] => {
140
+ ( _ , [ ..] ) => {
129
141
writeln ! (
130
142
effects. get_error_stream( ) ,
131
143
"Cannot split merge commit {}." ,
@@ -204,14 +216,18 @@ pub fn split(
204
216
} ;
205
217
206
218
let target_entry = target_tree. get_path ( path) ?;
207
- let temp_tree_oid = match ( parent_entry, target_entry) {
219
+ let temp_tree_oid = match ( parent_entry, target_entry, & split_mode ) {
208
220
// added => remove from remainder commit
209
- ( None , Some ( _) ) => remainder_tree. remove ( & repo, path) ?,
221
+ ( None , Some ( _) , SplitMode :: InsertAfter ) | ( None , Some ( _) , SplitMode :: DetachAfter ) => {
222
+ remainder_tree. remove ( & repo, path) ?
223
+ }
210
224
211
225
// deleted or modified => replace w/ parent content in split commit
212
- ( Some ( parent_entry) , _) => remainder_tree. add_or_replace ( & repo, path, & parent_entry) ?,
226
+ ( Some ( parent_entry) , _, _) => {
227
+ remainder_tree. add_or_replace ( & repo, path, & parent_entry) ?
228
+ }
213
229
214
- ( None , _) => {
230
+ ( None , _, _ ) => {
215
231
if path. exists ( ) {
216
232
writeln ! (
217
233
effects. get_error_stream( ) ,
@@ -263,35 +279,37 @@ pub fn split(
263
279
new_commit_oid: MaybeZeroOid :: NonZero ( remainder_commit_oid) ,
264
280
} ] ) ?;
265
281
266
- let extracted_commit_oid = {
267
- let extracted_tree = repo. cherry_pick_fast (
268
- & target_commit,
269
- & remainder_commit,
270
- & CherryPickFastOptions {
271
- reuse_parent_tree_if_possible : true ,
272
- } ,
273
- ) ?;
274
- let extracted_commit_oid = repo. create_commit (
275
- None ,
276
- & target_commit. get_author ( ) ,
277
- & target_commit. get_committer ( ) ,
278
- format ! ( "temp(split): {message}" ) . as_str ( ) ,
279
- & extracted_tree,
280
- vec ! [ & remainder_commit] ,
281
- ) ?;
282
+ let extracted_commit_oid = match split_mode {
283
+ SplitMode :: InsertAfter | SplitMode :: DetachAfter => {
284
+ let extracted_tree = repo. cherry_pick_fast (
285
+ & target_commit,
286
+ & remainder_commit,
287
+ & CherryPickFastOptions {
288
+ reuse_parent_tree_if_possible : true ,
289
+ } ,
290
+ ) ?;
291
+ let extracted_commit_oid = repo. create_commit (
292
+ None ,
293
+ & target_commit. get_author ( ) ,
294
+ & target_commit. get_committer ( ) ,
295
+ format ! ( "temp(split): {message}" ) . as_str ( ) ,
296
+ & extracted_tree,
297
+ vec ! [ & remainder_commit] ,
298
+ ) ?;
282
299
283
- // see git-branchless/src/commands/amend.rs:172
284
- // TODO maybe this should happen after we've confirmed the rebase has succeeded
285
- mark_commit_reachable ( & repo, extracted_commit_oid)
286
- . wrap_err ( "Marking commit as reachable for GC purposes." ) ?;
300
+ // see git-branchless/src/commands/amend.rs:172
301
+ // TODO maybe this should happen after we've confirmed the rebase has succeeded?
302
+ mark_commit_reachable ( & repo, extracted_commit_oid)
303
+ . wrap_err ( "Marking commit as reachable for GC purposes." ) ?;
287
304
288
- event_log_db. add_events ( vec ! [ Event :: CommitEvent {
289
- timestamp: now. duration_since( UNIX_EPOCH ) ?. as_secs_f64( ) ,
290
- event_tx_id,
291
- commit_oid: extracted_commit_oid,
292
- } ] ) ?;
305
+ event_log_db. add_events ( vec ! [ Event :: CommitEvent {
306
+ timestamp: now. duration_since( UNIX_EPOCH ) ?. as_secs_f64( ) ,
307
+ event_tx_id,
308
+ commit_oid: extracted_commit_oid,
309
+ } ] ) ?;
293
310
294
- Some ( extracted_commit_oid)
311
+ Some ( extracted_commit_oid)
312
+ }
295
313
} ;
296
314
297
315
// push the new commits into the dag for the rebase planner
@@ -338,23 +356,25 @@ pub fn split(
338
356
rewritten_oids : Vec < ( NonZeroOid , MaybeZeroOid ) > ,
339
357
}
340
358
341
- let cleanup = match ( target_state, extracted_commit_oid) {
359
+ let cleanup = match ( target_state, & split_mode , extracted_commit_oid) {
342
360
// branch @ split commit checked out: extend branch to include extracted
343
361
// commit; branch will stay checked out w/o any explicit checkout
344
- ( TargetState :: CurrentBranch , Some ( extracted_commit_oid) ) => CleanUp {
345
- checkout_target : None ,
346
- rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( extracted_commit_oid) ) ] ,
347
- } ,
362
+ ( TargetState :: CurrentBranch , SplitMode :: InsertAfter , Some ( extracted_commit_oid) ) => {
363
+ CleanUp {
364
+ checkout_target : None ,
365
+ rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( extracted_commit_oid) ) ] ,
366
+ }
367
+ }
348
368
349
369
// commit to split checked out as detached HEAD, don't extend any
350
370
// branches, but explicitly check out the newly split commit
351
- ( TargetState :: DetachedHead , _) => CleanUp {
371
+ ( TargetState :: DetachedHead , _, _ ) => CleanUp {
352
372
checkout_target : Some ( CheckoutTarget :: Oid ( remainder_commit_oid) ) ,
353
373
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
354
374
} ,
355
375
356
376
// some other commit or branch was checked out, default behavior is fine
357
- ( TargetState :: CurrentBranch , _) | ( TargetState :: Other , _) => CleanUp {
377
+ ( TargetState :: CurrentBranch , _, _ ) | ( TargetState :: Other , _ , _) => CleanUp {
358
378
checkout_target : None ,
359
379
rewritten_oids : vec ! [ ( target_oid, MaybeZeroOid :: NonZero ( remainder_commit_oid) ) ] ,
360
380
} ,
@@ -393,9 +413,18 @@ pub fn split(
393
413
let mut builder = RebasePlanBuilder :: new ( & dag, permissions) ;
394
414
let children = dag. query_children ( CommitSet :: from ( target_oid) ) ?;
395
415
for child in dag. commit_set_to_vec ( & children) ? {
396
- match extracted_commit_oid {
397
- None => builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?,
398
- Some ( extracted_commit_oid) => {
416
+ match ( & split_mode, extracted_commit_oid) {
417
+ ( _, None ) => builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?,
418
+ ( _, Some ( extracted_commit_oid) ) => {
419
+ builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
420
+ }
421
+ }
422
+
423
+ match ( & split_mode, extracted_commit_oid) {
424
+ ( _, None ) | ( SplitMode :: DetachAfter , Some ( _) ) => {
425
+ builder. move_subtree ( child, vec ! [ remainder_commit_oid] ) ?
426
+ }
427
+ ( _, Some ( extracted_commit_oid) ) => {
399
428
builder. move_subtree ( child, vec ! [ extracted_commit_oid] ) ?
400
429
}
401
430
}
0 commit comments