1use thor_core::{find_repo, list_worktrees, remove_worktree};
2use std::path::PathBuf;
3use std::process::Command;
4
5pub struct DoneOpts {
7 pub no_push: bool,
8 pub pr: bool,
9}
10
11pub enum DoneResult {
13 Merged { target_path: PathBuf },
14 PrCreated { url: String, target_path: PathBuf },
15}
16
17pub async fn done(target: &str, opts: &DoneOpts) -> anyhow::Result<DoneResult> {
19 let repo = find_repo()?;
20 let worktrees = list_worktrees(&repo).await?;
21 let cwd = std::env::current_dir()?;
22
23 let current_wt = worktrees.iter()
25 .find(|wt| cwd.starts_with(&wt.path))
26 .ok_or_else(|| anyhow::anyhow!("Not in a worktree"))?;
27
28 let current_branch = current_wt.branch.as_ref()
29 .ok_or_else(|| anyhow::anyhow!("Current worktree has no branch (detached HEAD)"))?
30 .clone();
31
32 if current_branch == target {
33 anyhow::bail!("Already on target branch '{}'", target);
34 }
35
36 let target_wt = worktrees.iter()
38 .find(|wt| wt.branch.as_deref() == Some(target))
39 .ok_or_else(|| anyhow::anyhow!("Target worktree '{}' not found", target))?;
40 let target_path = target_wt.path.clone();
41
42 if opts.pr {
43 let push = Command::new("git")
45 .args(["push", "-u", "origin", ¤t_branch])
46 .current_dir(&cwd)
47 .status()?;
48 if !push.success() {
49 anyhow::bail!("Failed to push branch '{}'", current_branch);
50 }
51
52 let gh_check = Command::new("gh")
54 .arg("--version")
55 .output();
56 if gh_check.is_err() || !gh_check.unwrap().status.success() {
57 anyhow::bail!("'gh' CLI not found. Install it: https://cli.github.com/");
58 }
59
60 let pr_output = Command::new("gh")
62 .args(["pr", "create", "--fill", "--base", target])
63 .current_dir(&cwd)
64 .output()?;
65 if !pr_output.status.success() {
66 let stderr = String::from_utf8_lossy(&pr_output.stderr);
67 anyhow::bail!("Failed to create PR: {}", stderr.trim());
68 }
69 let url = String::from_utf8_lossy(&pr_output.stdout).trim().to_string();
70
71 let _ = Command::new("git")
74 .args(["worktree", "remove", &cwd.display().to_string()])
75 .current_dir(&target_path)
76 .status();
77
78 Ok(DoneResult::PrCreated { url, target_path })
79 } else {
80 if target_wt.ahead > 0 || target_wt.behind > 0 || target_wt.has_upstream() {
83 let _ = Command::new("git")
84 .args(["pull", "--rebase"])
85 .current_dir(&target_path)
86 .status();
87 }
88
89 let merge_msg = format!("Merge branch '{}' into {}", current_branch, target);
91 let merge = Command::new("git")
92 .args(["merge", "--no-ff", ¤t_branch, "-m", &merge_msg])
93 .current_dir(&target_path)
94 .status()?;
95 if !merge.success() {
96 anyhow::bail!("Merge conflict. Resolve in {} and retry.", target_path.display());
97 }
98
99 if !opts.no_push {
101 let _ = Command::new("git")
102 .args(["push"])
103 .current_dir(&target_path)
104 .status();
105 }
106
107 let _ = remove_worktree(&repo, ¤t_branch, true).await;
109
110 Ok(DoneResult::Merged { target_path })
111 }
112}