1use std::collections::{BTreeMap, BTreeSet};
6
7use anyhow::{Result, bail};
8
9use crate::git;
10use crate::settings;
11use crate::style;
12
13mod nav;
14mod restack;
15
16pub use nav::{
17 behind_parent_hint, checkout_bottom, checkout_child, checkout_parent, checkout_top,
18 print_children, print_parent, print_stack,
19};
20pub use restack::{abort_restack, continue_restack, restack};
21
22const PARENT_KEY: &str = "stkParent";
23const BASE_KEY: &str = "stkBase";
24
25pub fn create_branch(branch: &str) -> Result<()> {
26 let parent = git::current_branch()?;
27 git::create_branch(branch)?;
28 set_parent(branch, &parent)?;
29 record_base(branch, &parent);
30 anstream::println!(
31 "created {} with parent {}",
32 style::branch(branch),
33 style::branch(&parent)
34 );
35 Ok(())
36}
37
38pub fn trunk_branch(branches: &[String]) -> Option<String> {
41 let remote = settings::remote().unwrap_or_else(|_| settings::DEFAULT_REMOTE.to_owned());
42 if let Some(default) = git::remote_default_branch(&remote) {
43 return Some(default);
44 }
45
46 ["main", "master"]
47 .iter()
48 .find(|name| branches.iter().any(|branch| branch == *name))
49 .map(|name| (*name).to_owned())
50}
51
52pub fn adopt_branch(branch: &str, parent: &str) -> Result<()> {
53 if branch == parent {
54 bail!("a branch cannot be its own stack parent");
55 }
56
57 let branches: BTreeSet<_> = git::local_branches()?.into_iter().collect();
58 if !branches.contains(branch) {
59 bail!("branch {branch} does not exist");
60 }
61 if !branches.contains(parent) {
62 bail!("parent branch {parent} does not exist");
63 }
64
65 set_parent(branch, parent)?;
66 record_base(branch, parent);
67 anstream::println!(
68 "attached {} to {}",
69 style::branch(branch),
70 style::branch(parent)
71 );
72 Ok(())
73}
74
75pub fn detach_branch(branch: Option<&str>) -> Result<()> {
76 let branch = branch
77 .map(str::to_owned)
78 .map_or_else(git::current_branch, Ok)?;
79 unset_parent(&branch)?;
80 unset_base(&branch)?;
81 anstream::println!("detached {}", style::branch(&branch));
82 Ok(())
83}
84
85pub fn rename_branch(old: &str, new: &str, dry_run: bool) -> Result<()> {
89 let children = children_for_branch(old)?;
90
91 if !dry_run {
92 git::rename_branch(old, new)?;
93 }
94 anstream::println!(
95 "{} {} -> {}",
96 if dry_run { "would rename" } else { "renamed" },
97 style::branch(old),
98 style::branch(new)
99 );
100
101 for child in &children {
102 if !dry_run {
103 set_parent_for_branch(child, new)?;
104 }
105 anstream::println!(
106 "{} {} -> {}",
107 if dry_run {
108 "would retarget"
109 } else {
110 "retargeted"
111 },
112 style::branch(child),
113 style::branch(new)
114 );
115 }
116 Ok(())
117}
118
119pub fn parent_for_branch(branch: &str) -> Result<Option<String>> {
120 parent_of(branch)
121}
122
123pub fn children_for_branch(branch: &str) -> Result<Vec<String>> {
124 children_of(branch)
125}
126
127pub fn set_parent_for_branch(branch: &str, parent: &str) -> Result<()> {
128 set_parent(branch, parent)
129}
130
131pub fn unset_parent_for_branch(branch: &str) -> Result<()> {
132 unset_parent(branch)
133}
134
135pub fn base_for_branch(branch: &str) -> Result<Option<String>> {
136 base_of(branch)
137}
138
139pub fn set_base_for_branch(branch: &str, base: &str) -> Result<()> {
140 git::config_set(&base_key(branch), base)
141}
142
143pub fn unset_base_for_branch(branch: &str) -> Result<()> {
144 unset_base(branch)
145}
146
147pub fn record_base(branch: &str, parent: &str) {
150 if let Ok(base) = git::merge_base(parent, branch) {
151 let _ = git::config_set(&base_key(branch), &base);
152 }
153}
154
155pub fn stack_root(branch: &str) -> Result<String> {
157 let parents = parent_map()?;
158 Ok(root_for(branch, &parents))
159}
160
161pub fn branch_and_descendants(branch: &str) -> Result<Vec<String>> {
162 let parents = parent_map()?;
163 let children = children_map(&parents);
164 let mut branches = vec![branch.to_owned()];
165 collect_descendants(branch, &children, &mut branches);
166 Ok(branches)
167}
168
169pub fn branch_parents(branches: &[String]) -> Result<Vec<(String, String)>> {
172 let mut pairs = Vec::new();
173 for branch in branches {
174 if let Some(parent) = parent_of(branch)? {
175 pairs.push((branch.clone(), parent));
176 }
177 }
178 Ok(pairs)
179}
180
181fn parent_map() -> Result<BTreeMap<String, String>> {
182 let mut parents = BTreeMap::new();
183 for branch in git::local_branches()? {
184 if let Some(parent) = parent_of(&branch)? {
185 parents.insert(branch, parent);
186 }
187 }
188 Ok(parents)
189}
190
191fn collect_descendants(
192 branch: &str,
193 children: &BTreeMap<String, Vec<String>>,
194 branches: &mut Vec<String>,
195) {
196 if let Some(branch_children) = children.get(branch) {
197 for child in branch_children {
198 branches.push(child.to_owned());
199 collect_descendants(child, children, branches);
200 }
201 }
202}
203
204fn children_of(parent: &str) -> Result<Vec<String>> {
205 Ok(parent_map()?
206 .into_iter()
207 .filter_map(|(branch, branch_parent)| (branch_parent == parent).then_some(branch))
208 .collect())
209}
210
211fn children_map(parents: &BTreeMap<String, String>) -> BTreeMap<String, Vec<String>> {
212 let mut children: BTreeMap<String, Vec<String>> = BTreeMap::new();
213 for (branch, parent) in parents {
214 children
215 .entry(parent.to_owned())
216 .or_default()
217 .push(branch.to_owned());
218 }
219 children
220}
221
222fn root_for(branch: &str, parents: &BTreeMap<String, String>) -> String {
223 let mut root = branch.to_owned();
224 let mut seen = BTreeSet::new();
225
226 while let Some(parent) = parents.get(&root) {
227 if !seen.insert(root.clone()) {
228 break;
229 }
230 root = parent.to_owned();
231 }
232
233 root
234}
235
236fn parent_of(branch: &str) -> Result<Option<String>> {
237 git::config_get(&parent_key(branch))
238}
239
240fn base_of(branch: &str) -> Result<Option<String>> {
241 git::config_get(&base_key(branch))
242}
243
244fn set_parent(branch: &str, parent: &str) -> Result<()> {
245 git::config_set(&parent_key(branch), parent)
246}
247
248fn unset_parent(branch: &str) -> Result<()> {
249 git::config_unset(&parent_key(branch))
250}
251
252fn unset_base(branch: &str) -> Result<()> {
253 git::config_unset(&base_key(branch))
254}
255
256fn parent_key(branch: &str) -> String {
257 format!("branch.{branch}.{PARENT_KEY}")
258}
259
260fn base_key(branch: &str) -> String {
261 format!("branch.{branch}.{BASE_KEY}")
262}