Skip to content

Commit a6a0e7f

Browse files
author
Brandon W Maister
committed
Retarget branches that are the target of the amended commit (#24)
1 parent 07aeb59 commit a6a0e7f

File tree

2 files changed

+102
-23
lines changed

2 files changed

+102
-23
lines changed

src/lib.rs

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ fn do_rebase(
5858
.rebase(Some(&branch_commit), Some(&first_parent), None, None)
5959
.context("starting rebase")?;
6060

61-
apply_diff_in_rebase(repo, rebase, diff)?;
61+
let mut branches = RepoBranches::for_repo(repo)?;
6262

63-
match do_rebase_inner(repo, rebase, fixup_message) {
63+
apply_diff_in_rebase(repo, rebase, diff, &mut branches)?;
64+
65+
match do_rebase_inner(repo, rebase, fixup_message, branches) {
6466
Ok(_) => {
6567
rebase.finish(None)?;
6668
Ok(())
@@ -81,6 +83,7 @@ fn apply_diff_in_rebase(
8183
repo: &Repository,
8284
rebase: &mut Rebase,
8385
diff: &Diff,
86+
branches: &mut RepoBranches,
8487
) -> Result<(), anyhow::Error> {
8588
match rebase.next() {
8689
Some(ref res) => {
@@ -94,11 +97,11 @@ fn apply_diff_in_rebase(
9497
// TODO: Support squash amends
9598

9699
let rewrit_id = target_commit.amend(None, None, None, None, None, Some(&tree))?;
97-
repo.reset(
98-
&repo.find_object(rewrit_id, None)?,
99-
git2::ResetType::Soft,
100-
None,
101-
)?;
100+
let rewrit_object = repo.find_object(rewrit_id, None)?;
101+
let rewrit_commit_id = repo.find_commit(rewrit_object.id())?.id();
102+
branches.retarget_branches(target_commit.id(), rewrit_commit_id, rebase)?;
103+
104+
repo.reset(&rewrit_object, git2::ResetType::Soft, None)?;
102105
}
103106
None => bail!("Unable to start rebase: no first step in rebase"),
104107
};
@@ -110,16 +113,10 @@ fn do_rebase_inner(
110113
repo: &Repository,
111114
rebase: &mut Rebase,
112115
fixup_message: Option<&str>,
116+
mut branches: RepoBranches,
113117
) -> Result<(), anyhow::Error> {
114118
let sig = repo.signature()?;
115119

116-
let mut branches: HashMap<Oid, Branch> = HashMap::new();
117-
for (branch, _type) in repo.branches(Some(git2::BranchType::Local))?.flatten() {
118-
let oid = branch.get().peel_to_commit()?.id();
119-
// TODO: handle multiple branches pointing to the same commit
120-
branches.insert(oid, branch);
121-
}
122-
123120
while let Some(ref res) = rebase.next() {
124121
use git2::RebaseOperationType::*;
125122

@@ -130,15 +127,7 @@ fn do_rebase_inner(
130127
let message = commit.message();
131128
if message.is_some() && message != fixup_message {
132129
let new_id = rebase.commit(None, &sig, None)?;
133-
if let Some(branch) = branches.get_mut(&commit.id()) {
134-
// Don't retarget the last branch, rebase.finish does that for us
135-
// TODO: handle multiple branches
136-
if rebase.operation_current() != Some(rebase.len() - 1) {
137-
branch
138-
.get_mut()
139-
.set_target(new_id, "git-instafix retarget historical branch")?;
140-
}
141-
}
130+
branches.retarget_branches(commit.id(), new_id, rebase)?;
142131
}
143132
}
144133
Some(Fixup) | Some(Squash) | Some(Exec) | Some(Edit) | Some(Reword) => {
@@ -152,6 +141,39 @@ fn do_rebase_inner(
152141
Ok(())
153142
}
154143

144+
struct RepoBranches<'a>(HashMap<Oid, Branch<'a>>);
145+
146+
impl<'a> RepoBranches<'a> {
147+
fn for_repo(repo: &'a Repository) -> Result<RepoBranches<'a>, anyhow::Error> {
148+
let mut branches: HashMap<Oid, Branch> = HashMap::new();
149+
for (branch, _type) in repo.branches(Some(git2::BranchType::Local))?.flatten() {
150+
let oid = branch.get().peel_to_commit()?.id();
151+
// TODO: handle multiple branches pointing to the same commit
152+
branches.insert(oid, branch);
153+
}
154+
Ok(RepoBranches(branches))
155+
}
156+
157+
/// Move branches whos commits have moved
158+
fn retarget_branches(
159+
&mut self,
160+
original_commit: Oid,
161+
target_commit: Oid,
162+
rebase: &mut Rebase<'_>,
163+
) -> Result<(), anyhow::Error> {
164+
if let Some(branch) = self.0.get_mut(&original_commit) {
165+
// Don't retarget the last branch, rebase.finish does that for us
166+
// TODO: handle multiple branches
167+
if rebase.operation_current() != Some(rebase.len() - 1) {
168+
branch
169+
.get_mut()
170+
.set_target(target_commit, "git-instafix retarget historical branch")?;
171+
}
172+
}
173+
Ok(())
174+
}
175+
}
176+
155177
fn commit_parent<'a>(commit: &'a Commit) -> Result<Commit<'a>, anyhow::Error> {
156178
match commit.parents().next() {
157179
Some(c) => Ok(c),

tests/basic.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,63 @@ new
268268
assert_eq!(out, expected, "\nactual:\n{}\nexpected:\n{}", out, expected);
269269
}
270270

271+
#[test]
272+
fn retarget_branch_target_of_edit() {
273+
let td = assert_fs::TempDir::new().unwrap();
274+
git_init(&td);
275+
276+
git_commits(&["a", "b"], &td);
277+
git(&["checkout", "-b", "intermediate"], &td);
278+
git_commits(&["c", "d", "target"], &td);
279+
280+
git(&["checkout", "-b", "changes"], &td);
281+
git_commits(&["e", "f"], &td);
282+
283+
let expected = "\
284+
* f HEAD -> changes
285+
* e
286+
* target intermediate
287+
* d
288+
* c
289+
* b main
290+
* a
291+
";
292+
let out = git_log(&td);
293+
assert_eq!(
294+
out, expected,
295+
"before rebase:\nactual:\n{}\nexpected:\n{}",
296+
out, expected
297+
);
298+
299+
td.child("new").touch().unwrap();
300+
git(&["add", "new"], &td);
301+
302+
fixup(&td).args(&["-P", "target"]).assert().success();
303+
304+
let out = git_log(&td);
305+
assert_eq!(
306+
out, expected,
307+
"after rebase\nactual:\n{}\nexpected:\n{}",
308+
out, expected
309+
);
310+
311+
let (files, err) = git_changed_files("target", &td);
312+
assert_eq!(
313+
files,
314+
"\
315+
file_target
316+
new
317+
",
318+
"out: {} err: {}",
319+
files,
320+
err
321+
);
322+
323+
// should be identical to before
324+
let out = git_log(&td);
325+
assert_eq!(out, expected, "\nactual:\n{}\nexpected:\n{}", out, expected);
326+
}
327+
271328
///////////////////////////////////////////////////////////////////////////////
272329
// Helpers
273330

0 commit comments

Comments
 (0)