Skip to content

Commit 089d051

Browse files
committed
implement auto-mounting
1 parent b366231 commit 089d051

3 files changed

Lines changed: 127 additions & 23 deletions

File tree

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
name = "git-stack"
33
version = "0.1.0"
44
edition = "2024"
5+
license = "GPL2"
6+
keywords = ["git", "stack", "vcs"]
7+
categories = ["development-tools"]
8+
description = "A git stacking CLI for basic stacked diff management."
9+
readme = "README.md"
10+
homepage = "https://github.com/wbbradley/git-stack"
11+
repository = "https://github.com/wbbradley/git-stack"
512

613
[dependencies]
714
anyhow = { version = "1.0.98", features = ["backtrace"] }

src/main.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -167,34 +167,45 @@ fn inner_main() -> Result<()> {
167167
branch,
168168
fetch,
169169
push,
170-
}) => restack(
171-
state,
172-
&repo,
173-
run_version,
174-
branch,
175-
current_branch,
176-
fetch,
177-
push,
178-
),
170+
}) => {
171+
let restack_branch = branch.clone().unwrap_or_else(|| current_branch.clone());
172+
state.try_auto_mount(&repo, &restack_branch)?;
173+
restack(state, &repo, run_version, branch, current_branch, fetch, push)
174+
}
179175
Some(Command::Mount { parent_branch }) => {
180176
state.mount(&repo, &current_branch, parent_branch)
181177
}
182-
Some(Command::Status { fetch }) => status(state, &repo, &current_branch, fetch),
178+
Some(Command::Status { fetch }) => {
179+
state.try_auto_mount(&repo, &current_branch)?;
180+
status(state, &repo, &current_branch, fetch)
181+
}
183182
Some(Command::Delete { branch_name }) => state.delete_branch(&repo, &branch_name),
184183
Some(Command::Cleanup { dry_run, all }) => {
185184
state.cleanup_missing_branches(&repo, dry_run, all)
186185
}
187-
Some(Command::Diff { branch }) => diff(state, &repo, &branch.unwrap_or(current_branch)),
188-
Some(Command::Log { branch }) => show_log(state, &repo, &branch.unwrap_or(current_branch)),
186+
Some(Command::Diff { branch }) => {
187+
let branch_to_diff = branch.clone().unwrap_or_else(|| current_branch.clone());
188+
state.try_auto_mount(&repo, &branch_to_diff)?;
189+
diff(state, &repo, &branch.unwrap_or(current_branch))
190+
}
191+
Some(Command::Log { branch }) => {
192+
let branch_to_log = branch.clone().unwrap_or_else(|| current_branch.clone());
193+
state.try_auto_mount(&repo, &branch_to_log)?;
194+
show_log(state, &repo, &branch.unwrap_or(current_branch))
195+
}
189196
Some(Command::Note { edit, branch }) => {
190197
let branch = branch.unwrap_or(current_branch);
198+
state.try_auto_mount(&repo, &branch)?;
191199
if edit {
192200
state.edit_note(&repo, &branch)
193201
} else {
194202
state.show_note(&repo, &branch)
195203
}
196204
}
197-
None => status(state, &repo, &current_branch, false),
205+
None => {
206+
state.try_auto_mount(&repo, &current_branch)?;
207+
status(state, &repo, &current_branch, false)
208+
}
198209
}
199210
}
200211

