Skip to content

Commit e85730b

Browse files
committed
feat(split): --reparent for --detach / --discard
When splitting a commit with `--discard` or `--detach`, the `--reparent` option ensures that the changes from the discarded or detached portion are squashed to the child commit, just like `amend --reparent`. Reparenting is not applied to `InsertAfter` or `InsertBefore` modes as it would simply be a no-op in these cases (the descendants are not changed in the first place).
1 parent 597ba55 commit e85730b

File tree

2 files changed

+143
-3
lines changed

2 files changed

+143
-3
lines changed

git-branchless/src/commands/split.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,13 @@ pub fn split(
8080
resolve_merge_conflicts,
8181
dump_rebase_constraints,
8282
dump_rebase_plan,
83-
reparent: _, // not yet implemented
83+
reparent,
8484
} = *move_options;
8585

86+
// reparenting is only relevant for non-Insert* modes
87+
let should_reparent =
88+
reparent && !matches!(split_mode, SplitMode::InsertAfter | SplitMode::InsertBefore);
89+
8690
let target_oid: NonZeroOid = match resolve_commits(
8791
effects,
8892
&repo,
@@ -534,10 +538,16 @@ pub fn split(
534538
for child in dag.commit_set_to_vec(&children)? {
535539
match (&split_mode, extracted_commit_oid) {
536540
(_, None) | (SplitMode::DetachAfter, Some(_)) => {
537-
builder.move_subtree(child, vec![remainder_commit_oid])?
541+
builder.move_subtree(child, vec![remainder_commit_oid])?;
542+
if should_reparent {
543+
builder.reparent_subtree(child, vec![remainder_commit_oid], &repo)?;
544+
}
538545
}
539546
(_, Some(extracted_commit_oid)) => {
540-
builder.move_subtree(child, vec![extracted_commit_oid])?
547+
builder.move_subtree(child, vec![extracted_commit_oid])?;
548+
if should_reparent {
549+
builder.reparent_subtree(child, vec![extracted_commit_oid], &repo)?;
550+
}
541551
}
542552
}
543553
}

git-branchless/tests/test_split.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,136 @@ fn test_split_discard() -> eyre::Result<()> {
615615
Ok(())
616616
}
617617

618+
#[test]
619+
fn test_split_reparent() -> eyre::Result<()> {
620+
let git = make_git()?;
621+
git.init_repo()?;
622+
git.detach_head()?;
623+
624+
git.write_file_txt("test1", "contents1")?;
625+
git.write_file_txt("test2", "contents2")?;
626+
git.write_file_txt("test3", "contents3")?;
627+
git.run(&["add", "."])?;
628+
git.run(&["commit", "-m", "first commit"])?;
629+
630+
git.write_file_txt("test3", "updated contents3")?;
631+
git.write_file_txt("test4", "contents4")?;
632+
git.write_file_txt("test5", "contents5")?;
633+
git.run(&["add", "."])?;
634+
git.run(&["commit", "-m", "second commit"])?;
635+
636+
{
637+
let (stdout, _stderr) = git.branchless("smartlog", &[])?;
638+
insta::assert_snapshot!(stdout, @r###"
639+
O f777ecc (master) create initial.txt
640+
|
641+
o e48cdc5 first commit
642+
|
643+
@ 8c3edf7 second commit
644+
"###);
645+
646+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD~"])?;
647+
insta::assert_snapshot!(&stdout, @"
648+
test1.txt | 1 +
649+
test2.txt | 1 +
650+
test3.txt | 1 +
651+
3 files changed, 3 insertions(+)
652+
");
653+
654+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD"])?;
655+
insta::assert_snapshot!(&stdout, @"
656+
test3.txt | 2 +-
657+
test4.txt | 1 +
658+
test5.txt | 1 +
659+
3 files changed, 3 insertions(+), 1 deletion(-)
660+
");
661+
}
662+
663+
{
664+
let (stdout, _stderr) =
665+
git.branchless("split", &["HEAD~", "test2.txt", "--discard", "--reparent"])?;
666+
insta::assert_snapshot!(&stdout, @r"
667+
Attempting rebase in-memory...
668+
[1/1] Committed as: 3aec2d3 second commit
669+
branchless: processing 1 rewritten commit
670+
branchless: running command: <git-executable> checkout 3aec2d3c59e90647cef2f4bbb49a4e09790611c6
671+
In-memory rebase succeeded.
672+
O f777ecc (master) create initial.txt
673+
|
674+
o 2932db7 first commit
675+
|
676+
@ 3aec2d3 second commit
677+
");
678+
679+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD~"])?;
680+
insta::assert_snapshot!(&stdout, @"
681+
test1.txt | 1 +
682+
test3.txt | 1 +
683+
2 files changed, 2 insertions(+)
684+
");
685+
686+
// the discarded test2 is effectively squashed into the second commit
687+
// due to --reparent
688+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD"])?;
689+
insta::assert_snapshot!(&stdout, @r"
690+
test2.txt | 1 +
691+
test3.txt | 2 +-
692+
test4.txt | 1 +
693+
test5.txt | 1 +
694+
4 files changed, 4 insertions(+), 1 deletion(-)
695+
");
696+
}
697+
698+
// similarly for --detach
699+
{
700+
let (stdout, _stderr) =
701+
git.branchless("split", &["HEAD~", "test1.txt", "--detach", "--reparent"])?;
702+
insta::assert_snapshot!(&stdout, @r"
703+
Attempting rebase in-memory...
704+
[1/1] Committed as: 6249d09 second commit
705+
branchless: processing 1 rewritten commit
706+
branchless: running command: <git-executable> checkout 6249d093020c9d764e62b8bf8e67228a4445b4f0
707+
In-memory rebase succeeded.
708+
O f777ecc (master) create initial.txt
709+
|
710+
o 52618ab first commit
711+
|\
712+
| o 4271e93 temp(split): test1.txt (+1)
713+
|
714+
@ 6249d09 second commit
715+
");
716+
}
717+
718+
{
719+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD~"])?;
720+
insta::assert_snapshot!(&stdout, @r"
721+
test3.txt | 1 +
722+
1 file changed, 1 insertion(+)
723+
");
724+
725+
// the detached test1 is effectively squashed into the second commit
726+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD"])?;
727+
insta::assert_snapshot!(&stdout, @r"
728+
test1.txt | 1 +
729+
test2.txt | 1 +
730+
test3.txt | 2 +-
731+
test4.txt | 1 +
732+
test5.txt | 1 +
733+
5 files changed, 5 insertions(+), 1 deletion(-)
734+
");
735+
736+
let (split_commit, _stderr) = git.run(&["query", "--raw", "exactly(siblings(HEAD), 1)"])?;
737+
let (stdout, _stderr) =
738+
git.run(&["show", "--pretty=format:", "--stat", split_commit.trim()])?;
739+
insta::assert_snapshot!(&stdout, @r"
740+
test1.txt | 1 +
741+
1 file changed, 1 insertion(+)
742+
");
743+
}
744+
745+
Ok(())
746+
}
747+
618748
#[test]
619749
fn test_split_discard_bug_checked_out_branch() -> eyre::Result<()> {
620750
let git = make_git()?;

0 commit comments

Comments
 (0)