Skip to content

Commit b86249a

Browse files
feat(split): add --detach flag
1 parent 672cc9a commit b86249a

File tree

4 files changed

+111
-10
lines changed

4 files changed

+111
-10
lines changed

git-branchless-opts/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,17 @@ pub enum Command {
660660
#[clap(value_parser, required = true)]
661661
files: Vec<String>,
662662

663+
/// Restack any descendents onto the split commit, not the extracted commit.
664+
#[clap(action, short = 'd', long = "detach")]
665+
detach: bool,
666+
663667
/// Options for resolving revset expressions.
664668
#[clap(flatten)]
665669
resolve_revset_options: ResolveRevsetOptions,
670+
671+
/// Options for moving commits.
672+
#[clap(flatten)]
673+
move_options: MoveOptions,
666674
},
667675

668676
/// Push commits to a remote.

git-branchless/src/commands/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,18 @@ fn command_main(ctx: CommandContext, opts: Opts) -> EyreExitOr<()> {
181181
},
182182

183183
Command::Split {
184+
detach,
184185
files,
185186
resolve_revset_options,
186187
revset,
188+
move_options,
187189
} => split::split(
188190
&effects,
189191
revset,
190192
&resolve_revset_options,
191193
files,
194+
detach,
195+
&move_options,
192196
&git_run_info,
193197
)?,
194198

git-branchless/src/commands/split.rs

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
time::{SystemTime, UNIX_EPOCH},
99
};
1010

