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,