Skip to content

Commit 01314f2

Browse files
fix(move): Support on-disk rebases with move --fixup
1 parent 8673f1b commit 01314f2

File tree

5 files changed

+589
-10
lines changed

5 files changed

+589
-10
lines changed

git-branchless-hook/src/lib.rs

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

579-
HookSubcommand::SkipUpstreamAppliedCommit { commit_oid } => {
579+
HookSubcommand::SkipUpstreamAppliedCommit {
580+
commit_oid,
581+
rewritten_oid,
582+
} => {
580583
let commit_oid: NonZeroOid = commit_oid.parse()?;
581-
hook_skip_upstream_applied_commit(&effects, commit_oid)?;
584+
let rewritten_oid: MaybeZeroOid = match rewritten_oid {
585+
Some(rewritten_oid) => rewritten_oid.parse()?,
586+
None => MaybeZeroOid::Zero,
587+
};
588+
hook_skip_upstream_applied_commit(&effects, commit_oid, rewritten_oid)?;
582589
}
583590
}
584591

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

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,69 @@ impl ToString for RebaseCommand {
148148
} => match commits_to_apply_oids.as_slice() {
149149
[] => String::new(),
150150
[commit_oid] => format!("pick {commit_oid}"),
151-
[..] => unimplemented!("On-disk fixups"),
151+
[pick_oid, fixup_oids @ ..] => {
152+
let mut picks = vec![format!("pick {pick_oid}")];
153+
let mut fixups = fixup_oids
154+
.iter()
155+
.map(|oid| format!("fixup {oid}"))
156+
.collect::<Vec<String>>();
157+
let mut cleanups = vec![];
158+
159+
// Since 0ca8681, the intermediate commits created as each
160+
// fixup is applied are left behind in the smartlog. This
161+
// forcibly removes all but the last of them. I don't
162+
// understand why this happens during `git branchless`
163+
// initiated rebases, but not during "normal" fixup rebases,
164+
// but this makes these artifacts go away.
165+
//
166+
// Re implementation: `intersperse()` would be ideal here,
167+
// but it's only in the nightly API.
168+
if fixups.len() > 1 {
169+
for i in 0..(fixups.len() - 1) {
170+
fixups.insert(
171+
i*2 + 1,
172+
// FIXME I'm assuming that $(...) is not portable and will need to be changed
173+
"exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)".to_string()
174+
)
175+
}
176+
}
177+
178+
// If the destination commit (ie `original_commit_oid`) does
179+
// not come first topologically among the commits being
180+
// rebased, then the final squashed commit will end up with
181+
// the wrong metadata. (It will end up with the metadata
182+
// from the commit that *does* come first, ie `pick_oid`.)
183+
// We have to add some additional steps to make sure the
184+
// smartlog and commit metadata are left as the user
185+
// expects.
186+
if pick_oid != original_commit_oid {
187+
// See above comment related to 0ca8681
188+
picks.insert(
189+
1,
190+
"exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)".to_string()
191+
);
192+
193+
cleanups = vec![
194+
// Hide the final squashed commit
195+
"exec git branchless hook-skip-upstream-applied-commit $(git rev-parse HEAD)".to_string(),
196+
197+
// Create a new final commit by applying the
198+
// metadata from the destination commit to the (now
199+
// hidden) new commit.
200+
format!("exec git commit --amend --no-edit --reuse-message {original_commit_oid}"),
201+
202+
// register the new commit as the rewritten version
203+
// of original_commit_oid
204+
format!("exec git branchless hook-skip-upstream-applied-commit {original_commit_oid} $(git rev-parse HEAD)")
205+
];
206+
}
207+
208+
picks
209+
.iter()
210+
.chain(fixups.iter())
211+
.chain(cleanups.iter())
212+
.join("\n")
213+
}
152214
},
153215
RebaseCommand::Merge {
154216
commit_oid,
@@ -1169,9 +1231,18 @@ impl<'a> RebasePlanBuilder<'a> {
11691231
};
11701232
let first_parent_oid = *parent_oids.first().unwrap();
11711233
first_dest_oid.get_or_insert(first_parent_oid);
1172-
acc.push(RebaseCommand::Reset {
1173-
target: OidOrLabel::Oid(first_parent_oid),
1174-
});
1234+
// FIXME this may be necessary in some fixup cases, but feels drastic
1235+
// I *think* it makes sense, though: if we're building from roots down (roots out?)
1236+
// then parents will be (should be) dealt with first anyway ... no maybe it's OK?
1237+
if !state
1238+
.constraints
1239+
.commits_to_move()
1240+
.contains(&first_parent_oid)
1241+
{
1242+
acc.push(RebaseCommand::Reset {
1243+
target: OidOrLabel::Oid(first_parent_oid),
1244+
});
1245+
}
11751246

11761247
let upstream_patch_ids = if *detect_duplicate_commits_via_patch_id {
11771248
let (effects, _progress) =

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,11 +516,12 @@ pub fn hook_drop_commit_if_empty(
516516
Ok(())
517517
}
518518

519-
/// For rebases, if a commit is known to have been applied upstream, skip it
519+
/// TODO For rebases, if a commit is known to have been applied upstream, skip it
520520
/// without attempting to apply it.
521521
pub fn hook_skip_upstream_applied_commit(
522522
effects: &Effects,
523523
commit_oid: NonZeroOid,
524+
rewritten_oid: MaybeZeroOid,
524525
) -> eyre::Result<()> {
525526
let repo = Repo::from_current_dir()?;
526527
let commit = repo.find_commit_or_fail(commit_oid)?;
@@ -546,7 +547,7 @@ pub fn hook_skip_upstream_applied_commit(
546547
add_rewritten_list_entries(
547548
&repo.get_tempfile_dir(),
548549
&repo.get_rebase_state_dir_path().join("rewritten-list"),
549-
&[(commit_oid, MaybeZeroOid::Zero)],
550+
&[(commit_oid, rewritten_oid)],
550551
)?;
551552

552553
Ok(())

git-branchless-opts/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ pub enum HookSubcommand {
232232
/// The OID of the commit that was skipped.
233233
#[clap(value_parser)]
234234
commit_oid: String,
235+
236+
/// FIXME The (optional) OID of the commit that was skipped.
237+
#[clap(value_parser)]
238+
rewritten_oid: Option<String>,
235239
},
236240
}
237241

0 commit comments

Comments
 (0)