Skip to main content

rung_git/
traits.rs

1//! Trait abstractions for git operations.
2//!
3//! This module defines the `GitOps` trait which abstracts git operations,
4//! enabling dependency injection and testability.
5
6use std::path::Path;
7
8use git2::Oid;
9
10use crate::{BlameResult, ConflictPrediction, Hunk, RemoteDivergence, Result};
11
12/// Trait for git repository operations.
13///
14/// This trait abstracts git operations, allowing for:
15/// - Dependency injection in commands/services
16/// - Mock implementations for testing
17/// - Alternative implementations (e.g., dry-run mode)
18///
19/// Note: Unlike `GitHubApi`, git operations are synchronous since
20/// git2 is a synchronous library.
21#[allow(clippy::missing_errors_doc)]
22pub trait GitOps {
23    // === Repository Info ===
24
25    /// Get the working directory path.
26    fn workdir(&self) -> Option<&Path>;
27
28    /// Get the current branch name.
29    ///
30    /// Returns an error if HEAD is detached or not on a branch.
31    fn current_branch(&self) -> Result<String>;
32
33    /// Check if HEAD is detached.
34    fn head_detached(&self) -> Result<bool>;
35
36    /// Check if a rebase is in progress.
37    fn is_rebasing(&self) -> bool;
38
39    // === Branch Operations ===
40
41    /// Check if a branch exists.
42    fn branch_exists(&self, name: &str) -> bool;
43
44    /// Create a new branch at the current HEAD.
45    ///
46    /// Returns the OID of the new branch's tip commit.
47    fn create_branch(&self, name: &str) -> Result<Oid>;
48
49    /// Checkout a branch.
50    fn checkout(&self, branch: &str) -> Result<()>;
51
52    /// Delete a local branch.
53    fn delete_branch(&self, name: &str) -> Result<()>;
54
55    /// List all local branches.
56    fn list_branches(&self) -> Result<Vec<String>>;
57
58    // === Commit Operations ===
59
60    /// Get the commit ID for a branch.
61    fn branch_commit(&self, branch: &str) -> Result<Oid>;
62
63    /// Get the commit ID for a remote branch.
64    fn remote_branch_commit(&self, branch: &str) -> Result<Oid>;
65
66    /// Get the commit message for a branch's tip.
67    fn branch_commit_message(&self, branch: &str) -> Result<String>;
68
69    /// Find the merge base of two commits.
70    fn merge_base(&self, one: Oid, two: Oid) -> Result<Oid>;
71
72    /// Get commits between two OIDs.
73    fn commits_between(&self, from: Oid, to: Oid) -> Result<Vec<Oid>>;
74
75    /// Count commits between two OIDs.
76    fn count_commits_between(&self, from: Oid, to: Oid) -> Result<usize>;
77
78    // === Working Directory ===
79
80    /// Check if the working directory is clean.
81    fn is_clean(&self) -> Result<bool>;
82
83    /// Require that the working directory is clean.
84    fn require_clean(&self) -> Result<()>;
85
86    /// Stage all changes.
87    fn stage_all(&self) -> Result<()>;
88
89    /// Check if there are staged changes.
90    fn has_staged_changes(&self) -> Result<bool>;
91
92    /// Create a commit with the staged changes.
93    fn create_commit(&self, message: &str) -> Result<Oid>;
94
95    /// Amend the last commit with staged changes.
96    fn amend_commit(&self, new_message: Option<&str>) -> Result<Oid>;
97
98    // === Rebase Operations ===
99
100    /// Rebase the current branch onto a target commit.
101    fn rebase_onto(&self, target: Oid) -> Result<()>;
102
103    /// Rebase using --onto semantics (rebase commits from `from` onto `onto`).
104    fn rebase_onto_from(&self, onto: Oid, from: Oid) -> Result<()>;
105
106    /// Get files with conflicts during a rebase.
107    fn conflicting_files(&self) -> Result<Vec<String>>;
108
109    /// Predict conflicts that would occur when rebasing a branch onto a target.
110    ///
111    /// Returns a list of commits that would cause conflicts along with the
112    /// conflicting files. An empty list means no conflicts are predicted.
113    fn predict_rebase_conflicts(&self, branch: &str, onto: Oid) -> Result<Vec<ConflictPrediction>>;
114
115    /// Abort a rebase in progress.
116    fn rebase_abort(&self) -> Result<()>;
117
118    /// Continue a rebase after resolving conflicts.
119    fn rebase_continue(&self) -> Result<()>;
120
121    // === Remote Operations ===
122
123    /// Get the origin URL.
124    fn origin_url(&self) -> Result<String>;
125
126    /// Check divergence between local and remote branch.
127    fn remote_divergence(&self, branch: &str) -> Result<RemoteDivergence>;
128
129    /// Detect the default branch (main/master).
130    ///
131    /// Returns `None` if neither main nor master exists.
132    fn detect_default_branch(&self) -> Option<String>;
133
134    /// Push a branch to the remote.
135    fn push(&self, branch: &str, force: bool) -> Result<()>;
136
137    /// Fetch all remotes.
138    fn fetch_all(&self) -> Result<()>;
139
140    /// Fetch a specific branch.
141    fn fetch(&self, branch: &str) -> Result<()>;
142
143    /// Pull with fast-forward only.
144    fn pull_ff(&self) -> Result<()>;
145
146    /// Reset a branch to a specific commit.
147    fn reset_branch(&self, branch: &str, commit: Oid) -> Result<()>;
148}
149
150/// Trait for absorb-specific git operations.
151///
152/// This trait abstracts the git operations needed for the absorb command,
153/// enabling dependency injection and testability.
154#[allow(clippy::missing_errors_doc)]
155pub trait AbsorbOps: GitOps {
156    /// Get the staged diff as a list of hunks.
157    fn staged_diff_hunks(&self) -> Result<Vec<Hunk>>;
158
159    /// Query git blame for a specific line range in a file.
160    fn blame_lines(&self, file_path: &str, start: u32, end: u32) -> Result<Vec<BlameResult>>;
161
162    /// Check if a commit is an ancestor of another commit.
163    fn is_ancestor(&self, ancestor: Oid, descendant: Oid) -> Result<bool>;
164
165    /// Create a fixup commit targeting the specified commit.
166    fn create_fixup_commit(&self, target: Oid) -> Result<Oid>;
167}