Skip to content

Commit baeb785

Browse files
committed
wip
1 parent e162683 commit baeb785

File tree

3 files changed

+135
-66
lines changed

3 files changed

+135
-66
lines changed

src/git.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,19 @@ pub(crate) fn run_git(args: &[&str]) -> Result<GitOutput> {
6868
})
6969
}
7070

71-
pub(crate) fn run_git_status(args: &[&str]) -> Result<ExitStatus> {
71+
pub(crate) fn run_git_status(args: &[&str], stdin: Option<&str>) -> Result<ExitStatus> {
7272
tracing::debug!("Running `git {}`", args.join(" "));
73-
Ok(Command::new("git").args(args).status()?)
73+
if let Some(stdin_text) = stdin {
74+
let mut child = Command::new("git")
75+
.args(args)
76+
.stdin(std::process::Stdio::piped())
77+
.spawn()?;
78+
let stdin = child.stdin.as_mut().context("Failed to open stdin")?;
79+
std::io::Write::write_all(stdin, stdin_text.as_bytes())?;
80+
Ok(child.wait()?)
81+
} else {
82+
Ok(Command::new("git").args(args).status()?)
83+
}
7484
}
7585

7686
pub(crate) fn git_fetch() -> Result<()> {
@@ -127,7 +137,7 @@ pub(crate) fn git_branch_status(
127137
})
128138
}
129139
pub(crate) fn is_ancestor(parent: &str, branch: &str) -> Result<bool> {
130-
Ok(run_git_status(&["merge-base", "--is-ancestor", parent, branch])?.success())
140+
Ok(run_git_status(&["merge-base", "--is-ancestor", parent, branch], None)?.success())
131141
}
132142
pub(crate) fn run_git_status_clean() -> Result<bool> {
133143
Ok(run_git(&["status", "--porcelain"])?.is_empty())

src/main.rs

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use anyhow::{Context, Result, anyhow, bail, ensure};
77
use clap::{Parser, Subcommand};
88
use git::{
99
DEFAULT_REMOTE, GitBranchStatus, after_text, git_branch_status, git_checkout_main, git_fetch,
10-
git_remote_main, is_ancestor, run_git_status, shas_match,
10+
git_remote_main, git_sha, is_ancestor, run_git_status, shas_match,
1111
};
1212
use state::{Branch, RebaseStep, load_state, save_state};
1313
use std::env;
@@ -310,56 +310,105 @@ fn restack(
310310
restack_branch,
311311
env::current_dir()?.display()
312312
);
313-
let source = run_git(&["rev-parse", &branch])?
314-
.output_or(format!("branch {branch} does not exist?"))?;
313+
let source = git_sha(&branch.name)?;
315314

316-
if is_ancestor(&parent, &branch)? {
315+
if is_ancestor(&parent, &branch.name)? {
317316
tracing::info!(
318-
"Branch '{}' is already up to date with '{}'.",
319-
branch,
317+
"Branch '{}' is already stacked on '{}'.",
318+
branch.name,
320319
parent
321320
);
322-
tracing::info!("Force-pushing '{}' to origin...", branch);
323-
if !shas_match(&format!("origin/{branch}"), &branch) {
324-
run_git(&["push", "-fu", "origin", &format!("{}:{}", branch, branch)])?;
321+
tracing::info!("Force-pushing '{}' to origin...", branch.name);
322+
if !shas_match(&format!("origin/{}", branch.name), &branch.name) {
323+
run_git(&[
324+
"push",
325+
"-fu",
326+
"origin",
327+
&format!("{branch_name}:{branch_name}", branch_name = branch.name),
328+
])?;
325329
}
326330
continue;
327331
} else {
328-
tracing::info!("Branch '{}' is not descended from '{}'...", branch, parent);
329-
if CREATE_BACKUP {
330-
let backup_branch = format!("{}-at-{}", branch, run_version);
331-
tracing::debug!(
332-
"Creating backup branch '{}' from '{}'...",
333-
backup_branch,
334-
branch
335-
);
336-
if !run_git_status(&["branch", &backup_branch, &source])?.success() {
337-
tracing::warn!("failed to create backup branch {}", backup_branch);
332+
tracing::info!("Branch '{}' is not stacked on '{}'...", branch.name, parent);
333+
make_backup(&run_version, branch, &source)?;
334+
335+
if let Some(lkg_parent) = branch.lkg_parent.as_deref() {
336+
tracing::info!("LKG parent: {}", lkg_parent);
337+
if is_ancestor(lkg_parent, &source)? {
338+
// The branch is still on top of the LKG parent. Let's create a format-patch of the
339+
// difference, and apply it on top of the new parent.
340+
let format_patch = run_git(&[
341+
"format-patch",
342+
"--stdout",
343+
&format!("{}..{}", &parent, &branch.name),
344+
])?
345+
.output();
346+
run_git(&["checkout", "-B", &branch.name, &parent])?;
347+
let Some(format_patch) = format_patch else {
348+
bail!("No diff between LKG and branch?! Might need to handle this case.");
349+
};
350+
let rebased = run_git_status(&["am", "--3way"], Some(&format_patch))?.success();
351+
if !rebased {
352+
eprintln!(
353+
"{} did not complete successfully.",
354+
"`git am`".green().bold()
355+
);
356+
eprintln!("Run `git mergetool` to resolve conflicts.");
357+
eprintln!(
358+
"Once you have finished with {}, re-run `git stack restack`.",
359+
"`git am --continue`".green().bold()
360+
);
361+
std::process::exit(1);
362+
}
363+
git_push(&branch.name)?;
364+
continue;
338365
}
339366
}
340-
tracing::info!("Initiating a rebase of '{}' onto '{}'...", branch, parent);
341-
if !run_git_status(&["checkout", "-q", &branch])?.success() {
342-
bail!("git checkout {} failed", branch);
343-
}
344-
let rebased = run_git_status(&["rebase", &parent])?.success();
367+
run_git(&["checkout", &branch.name])?;
368+
let rebased = run_git_status(&["rebase", &parent], None)?.success();
369+
345370
if !rebased {
346-
tracing::warn!("Rebase did not complete automatically.");
347-
tracing::warn!("Run `git mergetool` to resolve conflicts.");
348-
tracing::info!("Once you have finished the rebase, re-run this script.");
371+
eprintln!("{} did not complete automatically.", "Rebase".blue().bold());
372+
eprintln!("Run `git mergetool` to resolve conflicts.");
373+
eprintln!(
374+
"Once you have finished the {}, re-run this script.",
375+
"rebase".blue().bold()
376+
);
349377
std::process::exit(1);
350378
}
351-
if !shas_match(&format!("origin/{branch}"), &branch) {
352-
run_git(&["push", "-fu", "origin", &format!("{}:{}", branch, branch)])?;
353-
}
379+
git_push(&branch.name)?;
354380
tracing::info!("Rebase completed successfully. Continuing...");
355381
}
356382
}
357383
tracing::info!("Restoring starting branch '{}'...", restack_branch);
358384
ensure!(
359-
run_git_status(&["checkout", "-q", &orig_branch])?.success(),
385+
run_git_status(&["checkout", "-q", &orig_branch], None)?.success(),
360386
"git checkout {} failed",
361387
restack_branch
362388
);
363389
tracing::info!("Done.");
364390
Ok(())
365391
}
392+
393+
fn git_push(branch: &str) -> Result<()> {
394+
if !shas_match(&format!("origin/{}", branch), &branch) {
395+
run_git(&["push", "-fu", "origin", &format!("{}:{}", branch, branch)])?;
396+
}
397+
Ok(())
398+
}
399+
400+
fn make_backup(run_version: &String, branch: &Branch, source: &str) -> Result<(), anyhow::Error> {
401+
if !CREATE_BACKUP {
402+
return Ok(());
403+
}
404+
let backup_branch = format!("{}-at-{}", branch.name, run_version);
405+
tracing::debug!(
406+
"Creating backup branch '{}' from '{}'...",
407+
backup_branch,
408+
branch.name
409+
);
410+
if !run_git_status(&["branch", &backup_branch, source], None)?.success() {
411+
tracing::warn!("failed to create backup branch {}", backup_branch);
412+
}
413+
Ok(())
414+
}

src/state.rs

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,28 +59,27 @@ impl State {
5959
current_upstream: Option<String>,
6060
branch_name: String,
6161
) -> Result<()> {
62+
let trunk = git_trunk()?;
63+
// Ensure the main branch is in the git-stack tree for this repo if we haven't
64+
// added it yet.
65+
self.trees
66+
.entry(repo.to_string())
67+
.or_insert_with(|| Branch::new(trunk.main_branch.clone(), None));
68+
save_state(self)?;
69+
6270
let branch_exists_in_tree = self.branch_exists_in_tree(repo, &branch_name);
63-
let current_branch_exists_in_tree = self.branch_exists_in_tree(repo, &current_branch);
6471

6572
if git_branch_exists(&branch_name) {
6673
if !branch_exists_in_tree {
6774
tracing::warn!(
6875
"Branch {branch_name} exists in the git repo but is not tracked by git-stack. \
69-
If you'd like to add it to the git-stack, please checkout the desired parent \
70-
branch, and then run `git-stack add {branch_name}` to stack {branch_name} on \
71-
top of the parent branch.",
76+
If you'd like to add it to the git-stack, please run `git-stack mount \
77+
<parent-branch>` to stack {branch_name} on top of the parent branch.",
7278
);
7379
}
7480
run_git(&["checkout", &branch_name])?;
7581
return Ok(());
7682
}
77-
let trunk = git_trunk()?;
78-
79-
// Ensure the main branch is in the git-stack tree for this repo if we haven't
80-
// added it yet.
81-
self.trees
82-
.entry(repo.to_string())
83-
.or_insert_with(|| Branch::new(trunk.main_branch.clone(), None));
8483

8584
let branch = self
8685
.get_tree_branch_mut(repo, &current_branch)
@@ -139,17 +138,16 @@ impl State {
139138
let trunk = git_trunk()?;
140139
// Find all the descendents of the starting branch.
141140
// Traverse the tree from the starting branch to the root,
142-
let mut path: Vec<&str> = vec![];
141+
let mut path: Vec<&Branch> = vec![];
143142
if !get_path(self.trees.get(repo).unwrap(), starting_branch, &mut path) {
144143
bail!("Branch {starting_branch} not found in the git-stack tree.");
145144
}
146-
path.insert(0, &trunk.remote_main);
147145
Ok(path
148146
.iter()
149147
.zip(path.iter().skip(1))
150148
.map(|(parent, child)| RebaseStep {
151-
parent: parent.to_string(),
152-
branch: child.to_string(),
149+
parent: parent.name.to_string(),
150+
branch: child,
153151
})
154152
.collect::<Vec<_>>())
155153
}
@@ -179,21 +177,25 @@ impl State {
179177
branch_name: &str,
180178
parent_branch: Option<String>,
181179
) -> Result<()> {
180+
let trunk = git_trunk()?;
181+
if trunk.main_branch == branch_name {
182+
bail!(
183+
"Branch {branch_name} cannot be stacked on anything else.",
184+
branch_name = branch_name.red()
185+
);
186+
}
187+
182188
let parent_branch = match parent_branch {
183189
Some(parent_branch) => parent_branch,
184190
None => {
185191
tracing::info!("No parent branch specified, using main branch.");
186192
// The branch might not exist in git, let's create it, and add it to the tree.
187-
let remote_main = git_remote_main(DEFAULT_REMOTE)?;
188-
let main_branch = after_text(&remote_main, format!("{DEFAULT_REMOTE}/"))
189-
.ok_or(anyhow!("no branch?"))?
190-
.to_string();
191193
// Ensure the main branch is in the git-stack tree for this repo if we haven't
192194
// added it yet.
193195
self.trees
194196
.entry(repo.to_string())
195-
.or_insert_with(|| Branch::new(main_branch.clone(), None));
196-
main_branch
197+
.or_insert_with(|| Branch::new(trunk.main_branch.clone(), None));
198+
trunk.main_branch
197199
}
198200
};
199201

@@ -266,23 +268,31 @@ impl State {
266268
tracing::debug!("Refreshing lkgs for all branches...");
267269
let trunk = git_trunk()?;
268270

269-
let mut parent_lkgs: HashMap<String, String> = HashMap::default();
271+
let mut parent_lkgs: HashMap<String, Option<String>> = HashMap::default();
270272

271-
// Traverse the tree from the root to the leaves, and update the lkgs as we go.
273+
// BFS Traverse the tree from the root to the leaves, and update the lkgs as we go.
272274
let mut queue: VecDeque<(Option<String>, String)> = VecDeque::new();
273275
queue.push_back((None, trunk.main_branch.clone()));
274276
while let Some((parent, branch)) = queue.pop_front() {
275277
if let Some(parent) = parent {
278+
let tree_branch = self.get_tree_branch(repo, &branch).unwrap();
279+
if let Some(lkg_parent) = tree_branch.lkg_parent.as_deref() {
280+
if is_ancestor(lkg_parent, &branch)? {
281+
parent_lkgs.insert(tree_branch.name.clone(), Some(lkg_parent.to_string()));
282+
} else {
283+
parent_lkgs.insert(tree_branch.name.clone(), None);
284+
}
285+
}
276286
if is_ancestor(&parent, &branch).unwrap_or(false) {
277-
if let Ok(lkg_parent) = git_sha(&parent) {
287+
if let Ok(new_lkg_parent) = git_sha(&parent) {
278288
tracing::debug!(
279-
?lkg_parent,
289+
lkg_parent = ?new_lkg_parent,
280290
"Branch {} is a descendent of {}",
281291
branch.yellow(),
282292
parent.yellow(),
283293
);
284294
// Save the LKG parent for the branch.
285-
parent_lkgs.insert(branch.clone(), lkg_parent);
295+
parent_lkgs.insert(branch.clone(), Some(new_lkg_parent));
286296
}
287297
}
288298
}
@@ -297,31 +307,31 @@ impl State {
297307
let branch = self
298308
.get_tree_branch_mut(repo, &branch)
299309
.ok_or_else(|| anyhow!("Branch {branch} not found in the git-stack tree."))?;
300-
branch.lkg_parent = Some(lkg_parent);
310+
branch.lkg_parent = lkg_parent;
301311
}
302312
save_state(self)?;
303313
Ok(())
304314
}
305315
}
306316

307-
fn get_path<'a>(branch: &'a Branch, target_branch: &str, path: &mut Vec<&'a str>) -> bool {
317+
fn get_path<'a>(branch: &'a Branch, target_branch: &str, path: &mut Vec<&'a Branch>) -> bool {
308318
if branch.name == target_branch {
309-
path.insert(0, &branch.name);
319+
path.insert(0, branch);
310320
return true;
311321
}
312322
for child_branch in &branch.branches {
313323
if get_path(child_branch, target_branch, path) {
314-
path.insert(0, &branch.name);
324+
path.insert(0, branch);
315325
return true;
316326
}
317327
}
318328
false
319329
}
320330

321331
#[derive(Debug)]
322-
pub(crate) struct RebaseStep {
332+
pub(crate) struct RebaseStep<'a> {
323333
pub(crate) parent: String,
324-
pub(crate) branch: String,
334+
pub(crate) branch: &'a Branch,
325335
}
326336

327337
fn find_branch_by_name<'a>(tree: &'a Branch, name: &str) -> Option<&'a Branch> {

0 commit comments

Comments
 (0)