git_workty/commands/
pr.rs1use 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}