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