Skip to content

Commit 8d1f132

Browse files
fix(move): Support on-disk rebases with move --fixup
1 parent 2cb8484 commit 8d1f132

File tree

5 files changed

+586
-13
lines changed

5 files changed

+586
-13
lines changed

git-branchless-hook/src/lib.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -665,9 +665,16 @@ pub fn command_main(ctx: CommandContext, args: HookArgs) -> EyreExitOr<()> {
665665
hook_register_extra_post_rewrite_hook()?;
666666
}
667667

668-
HookSubcommand::SkipUpstreamAppliedCommit { commit_oid } => {
668+
HookSubcommand::SkipUpstreamAppliedCommit {
669+
commit_oid,
670+
rewritten_oid,
671+
} => {
669672
let commit_oid: NonZeroOid = commit_oid.parse()?;
670-
hook_skip_upstream_applied_commit(&effects, commit_oid)?;
673+
let rewritten_oid: MaybeZeroOid = match rewritten_oid {
674+
Some(rewritten_oid) => rewritten_oid.parse()?,
675+
None => MaybeZeroOid::Zero,
676+
};
677+
hook_skip_upstream_applied_commit(&effects, commit_oid, rewritten_oid)?;
671678
}
672679
}
673680

git-branchless-lib/src/core/rewrite/plan.rs

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::sync::Arc;
66

77
use chashmap::CHashMap;
88
use eyre::Context;
9-
use itertools::Itertools;
9+
use itertools::{intersperse, Itertools};
1010
use rayon::{prelude::*, ThreadPool};
1111
use tracing::{instrument, warn};
1212

@@ -133,12 +133,68 @@ impl RebaseCommand {
133133
RebaseCommand::CreateLabel { label_name } => format!("label {label_name}"),
134134
RebaseCommand::Reset { target } => format!("reset {target}"),
135135
RebaseCommand::Pick {
136-
original_commit_oid: _,
136+
original_commit_oid,
137137
commits_to_apply_oids,
138138
} => match commits_to_apply_oids.as_slice() {
139139
[] => String::new(),
140140
[commit_oid] => format!("pick {commit_oid}"),
141-
[..] => unimplemented!("On-disk fixups are not yet supported"),
141+
[pick_oid, fixup_oids @ ..] => {
142+
let mut picks = vec![format!("pick {pick_oid}")];
143+
let mut fixups = fixup_oids
144+
.iter()
145+
.map(|oid| format!("fixup {oid}"))
146+
.collect::<Vec<String>>();
147+
let mut cleanups = vec![];
148+
149+
// Since 0ca8681, the intermediate commits created as each
150+
// fixup is applied are left behind in the smartlog. This
151+
// forcibly removes all but the last of them. I don't
152+
// understand why this happens during `git branchless`
153+
// initiated rebases, but not during "normal" fixup rebases,
154+
// but this makes these artifacts go away.
155+
if fixups.len() > 1 {
156+
fixups = intersperse(
157+
fixups,
158+
"exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)".to_string()
159+
).collect()
160+
}
161+
162+
// If the destination commit (ie `original_commit_oid`) does
163+
// not come first topologically among the commits being
164+
// rebased, then the final squashed commit will end up with
165+
// the wrong metadata. (It will end up with the metadata
166+
// from the commit that *does* come first, ie `pick_oid`.)
167+
// We have to add some additional steps to make sure the
168+
// smartlog and commit metadata are left as the user
169+
// expects.
170+
if pick_oid != original_commit_oid {
171+
// See above comment related to 0ca8681
172+
picks.insert(
173+
1,
174+
"exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)".to_string()
175+
);
176+
177+
cleanups = vec![
178+
// Hide the final squashed commit
179+
"exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)".to_string(),
180+
181+
// Create a new final commit by applying the
182+
// metadata from the destination commit to the just
183+
// now hidden commit.
184+
format!("exec git commit --amend --no-edit --reuse-message {original_commit_oid}"),
185+
186+
// Finally, register the new final commit as the
187+
// rewritten version of original_commit_oid
188+
format!("exec git branchless hook-skip-upstream-applied-commit {original_commit_oid} $(git rev-parse HEAD)")
189+
];
190+
}
191+
192+
picks
193+
.iter()
194+
.chain(fixups.iter())
195+
.chain(cleanups.iter())
196+
.join("\n")
197+
}
142198
},
143199
RebaseCommand::Merge {
144200
commit_oid,
@@ -1172,9 +1228,16 @@ impl<'a> RebasePlanBuilder<'a> {
11721228

11731229
let first_parent_oid = *parent_oids.first().unwrap();
11741230
first_dest_oid.get_or_insert(first_parent_oid);
1175-
acc.push(RebaseCommand::Reset {
1176-
target: OidOrLabel::Oid(first_parent_oid),
1177-
});
1231+
// FIXME This feels heavy handed, but seems to be necessary for some fixup cases.
1232+
if !state
1233+
.constraints
1234+
.commits_to_move()
1235+
.contains(&first_parent_oid)
1236+
{
1237+
acc.push(RebaseCommand::Reset {
1238+
target: OidOrLabel::Oid(first_parent_oid),
1239+
});
1240+
}
11781241

11791242
let upstream_patch_ids = if *detect_duplicate_commits_via_patch_id {
11801243
let (effects, _progress) =

git-branchless-lib/src/core/rewrite/rewrite_hooks.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -518,11 +518,14 @@ pub fn hook_drop_commit_if_empty(
518518
Ok(())
519519
}
520520

521-
/// For rebases, if a commit is known to have been applied upstream, skip it
522-
/// without attempting to apply it.
521+
/// For rebases, update the status of a commit that is known to have been
522+
/// applied upstream. It can either be skipped entirely (when called with
523+
/// `MaybeZeroOid::Zero`) or be marked as having been rewritten to a
524+
/// different commit entirely.
523525
pub fn hook_skip_upstream_applied_commit(
524526
effects: &Effects,
525527
commit_oid: NonZeroOid,
528+
rewritten_oid: MaybeZeroOid,
526529
) -> eyre::Result<()> {
527530
let repo = Repo::from_current_dir()?;
528531
let commit = repo.find_commit_or_fail(commit_oid)?;
@@ -548,7 +551,7 @@ pub fn hook_skip_upstream_applied_commit(
548551
add_rewritten_list_entries(
549552
&repo.get_tempfile_dir()?,
550553
&repo.get_rebase_state_dir_path().join("rewritten-list"),
551-
&[(commit_oid, MaybeZeroOid::Zero)],
554+
&[(commit_oid, rewritten_oid)],
552555
)?;
553556

554557
Ok(())

git-branchless-opts/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ pub enum HookSubcommand {
235235
/// The OID of the commit that was skipped.
236236
#[clap(value_parser)]
237237
commit_oid: String,
238+
239+
/// The OID of the commit that was skipped (if Some) or removed (if None).
240+
#[clap(value_parser)]
241+
rewritten_oid: Option<String>,
238242
},
239243
}
240244

0 commit comments

Comments
 (0)