Skip to content

Commit 4ad08b5

Browse files
committed
refactor: Extract Branch and Chain to separate files
Phase 2 of simple file split refactoring: - Create src/branch.rs with Branch struct and all methods (309 lines) - Create src/chain.rs with Chain struct and all methods (371 lines) - Update src/main.rs to remove Branch/Chain code and add module imports - Remove unused imports (Between, Rng, HashMap, FromIterator) - Net reduction of 655 lines from main.rs All 52 tests passing.
1 parent df2e28b commit 4ad08b5

File tree

3 files changed

+684
-655
lines changed

3 files changed

+684
-655
lines changed

src/branch.rs

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
use std::io::{self, Write};
2+
use std::iter::FromIterator;
3+
use std::process::Command;
4+
5+
use between::Between;
6+
use colored::*;
7+
use git2::{BranchType, Error, ErrorCode};
8+
use rand::Rng;
9+
10+
use crate::types::*;
11+
use crate::{Chain, GitChain};
12+
13+
fn chain_name_key(branch_name: &str) -> String {
14+
format!("branch.{}.chain-name", branch_name)
15+
}
16+
17+
fn chain_order_key(branch_name: &str) -> String {
18+
format!("branch.{}.chain-order", branch_name)
19+
}
20+
21+
fn root_branch_key(branch_name: &str) -> String {
22+
format!("branch.{}.root-branch", branch_name)
23+
}
24+
25+
fn generate_chain_order() -> String {
26+
let between = Between::init();
27+
let chars = between.chars();
28+
let chars_length = chars.len();
29+
assert!(chars_length >= 3);
30+
let last_chars_index = chars_length - 1;
31+
32+
// Use character that is not either between.low() or between.high().
33+
// This guarantees that the next generated string sorts before or after the string generated in this function.
34+
let character_range = 1..=(last_chars_index - 1);
35+
let mut rng = rand::thread_rng();
36+
37+
let mut len = 5;
38+
let mut str: Vec<char> = vec![];
39+
40+
while len >= 1 {
41+
let index: usize = rng.gen_range(character_range.clone());
42+
let character_candidate = *chars.get(index).unwrap();
43+
str.push(character_candidate);
44+
len -= 1;
45+
}
46+
47+
String::from_iter(str)
48+
}
49+
50+
fn generate_chain_order_after(chain_order: &str) -> Option<String> {
51+
let between = Between::init();
52+
between.after(chain_order)
53+
}
54+
55+
fn generate_chain_order_before(chain_order: &str) -> Option<String> {
56+
let between = Between::init();
57+
between.before(chain_order)
58+
}
59+
60+
fn generate_chain_order_between(before: &str, after: &str) -> Option<String> {
61+
let between = Between::init();
62+
between.between(before, after)
63+
}
64+
65+
#[derive(Clone, PartialEq)]
66+
pub struct Branch {
67+
pub branch_name: String,
68+
pub chain_name: String,
69+
pub chain_order: String,
70+
pub root_branch: String,
71+
}
72+
73+
impl Branch {
74+
pub fn delete_all_configs(git_chain: &GitChain, branch_name: &str) -> Result<(), Error> {
75+
git_chain.delete_git_config(&chain_name_key(branch_name))?;
76+
git_chain.delete_git_config(&chain_order_key(branch_name))?;
77+
git_chain.delete_git_config(&root_branch_key(branch_name))?;
78+
Ok(())
79+
}
80+
81+
pub fn remove_from_chain(self, git_chain: &GitChain) -> Result<(), Error> {
82+
Branch::delete_all_configs(git_chain, &self.branch_name)
83+
}
84+
85+
pub fn get_branch_with_chain(
86+
git_chain: &GitChain,
87+
branch_name: &str,
88+
) -> Result<BranchSearchResult, Error> {
89+
let chain_name = git_chain.get_git_config(&chain_name_key(branch_name))?;
90+
let chain_order = git_chain.get_git_config(&chain_order_key(branch_name))?;
91+
let root_branch = git_chain.get_git_config(&root_branch_key(branch_name))?;
92+
93+
if chain_name.is_none()
94+
|| chain_order.is_none()
95+
|| root_branch.is_none()
96+
|| !git_chain.git_local_branch_exists(branch_name)?
97+
{
98+
Branch::delete_all_configs(git_chain, branch_name)?;
99+
return Ok(BranchSearchResult::NotPartOfAnyChain);
100+
}
101+
102+
let branch = Branch {
103+
branch_name: branch_name.to_string(),
104+
chain_name: chain_name.unwrap(),
105+
chain_order: chain_order.unwrap(),
106+
root_branch: root_branch.unwrap(),
107+
};
108+
109+
Ok(BranchSearchResult::Branch(branch))
110+
}
111+
112+
fn generate_chain_order(
113+
git_chain: &GitChain,
114+
chain_name: &str,
115+
sort_option: &SortBranch,
116+
) -> Result<String, Error> {
117+
let chain_order = if Chain::chain_exists(git_chain, chain_name)? {
118+
// invariant: a chain exists if and only if it has at least one branch.
119+
let chain = Chain::get_chain(git_chain, chain_name)?;
120+
assert!(!chain.branches.is_empty());
121+
122+
let maybe_chain_order = match sort_option {
123+
SortBranch::First => {
124+
let first_branch = chain.branches.first().unwrap();
125+
generate_chain_order_before(&first_branch.chain_order)
126+
}
127+
SortBranch::Last => {
128+
let last_branch = chain.branches.last().unwrap();
129+
generate_chain_order_after(&last_branch.chain_order)
130+
}
131+
SortBranch::Before(after_branch) => match chain.before(after_branch) {
132+
None => generate_chain_order_before(&after_branch.chain_order),
133+
Some(before_branch) => generate_chain_order_between(
134+
&before_branch.chain_order,
135+
&after_branch.chain_order,
136+
),
137+
},
138+
SortBranch::After(before_branch) => match chain.after(before_branch) {
139+
None => generate_chain_order_after(&before_branch.chain_order),
140+
Some(after_branch) => generate_chain_order_between(
141+
&before_branch.chain_order,
142+
&after_branch.chain_order,
143+
),
144+
},
145+
};
146+
147+
match maybe_chain_order {
148+
Some(chain_order) => chain_order,
149+
None => {
150+
let mut chain_order = generate_chain_order();
151+
// last resort
152+
while chain.has_chain_order(&chain_order) {
153+
chain_order = generate_chain_order();
154+
}
155+
chain_order
156+
}
157+
}
158+
} else {
159+
generate_chain_order()
160+
};
161+
162+
Ok(chain_order)
163+
}
164+
165+
pub fn setup_branch(
166+
git_chain: &GitChain,
167+
chain_name: &str,
168+
root_branch: &str,
169+
branch_name: &str,
170+
sort_option: &SortBranch,
171+
) -> Result<(), Error> {
172+
Branch::delete_all_configs(git_chain, branch_name)?;
173+
174+
let chain_order = Branch::generate_chain_order(git_chain, chain_name, sort_option)?;
175+
git_chain.set_git_config(&chain_order_key(branch_name), &chain_order)?;
176+
git_chain.set_git_config(&root_branch_key(branch_name), root_branch)?;
177+
git_chain.set_git_config(&chain_name_key(branch_name), chain_name)?;
178+
179+
Ok(())
180+
}
181+
182+
pub fn display_status(&self, git_chain: &GitChain, show_prs: bool) -> Result<(), Error> {
183+
let chain = Chain::get_chain(git_chain, &self.chain_name)?;
184+
185+
let current_branch = git_chain.get_current_branch_name()?;
186+
187+
chain.display_list(git_chain, &current_branch, show_prs)?;
188+
189+
Ok(())
190+
}
191+
192+
pub fn change_root_branch(
193+
&self,
194+
git_chain: &GitChain,
195+
new_root_branch: &str,
196+
) -> Result<(), Error> {
197+
git_chain.set_git_config(&root_branch_key(&self.branch_name), new_root_branch)?;
198+
Ok(())
199+
}
200+
201+
pub fn move_branch(
202+
&self,
203+
git_chain: &GitChain,
204+
chain_name: &str,
205+
sort_option: &SortBranch,
206+
) -> Result<(), Error> {
207+
Branch::setup_branch(
208+
git_chain,
209+
chain_name,
210+
&self.root_branch,
211+
&self.branch_name,
212+
sort_option,
213+
)?;
214+
Ok(())
215+
}
216+
217+
pub fn backup(&self, git_chain: &GitChain) -> Result<(), Error> {
218+
let (object, _reference) = git_chain.repo.revparse_ext(&self.branch_name)?;
219+
let commit = git_chain.repo.find_commit(object.id())?;
220+
221+
let backup_branch = format!("backup-{}/{}", self.chain_name, self.branch_name);
222+
223+
git_chain.repo.branch(&backup_branch, &commit, true)?;
224+
225+
Ok(())
226+
}
227+
228+
pub fn push(&self, git_chain: &GitChain, force_push: bool) -> Result<bool, Error> {
229+
// get branch's upstream
230+
231+
let branch = match git_chain
232+
.repo
233+
.find_branch(&self.branch_name, BranchType::Local)
234+
{
235+
Ok(branch) => branch,
236+
Err(e) => {
237+
if e.code() == ErrorCode::NotFound {
238+
// do nothing
239+
return Ok(false);
240+
}
241+
return Err(e);
242+
}
243+
};
244+
245+
match branch.upstream() {
246+
Ok(_remote_branch) => {
247+
let remote = git_chain
248+
.repo
249+
.branch_upstream_remote(branch.get().name().unwrap())?;
250+
let remote = remote.as_str().unwrap();
251+
252+
let output = if force_push {
253+
// git push --force-with-lease <remote> <branch>
254+
Command::new("git")
255+
.arg("push")
256+
.arg("--force-with-lease")
257+
.arg(remote)
258+
.arg(&self.branch_name)
259+
.output()
260+
.unwrap_or_else(|_| {
261+
panic!(
262+
"Unable to push branch to their upstream: {}",
263+
self.branch_name.bold()
264+
)
265+
})
266+
} else {
267+
// git push <remote> <branch>
268+
Command::new("git")
269+
.arg("push")
270+
.arg(remote)
271+
.arg(&self.branch_name)
272+
.output()
273+
.unwrap_or_else(|_| {
274+
panic!(
275+
"Unable to push branch to their upstream: {}",
276+
self.branch_name.bold()
277+
)
278+
})
279+
};
280+
281+
if output.status.success() {
282+
if force_push {
283+
println!("✅ Force pushed {}", self.branch_name.bold());
284+
} else {
285+
println!("✅ Pushed {}", self.branch_name.bold());
286+
}
287+
288+
Ok(true)
289+
} else {
290+
io::stdout().write_all(&output.stdout).unwrap();
291+
io::stderr().write_all(&output.stderr).unwrap();
292+
println!("🛑 Unable to push {}", self.branch_name.bold());
293+
Ok(false)
294+
}
295+
}
296+
Err(e) => {
297+
if e.code() == ErrorCode::NotFound {
298+
println!(
299+
"🛑 Cannot push. Branch has no upstream: {}",
300+
self.branch_name.bold()
301+
);
302+
// do nothing
303+
return Ok(false);
304+
}
305+
Err(e)
306+
}
307+
}
308+
}
309+
}

0 commit comments

Comments
 (0)