use crate::bootstrap::{self, BootstrapCtx, BootstrapReport};
use crate::config::Config;
use crate::error::{GwmError, Result};
use crate::lifecycle::{self, HookContext, HookPhase, HookSkips};
use crate::naming::kebab;
use crate::{github, launcher, worktree};
use git2::Repository;
use std::path::{Path, PathBuf};
pub fn head_slug(head_ref: &str) -> String {
let last = head_ref.rsplit('/').next().unwrap_or(head_ref);
kebab(last)
}
fn review_tail(number: u64, author: &str, slug: &str) -> String {
let mut tail = format!("pr-{number}");
let author = kebab(author);
if !author.is_empty() {
tail.push('-');
tail.push_str(&author);
}
let slug = kebab(slug);
if !slug.is_empty() {
tail.push('-');
tail.push_str(&slug);
}
tail
}
pub fn review_branch_name(number: u64, author: &str, slug: &str) -> String {
format!("review/{}", review_tail(number, author, slug))
}
pub fn review_dirname(number: u64, author: &str, slug: &str) -> String {
format!("review-{}", review_tail(number, author, slug))
}
pub fn dirname_from_branch(branch: &str) -> String {
branch.replace('/', "-")
}
pub fn fetch_pr_head_ref(workdir: &Path, number: u64, branch: &str) -> Result<()> {
let refspec = format!("pull/{number}/head:refs/heads/{branch}");
worktree::run_git_logged(workdir, &["fetch", "origin", &refspec])?;
Ok(())
}
#[derive(Debug, Clone)]
pub struct ReviewSpec<'a> {
pub number: u64,
pub branch: &'a str,
pub dirname: &'a str,
pub target: &'a Path,
pub base_ref: Option<&'a str>,
}
pub fn materialize(repo: &Repository, workdir: &Path, spec: &ReviewSpec) -> Result<PathBuf> {
if repo.find_branch(spec.branch, git2::BranchType::Local).is_ok() {
return Err(GwmError::Other(format!(
"review branch '{}' already exists; remove the existing review worktree first (e.g. `gwm remove {} --delete-branch`)",
spec.branch, spec.dirname
)));
}
if spec.target.exists() {
return Err(GwmError::WorktreeExists(
spec.dirname.into(),
spec.target.display().to_string(),
));
}
fetch_pr_head_ref(workdir, spec.number, spec.branch)?;
let created = match worktree::add(repo, spec.dirname, spec.target, spec.branch, true) {
Ok(path) => path,
Err(e) => {
if let Ok(mut b) = repo.find_branch(spec.branch, git2::BranchType::Local) {
let _ = b.delete();
}
return Err(e);
}
};
github::link_pr(repo, spec.branch, spec.number)?;
if let Some(base) = spec.base_ref {
let _ = launcher::write_gwm_base(repo, spec.branch, base);
}
Ok(created)
}
#[derive(Debug)]
pub struct ReviewSetupReports {
pub pre_bootstrap: BootstrapReport,
pub bootstrap: BootstrapReport,
pub post_bootstrap: BootstrapReport,
pub post_create: BootstrapReport,
}
pub fn run_post_setup(
config: &Config,
ctx: &HookContext,
main_repo: &Path,
worktree: &Path,
skips: &HookSkips,
bootstrap: bool,
) -> Result<Option<ReviewSetupReports>> {
if !bootstrap {
return Ok(None);
}
let pre_bootstrap = lifecycle::run_phase(config, HookPhase::PreBootstrap, ctx, skips, false)?;
let bctx = BootstrapCtx {
main_repo,
worktree,
config,
};
let bootstrap = bootstrap::run_core(&bctx)?;
let post_bootstrap = lifecycle::run_phase(config, HookPhase::PostBootstrap, ctx, skips, false)?;
let post_create = lifecycle::run_phase(config, HookPhase::PostCreate, ctx, skips, true)?;
Ok(Some(ReviewSetupReports {
pre_bootstrap,
bootstrap,
post_bootstrap,
post_create,
}))
}