src/state.rs

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use std::{
22
cell::{Cell, Ref, RefCell},
33
collections::{BTreeMap, HashMap, VecDeque},
4-
default,
5-
fs,
4+
default, fs,
65
path::PathBuf,
76
process::Command,
87
rc::Rc,
@@ -14,14 +13,8 @@ use serde::{Deserialize, Serialize};
1413

1514
use crate::{
1615
git::{
17-
DEFAULT_REMOTE,
18-
GitTrunk,
19-
after_text,
20-
git_branch_exists,
21-
git_remote_main,
22-
git_sha,
23-
git_trunk,
24-
is_ancestor,
16+
DEFAULT_REMOTE, GitTrunk, after_text, git_branch_exists, git_remote_main, git_sha,
17+
git_trunk, is_ancestor,
2518
},
2619
run_git,
2720
};
@@ -587,6 +580,78 @@ impl State {
587580
.status()?;
588581
Ok(())
589582
}
583+
584+
/// Try to auto-mount the current branch if it's not in the tree.
585+
/// Returns Ok(true) if the branch was auto-mounted, Ok(false) if it was already in the tree,
586+
/// or Err if auto-mount failed.
587+
pub(crate) fn try_auto_mount(&mut self, repo: &str, branch_name: &str) -> Result<bool> {
588+
// Check if the branch is already in the tree
589+
if self.branch_exists_in_tree(repo, branch_name) {
590+
return Ok(false);
591+
}
592+
593+
// Check if this branch exists in git
594+
if !git_branch_exists(branch_name) {
595+
bail!("Branch {branch_name} does not exist in git");
596+
}
597+
598+
// Get the tree for this repo
599+
let Some(tree) = self.get_tree(repo) else {
600+
bail!("No tree found for repo {repo}");
601+
};
602+
603+
// Collect all mounted branches
604+
let mut all_branches = Vec::new();
605+
collect_all_branches(tree, &mut all_branches);
606+
607+
// Find all mounted branches that are ancestors of the current branch
608+
let mut ancestor_branches = Vec::new();
609+
for mounted_branch in &all_branches {
610+
if let Ok(true) = is_ancestor(mounted_branch, branch_name) {
611+
ancestor_branches.push(mounted_branch.clone());
612+
}
613+
}
614+
615+
// Determine the parent branch
616+
let parent_branch = if ancestor_branches.is_empty() {
617+
// No ancestors found, default to the trunk/main branch
618+
let trunk = git_trunk()?;
619+
tracing::info!(
620+
"No mounted ancestor branches found for {}. Defaulting to trunk branch {}.",
621+
branch_name,
622+
trunk.main_branch
623+
);
624+
trunk.main_branch
625+
} else {
626+
// Find the deepest ancestor branch (highest depth in the tree)
627+
let mut deepest_branch = None;
628+
let mut max_depth = 0;
629+
630+
for ancestor in &ancestor_branches {
631+
if let Some(depth) = get_branch_depth(tree, ancestor, 0)
632+
&& depth >= max_depth
633+
{
634+
max_depth = depth;
635+
deepest_branch = Some(ancestor.clone());
636+
}
637+
}
638+
639+
deepest_branch
640+
.ok_or_else(|| anyhow!("Failed to determine parent branch for auto-mount"))?
641+
};
642+
643+
tracing::info!("Auto-mounting branch {} on {}", branch_name, parent_branch);
644+
println!(
645+
"Auto-mounting branch {} on {}...",
646+
branch_name.yellow(),
647+
parent_branch.yellow()
648+
);
649+
650+
// Mount the branch
651+
self.mount(repo, branch_name, Some(parent_branch))?;
652+
653+
Ok(true)
654+
}
590655
}
591656

592657
fn get_path<'a>(branch: &'a Branch, target_branch: &str, path: &mut Vec<&'a Branch>) -> bool {
@@ -731,6 +796,27 @@ fn find_stack_with_branch<'a>(
731796
))
732797
}
733798

799+
/// Collect all branch names from the tree recursively.
800+
fn collect_all_branches(branch: &Branch, branches: &mut Vec<String>) {
801+
branches.push(branch.name.clone());
802+
for child in &branch.branches {
803+
collect_all_branches(child, branches);
804+
}
805+
}
806+
807+
/// Calculate the depth of a branch in the tree. Returns None if the branch is not found.
808+
fn get_branch_depth(tree: &Branch, target: &str, current_depth: usize) -> Option<usize> {
809+
if tree.name == target {
810+
return Some(current_depth);
811+
}
812+
for child in &tree.branches {
813+
if let Some(depth) = get_branch_depth(child, target, current_depth + 1) {
814+
return Some(depth);
815+
}
816+
}
817+
None
818+
}
819+
734820
fn get_xdg_path() -> anyhow::Result<PathBuf> {
735821
let base_dirs = xdg::BaseDirectories::with_prefix(env!("CARGO_PKG_NAME"));
736822
base_dirs

0 commit comments

Comments
 (0)