Skip to content

Commit 104cda3

Browse files
authored
fix: Stash and pop any unstaged changes before rebasing (#22)
In this particular case poping is I think guaranteed to be conflict free.
1 parent 9bb7399 commit 104cda3

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* Remove last dependency on external git binary, using libgit2 for all git interactions
44
* Show backtraces on error if RUST_BACKTRACE=1 is in the environment
5+
* Correctly stash and unstash changes before the rebase
56

67
# Version 0.2.0
78

src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,19 @@ pub fn instafix(
2626
let commit_to_amend = select_commit_to_amend(&repo, upstream, max_commits, &message_pattern)?;
2727
eprintln!("Selected {}", disp(&commit_to_amend));
2828
do_fixup_commit(&repo, &head_branch, &commit_to_amend, squash)?;
29+
let needs_stash = worktree_is_dirty(&repo)?;
30+
if needs_stash {
31+
// TODO: is it reasonable to create a new repo to work around lifetime issues?
32+
let mut repo = Repository::open(".")?;
33+
let sig = repo.signature()?.clone();
34+
repo.stash_save(&sig, "git-instafix stashing changes", None)?;
35+
}
2936
let current_branch = Branch::wrap(repo.head()?);
3037
do_rebase(&repo, &current_branch, &commit_to_amend, &diff)?;
38+
if needs_stash {
39+
let mut repo = Repository::open(".")?;
40+
repo.stash_pop(0, None)?;
41+
}
3142

3243
Ok(())
3344
}
@@ -253,6 +264,16 @@ fn create_diff(repo: &Repository, require_newline: bool) -> Result<Diff, anyhow:
253264
Ok(diff)
254265
}
255266

267+
fn worktree_is_dirty(repo: &Repository) -> Result<bool, anyhow::Error> {
268+
let head = repo.head()?;
269+
let head_tree = head.peel_to_tree()?;
270+
let staged_diff = repo.diff_tree_to_index(Some(&head_tree), None, None)?;
271+
let dirty_diff = repo.diff_index_to_workdir(None, None)?;
272+
let diffstat = staged_diff.stats()?;
273+
let dirty_workdir_stats = dirty_diff.stats()?;
274+
Ok(diffstat.files_changed() > 0 || dirty_workdir_stats.files_changed() > 0)
275+
}
276+
256277
/// Commit the current index as a fixup or squash commit
257278
fn do_fixup_commit<'a>(
258279
repo: &'a Repository,

tests/basic.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,57 @@ new
113113
);
114114
}
115115

116+
#[test]
117+
fn stashes_before_rebase() {
118+
let td = assert_fs::TempDir::new().unwrap();
119+
git_init(&td);
120+
121+
git_commits(&["a", "b"], &td);
122+
git(&["checkout", "-b", "changes"], &td);
123+
git(&["branch", "-u", "main"], &td);
124+
git_commits(&["target", "d"], &td);
125+
126+
let log = git_log(&td);
127+
assert_eq!(
128+
log,
129+
"\
130+
* d HEAD -> changes
131+
* target
132+
* b main
133+
* a
134+
",
135+
"log:\n{}",
136+
log
137+
);
138+
139+
td.child("new").touch().unwrap();
140+
141+
let edited_file = "file_d";
142+
td.child(edited_file).write_str("somthing").unwrap();
143+
144+
git(&["add", "new"], &td);
145+
let tracked_changed_files = git_worktree_changed_files(&td);
146+
assert_eq!(tracked_changed_files.trim(), edited_file);
147+
148+
fixup(&td).args(&["-P", "target"]).assert().success();
149+
150+
let (files, err) = git_changed_files("target", &td);
151+
152+
assert_eq!(
153+
files,
154+
"\
155+
file_target
156+
new
157+
",
158+
"out: {} err: {}",
159+
files,
160+
err
161+
);
162+
163+
let popped_stashed_files = git_worktree_changed_files(&td);
164+
assert_eq!(popped_stashed_files.trim(), edited_file);
165+
}
166+
116167
#[test]
117168
fn test_no_commit_in_range() {
118169
let td = assert_fs::TempDir::new().unwrap();
@@ -254,6 +305,10 @@ fn git_changed_files(name: &str, tempdir: &assert_fs::TempDir) -> (String, Strin
254305
(string(out.stdout), string(out.stderr))
255306
}
256307

308+
fn git_worktree_changed_files(td: &assert_fs::TempDir) -> String {
309+
string(git_out(&["diff", "--name-only"], td).stdout)
310+
}
311+
257312
/// Run git in tempdir with args and panic if theres an error
258313
fn git(args: &[&str], tempdir: &assert_fs::TempDir) {
259314
git_inner(args, tempdir).ok().unwrap();

0 commit comments

Comments
 (0)