rstask_core/
git.rs

1// Placeholder for git module - to be implemented
2use crate::Result;
3use git2::Repository;
4use std::path::Path;
5
6pub fn ensure_repo_exists(repo_path: &Path) -> Result<()> {
7    if !repo_path.exists() {
8        std::fs::create_dir_all(repo_path)?;
9        Repository::init(repo_path)?;
10    }
11    Ok(())
12}
13
14pub fn git_commit(repo_path: &Path, message: &str) -> Result<()> {
15    use std::process::Command;
16
17    // Add all files
18    let add_status = Command::new("git")
19        .args(["-C", &repo_path.to_string_lossy(), "add", "."])
20        .status()?;
21
22    if !add_status.success() {
23        return Err(crate::RstaskError::Other("git add failed".to_string()));
24    }
25
26    // Check if there are changes to commit
27    let diff_status = Command::new("git")
28        .args([
29            "-C",
30            &repo_path.to_string_lossy(),
31            "diff-index",
32            "--quiet",
33            "HEAD",
34            "--",
35        ])
36        .status();
37
38    // If diff-index returns 0, no changes
39    if let Ok(status) = diff_status
40        && status.success()
41    {
42        eprintln!("No changes detected");
43        return Ok(());
44    }
45
46    // Commit with output shown
47    let commit_status = Command::new("git")
48        .args([
49            "-C",
50            &repo_path.to_string_lossy(),
51            "commit",
52            "--no-gpg-sign",
53            "-m",
54            message,
55        ])
56        .status()?;
57
58    if !commit_status.success() {
59        return Err(crate::RstaskError::Other("git commit failed".to_string()));
60    }
61
62    Ok(())
63}
64
65pub fn git_pull(repo_path: &str) -> Result<()> {
66    let repo = Repository::open(repo_path)?;
67
68    // Fetch from origin
69    let mut remote = repo.find_remote("origin")?;
70    remote.fetch(&["master"], None, None)?;
71
72    // Merge FETCH_HEAD into current branch
73    let fetch_head = repo.find_reference("FETCH_HEAD")?;
74    let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?;
75
76    let analysis = repo.merge_analysis(&[&fetch_commit])?;
77
78    if analysis.0.is_up_to_date() {
79        // Already up to date
80        Ok(())
81    } else if analysis.0.is_fast_forward() {
82        // Fast-forward merge
83        let refname = "refs/heads/master";
84        let mut reference = repo.find_reference(refname)?;
85        reference.set_target(fetch_commit.id(), "Fast-Forward")?;
86        repo.set_head(refname)?;
87        repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?;
88        Ok(())
89    } else {
90        // Would require actual merge - for now just error
91        Err(crate::RstaskError::Git(git2::Error::from_str(
92            "merge required",
93        )))
94    }
95}
96
97pub fn git_push(repo_path: &str) -> Result<()> {
98    let repo = Repository::open(repo_path)?;
99    let mut remote = repo.find_remote("origin")?;
100
101    // Push master branch
102    remote.push(&["refs/heads/master:refs/heads/master"], None)?;
103
104    Ok(())
105}
106
107pub fn git_reset(repo_path: &Path) -> Result<()> {
108    let repo = Repository::open(repo_path)?;
109
110    // Reset to HEAD~1 (one commit back)
111    let head = repo.head()?;
112    let head_commit = head.peel_to_commit()?;
113
114    let parent = head_commit.parent(0).map_err(|_| {
115        crate::RstaskError::Git(git2::Error::from_str("no parent commit to reset to"))
116    })?;
117
118    repo.reset(parent.as_object(), git2::ResetType::Hard, None)?;
119    Ok(())
120}