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}