Skip to content

Commit

Permalink
Added checkout subcommand
Browse files Browse the repository at this point in the history
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
  • Loading branch information
StefanBossbaly authored and Stefan Bossbaly committed Sep 18, 2024
1 parent 8523090 commit 525d672
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 1 deletion.
14 changes: 13 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ enum Commands {
local: bool,
},

/// 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 <REMOTE>[/<BRANCH>[:MANIFEST]]
Expand Down Expand Up @@ -392,6 +398,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::Checkout { .. } => write!(f, "checkout"),
Commands::Clone { .. } => write!(f, "clone"),
Commands::Fetch { .. } => write!(f, "fetch"),
Commands::Sync { .. } => write!(f, "sync"),
Expand Down Expand Up @@ -447,6 +454,10 @@ fn parse_group_filters(group_filters: &str) -> Vec<GroupFilter> {
group_filters
}

fn cmd_checkout(config: &Config, pool: &mut Pool, tree: &Tree, target_branch: &str) -> Result<i32, Error> {
tree.checkout(config, pool, target_branch)
}

fn cmd_clone(
config: &Config,
pool: &mut Pool,
Expand Down Expand Up @@ -660,7 +671,7 @@ fn cmd_import(config: &Config, pool: &mut Pool, target_path: Option<PathBuf>, co

let mut job = Job::with_name("import");
for (remote, projects) in &remote_projects {
let remote_config = config.find_remote(&remote)?;
let remote_config = config.find_remote(remote)?;
let depot = config.find_depot(&remote_config.depot)?;
std::fs::create_dir_all(&depot.path).context("failed to create depot directory")?;
let depot_metadata = std::fs::metadata(&depot.path)?;
Expand Down Expand Up @@ -976,6 +987,7 @@ fn main() {
fetch,
)
}
Commands::Checkout { branch } => cmd_checkout(&config, &mut pool, &Tree::find_from_path(cwd)?, &branch),
Commands::Clone {
target,
directory,
Expand Down
65 changes: 65 additions & 0 deletions src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,71 @@ impl Tree {
Ok(())
}

pub fn checkout(&self, config: &Config, pool: &mut Pool, target_branch: &str) -> Result<i32, Error> {
let projects = self.collect_manifest_projects(config, &self.read_manifest()?, None, None)?;

let mut job = Job::with_name("checkout");

for project in projects {
job.add_task(
project.project_path.clone(),
move || -> Result<Option<String>, Error> {
let repo = git2::Repository::open(self.path.join(&project.project_path))
.context(format!("failed to open object repository {:?}", project.project_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 checkout_projects = results
.successful
.into_iter()
.filter_map(|result| result.result)
.peekable();

if checkout_projects.peek().is_none() {
eprintln!("error: no project has branch {target_branch}");
Ok(1)
} else {
for checkout_project in checkout_projects {
println!("Checked out {checkout_project}");
}
Ok(0)
}
}

pub fn sync(
&mut self,
config: &Config,
Expand Down

0 comments on commit 525d672

Please sign in to comment.