11-
use git_branchless_opts::{ResolveRevsetOptions, Revset};
11+
use git_branchless_opts::{MoveOptions, ResolveRevsetOptions, Revset};
1212
use git_branchless_revset::resolve_commits;
1313
use lib::{
1414
core::{
@@ -41,6 +41,8 @@ pub fn split(
4141
revset: Revset,
4242
resolve_revset_options: &ResolveRevsetOptions,
4343
files_to_extract: Vec<String>,
44+
detach: bool,
45+
move_options: &MoveOptions,
4446
git_run_info: &GitRunInfo,
4547
) -> EyreExitOr<()> {
4648
let repo = Repo::from_current_dir()?;
@@ -61,6 +63,16 @@ pub fn split(
6163
let pool = ThreadPoolBuilder::new().build()?;
6264
let repo_pool = RepoResource::new_pool(&repo)?;
6365

66+
let MoveOptions {
67+
force_rewrite_public_commits,
68+
force_in_memory,
69+
force_on_disk,
70+
detect_duplicate_commits_via_patch_id,
71+
resolve_merge_conflicts,
72+
dump_rebase_constraints,
73+
dump_rebase_plan,
74+
} = *move_options;
75+
6476
let commit_to_split_oid: NonZeroOid = match resolve_commits(
6577
effects,
6678
&repo,
@@ -89,10 +101,10 @@ pub fn split(
89101
let permissions = match RebasePlanPermissions::verify_rewrite_set(
90102
&dag,
91103
BuildRebasePlanOptions {
92-
force_rewrite_public_commits: false,
93-
dump_rebase_constraints: false,
94-
dump_rebase_plan: false,
95-
detect_duplicate_commits_via_patch_id: false,
104+
force_rewrite_public_commits,
105+
dump_rebase_constraints,
106+
dump_rebase_plan,
107+
detect_duplicate_commits_via_patch_id,
96108
},
97109
&vec![commit_to_split_oid].into_iter().collect(),
98110
)? {
@@ -268,14 +280,24 @@ pub fn split(
268280
commit_oid: extracted_commit_oid,
269281
}])?;
270282

283+
// push the new commits into the dag for the rebase planner
284+
dag.sync_from_oids(
285+
effects,
286+
&repo,
287+
CommitSet::empty(),
288+
vec![split_commit_oid, extracted_commit_oid]
289+
.into_iter()
290+
.collect(),
291+
)?;
292+
271293
let head_info = repo.get_head_info()?;
272294
let (checkout_target, rewritten_oids) = match head_info {
273295
// branch @ split commit checked out: extend branch to include extracted
274296
// commit; branch will stay checked out w/o any explicit checkout
275297
ResolvedReferenceInfo {
276298
oid: Some(oid),
277299
reference_name: Some(_),
278-
} if oid == commit_to_split_oid => (
300+
} if oid == commit_to_split_oid && !detach => (
279301
None,
280302
vec![(
281303
commit_to_split_oid,
@@ -331,7 +353,11 @@ pub fn split(
331353
let mut builder = RebasePlanBuilder::new(&dag, permissions);
332354
let children = dag.query_children(CommitSet::from(commit_to_split_oid))?;
333355
for child in dag.commit_set_to_vec(&children)? {
334-
builder.move_subtree(child, vec![extracted_commit_oid])?;
356+
if detach {
357+
builder.move_subtree(child, vec![split_commit_oid])?;
358+
} else {
359+
builder.move_subtree(child, vec![extracted_commit_oid])?;
360+
}
335361
}
336362
let rebase_plan = builder.build(effects, &pool, &repo_pool)?;
337363

@@ -345,9 +371,9 @@ pub fn split(
345371
now,
346372
event_tx_id,
347373
preserve_timestamps: get_restack_preserve_timestamps(&repo)?,
348-
force_in_memory: true,
349-
force_on_disk: false,
350-
resolve_merge_conflicts: false,
374+
force_in_memory,
375+
force_on_disk,
376+
resolve_merge_conflicts,
351377
check_out_commit_options: CheckOutCommitOptions {
352378
additional_args: Default::default(),
353379
force_detach: false,

git-branchless/tests/test_split.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,69 @@ fn test_split_restacks_descendents() -> eyre::Result<()> {
439439
Ok(())
440440
}
441441

442+
#[test]
443+
fn test_split_detach() -> eyre::Result<()> {
444+
let git = make_git()?;
445+
git.init_repo()?;
446+
git.detach_head()?;
447+
448+
git.write_file_txt("test1", "contents1")?;
449+
git.write_file_txt("test2", "contents2")?;
450+
git.write_file_txt("test3", "contents3")?;
451+
git.run(&["add", "."])?;
452+
git.run(&["commit", "-m", "first commit"])?;
453+
454+
git.commit_file("test3", 1)?;
455+
456+
{
457+
let (stdout, _stderr) = git.branchless("smartlog", &[])?;
458+
insta::assert_snapshot!(stdout, @r###"
459+
O f777ecc (master) create initial.txt
460+
|
461+
o e48cdc5 first commit
462+
|
463+
@ 3d220e0 create test3.txt
464+
"###);
465+
}
466+
467+
{
468+
let (stdout, _stderr) = git.branchless("split", &["HEAD~", "test2.txt", "--detach"])?;
469+
insta::assert_snapshot!(&stdout, @r###"
470+
Attempting rebase in-memory...
471+
[1/1] Committed as: f88fbe5 create test3.txt
472+
branchless: processing 1 rewritten commit
473+
branchless: running command: <git-executable> checkout f88fbe5901493ffe1c669cdb8aa5f056dc0bb605
474+
In-memory rebase succeeded.
475+
O f777ecc (master) create initial.txt
476+
|
477+
o 2932db7 first commit
478+
|\
479+
| o 01523cc temp(split): test2.txt (+1)
480+
|
481+
@ f88fbe5 create test3.txt
482+
"###);
483+
}
484+
485+
{
486+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD~"])?;
487+
insta::assert_snapshot!(&stdout, @"
488+
test1.txt | 1 +
489+
test3.txt | 1 +
490+
2 files changed, 2 insertions(+)
491+
");
492+
493+
let (split_commit, _stderr) = git.run(&["query", "--raw", "exactly(siblings(HEAD), 1)"])?;
494+
let (stdout, _stderr) =
495+
git.run(&["show", "--pretty=format:", "--stat", split_commit.trim()])?;
496+
insta::assert_snapshot!(&stdout, @"
497+
test2.txt | 1 +
498+
1 file changed, 1 insertion(+)
499+
");
500+
}
501+
502+
Ok(())
503+
}
504+
442505
#[test]
443506
fn test_split_undo_works() -> eyre::Result<()> {
444507
let git = make_git()?;

0 commit comments

Comments
 (0)