1use crate::core::git::*;
2use crate::core::traits::*;
3use crate::{GitXError, Result};
4
5pub struct CommitCommands;
7
8impl CommitCommands {
9 pub fn fixup(commit_hash: &str, auto_rebase: bool) -> Result<String> {
11 FixupCommand::new(commit_hash.to_string(), auto_rebase).execute()
12 }
13
14 pub fn undo() -> Result<String> {
16 UndoCommand::new().execute()
17 }
18
19 pub fn bisect(action: BisectAction) -> Result<String> {
21 BisectCommand::new(action).execute()
22 }
23}
24
25pub struct FixupCommand {
27 commit_hash: String,
28 auto_rebase: bool,
29}
30
31impl FixupCommand {
32 pub fn new(commit_hash: String, auto_rebase: bool) -> Self {
33 Self {
34 commit_hash,
35 auto_rebase,
36 }
37 }
38
39 fn has_staged_changes() -> Result<bool> {
40 let staged = GitOperations::staged_files()?;
41 Ok(!staged.is_empty())
42 }
43}
44
45impl Command for FixupCommand {
46 fn execute(&self) -> Result<String> {
47 if GitOperations::run(&["rev-parse", "--verify", &self.commit_hash]).is_err() {
50 if GitOperations::repo_root().is_err() {
52 return Err(GitXError::GitCommand(
53 "Commit hash does not exist".to_string(),
54 ));
55 } else {
56 return Err(GitXError::Parse(format!(
57 "Invalid commit hash format: '{}'",
58 self.commit_hash
59 )));
60 }
61 }
62
63 if !Self::has_staged_changes()? {
65 return Err(GitXError::GitCommand(
66 "No staged changes found. Please stage your changes first with 'git add'"
67 .to_string(),
68 ));
69 }
70
71 CommitOperations::fixup(&self.commit_hash)?;
73
74 let mut result = format!("ā
Fixup commit created for {}", self.commit_hash);
75
76 if self.auto_rebase {
77 result.push_str("\nš Starting interactive rebase with autosquash");
78 match GitOperations::run_status(&[
80 "rebase",
81 "-i",
82 "--autosquash",
83 &format!("{}^", self.commit_hash),
84 ]) {
85 Ok(_) => {
86 result.push_str("\nā
Interactive rebase completed successfully");
87 }
88 Err(_) => {
89 result.push_str(&format!(
90 "\nš” To squash the fixup commit, run: git rebase -i --autosquash {}^",
91 self.commit_hash
92 ));
93 }
94 }
95 } else {
96 result.push_str(&format!(
97 "\nš” To squash the fixup commit, run: git rebase -i --autosquash {}^",
98 self.commit_hash
99 ));
100 }
101
102 Ok(result)
103 }
104
105 fn name(&self) -> &'static str {
106 "fixup"
107 }
108
109 fn description(&self) -> &'static str {
110 "Create fixup commits for easier interactive rebasing"
111 }
112}
113
114impl GitCommand for FixupCommand {}
115
116pub struct UndoCommand;
118
119impl Default for UndoCommand {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl UndoCommand {
126 pub fn new() -> Self {
127 Self
128 }
129}
130
131impl Command for UndoCommand {
132 fn execute(&self) -> Result<String> {
133 GitOperations::run_status(&["reset", "--soft", "HEAD~1"])?;
134 Ok("ā
Last commit undone (soft reset). Changes kept in working directory.".to_string())
135 }
136
137 fn name(&self) -> &'static str {
138 "undo"
139 }
140
141 fn description(&self) -> &'static str {
142 "Undo the last commit (without losing changes)"
143 }
144}
145
146impl GitCommand for UndoCommand {}
147impl Destructive for UndoCommand {
148 fn destruction_description(&self) -> String {
149 "This will undo your last commit (but keep the changes staged)".to_string()
150 }
151}
152
153#[derive(Debug, Clone)]
155pub enum BisectAction {
156 Start { bad: String, good: String },
157 Good,
158 Bad,
159 Skip,
160 Reset,
161 Status,
162}
163
164pub struct BisectCommand {
166 action: BisectAction,
167}
168
169impl BisectCommand {
170 pub fn new(action: BisectAction) -> Self {
171 Self { action }
172 }
173
174 fn is_bisecting() -> Result<bool> {
175 match GitOperations::repo_root() {
177 Ok(root) => {
178 let bisect_head = std::path::Path::new(&root).join(".git").join("BISECT_HEAD");
179 Ok(bisect_head.exists())
180 }
181 Err(_) => Ok(false),
182 }
183 }
184
185 fn execute_bisect_action(&self) -> Result<String> {
186 match &self.action {
187 BisectAction::Start { bad, good } => {
188 if GitOperations::run(&["rev-parse", "--verify", bad]).is_err() {
193 return Err(GitXError::GitCommand(format!(
194 "Reference '{bad}' does not exist"
195 )));
196 }
197 if GitOperations::run(&["rev-parse", "--verify", good]).is_err() {
198 return Err(GitXError::GitCommand(format!(
199 "Reference '{good}' does not exist"
200 )));
201 }
202
203 let output = GitOperations::run(&["bisect", "start", bad, good])?;
205
206 let mut result =
207 format!("š Starting bisect between {bad} (bad) and {good} (good)");
208 if !output.trim().is_empty() {
209 result = format!("{}\n{}", output.trim(), result);
210 }
211 result.push_str("\nā
Checked out commit");
212
213 Ok(result)
214 }
215 BisectAction::Good => {
216 if !Self::is_bisecting()? {
217 return Err(GitXError::GitCommand(
218 "Not currently in bisect mode".to_string(),
219 ));
220 }
221 GitOperations::run_status(&["bisect", "good"])?;
222 Ok("ā
Marked current commit as good".to_string())
223 }
224 BisectAction::Bad => {
225 if !Self::is_bisecting()? {
226 return Err(GitXError::GitCommand(
227 "Not currently in bisect mode".to_string(),
228 ));
229 }
230 GitOperations::run_status(&["bisect", "bad"])?;
231 Ok("ā Marked current commit as bad".to_string())
232 }
233 BisectAction::Skip => {
234 if !Self::is_bisecting()? {
235 return Err(GitXError::GitCommand(
236 "Not currently in bisect mode".to_string(),
237 ));
238 }
239 GitOperations::run_status(&["bisect", "skip"])?;
240 Ok("āļø Skipped current commit".to_string())
241 }
242 BisectAction::Reset => {
243 if !Self::is_bisecting()? {
244 return Ok("Not currently in bisect mode".to_string());
245 }
246 GitOperations::run_status(&["bisect", "reset"])?;
247 Ok("š Reset bisect and returned to original branch".to_string())
248 }
249 BisectAction::Status => {
250 if !Self::is_bisecting()? {
251 return Ok("Not currently in bisect mode".to_string());
252 }
253
254 let log = GitOperations::run(&["bisect", "log"])
255 .unwrap_or_else(|_| "No bisect log available".to_string());
256 Ok(format!("š Bisect status:\n{log}"))
257 }
258 }
259 }
260}
261
262impl Command for BisectCommand {
263 fn execute(&self) -> Result<String> {
264 self.execute_bisect_action()
265 }
266
267 fn name(&self) -> &'static str {
268 "bisect"
269 }
270
271 fn description(&self) -> &'static str {
272 "Simplified Git bisect workflow for finding bugs"
273 }
274}
275
276impl GitCommand for BisectCommand {}
277
278impl Destructive for BisectCommand {
279 fn destruction_description(&self) -> String {
280 match &self.action {
281 BisectAction::Start { .. } => {
282 "This will start a bisect session and change your working directory".to_string()
283 }
284 BisectAction::Reset => {
285 "This will reset the bisect session and return to your original branch".to_string()
286 }
287 _ => "This will change your working directory to a different commit".to_string(),
288 }
289 }
290}