git_claw/infra/
worktree.rs1use crate::infra::error::WorktreeError;
4use std::fs;
5use std::path::Path;
6use std::process::Command;
7
8pub fn create_worktree(
11 repo_root: &Path,
12 worktree_path: &Path,
13 branch: &str,
14 base_branch: &str,
15) -> Result<(), WorktreeError> {
16 if worktree_path.exists() {
17 if !worktree_path.join(".git").exists() {
20 let _ = fs::remove_dir_all(worktree_path);
21 } else {
22 return Err(WorktreeError::DirectoryAlreadyExists(
23 worktree_path.to_string_lossy().to_string(),
24 ));
25 }
26 }
27
28 if let Some(parent) = worktree_path.parent() {
29 fs::create_dir_all(parent)?;
30 }
31
32 let mut cmd = Command::new("git");
33 cmd.current_dir(repo_root);
34 cmd.args([
35 "worktree",
36 "add",
37 "-b",
38 branch,
39 &worktree_path.to_string_lossy(),
40 base_branch,
41 ]);
42
43 let output = cmd.output()?;
44 if !output.status.success() {
45 let stderr = String::from_utf8_lossy(&output.stderr);
46
47 if worktree_path.exists() {
49 let _ = fs::remove_dir_all(worktree_path);
50 }
51
52 let mut prune_cmd = Command::new("git");
54 prune_cmd.current_dir(repo_root);
55 prune_cmd.args(["worktree", "prune"]);
56 let _ = prune_cmd.output();
57
58 if stderr.contains("already exists") {
59 return Err(WorktreeError::BranchAlreadyExists(branch.to_string()));
60 }
61
62 return Err(WorktreeError::CreationFailed {
63 path: worktree_path.to_string_lossy().to_string(),
64 message: stderr.trim().to_string(),
65 });
66 }
67
68 Ok(())
69}
70
71pub fn remove_worktree(
73 repo_root: &Path,
74 worktree_path: &Path,
75 force: bool,
76) -> Result<(), WorktreeError> {
77 let mut cmd = Command::new("git");
78 cmd.current_dir(repo_root);
79 cmd.arg("worktree");
80 cmd.arg("remove");
81 if force {
82 cmd.arg("--force");
83 }
84 cmd.arg(worktree_path);
85
86 let output = cmd.output()?;
87 if !output.status.success() {
88 let stderr = String::from_utf8_lossy(&output.stderr);
89 if !worktree_path.exists() {
92 return Err(WorktreeError::RemovalFailed {
93 path: worktree_path.to_string_lossy().to_string(),
94 message: stderr.trim().to_string(),
95 });
96 }
97 }
98
99 if force && worktree_path.exists() {
101 let _ = fs::remove_dir_all(worktree_path);
102 }
103
104 let mut prune = Command::new("git");
106 prune.current_dir(repo_root);
107 prune.args(["worktree", "prune"]);
108 let _ = prune.output();
109
110 Ok(())
111}
112
113pub fn delete_branch(repo_root: &Path, branch: &str, force: bool) -> Result<(), WorktreeError> {
115 let mut cmd = Command::new("git");
116 cmd.current_dir(repo_root);
117 cmd.arg("branch");
118 if force {
119 cmd.arg("-D");
120 } else {
121 cmd.arg("-d");
122 }
123 cmd.arg(branch);
124
125 let output = cmd.output()?;
126 if !output.status.success() {
127 let stderr = String::from_utf8_lossy(&output.stderr);
128 return Err(WorktreeError::RemovalFailed {
129 path: branch.to_string(),
130 message: stderr.trim().to_string(),
131 });
132 }
133
134 Ok(())
135}