From ba36a9d2e64f337f9bbe3b4d95948598099075cb Mon Sep 17 00:00:00 2001 From: Stefan Bossbaly Date: Mon, 12 Aug 2024 21:34:17 -0400 Subject: [PATCH] Added checkout subcommand This [`command`] allows for the checking out of a topic branch across multiple repos. When working on a change that effects multiple repos, the general flow is to create a branch with the same name for ever repo that the change touches. These changes are then sent to gerrit and linked together under a topic. When landing, these changes are applied to their respective repos atomically. This change adds the checkout subcommand which allows the developer to change topic branches in their repo checkout easily. [`command`]: https://gerrit.googlesource.com/git-repo/+/refs/heads/main/subcmds/branches.py --- src/main.rs | 11 ++++++++++ src/tree.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/main.rs b/src/main.rs index 8b054ba..10585d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -128,6 +128,12 @@ enum Commands { /// List the topic branches Branches {}, + /// Checkout a branch for development + Checkout { + /// Specify a branch to checkout + branch: String, + }, + /// Checkout a new tree into a new directory Clone { /// The target to checkout in the format [/[:MANIFEST]] @@ -397,6 +403,7 @@ impl std::fmt::Display for Commands { match self { Commands::Init { .. } => write!(f, "init"), Commands::Branches { .. } => write!(f, "branches"), + Commands::Checkout { .. } => write!(f, "checkout"), Commands::Clone { .. } => write!(f, "clone"), Commands::Fetch { .. } => write!(f, "fetch"), Commands::Sync { .. } => write!(f, "sync"), @@ -1025,6 +1032,10 @@ fn main() { let tree = Tree::find_from_path(cwd)?; cmd_branches(config, &mut pool, &tree) } + Commands::Checkout { branch } => { + let tree = Tree::find_from_path(cwd)?; + tree.checkout(&config, &mut pool, &branch) + } Commands::Clone { target, directory, diff --git a/src/tree.rs b/src/tree.rs index 343dea2..d5ad584 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1066,6 +1066,67 @@ impl Tree { Ok(()) } + pub fn checkout(&self, config: &Config, pool: &mut Pool, target_branch: &str) -> Result { + let projects = self.collect_manifest_projects(config, &self.read_manifest()?, None, None)?; + + let mut job = Job::with_name("checkout"); + + for project in &projects { + let path = self.path.join(&project.project_path); + job.add_task(&project.project_path, move || -> Result, Error> { + let repo = git2::Repository::open(&path).context(format!("failed to open object repository {:?}", path))?; + + let maybe_parse = match repo.revparse_ext(target_branch) { + Ok(result) => Ok(Some(result)), + Err(error) => { + if error.code() == git2::ErrorCode::NotFound { + Ok(None) + } else { + Err(error) + } + } + }?; + + if let Some((object, reference)) = maybe_parse { + repo.checkout_tree(&object, None)?; + + match reference { + Some(repo_ref) => repo.set_head(repo_ref.name().unwrap())?, + None => repo.set_head_detached(object.id())?, + } + + Ok(Some(&project.project_path)) + } else { + Ok(None) + } + }); + } + + let results = pool.execute(job); + + if !results.failed.is_empty() { + for error in results.failed { + eprintln!("{}: {}", error.name, error.result); + } + return Ok(1); + } + + let mut has_projects = false; + let checkout_projects = results.successful.into_iter().filter_map(|result| result.result); + + for checkout_project in checkout_projects { + has_projects = true; + println!("Checked out {checkout_project}"); + } + + if has_projects { + Ok(0) + } else { + eprintln!("error: no project has branch {target_branch}"); + Ok(1) + } + } + pub fn sync( &mut self, config: &Config,