gestura_core_tools/
git_async.rs

1//! Async git operations for pipeline integration
2//!
3//! Wraps the synchronous [`GitTools`] via
4//! `tokio::task::spawn_blocking` for use in async contexts (pipeline, GUI).
5
6use crate::error::AppError;
7use crate::error::Result;
8use crate::git::{GitTools, GitWorktreeInfo};
9use std::path::{Path, PathBuf};
10
11/// Execute a git operation asynchronously.
12pub async fn execute_git(operation: &str, path: &str) -> Result<String> {
13    let op = operation.to_string();
14    let work_path = path.to_string();
15
16    tokio::task::spawn_blocking(move || {
17        let tools = GitTools::new(Some(std::path::PathBuf::from(&work_path)));
18        match op.as_str() {
19            "status" => tools.status().map(|s| {
20                format!(
21                    "Branch: {}\nStaged: {} files\nUnstaged: {} files\nUntracked: {} files",
22                    s.branch,
23                    s.staged.len(),
24                    s.unstaged.len(),
25                    s.untracked.len()
26                )
27            }),
28            "log" => tools.log(Some(10), None).map(|commits| {
29                commits
30                    .iter()
31                    .map(|c| format!("{} - {} ({})", c.short_hash, c.message, c.author))
32                    .collect::<Vec<_>>()
33                    .join("\n")
34            }),
35            "diff" => tools.diff(false, None),
36            "diff-staged" => tools.diff(true, None),
37            "branches" => tools.branches(false).map(|branches| {
38                branches
39                    .iter()
40                    .map(|b| {
41                        if b.is_current {
42                            format!("* {}", b.name)
43                        } else {
44                            format!("  {}", b.name)
45                        }
46                    })
47                    .collect::<Vec<_>>()
48                    .join("\n")
49            }),
50            _ => Err(AppError::Io(std::io::Error::other(format!(
51                "Unknown git operation: {}",
52                op
53            )))),
54        }
55    })
56    .await
57    .map_err(|e| AppError::Io(std::io::Error::other(format!("Task join error: {}", e))))?
58}
59
60/// Resolve the repository top-level path asynchronously.
61pub async fn rev_parse_toplevel(path: impl AsRef<Path>) -> Result<PathBuf> {
62    let work_path = path.as_ref().to_path_buf();
63    tokio::task::spawn_blocking(move || GitTools::new(Some(work_path)).rev_parse_toplevel())
64        .await
65        .map_err(|e| AppError::Io(std::io::Error::other(format!("Task join error: {}", e))))?
66}
67
68/// Get the current branch asynchronously.
69pub async fn current_branch(path: impl AsRef<Path>) -> Result<String> {
70    let work_path = path.as_ref().to_path_buf();
71    tokio::task::spawn_blocking(move || GitTools::new(Some(work_path)).current_branch())
72        .await
73        .map_err(|e| AppError::Io(std::io::Error::other(format!("Task join error: {}", e))))?
74}
75
76/// List worktrees asynchronously.
77pub async fn worktree_list(path: impl AsRef<Path>) -> Result<Vec<GitWorktreeInfo>> {
78    let work_path = path.as_ref().to_path_buf();
79    tokio::task::spawn_blocking(move || GitTools::new(Some(work_path)).worktree_list())
80        .await
81        .map_err(|e| AppError::Io(std::io::Error::other(format!("Task join error: {}", e))))?
82}
83
84/// Create a worktree asynchronously.
85pub async fn worktree_add(
86    path: impl AsRef<Path>,
87    worktree_path: impl AsRef<Path>,
88    branch: String,
89    base_branch: String,
90    create_branch: bool,
91) -> Result<GitWorktreeInfo> {
92    let work_path = path.as_ref().to_path_buf();
93    let worktree_path = worktree_path.as_ref().to_path_buf();
94    tokio::task::spawn_blocking(move || {
95        GitTools::new(Some(work_path)).worktree_add(
96            &worktree_path,
97            &branch,
98            &base_branch,
99            create_branch,
100        )
101    })
102    .await
103    .map_err(|e| AppError::Io(std::io::Error::other(format!("Task join error: {}", e))))?
104}
105
106/// Remove a worktree asynchronously.
107pub async fn worktree_remove(
108    path: impl AsRef<Path>,
109    worktree_path: impl AsRef<Path>,
110    force: bool,
111) -> Result<()> {
112    let work_path = path.as_ref().to_path_buf();
113    let worktree_path = worktree_path.as_ref().to_path_buf();
114    tokio::task::spawn_blocking(move || {
115        GitTools::new(Some(work_path)).worktree_remove(&worktree_path, force)
116    })
117    .await
118    .map_err(|e| AppError::Io(std::io::Error::other(format!("Task join error: {}", e))))?
119}
120
121/// Check whether a worktree is clean asynchronously.
122pub async fn is_worktree_clean(
123    path: impl AsRef<Path>,
124    worktree_path: impl AsRef<Path>,
125) -> Result<bool> {
126    let work_path = path.as_ref().to_path_buf();
127    let worktree_path = worktree_path.as_ref().to_path_buf();
128    tokio::task::spawn_blocking(move || {
129        GitTools::new(Some(work_path)).is_worktree_clean(&worktree_path)
130    })
131    .await
132    .map_err(|e| AppError::Io(std::io::Error::other(format!("Task join error: {}", e))))?
133}