1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use crate::commands;
use crate::config::Config;
use crate::engine::Stack;
use crate::git::GitRepo;
use anyhow::Result;
use colored::Colorize;
use std::process::Command;
pub fn run(no_pr: bool, no_submit: bool, auto_stash_pop: bool) -> Result<()> {
let repo = GitRepo::open()?;
let original = repo.current_branch()?;
println!("{}", "Cascading stack...".bold());
// Warn if local trunk is behind its remote-tracking ref. This uses the
// cached remote refs (no network call) so it's instant. The user can run
// `stax rs` to fetch and sync trunk before cascading.
warn_if_trunk_stale(&repo);
commands::navigate::bottom()?;
// A bottom-anchored restack already walks ancestors/current/descendants for
// this stack, so we do not need a follow-up upstack restack.
commands::restack::run(
false,
false,
false,
true,
true,
auto_stash_pop,
commands::restack::SubmitAfterRestack::No,
)?;
if repo.rebase_in_progress()? {
return Ok(());
}
if no_submit {
println!("{}", "Skipping push and PRs (--no-submit)".dimmed());
} else {
commands::submit::run(
commands::submit::SubmitScope::Stack,
false, // draft
no_pr, // no_pr (push but skip PR creation/updates)
false, // no_fetch
false, // force
true, // yes
true, // no_prompt
vec![], // reviewers
vec![], // labels
vec![], // assignees
false, // quiet
false, // open
false, // verbose
None, // template
false, // no_template
false, // edit
false, // ai_body
false, // rerequest_review
)?;
}
if !repo.rebase_in_progress()? && repo.current_branch()? != original {
repo.checkout(&original)?;
}
Ok(())
}
/// Check whether local trunk is behind its remote-tracking ref and print a
/// warning if so. Uses the cached remote refs — no network call. Non-fatal:
/// the user may intentionally be working offline or not ready to sync yet.
fn warn_if_trunk_stale(repo: &GitRepo) {
let Ok(config) = Config::load() else { return };
let Ok(stack) = Stack::load(repo) else { return };
let Ok(workdir) = repo.workdir() else { return };
let remote_ref = format!("{}/{}", config.remote_name(), stack.trunk);
// Count commits on remote that aren't in local trunk.
// git rev-list --count <local>..<remote> — uses only local git objects.
let output = Command::new("git")
.args([
"rev-list",
"--count",
&format!("{}..{}", stack.trunk, remote_ref),
])
.current_dir(workdir)
.output();
if let Ok(out) = output {
if out.status.success() {
let count_str = String::from_utf8_lossy(&out.stdout);
let count: u64 = count_str.trim().parse().unwrap_or(0);
if count > 0 {
println!(
" {} {} is {} commit{} behind {} — run {} to sync first",
"warning:".yellow().bold(),
stack.trunk.cyan(),
count.to_string().yellow(),
if count == 1 { "" } else { "s" },
remote_ref.cyan(),
"stax rs".bold(),
);
}
}
// If rev-list fails (e.g. remote ref doesn't exist yet), silently skip.
}
}