Skip to content

Commit 69d3461

Browse files
authored
Merge pull request #209 from epage/remote
feat(git): Allow remote refs for --base/--onto
2 parents e295132 + 2abcd5d commit 69d3461

File tree

9 files changed

+241
-128
lines changed

9 files changed

+241
-128
lines changed

src/bin/git-stack/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ pub fn protected(args: &crate::args::Args) -> proc_exit::ExitResult {
6565
for (branch_id, branches) in branches.iter() {
6666
if protected_branches.contains_oid(branch_id) {
6767
for branch in branches {
68-
writeln!(std::io::stdout(), "{}", branch.name)?;
68+
writeln!(std::io::stdout(), "{}", branch)?;
6969
}
7070
} else {
7171
for branch in branches {
72-
log::debug!("Unprotected: {}", branch.name);
72+
log::debug!("Unprotected: {}", branch);
7373
}
7474
}
7575
}

src/bin/git-stack/stack.rs

Lines changed: 90 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl State {
169169
git2::Error::new(
170170
git2::ErrorCode::NotFound,
171171
git2::ErrorClass::Reference,
172-
format!("could not find base between {} and HEAD", base.name),
172+
format!("could not find base between {} and HEAD", base),
173173
)
174174
})
175175
.with_code(proc_exit::Code::USAGE_ERR)?;
@@ -239,28 +239,32 @@ struct StackState {
239239

240240
impl StackState {
241241
fn update(&mut self, repo: &dyn git_stack::git::Repo) -> eyre::Result<()> {
242-
self.base = repo
243-
.find_local_branch(self.base.name.as_str())
244-
.ok_or_else(|| eyre::eyre!("can no longer find branch {}", self.base.name))?;
245-
self.onto = repo
246-
.find_local_branch(self.onto.name.as_str())
247-
.ok_or_else(|| eyre::eyre!("can no longer find branch {}", self.onto.name))?;
242+
self.base = update_branch(repo, &self.base)?;
243+
self.onto = update_branch(repo, &self.onto)?;
248244
self.branches.update(repo);
249245
Ok(())
250246
}
251247

252248
fn graphed_branches(&self) -> git_stack::git::Branches {
253249
let mut graphed_branches = self.branches.clone();
254-
if !graphed_branches.contains_oid(self.base.id) {
255-
graphed_branches.insert(self.base.clone());
256-
}
257-
if !graphed_branches.contains_oid(self.onto.id) {
258-
graphed_branches.insert(self.onto.clone());
259-
}
250+
graphed_branches.insert(self.base.clone());
251+
graphed_branches.insert(self.onto.clone());
260252
graphed_branches
261253
}
262254
}
263255

256+
fn update_branch(
257+
repo: &dyn git_stack::git::Repo,
258+
branch: &git_stack::git::Branch,
259+
) -> eyre::Result<git_stack::git::Branch> {
260+
if let Some(remote) = &branch.remote {
261+
repo.find_remote_branch(remote, &branch.name)
262+
} else {
263+
repo.find_local_branch(&branch.name)
264+
}
265+
.ok_or_else(|| eyre::eyre!("Can no longer find branch {}", branch))
266+
}
267+
264268
pub fn stack(
265269
args: &crate::args::Args,
266270
colored_stdout: bool,
@@ -280,7 +284,7 @@ pub fn stack(
280284
.flat_map(|stack| stack.branches.iter())
281285
.filter(|(oid, _)| !state.protected_branches.contains_oid(*oid))
282286
.flat_map(|(_, b)| b.iter())
283-
.filter_map(|b| b.push_id.map(|_| b.name.as_str()))
287+
.filter_map(|b| b.push_id.and_then(|_| b.local_name()))
284288
.collect();
285289
push_branches.sort_unstable();
286290
if !push_branches.is_empty() {
@@ -297,14 +301,11 @@ pub fn stack(
297301
match git_fetch_upstream(&mut state.repo, stack.onto.name.as_str()) {
298302
Ok(_) => (),
299303
Err(err) => {
300-
log::warn!("Skipping pull of `{}`, {}", stack.onto.name, err);
304+
log::warn!("Skipping pull of `{}`, {}", stack.onto, err);
301305
}
302306
}
303307
} else {
304-
log::warn!(
305-
"Skipping pull of `{}`, not a protected branch",
306-
stack.onto.name
307-
);
308+
log::warn!("Skipping pull of `{}`, not a protected branch", stack.onto);
308309
}
309310
}
310311
state.update().with_code(proc_exit::Code::FAILURE)?;
@@ -355,7 +356,9 @@ pub fn stack(
355356
.map(|stack| {
356357
let script = plan_changes(&state, stack).with_code(proc_exit::Code::FAILURE)?;
357358
if script.is_branch_deleted(&head_branch) {
358-
head_branch = stack.onto.name.clone();
359+
if let Some(local_name) = stack.onto.local_name() {
360+
head_branch = local_name.to_owned();
361+
}
359362
}
360363
Ok(script)
361364
})
@@ -411,7 +414,7 @@ pub fn stack(
411414
}
412415

413416
fn plan_changes(state: &State, stack: &StackState) -> eyre::Result<git_stack::git::Script> {
414-
log::trace!("Planning stack changes with base={}", stack.base.name,);
417+
log::trace!("Planning stack changes with base={}", stack.base,);
415418
let graphed_branches = stack.graphed_branches();
416419
let base_commit = state
417420
.repo
@@ -445,7 +448,7 @@ fn plan_changes(state: &State, stack: &StackState) -> eyre::Result<git_stack::gi
445448

446449
let mut dropped_branches = Vec::new();
447450
if state.rebase {
448-
log::trace!("Rebasing onto {}", stack.onto.name);
451+
log::trace!("Rebasing onto {}", stack.onto);
449452
let onto_id = stack.onto.pull_id.unwrap_or(stack.onto.id);
450453
let pull_start_id = stack.onto.id;
451454
let pull_start_id = state
@@ -542,12 +545,12 @@ fn show(state: &State, colored_stdout: bool, colored_stderr: bool) -> eyre::Resu
542545
if graphed_branches.len() == 1 && abbrev_graph {
543546
let branches = graphed_branches.iter().next().unwrap().1;
544547
if branches.len() == 1 && branches[0].id != state.head_commit.id {
545-
empty_stacks.push(format!("{}", palette_stderr.info.paint(&branches[0].name)));
548+
empty_stacks.push(format!("{}", palette_stderr.info.paint(&branches[0])));
546549
continue;
547550
}
548551
}
549552

550-
log::trace!("Rendering stack base={}", stack.base.name,);
553+
log::trace!("Rendering stack base={}", stack.base,);
551554
let base_commit = state
552555
.repo
553556
.find_commit(stack.base.id)
@@ -605,7 +608,7 @@ fn show(state: &State, colored_stdout: bool, colored_stderr: bool) -> eyre::Resu
605608
if state.dry_run {
606609
// Show as-if we performed all mutations
607610
if state.rebase {
608-
log::trace!("Rebasing onto {}", stack.onto.name);
611+
log::trace!("Rebasing onto {}", stack.onto);
609612
let onto_id = stack.onto.pull_id.unwrap_or(stack.onto.id);
610613
let pull_start_id = stack.onto.id;
611614
let pull_start_id = state
@@ -709,11 +712,27 @@ fn show(state: &State, colored_stdout: bool, colored_stderr: bool) -> eyre::Resu
709712
}
710713

711714
fn resolve_explicit_base(
712-
repo: &dyn git_stack::git::Repo,
715+
repo: &git_stack::git::GitRepo,
713716
base: &str,
714717
) -> eyre::Result<git_stack::git::Branch> {
715-
repo.find_local_branch(base)
716-
.ok_or_else(|| eyre::eyre!("could not find branch {:?}", base))
718+
let (_obj, r) = repo.raw().revparse_ext(base)?;
719+
let r = r.ok_or_else(|| eyre::eyre!("Expected branch, got `{}`", base))?;
720+
if r.is_tag() {
721+
eyre::bail!("Expected branch, got tag `{}`", base);
722+
}
723+
724+
if r.is_remote() {
725+
let (remote, name) = r
726+
.shorthand()
727+
.ok_or_else(|| eyre::eyre!("Expected branch, got `{}`", base))?
728+
.split_once('/')
729+
.unwrap();
730+
repo.find_remote_branch(remote, name)
731+
.ok_or_else(|| eyre::eyre!("Could not find branch {:?}", r.shorthand()))
732+
} else {
733+
repo.find_local_branch(base)
734+
.ok_or_else(|| eyre::eyre!("Could not find branch {:?}", base))
735+
}
717736
}
718737

719738
fn resolve_implicit_base(
@@ -723,13 +742,13 @@ fn resolve_implicit_base(
723742
protected_branches: &git_stack::git::Branches,
724743
) -> eyre::Result<git_stack::git::Branch> {
725744
let branch = git_stack::git::find_protected_base(repo, protected_branches, head_oid)
726-
.ok_or_else(|| eyre::eyre!("could not find a protected branch to use as a base"))?;
745+
.ok_or_else(|| eyre::eyre!("Could not find a protected branch to use as a base"))?;
727746
log::debug!(
728747
"Chose branch {} as the base for {}",
729-
branch.name,
748+
branch,
730749
branches
731750
.get(head_oid)
732-
.map(|b| b[0].name.clone())
751+
.map(|b| b[0].to_string())
733752
.or_else(|| {
734753
repo.find_commit(head_oid)?
735754
.summary
@@ -828,7 +847,7 @@ fn git_push(
828847
if failed.is_empty() {
829848
Ok(())
830849
} else {
831-
eyre::bail!("could not push {}", failed.into_iter().join(", "));
850+
eyre::bail!("Could not push {}", failed.into_iter().join(", "));
832851
}
833852
}
834853

@@ -839,10 +858,16 @@ fn git_push_node(
839858
) -> Vec<String> {
840859
let mut failed = Vec::new();
841860
for branch in node.branches.iter() {
861+
let local_branch = if let Some(local_name) = branch.local_name() {
862+
local_name
863+
} else {
864+
continue;
865+
};
866+
842867
if node.pushable {
843868
let raw_branch = repo
844869
.raw()
845-
.find_branch(&branch.name, git2::BranchType::Local)
870+
.find_branch(local_branch, git2::BranchType::Local)
846871
.expect("all referenced branches exist");
847872
let upstream_set = raw_branch.upstream().is_ok();
848873

@@ -852,26 +877,26 @@ fn git_push_node(
852877
args.push("--set-upstream");
853878
}
854879
args.push(remote);
855-
args.push(&branch.name);
880+
args.push(local_branch);
856881
log::trace!("git {}", args.join(" "),);
857882
if !dry_run {
858883
let status = std::process::Command::new("git").args(&args).status();
859884
match status {
860885
Ok(status) => {
861886
if !status.success() {
862-
failed.push(branch.name.clone());
887+
failed.push(local_branch.to_owned());
863888
}
864889
}
865890
Err(err) => {
866891
log::debug!("`git push` failed with {}", err);
867-
failed.push(branch.name.clone());
892+
failed.push(local_branch.to_owned());
868893
}
869894
}
870895
}
871896
} else if node.action.is_protected() {
872-
log::debug!("Skipping push of `{}`, protected", branch.name);
897+
log::debug!("Skipping push of `{}`, protected", branch);
873898
} else {
874-
log::debug!("Skipping push of `{}`", branch.name);
899+
log::debug!("Skipping push of `{}`", branch);
875900
}
876901
}
877902

@@ -891,8 +916,8 @@ fn list(
891916
let mut branches: Vec<_> = node.branches.iter().collect();
892917
branches.sort();
893918
for b in branches {
894-
if protected.into_iter().flatten().contains(&b) {
895-
// Base / protected branches are just show for context, they aren't part of the
919+
if b.remote.is_some() || protected.into_iter().flatten().contains(&b) {
920+
// Base, remote, and protected branches are just shown for context, they aren't part of the
896921
// stack, so skip them here
897922
continue;
898923
}
@@ -1305,15 +1330,27 @@ impl<'r> std::fmt::Display for RenderNode<'r> {
13051330
} else {
13061331
let mut branches: Vec<_> = node.branches.iter().collect();
13071332
branches.sort_by_key(|b| {
1308-
let is_head = self.head_branch.id == b.id && self.head_branch.name == b.name;
1333+
let is_head = self.head_branch.id == b.id
1334+
&& self.head_branch.remote == b.remote
1335+
&& self.head_branch.name == b.name;
13091336
let head_first = !is_head;
1310-
(head_first, &b.name)
1337+
(head_first, &b.remote, &b.name)
13111338
});
13121339
write!(
13131340
f,
13141341
"{}",
13151342
branches
1316-
.into_iter()
1343+
.iter()
1344+
.filter(|b| {
1345+
if b.remote.is_some() {
1346+
let local_present = branches
1347+
.iter()
1348+
.any(|b| b.local_name() == Some(b.name.as_str()));
1349+
!local_present
1350+
} else {
1351+
true
1352+
}
1353+
})
13171354
.map(|b| {
13181355
format!(
13191356
"{}{}",
@@ -1361,17 +1398,22 @@ fn format_branch_name<'d>(
13611398
protected_branches: &'d git_stack::git::Branches,
13621399
palette: &'d Palette,
13631400
) -> impl std::fmt::Display + 'd {
1364-
if head_branch.id == branch.id && head_branch.name == branch.name {
1365-
palette.highlight.paint(branch.name.as_str())
1401+
if head_branch.id == branch.id
1402+
&& head_branch.remote == branch.remote
1403+
&& head_branch.name == branch.name
1404+
{
1405+
palette.highlight.paint(branch.to_string())
13661406
} else {
13671407
let protected = protected_branches.get(branch.id);
13681408
if protected.into_iter().flatten().contains(&branch) {
1369-
palette.info.paint(branch.name.as_str())
1409+
palette.info.paint(branch.to_string())
1410+
} else if branch.remote.is_some() {
1411+
palette.info.paint(branch.to_string())
13701412
} else if node.action.is_protected() {
13711413
// Either haven't started dev or it got merged
1372-
palette.warn.paint(branch.name.as_str())
1414+
palette.warn.paint(branch.to_string())
13731415
} else {
1374-
palette.good.paint(branch.name.as_str())
1416+
palette.good.paint(branch.to_string())
13751417
}
13761418
}
13771419
}

0 commit comments

Comments
 (0)