From 69b75605e481c306e9d04b50a775a0cd93a0bf05 Mon Sep 17 00:00:00 2001 From: Stefan Bossbaly Date: Wed, 21 Aug 2024 07:36:20 -0700 Subject: [PATCH] Added branches subcommand Added the branches subcommand which will print out all projects that have a topic branch in their repo. This makes it very easy to find which repos have what topic branches and pairs with the checkout subcommand which allows developers to easily switch between their cross repo changes and check on the status of these changes. --- src/main.rs | 49 +++++++++++++++++++++++++++++++++++++++++ src/tree.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 07320a2..0af5bbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,6 +124,9 @@ enum Commands { local: bool, }, + /// List the topic branches + Branches {}, + /// Checkout a new tree into a new directory Clone { /// The target to checkout in the format [/[:MANIFEST]] @@ -392,6 +395,7 @@ impl std::fmt::Display for Commands { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Commands::Init { .. } => write!(f, "init"), + Commands::Branches { .. } => write!(f, "branches"), Commands::Clone { .. } => write!(f, "clone"), Commands::Fetch { .. } => write!(f, "fetch"), Commands::Sync { .. } => write!(f, "sync"), @@ -447,6 +451,47 @@ fn parse_group_filters(group_filters: &str) -> Vec { group_filters } +fn cmd_branches(config: Config, pool: &mut Pool, tree: &Tree) -> Result { + let results = tree.branches(config, pool)?; + + if results.failed.is_empty() { + let projects_with_topic_branch = results.successful.into_iter().filter_map(|execution_result| { + if execution_result.result.branches.is_empty() { + Some(execution_result.result) + } else { + None + } + }); + + for project in projects_with_topic_branch { + println!( + "{}: {}", + project.name, + project + .branches + .iter() + .map(|branch| { + if branch.is_head { + format!("*{}", branch.name) + } else { + branch.name.clone() + } + }) + .collect::>() + .join(", ") + ); + } + + Ok(0) + } else { + for error in results.failed { + eprintln!("{}: {}", error.name, error.result); + } + + Ok(1) + } +} + fn cmd_clone( config: &Config, pool: &mut Pool, @@ -976,6 +1021,10 @@ fn main() { fetch, ) } + Commands::Branches {} => { + let tree = Tree::find_from_path(cwd)?; + cmd_branches(config, &mut pool, &tree) + } Commands::Clone { target, directory, diff --git a/src/tree.rs b/src/tree.rs index 46670d3..5d8dc46 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -319,6 +319,18 @@ pub struct ProjectStatus { pub behind: usize, } +#[derive(Debug)] +pub struct ProjectBranchStatus { + pub name: String, + pub branches: Vec, +} + +#[derive(Debug)] +pub struct BranchStatus { + pub name: String, + pub is_head: bool, +} + pub struct BranchInfo<'repo> { pub name: String, pub commit: git2::Commit<'repo>, @@ -805,10 +817,12 @@ impl Tree { } // Do a dry run first to look for dirty changes. - repo.checkout_tree( - new_head.as_object(), - Some(git2::build::CheckoutBuilder::new().dry_run()), - ).context(format!("failed to dry run checkout to {:?}", new_head))?; + repo + .checkout_tree( + new_head.as_object(), + Some(git2::build::CheckoutBuilder::new().dry_run()), + ) + .context(format!("failed to dry run checkout to {:?}", new_head))?; repo .checkout_tree(new_head.as_object(), None) @@ -1157,6 +1171,47 @@ impl Tree { Ok(0) } + pub fn branches( + &self, + config: Config, + pool: &mut Pool, + ) -> Result, Error> { + let projects = self.collect_manifest_projects(&config, &self.read_manifest()?, None, None)?; + + let mut job = Job::with_name("branches"); + + for project in projects { + job.add_task( + project.project_path.clone(), + move || -> Result { + let repo = git2::Repository::open(self.path.join(&project.project_path)) + .context(format!("failed to open object repository {:?}", project.project_path))?; + + let topic_branch_results = repo.branches(Some(git2::BranchType::Local))?; + let mut branches = Vec::new(); + + for topic_branch_result in topic_branch_results { + let (topic_branch, _) = topic_branch_result?; + branches.push(BranchStatus { + name: topic_branch + .name()? + .expect("Branch should have utf-8 encoded name") + .to_owned(), + is_head: topic_branch.is_head(), + }); + } + + Ok(ProjectBranchStatus { + name: project.project_name.clone(), + branches, + }) + }, + ); + } + + Ok(pool.execute(job)) + } + pub fn status( &self, config: &Config,