miyabi_worktree/
git.rs

1//! Git操作モジュール - Worktree作成・削除・管理
2//!
3//! Gitの低レベル操作を担当します。
4
5use std::path::{Path, PathBuf};
6use std::process::Command;
7
8/// Git操作結果
9pub type GitResult<T> = Result<T, GitError>;
10
11/// Gitエラー
12#[derive(Debug, thiserror::Error)]
13pub enum GitError {
14    #[error("Git command failed: {0}")]
15    CommandFailed(String),
16
17    #[error("Worktree already exists: {0}")]
18    WorktreeExists(PathBuf),
19
20    #[error("Worktree not found: {0}")]
21    WorktreeNotFound(PathBuf),
22
23    #[error("Invalid path: {0}")]
24    InvalidPath(String),
25}
26
27/// Git Worktree操作
28pub struct GitWorktreeOps {
29    repo_path: PathBuf,
30}
31
32impl GitWorktreeOps {
33    /// 新しいGit操作ハンドラを作成
34    pub fn new(repo_path: impl AsRef<Path>) -> Self {
35        Self {
36            repo_path: repo_path.as_ref().to_path_buf(),
37        }
38    }
39
40    /// Worktreeを作成
41    pub fn create_worktree(
42        &self,
43        worktree_path: impl AsRef<Path>,
44        branch_name: &str,
45    ) -> GitResult<PathBuf> {
46        let worktree_path = worktree_path.as_ref();
47
48        if worktree_path.exists() {
49            return Err(GitError::WorktreeExists(worktree_path.to_path_buf()));
50        }
51
52        let output = Command::new("git")
53            .arg("worktree")
54            .arg("add")
55            .arg(worktree_path)
56            .arg("-b")
57            .arg(branch_name)
58            .current_dir(&self.repo_path)
59            .output()
60            .map_err(|e| GitError::CommandFailed(e.to_string()))?;
61
62        if !output.status.success() {
63            return Err(GitError::CommandFailed(
64                String::from_utf8_lossy(&output.stderr).to_string(),
65            ));
66        }
67
68        Ok(worktree_path.to_path_buf())
69    }
70
71    /// Worktreeを削除
72    pub fn remove_worktree(&self, worktree_path: impl AsRef<Path>) -> GitResult<()> {
73        let worktree_path = worktree_path.as_ref();
74
75        if !worktree_path.exists() {
76            return Err(GitError::WorktreeNotFound(worktree_path.to_path_buf()));
77        }
78
79        let output = Command::new("git")
80            .arg("worktree")
81            .arg("remove")
82            .arg(worktree_path)
83            .arg("--force")
84            .current_dir(&self.repo_path)
85            .output()
86            .map_err(|e| GitError::CommandFailed(e.to_string()))?;
87
88        if !output.status.success() {
89            return Err(GitError::CommandFailed(
90                String::from_utf8_lossy(&output.stderr).to_string(),
91            ));
92        }
93
94        Ok(())
95    }
96
97    /// 全Worktreeをリスト表示
98    pub fn list_worktrees(&self) -> GitResult<Vec<PathBuf>> {
99        let output = Command::new("git")
100            .arg("worktree")
101            .arg("list")
102            .arg("--porcelain")
103            .current_dir(&self.repo_path)
104            .output()
105            .map_err(|e| GitError::CommandFailed(e.to_string()))?;
106
107        if !output.status.success() {
108            return Err(GitError::CommandFailed(
109                String::from_utf8_lossy(&output.stderr).to_string(),
110            ));
111        }
112
113        let stdout = String::from_utf8_lossy(&output.stdout);
114        let paths: Vec<PathBuf> = stdout
115            .lines()
116            .filter(|line| line.starts_with("worktree "))
117            .filter_map(|line| line.strip_prefix("worktree "))
118            .map(PathBuf::from)
119            .collect();
120
121        Ok(paths)
122    }
123
124    /// Worktreeをクリーンアップ(prune)
125    pub fn prune_worktrees(&self) -> GitResult<()> {
126        let output = Command::new("git")
127            .arg("worktree")
128            .arg("prune")
129            .current_dir(&self.repo_path)
130            .output()
131            .map_err(|e| GitError::CommandFailed(e.to_string()))?;
132
133        if !output.status.success() {
134            return Err(GitError::CommandFailed(
135                String::from_utf8_lossy(&output.stderr).to_string(),
136            ));
137        }
138
139        Ok(())
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use tempfile::TempDir;
147
148    #[test]
149    fn test_git_worktree_ops_creation() {
150        let temp_dir = TempDir::new().unwrap();
151        let ops = GitWorktreeOps::new(temp_dir.path());
152        assert_eq!(ops.repo_path, temp_dir.path());
153    }
154
155    #[test]
156    fn test_invalid_path_error() {
157        let ops = GitWorktreeOps::new("/nonexistent/path");
158        // Note: This test just verifies the struct can be created
159        // Actual Git operations would fail at runtime
160        assert!(ops.repo_path.to_str().is_some());
161    }
162}