Skip to content

Commit 455e652

Browse files
feat(split): add --discard flag
1 parent b86249a commit 455e652

File tree

4 files changed

+107
-35
lines changed

4 files changed

+107
-35
lines changed

git-branchless-opts/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,9 +661,13 @@ pub enum Command {
661661
files: Vec<String>,
662662

663663
/// Restack any descendents onto the split commit, not the extracted commit.
664-
#[clap(action, short = 'd', long = "detach")]
664+
#[clap(action, short = 'd', long)]
665665
detach: bool,
666666

667+
/// After extracting the changes, don't recommit them.
668+
#[clap(action, short = 'D', long = "discard", conflicts_with("detach"))]
669+
discard: bool,
670+
667671
/// Options for resolving revset expressions.
668672
#[clap(flatten)]
669673
resolve_revset_options: ResolveRevsetOptions,

git-branchless/src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ fn command_main(ctx: CommandContext, opts: Opts) -> EyreExitOr<()> {
182182

183183
Command::Split {
184184
detach,
185+
discard,
185186
files,
186187
resolve_revset_options,
187188
revset,
@@ -192,6 +193,7 @@ fn command_main(ctx: CommandContext, opts: Opts) -> EyreExitOr<()> {
192193
&resolve_revset_options,
193194
files,
194195
detach,
196+
discard,
195197
&move_options,
196198
&git_run_info,
197199
)?,

git-branchless/src/commands/split.rs

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub fn split(
4242
resolve_revset_options: &ResolveRevsetOptions,
4343
files_to_extract: Vec<String>,
4444
detach: bool,
45+
discard: bool,
4546
move_options: &MoveOptions,
4647
git_run_info: &GitRunInfo,
4748
) -> EyreExitOr<()> {
@@ -248,46 +249,57 @@ pub fn split(
248249
return Ok(Err(ExitCode(1)));
249250
};
250251

251-
let extracted_tree = repo.cherry_pick_fast(
252-
&commit_to_split,
253-
&split_commit,
254-
&CherryPickFastOptions {
255-
reuse_parent_tree_if_possible: true,
256-
},
257-
)?;
258-
let extracted_commit_oid = repo.create_commit(
259-
None,
260-
&commit_to_split.get_author(),
261-
&commit_to_split.get_committer(),
262-
format!("temp(split): {message}").as_str(),
263-
&extracted_tree,
264-
vec![&split_commit],
265-
)?;
266-
267-
// see git-branchless/src/commands/amend.rs:172
268-
// TODO maybe this should happen after we've confirmed the rebase has succeeded
269-
mark_commit_reachable(&repo, extracted_commit_oid)
270-
.wrap_err("Marking commit as reachable for GC purposes.")?;
271252
event_log_db.add_events(vec![Event::RewriteEvent {
272253
timestamp: now.duration_since(UNIX_EPOCH)?.as_secs_f64(),
273254
event_tx_id,
274255
old_commit_oid: MaybeZeroOid::NonZero(commit_to_split_oid),
275256
new_commit_oid: MaybeZeroOid::NonZero(split_commit_oid),
276257
}])?;
277-
event_log_db.add_events(vec![Event::CommitEvent {
278-
timestamp: now.duration_since(UNIX_EPOCH)?.as_secs_f64(),
279-
event_tx_id,
280-
commit_oid: extracted_commit_oid,
281-
}])?;
258+
259+
let extracted_commit_oid = if discard {
260+
None
261+
} else {
262+
let extracted_tree = repo.cherry_pick_fast(
263+
&commit_to_split,
264+
&split_commit,
265+
&CherryPickFastOptions {
266+
reuse_parent_tree_if_possible: true,
267+
},
268+
)?;
269+
let extracted_commit_oid = repo.create_commit(
270+
None,
271+
&commit_to_split.get_author(),
272+
&commit_to_split.get_committer(),
273+
format!("temp(split): {message}").as_str(),
274+
&extracted_tree,
275+
vec![&split_commit],
276+
)?;
277+
278+
// see git-branchless/src/commands/amend.rs:172
279+
// TODO maybe this should happen after we've confirmed the rebase has succeeded
280+
mark_commit_reachable(&repo, extracted_commit_oid)
281+
.wrap_err("Marking commit as reachable for GC purposes.")?;
282+
283+
event_log_db.add_events(vec![Event::CommitEvent {
284+
timestamp: now.duration_since(UNIX_EPOCH)?.as_secs_f64(),
285+
event_tx_id,
286+
commit_oid: extracted_commit_oid,
287+
}])?;
288+
289+
Some(extracted_commit_oid)
290+
};
282291

283292
// push the new commits into the dag for the rebase planner
284293
dag.sync_from_oids(
285294
effects,
286295
&repo,
287296
CommitSet::empty(),
288-
vec![split_commit_oid, extracted_commit_oid]
289-
.into_iter()
290-
.collect(),
297+
match extracted_commit_oid {
298+
None => CommitSet::from(split_commit_oid),
299+
Some(extracted_commit_oid) => vec![split_commit_oid, extracted_commit_oid]
300+
.into_iter()
301+
.collect(),
302+
},
291303
)?;
292304

293305
let head_info = repo.get_head_info()?;
@@ -297,11 +309,11 @@ pub fn split(
297309
ResolvedReferenceInfo {
298310
oid: Some(oid),
299311
reference_name: Some(_),
300-
} if oid == commit_to_split_oid && !detach => (
312+
} if oid == commit_to_split_oid && !detach && extracted_commit_oid.is_some() => (
301313
None,
302314
vec![(
303315
commit_to_split_oid,
304-
MaybeZeroOid::NonZero(extracted_commit_oid),
316+
MaybeZeroOid::NonZero(extracted_commit_oid.unwrap()),
305317
)],
306318
),
307319

@@ -353,10 +365,11 @@ pub fn split(
353365
let mut builder = RebasePlanBuilder::new(&dag, permissions);
354366
let children = dag.query_children(CommitSet::from(commit_to_split_oid))?;
355367
for child in dag.commit_set_to_vec(&children)? {
356-
if detach {
357-
builder.move_subtree(child, vec![split_commit_oid])?;
358-
} else {
359-
builder.move_subtree(child, vec![extracted_commit_oid])?;
368+
match (detach, extracted_commit_oid) {
369+
(_, None) | (true, Some(_)) => builder.move_subtree(child, vec![split_commit_oid])?,
370+
(_, Some(extracted_commit_oid)) => {
371+
builder.move_subtree(child, vec![extracted_commit_oid])?
372+
}
360373
}
361374
}
362375
let rebase_plan = builder.build(effects, &pool, &repo_pool)?;

git-branchless/tests/test_split.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,59 @@ fn test_split_detach() -> eyre::Result<()> {
502502
Ok(())
503503
}
504504

505+
#[test]
506+
fn test_split_discard() -> eyre::Result<()> {
507+
let git = make_git()?;
508+
git.init_repo()?;
509+
git.detach_head()?;
510+
511+
git.write_file_txt("test1", "contents1")?;
512+
git.write_file_txt("test2", "contents2")?;
513+
git.write_file_txt("test3", "contents3")?;
514+
git.run(&["add", "."])?;
515+
git.run(&["commit", "-m", "first commit"])?;
516+
517+
git.commit_file("test3", 1)?;
518+
519+
{
520+
let (stdout, _stderr) = git.branchless("smartlog", &[])?;
521+
insta::assert_snapshot!(stdout, @r###"
522+
O f777ecc (master) create initial.txt
523+
|
524+
o e48cdc5 first commit
525+
|
526+
@ 3d220e0 create test3.txt
527+
"###);
528+
}
529+
530+
{
531+
let (stdout, _stderr) = git.branchless("split", &["HEAD~", "test2.txt", "--discard"])?;
532+
insta::assert_snapshot!(&stdout, @r###"
533+
Attempting rebase in-memory...
534+
[1/1] Committed as: f88fbe5 create test3.txt
535+
branchless: processing 1 rewritten commit
536+
branchless: running command: <git-executable> checkout f88fbe5901493ffe1c669cdb8aa5f056dc0bb605
537+
In-memory rebase succeeded.
538+
O f777ecc (master) create initial.txt
539+
|
540+
o 2932db7 first commit
541+
|
542+
@ f88fbe5 create test3.txt
543+
"###);
544+
}
545+
546+
{
547+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD~"])?;
548+
insta::assert_snapshot!(&stdout, @"
549+
test1.txt | 1 +
550+
test3.txt | 1 +
551+
2 files changed, 2 insertions(+)
552+
");
553+
}
554+
555+
Ok(())
556+
}
557+
505558
#[test]
506559
fn test_split_undo_works() -> eyre::Result<()> {
507560
let git = make_git()?;

0 commit comments

Comments
 (0)