Skip to content

Commit 031542d

Browse files
authored
feat: Support arbitrary references as merge-base targets (#38)
This allows doing things like `git instafix -u v0.1.0`. Fixes #37
1 parent 6a92cf0 commit 031542d

File tree

3 files changed

+153
-38
lines changed

3 files changed

+153
-38
lines changed

CHANGELOG.md

+21-18
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,50 @@
11
# Unreleased
22

3+
- Support arbitrary refs (i.e. tags like `v0.1.0` and full refspecs
4+
like `ref/pull/ID`) as the merge-base selector.
5+
36
# Version 0.2.6
47

5-
* Fix and improve the experience of working with a main-only workflow.
8+
- Fix and improve the experience of working with a main-only workflow.
69
- Provide a tailored error message if your current branch is the selected upstream branch
710
- Work correctly with explicitly-defined remote upstream branches.
811

912
# Version 0.2.5
1013

11-
* Correctly find git repos in parent dirs of CWD
12-
* Enable experimental github release attestations in cargo-dist
14+
- Correctly find git repos in parent dirs of CWD
15+
- Enable experimental github release attestations in cargo-dist
1316

1417
# Version 0.2.4
1518

16-
* Retarget multiple branches pointing at the same commit, eg:
19+
- Retarget multiple branches pointing at the same commit, eg:
1720
> updated branch my-cool-branch: deadbeef -> c0ffee
1821
1922
# Version 0.2.3
2023

21-
* Allow setting the diff theme
22-
* Read configuration from git config as well as arguments and env vars
23-
* Choose whether to display full diff or just a diffstat based on terminal
24+
- Allow setting the diff theme
25+
- Read configuration from git config as well as arguments and env vars
26+
- Choose whether to display full diff or just a diffstat based on terminal
2427
height instead of a constant
25-
* Add -u alias for --default-upstream-branch
28+
- Add -u alias for --default-upstream-branch
2629

2730
# Version 0.2.2
2831

29-
* Correctly retarget branches if the target of the edit is also a branch (#24)
30-
* Check if main, master, develop, or trunk exist as reasonable default upstream branches
31-
* Leave the repo in a less confusing state if the edit target is a conflict
32+
- Correctly retarget branches if the target of the edit is also a branch (#24)
33+
- Check if main, master, develop, or trunk exist as reasonable default upstream branches
34+
- Leave the repo in a less confusing state if the edit target is a conflict
3235

3336
# Version 0.2.1
3437

35-
* Remove last dependency on external git binary, using libgit2 for all git interactions
36-
* Show backtraces on error if RUST_BACKTRACE=1 is in the environment
37-
* Correctly stash and unstash changes before the rebase
38+
- Remove last dependency on external git binary, using libgit2 for all git interactions
39+
- Show backtraces on error if RUST_BACKTRACE=1 is in the environment
40+
- Correctly stash and unstash changes before the rebase
3841

3942
# Version 0.2.0
4043

41-
* Rename to git-instafix because there are a bunch of existing projects named git-fixup
44+
- Rename to git-instafix because there are a bunch of existing projects named git-fixup
4245

4346
# Version 0.1.9
4447

45-
* CI and doc improvements
46-
* Use libgit2 instead of shelling out for more things.
47-
* Create binaries and install scripts with cargo-dist
48+
- CI and doc improvements
49+
- Use libgit2 instead of shelling out for more things.
50+
- Create binaries and install scripts with cargo-dist

src/selecter.rs

+10-19
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,14 @@ use std::collections::HashMap;
55
use anyhow::{anyhow, bail};
66
use console::style;
77
use dialoguer::Select;
8-
use git2::BranchType;
9-
use git2::Commit;
10-
use git2::Oid;
11-
use git2::{Branch, Repository};
8+
use git2::{Branch, BranchType, Commit, Oid, Reference, Repository};
129

1310
use crate::config;
1411
use crate::format_ref;
1512

1613
pub(crate) struct CommitSelection<'a> {
1714
pub commit: Commit<'a>,
18-
pub branch: Branch<'a>,
15+
pub reference: Reference<'a>,
1916
}
2017

2118
pub(crate) fn select_commit_to_amend<'a>(
@@ -38,9 +35,9 @@ pub(crate) fn select_commit_to_amend<'a>(
3835
let head = repo.head()?;
3936
let current_branch_name = head
4037
.shorthand()
41-
.ok_or_else(|| anyhow!("HEAD is not a branch"))?;
38+
.ok_or_else(|| anyhow!("HEAD's name is invalid utf-8"))?;
4239
if repo.head()?.peel_to_commit()?.id() == upstream.commit.id()
43-
&& current_branch_name == upstream.branch.name().unwrap().unwrap()
40+
&& current_branch_name == upstream.reference.name().unwrap()
4441
{
4542
let upstream_setting = config::UPSTREAM_SETTING;
4643
bail!(
@@ -132,24 +129,18 @@ pub(crate) fn get_merge_base<'a>(
132129
upstream_name: Option<&str>,
133130
) -> Result<Option<CommitSelection<'a>>, anyhow::Error> {
134131
let (upstream, branch) = if let Some(explicit_upstream_name) = upstream_name {
135-
let mut bt = BranchType::Local;
136-
let branch = repo
137-
.find_branch(explicit_upstream_name, BranchType::Local)
138-
.or_else(|_| {
139-
bt = BranchType::Remote;
140-
repo.find_branch(explicit_upstream_name, BranchType::Remote)
141-
})?;
142-
let b2 = repo.find_branch(explicit_upstream_name, bt)?;
143-
(branch.into_reference().peel_to_commit()?, b2)
132+
let reference = repo.resolve_reference_from_short_name(explicit_upstream_name)?;
133+
let r2 = repo.resolve_reference_from_short_name(explicit_upstream_name)?;
134+
(reference.peel_to_commit()?, r2)
144135
} else if let Some(branch) = find_default_upstream_branch(repo) {
145136
(
146137
branch.into_reference().peel_to_commit()?,
147-
find_default_upstream_branch(repo).unwrap(),
138+
find_default_upstream_branch(repo).unwrap().into_reference(),
148139
)
149140
} else if let Ok(upstream) = head_branch.upstream() {
150141
(
151142
upstream.into_reference().peel_to_commit()?,
152-
head_branch.upstream().unwrap(),
143+
head_branch.upstream().unwrap().into_reference(),
153144
)
154145
} else {
155146
return Ok(None);
@@ -166,7 +157,7 @@ pub(crate) fn get_merge_base<'a>(
166157

167158
Ok(Some(CommitSelection {
168159
commit: commit.peel_to_commit()?,
169-
branch,
160+
reference: branch,
170161
}))
171162
}
172163

tests/basic.rs

+122-1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,127 @@ new
153153
);
154154
}
155155

156+
#[test]
157+
fn simple_straightline_tag_and_reference() {
158+
let td = assert_fs::TempDir::new().unwrap();
159+
git_init(&td);
160+
161+
let base = "v0.1.0";
162+
163+
git_commits(&["a", "b"], &td);
164+
git(&["tag", base], &td);
165+
git(&["checkout", "-b", "changes"], &td);
166+
git_commits(&["target", "d"], &td);
167+
168+
let log = git_log(&td);
169+
assert_eq!(
170+
log,
171+
"\
172+
* d HEAD -> changes
173+
* target
174+
* b tag: v0.1.0, main
175+
* a
176+
",
177+
"log:\n{}",
178+
log
179+
);
180+
181+
td.child("new").touch().unwrap();
182+
git(&["add", "new"], &td);
183+
184+
fixup(&td)
185+
.args(["-P", "target", "-u", base])
186+
.assert()
187+
.success();
188+
189+
let (files, err) = git_changed_files("target", &td);
190+
191+
assert_eq!(
192+
files,
193+
"\
194+
file_target
195+
new
196+
",
197+
"out: {} err: {}",
198+
files,
199+
err
200+
);
201+
202+
// also check that we can use the full refspec definition
203+
204+
td.child("new-full-ref").touch().unwrap();
205+
git(&["add", "new-full-ref"], &td);
206+
fixup(&td)
207+
.args(["-P", "target", "-u", &format!("refs/tags/{base}")])
208+
.unwrap();
209+
210+
let (files, err) = git_changed_files("target", &td);
211+
assert_eq!(
212+
files,
213+
"\
214+
file_target
215+
new
216+
new-full-ref
217+
",
218+
"out: {} err: {}",
219+
files,
220+
err
221+
);
222+
}
223+
224+
#[test]
225+
fn simple_straightline_remote_branch() {
226+
let remote_td = assert_fs::TempDir::new().unwrap();
227+
git_init(&remote_td);
228+
git_commits(&["a", "b"], &remote_td);
229+
230+
let td = assert_fs::TempDir::new().unwrap();
231+
git_init(&td);
232+
let remote_path = &remote_td
233+
.path()
234+
.as_os_str()
235+
.to_owned()
236+
.into_string()
237+
.unwrap();
238+
git(&["remote", "add", "origin", remote_path], &td);
239+
git(&["pull", "origin", "main:main"], &td);
240+
git_commits(&["target", "d"], &td);
241+
242+
let log = git_log(&td);
243+
assert_eq!(
244+
log,
245+
"\
246+
* d HEAD -> main
247+
* target
248+
* b origin/main
249+
* a
250+
",
251+
"log:\n{}",
252+
log
253+
);
254+
255+
td.child("new").touch().unwrap();
256+
git(&["add", "new"], &td);
257+
258+
fixup(&td)
259+
.args(["-P", "target", "-u", "origin/main"])
260+
.assert()
261+
.success();
262+
263+
let (files, err) = git_changed_files("target", &td);
264+
265+
assert_eq!(
266+
files,
267+
"\
268+
file_target
269+
new
270+
",
271+
"out: {} err: {}",
272+
files,
273+
err
274+
);
275+
}
276+
156277
#[test]
157278
fn stashes_before_rebase() {
158279
let td = assert_fs::TempDir::new().unwrap();
@@ -389,7 +510,7 @@ fn git_init_default_branch_name(name: &str, tempdir: &assert_fs::TempDir) {
389510
fn git_file_commit(name: &str, tempdir: &assert_fs::TempDir) {
390511
tempdir.child(format!("file_{}", name)).touch().unwrap();
391512
git(&["add", "-A"], tempdir);
392-
git(&["commit", "-m", &name], tempdir);
513+
git(&["commit", "-m", name], tempdir);
393514
}
394515

395516
/// Get the git shown output for the target commit

0 commit comments

Comments
 (0)