Skip to content

Commit c71857d

Browse files
committed
refactor: Split GitChain into modular submodules
Create git_chain/ subdirectory with: - mod.rs: GitChain struct definition - core.rs: Core repository operations (15 methods) - merge.rs: Merge-related operations (14 methods) - operations.rs: High-level chain operations (6 methods + helper) Extracted ~1600 lines from main.rs into organized git_chain module. All 52 tests pass.
1 parent 4ad08b5 commit c71857d

File tree

5 files changed

+1613
-1604
lines changed

5 files changed

+1613
-1604
lines changed

src/git_chain/core.rs

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
use std::process;
2+
3+
use colored::*;
4+
use git2::{BranchType, Config, ConfigLevel, Error, ErrorCode, ObjectType, Repository};
5+
use regex::Regex;
6+
7+
use super::GitChain;
8+
use crate::types::*;
9+
use crate::{executable_name, Branch, Chain};
10+
11+
impl GitChain {
12+
pub fn init() -> Result<Self, Error> {
13+
let name_of_current_executable = executable_name();
14+
15+
let repo = Repository::discover(".")?;
16+
17+
if repo.is_bare() {
18+
eprintln!(
19+
"Cannot run {} on bare git repository.",
20+
name_of_current_executable
21+
);
22+
process::exit(1);
23+
}
24+
25+
let git_chain = GitChain {
26+
repo,
27+
executable_name: name_of_current_executable,
28+
};
29+
Ok(git_chain)
30+
}
31+
32+
pub fn get_current_branch_name(&self) -> Result<String, Error> {
33+
let head = match self.repo.head() {
34+
Ok(head) => Some(head),
35+
Err(ref e)
36+
if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound =>
37+
{
38+
None
39+
}
40+
Err(e) => return Err(e),
41+
};
42+
43+
let head = head.as_ref().and_then(|h| h.shorthand());
44+
45+
match head {
46+
Some(branch_name) => Ok(branch_name.to_string()),
47+
None => Err(Error::from_str("Unable to get current branch name.")),
48+
}
49+
}
50+
51+
pub fn get_local_git_config(&self) -> Result<Config, Error> {
52+
self.repo.config()?.open_level(ConfigLevel::Local)
53+
}
54+
55+
pub fn get_git_config(&self, key: &str) -> Result<Option<String>, Error> {
56+
let local_config = self.get_local_git_config()?;
57+
match local_config.get_string(key) {
58+
Ok(value) => Ok(Some(value)),
59+
Err(ref e) if e.code() == ErrorCode::NotFound => Ok(None),
60+
Err(e) => Err(e),
61+
}
62+
}
63+
64+
pub fn get_git_configs_matching_key(
65+
&self,
66+
regexp: &Regex,
67+
) -> Result<Vec<(String, String)>, Error> {
68+
let local_config = self.get_local_git_config()?;
69+
let mut entries = vec![];
70+
71+
local_config.entries(None)?.for_each(|entry| {
72+
if let Some(key) = entry.name() {
73+
if regexp.is_match(key) && entry.has_value() {
74+
let key = key.to_string();
75+
let value = entry.value().unwrap().to_string();
76+
entries.push((key, value));
77+
}
78+
}
79+
})?;
80+
81+
Ok(entries)
82+
}
83+
84+
pub fn set_git_config(&self, key: &str, value: &str) -> Result<(), Error> {
85+
let mut local_config = self.get_local_git_config()?;
86+
local_config.set_str(key, value)?;
87+
Ok(())
88+
}
89+
90+
pub fn delete_git_config(&self, key: &str) -> Result<(), Error> {
91+
let mut local_config = self.get_local_git_config()?;
92+
match local_config.remove(key) {
93+
Ok(()) => Ok(()),
94+
Err(ref e) if e.code() == ErrorCode::NotFound => Ok(()),
95+
Err(e) => Err(e),
96+
}
97+
}
98+
99+
pub fn checkout_branch(&self, branch_name: &str) -> Result<(), Error> {
100+
let (object, reference) = self.repo.revparse_ext(branch_name)?;
101+
102+
// set working directory
103+
self.repo.checkout_tree(&object, None)?;
104+
105+
// set HEAD to branch_name
106+
match reference {
107+
// ref_name is an actual reference like branches or tags
108+
Some(ref_name) => self.repo.set_head(ref_name.name().unwrap()),
109+
// this is a commit, not a reference
110+
None => self.repo.set_head_detached(object.id()),
111+
}
112+
.unwrap_or_else(|_| panic!("Failed to set HEAD to branch {}", branch_name));
113+
114+
Ok(())
115+
}
116+
117+
pub fn git_branch_exists(&self, branch_name: &str) -> Result<bool, Error> {
118+
Ok(self.git_local_branch_exists(branch_name)?
119+
|| self.git_remote_branch_exists(branch_name)?)
120+
}
121+
122+
pub fn git_local_branch_exists(&self, branch_name: &str) -> Result<bool, Error> {
123+
match self.repo.find_branch(branch_name, BranchType::Local) {
124+
Ok(_branch) => Ok(true),
125+
Err(ref e) if e.code() == ErrorCode::NotFound => Ok(false),
126+
Err(e) => Err(e),
127+
}
128+
}
129+
130+
pub fn git_remote_branch_exists(&self, branch_name: &str) -> Result<bool, Error> {
131+
match self.repo.find_branch(branch_name, BranchType::Remote) {
132+
Ok(_branch) => Ok(true),
133+
Err(ref e) if e.code() == ErrorCode::NotFound => Ok(false),
134+
Err(e) => Err(e),
135+
}
136+
}
137+
138+
pub fn display_branch_not_part_of_chain_error(&self, branch_name: &str) {
139+
eprintln!("❌ Branch is not part of any chain: {}", branch_name.bold());
140+
eprintln!(
141+
"To initialize a chain for this branch, run {} init <chain_name> <root_branch>",
142+
self.executable_name
143+
);
144+
}
145+
146+
pub fn run_status(&self, show_prs: bool) -> Result<(), Error> {
147+
let branch_name = self.get_current_branch_name()?;
148+
println!("On branch: {}", branch_name.bold());
149+
println!();
150+
151+
let results = Branch::get_branch_with_chain(self, &branch_name)?;
152+
153+
match results {
154+
BranchSearchResult::NotPartOfAnyChain => {
155+
self.display_branch_not_part_of_chain_error(&branch_name);
156+
process::exit(1);
157+
}
158+
BranchSearchResult::Branch(branch) => {
159+
branch.display_status(self, show_prs)?;
160+
}
161+
}
162+
163+
Ok(())
164+
}
165+
166+
pub fn init_chain(
167+
&self,
168+
chain_name: &str,
169+
root_branch: &str,
170+
branch_name: &str,
171+
sort_option: SortBranch,
172+
) -> Result<(), Error> {
173+
let results = Branch::get_branch_with_chain(self, branch_name)?;
174+
175+
match results {
176+
BranchSearchResult::NotPartOfAnyChain => {
177+
Branch::setup_branch(self, chain_name, root_branch, branch_name, &sort_option)?;
178+
179+
match Branch::get_branch_with_chain(self, branch_name)? {
180+
BranchSearchResult::NotPartOfAnyChain => {
181+
eprintln!("Unable to set up chain for branch: {}", branch_name.bold());
182+
process::exit(1);
183+
}
184+
BranchSearchResult::Branch(branch) => {
185+
println!("🔗 Succesfully set up branch: {}", branch_name.bold());
186+
println!();
187+
branch.display_status(self, false)?;
188+
}
189+
};
190+
}
191+
BranchSearchResult::Branch(branch) => {
192+
eprintln!("❌ Unable to initialize branch to a chain.",);
193+
eprintln!();
194+
eprintln!("Branch already part of a chain: {}", branch_name.bold());
195+
eprintln!("It is part of the chain: {}", branch.chain_name.bold());
196+
eprintln!("With root branch: {}", branch.root_branch.bold());
197+
process::exit(1);
198+
}
199+
};
200+
201+
Ok(())
202+
}
203+
204+
pub fn remove_branch_from_chain(&self, branch_name: String) -> Result<(), Error> {
205+
let results = Branch::get_branch_with_chain(self, &branch_name)?;
206+
207+
match results {
208+
BranchSearchResult::NotPartOfAnyChain => {
209+
Branch::delete_all_configs(self, &branch_name)?;
210+
211+
println!(
212+
"Unable to remove branch from its chain: {}",
213+
branch_name.bold()
214+
);
215+
println!("It is not part of any chain. Nothing to do.");
216+
}
217+
BranchSearchResult::Branch(branch) => {
218+
let chain_name = branch.chain_name.clone();
219+
let root_branch = branch.root_branch.clone();
220+
branch.remove_from_chain(self)?;
221+
222+
println!(
223+
"Removed branch {} from chain {}",
224+
branch_name.bold(),
225+
chain_name.bold()
226+
);
227+
println!("Its root branch was: {}", root_branch.bold());
228+
}
229+
};
230+
Ok(())
231+
}
232+
233+
pub fn list_chains(&self, current_branch: &str, show_prs: bool) -> Result<(), Error> {
234+
let list = Chain::get_all_chains(self)?;
235+
236+
if list.is_empty() {
237+
println!("No chains to list.");
238+
println!(
239+
"To initialize a chain for this branch, run {} init <chain_name> <root_branch>",
240+
self.executable_name
241+
);
242+
return Ok(());
243+
}
244+
245+
for (index, chain) in list.iter().enumerate() {
246+
chain.display_list(self, current_branch, show_prs)?;
247+
248+
if index != list.len() - 1 {
249+
println!();
250+
}
251+
}
252+
253+
Ok(())
254+
}
255+
256+
pub fn move_branch(
257+
&self,
258+
chain_name: &str,
259+
branch_name: &str,
260+
sort_option: &SortBranch,
261+
) -> Result<(), Error> {
262+
match Branch::get_branch_with_chain(self, branch_name)? {
263+
BranchSearchResult::NotPartOfAnyChain => {
264+
self.display_branch_not_part_of_chain_error(branch_name);
265+
process::exit(1);
266+
}
267+
BranchSearchResult::Branch(branch) => {
268+
branch.move_branch(self, chain_name, sort_option)?;
269+
270+
match Branch::get_branch_with_chain(self, &branch.branch_name)? {
271+
BranchSearchResult::NotPartOfAnyChain => {
272+
eprintln!("Unable to move branch: {}", branch.branch_name.bold());
273+
process::exit(1);
274+
}
275+
BranchSearchResult::Branch(branch) => {
276+
println!("🔗 Succesfully moved branch: {}", branch.branch_name.bold());
277+
println!();
278+
branch.display_status(self, false)?;
279+
}
280+
};
281+
}
282+
};
283+
284+
Ok(())
285+
}
286+
287+
pub fn get_commit_hash_of_head(&self) -> Result<String, Error> {
288+
let head = self.repo.head()?;
289+
let oid = head.target().unwrap();
290+
let commit = self.repo.find_commit(oid).unwrap();
291+
Ok(commit.id().to_string())
292+
}
293+
294+
pub fn get_tree_id_from_branch_name(&self, branch_name: &str) -> Result<String, Error> {
295+
match self
296+
.repo
297+
.revparse_single(&format!("{}^{{tree}}", branch_name))
298+
{
299+
Ok(tree_object) => {
300+
assert_eq!(tree_object.kind().unwrap(), ObjectType::Tree);
301+
Ok(tree_object.id().to_string())
302+
}
303+
Err(_err) => Err(Error::from_str(&format!(
304+
"Unable to get tree id of branch {}",
305+
branch_name.bold()
306+
))),
307+
}
308+
}
309+
310+
pub fn dirty_working_directory(&self) -> Result<bool, Error> {
311+
// perform equivalent to git diff-index HEAD
312+
let obj = self.repo.revparse_single("HEAD")?;
313+
let tree = obj.peel(ObjectType::Tree)?;
314+
315+
let diff = self
316+
.repo
317+
.diff_tree_to_workdir_with_index(tree.as_tree(), None)?;
318+
319+
let diff_stats = diff.stats()?;
320+
let has_changes = diff_stats.files_changed() > 0
321+
|| diff_stats.insertions() > 0
322+
|| diff_stats.deletions() > 0;
323+
324+
Ok(has_changes)
325+
}
326+
}

0 commit comments

Comments
 (0)