Skip to content

Commit cd6ab6e

Browse files
authored
Check common branch names instead of requiring GIT_INSTAFIX_UPSTREAM (#25)
Fixes #23
1 parent 754e664 commit cd6ab6e

File tree

2 files changed

+92
-24
lines changed

2 files changed

+92
-24
lines changed

src/lib.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,32 @@ use anyhow::{anyhow, bail, Context};
44
use console::style;
55
use dialoguer::{Confirm, Select};
66
use git2::{
7-
Branch, Commit, Diff, DiffFormat, DiffStatsFormat, Object, ObjectType, Oid, Rebase, Repository,
7+
Branch, BranchType, Commit, Diff, DiffFormat, DiffStatsFormat, Object, Oid, Rebase, Repository,
88
};
99
use syntect::easy::HighlightLines;
1010
use syntect::highlighting::ThemeSet;
1111
use syntect::parsing::SyntaxSet;
1212
use syntect::util::as_24_bit_terminal_escaped;
1313

14+
const DEFAULT_UPSTREAM_BRANCHES: &[&str] = &["main", "master", "develop", "trunk"];
15+
1416
pub fn instafix(
1517
squash: bool,
1618
max_commits: usize,
1719
message_pattern: Option<String>,
1820
upstream_branch_name: Option<&str>,
1921
require_newline: bool,
2022
) -> Result<(), anyhow::Error> {
21-
let repo = Repository::open(".")?;
22-
let diff = create_diff(&repo, require_newline)?;
23+
let repo = Repository::open(".").context("opening repo")?;
24+
let diff = create_diff(&repo, require_newline).context("creating diff")?;
2325
let head = repo.head().context("finding head commit")?;
2426
let head_branch = Branch::wrap(head);
25-
let upstream = get_upstream(&repo, &head_branch, upstream_branch_name)?;
26-
let commit_to_amend = select_commit_to_amend(&repo, upstream, max_commits, &message_pattern)?;
27+
let upstream =
28+
get_merge_base(&repo, &head_branch, upstream_branch_name).context("creating merge base")?;
29+
let commit_to_amend = select_commit_to_amend(&repo, upstream, max_commits, &message_pattern)
30+
.context("selecting commit to amend")?;
2731
eprintln!("Selected {}", disp(&commit_to_amend));
28-
do_fixup_commit(&repo, &head_branch, &commit_to_amend, squash)?;
32+
do_fixup_commit(&repo, &head_branch, &commit_to_amend, squash).context("doing fixup commit")?;
2933
let needs_stash = worktree_is_dirty(&repo)?;
3034
if needs_stash {
3135
// TODO: is it reasonable to create a new repo to work around lifetime issues?
@@ -190,24 +194,21 @@ fn disp(commit: &Commit) -> String {
190194
)
191195
}
192196

193-
fn get_upstream<'a>(
197+
fn get_merge_base<'a>(
194198
repo: &'a Repository,
195199
head_branch: &'a Branch,
196200
upstream_name: Option<&str>,
197201
) -> Result<Option<Object<'a>>, anyhow::Error> {
198-
let upstream = if let Some(upstream_name) = upstream_name {
199-
let branch = repo
200-
.branches(None)?
201-
.filter_map(|branch| branch.ok().map(|(b, _type)| b))
202-
.find(|b| {
203-
b.name()
204-
.map(|n| n.expect("valid utf8 branchname") == upstream_name)
205-
.unwrap_or(false)
206-
})
207-
.ok_or_else(|| anyhow!("cannot find branch with name {:?}", upstream_name))?;
208-
branch.into_reference().peel(ObjectType::Commit)?
202+
let upstream = if let Some(explicit_upstream_name) = upstream_name {
203+
let branch = repo.find_branch(explicit_upstream_name, BranchType::Local)?;
204+
branch.into_reference().peel_to_commit()?
205+
} else if let Some(branch) = DEFAULT_UPSTREAM_BRANCHES
206+
.iter()
207+
.find_map(|b| repo.find_branch(b, BranchType::Local).ok())
208+
{
209+
branch.into_reference().peel_to_commit()?
209210
} else if let Ok(upstream) = head_branch.upstream() {
210-
upstream.into_reference().peel(ObjectType::Commit)?
211+
upstream.into_reference().peel_to_commit()?
211212
} else {
212213
return Ok(None);
213214
};
@@ -335,6 +336,8 @@ fn select_commit_to_amend<'a>(
335336
})
336337
.collect();
337338
if let Some(message_pattern) = message_pattern.as_ref() {
339+
let first = commit_id_and_summary(&commits, commits.len() - 1);
340+
let last = commit_id_and_summary(&commits, 0);
338341
commits
339342
.into_iter()
340343
.find(|commit| {
@@ -343,7 +346,13 @@ fn select_commit_to_amend<'a>(
343346
.map(|s| s.contains(message_pattern))
344347
.unwrap_or(false)
345348
})
346-
.ok_or_else(|| anyhow::anyhow!("No commit contains the pattern in its summary"))
349+
.ok_or_else(|| {
350+
anyhow::anyhow!(
351+
"No commit contains the pattern in its summary between {}..{}",
352+
first,
353+
last
354+
)
355+
})
347356
} else {
348357
let rev_aliases = commits
349358
.iter()
@@ -375,6 +384,20 @@ fn select_commit_to_amend<'a>(
375384
}
376385
}
377386

