Skip to main content

git_workty/commands/
pr.rs

1use crate::config::Config;
2use crate::gh::{checkout_pr, get_pr_branch, is_gh_authenticated, is_gh_installed};
3use crate::git::GitRepo;
4use crate::ui::{print_info, print_success};
5use crate::worktree::{list_worktrees, slug_from_branch};
6use anyhow::{bail, Context, Result};
7use std::process::Command;
8
9pub struct PrOptions {
10    pub number: u32,
11    pub print_path: bool,
12    pub open: bool,
13}
14
15pub fn execute(repo: &GitRepo, opts: PrOptions) -> Result<()> {
16    if !is_gh_installed() {
17        bail!("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/");
18    }
19
20    if !is_gh_authenticated() {
21        bail!("GitHub CLI is not authenticated. Run `gh auth login` to authenticate.");
22    }
23
24    let config = Config::load(repo)?;
25    let pr_name = format!("pr-{}", opts.number);
26
27    let worktrees = list_worktrees(repo)?;
28    if let Some(existing) = worktrees.iter().find(|wt| {
29        wt.branch_short.as_deref() == Some(&pr_name)
30            || wt.path.file_name().and_then(|s| s.to_str()) == Some(&pr_name)
31    }) {
32        print_info(&format!(
33            "PR #{} already has a worktree at {}",
34            opts.number,
35            existing.path.display()
36        ));
37        println!("{}", existing.path.display());
38        return Ok(());
39    }
40
41    let branch_name = get_pr_branch(opts.number)?;
42    print_info(&format!(
43        "PR #{} uses branch '{}'",
44        opts.number, branch_name
45    ));
46
47    let slug = slug_from_branch(&pr_name);
48    let worktree_path = config.worktree_path(repo, &slug);
49
50    if worktree_path.exists() {
51        bail!(
52            "Directory already exists: {}\nUse a different path or remove the existing directory.",
53            worktree_path.display()
54        );
55    }
56
57    if let Some(parent) = worktree_path.parent() {
58        std::fs::create_dir_all(parent)
59            .with_context(|| format!("Failed to create directory: {}", parent.display()))?;
60    }
61
62    let path_str = worktree_path
63        .to_str()
64        .ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8: {:?}", worktree_path))?;
65
66    let output = Command::new("git")
67        .current_dir(&repo.root)
68        .args(["worktree", "add", path_str, "--detach"])
69        .output()
70        .context("Failed to create worktree")?;
71
72    if !output.status.success() {
73        let stderr = String::from_utf8_lossy(&output.stderr);
74        bail!("Failed to create worktree: {}", stderr.trim());
75    }
76
77    checkout_pr(&worktree_path, opts.number)?;
78
79    if opts.print_path {
80        println!("{}", worktree_path.display());
81    } else {
82        print_success(&format!(
83            "Created PR worktree at {}",
84            worktree_path.display()
85        ));
86    }
87
88    if opts.open {
89        if let Some(open_cmd) = &config.open_cmd {
90            let _ = Command::new(open_cmd).arg(&worktree_path).spawn();
91        }
92    }
93
94    Ok(())
95}