387+
fn commit_id_and_summary(commits: &[Commit<'_>], idx: usize) -> String {
388+
let first = commits
389+
.get(idx)
390+
.map(|c| {
391+
format!(
392+
"{} ({})",
393+
&c.id().to_string()[..10],
394+
c.summary().unwrap_or("<unknown>")
395+
)
396+
})
397+
.unwrap_or_else(|| "<unknown>".into());
398+
first
399+
}
400+
378401
fn format_ref(rf: &git2::Reference<'_>) -> Result<String, anyhow::Error> {
379402
let shorthand = rf.shorthand().unwrap_or("<unnamed>");
380403
let sha = rf.peel_to_commit()?.id().to_string();

tests/basic.rs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ fn test_can_compile() {
2020
}
2121

2222
#[test]
23-
fn test_straightforward() {
23+
fn straightforward() {
2424
let td = assert_fs::TempDir::new().unwrap();
2525
git_init(&td);
2626

2727
git_file_commit("a", &td);
2828
git_file_commit("b", &td);
2929
git(&["checkout", "-b", "changes", "HEAD~"], &td);
30-
git(&["branch", "-u", "main"], &td);
3130
for n in &["c", "d", "e"] {
3231
git_file_commit(&n, &td);
3332
}
@@ -71,6 +70,47 @@ new
7170
);
7271
}
7372

73+
#[test]
74+
fn uses_merge_base_for_all_defaults() {
75+
for branch in ["main", "develop", "trunk", "master"] {
76+
eprintln!("testing branch {branch}");
77+
let td = assert_fs::TempDir::new().unwrap();
78+
git_init_default_branch_name(branch, &td);
79+
80+
git_commits(&["a", "b", "c", "d"], &td);
81+
git(&["checkout", "-b", "changes", ":/c"], &td);
82+
git_commits(&["f", "g"], &td);
83+
84+
let expected = format!(
85+
"\
86+
* g HEAD -> changes
87+
* f
88+
| * d {branch}
89+
|/
90+
* c
91+
* b
92+
* a
93+
"
94+
);
95+
let actual = git_log(&td);
96+
assert_eq!(
97+
expected, actual,
98+
"expected:\n{}\nactual:\n{}",
99+
expected, actual
100+
);
101+
102+
// commits *before* the merge base of a default branch don't get found by
103+
// default
104+
td.child("new").touch().unwrap();
105+
git(&["add", "new"], &td);
106+
fixup(&td).args(&["-P", "b"]).assert().failure();
107+
// commits *after* the merge base of a default branch *do* get found by default
108+
git(&["reset", "HEAD~"], &td);
109+
git(&["add", "new"], &td);
110+
fixup(&td).args(&["-P", "f"]).assert().success();
111+
}
112+
}
113+
74114
#[test]
75115
fn simple_straightline_commits() {
76116
let td = assert_fs::TempDir::new().unwrap();
@@ -335,7 +375,11 @@ fn git_commits(ids: &[&str], tempdir: &assert_fs::TempDir) {
335375
}
336376

337377
fn git_init(tempdir: &assert_fs::TempDir) {
338-
git(&["init", "--initial-branch=main"], &tempdir);
378+
git_init_default_branch_name("main", tempdir)
379+
}
380+
381+
fn git_init_default_branch_name(name: &str, tempdir: &assert_fs::TempDir) {
382+
git(&["init", "--initial-branch", name], &tempdir);
339383
git(&["config", "user.email", "[email protected]"], &tempdir);
340384
git(&["config", "user.name", "nobody"], &tempdir);
341385
}
@@ -403,6 +447,7 @@ fn git_inner(args: &[&str], tempdir: &assert_fs::TempDir) -> Command {
403447
/// Get something that can get args added to it
404448
fn fixup(dir: &assert_fs::TempDir) -> Command {
405449
let mut c = Command::cargo_bin("git-instafix").unwrap();
406-
c.current_dir(&dir.path());
450+
c.current_dir(&dir.path())
451+
.env_remove("GIT_INSTAFIX_UPSTREAM");
407452
c
408453
}

0 commit comments

Comments
 (0)