1use std::process::Command;
2
3pub fn run(commit_hash: String, rebase: bool) {
4 if let Err(msg) = validate_commit_hash(&commit_hash) {
6 eprintln!("{}", format_error_message(msg));
7 return;
8 }
9
10 let has_changes = match check_for_changes() {
12 Ok(has_changes) => has_changes,
13 Err(msg) => {
14 eprintln!("{}", format_error_message(msg));
15 return;
16 }
17 };
18
19 if !has_changes {
20 eprintln!("{}", format_no_changes_message());
21 return;
22 }
23
24 let short_hash = match get_short_commit_hash(&commit_hash) {
26 Ok(hash) => hash,
27 Err(msg) => {
28 eprintln!("{}", format_error_message(msg));
29 return;
30 }
31 };
32
33 println!("{}", format_creating_fixup_message(&short_hash));
34
35 if let Err(msg) = create_fixup_commit(&commit_hash) {
37 eprintln!("{}", format_error_message(msg));
38 return;
39 }
40
41 println!("{}", format_fixup_created_message(&short_hash));
42
43 if rebase {
45 println!("{}", format_starting_rebase_message());
46 if let Err(msg) = run_autosquash_rebase(&commit_hash) {
47 eprintln!("{}", format_error_message(msg));
48 eprintln!("{}", format_manual_rebase_hint(&commit_hash));
49 return;
50 }
51 println!("{}", format_rebase_success_message());
52 } else {
53 println!("{}", format_manual_rebase_hint(&commit_hash));
54 }
55}
56
57fn validate_commit_hash(commit_hash: &str) -> Result<(), &'static str> {
59 let output = Command::new("git")
60 .args([
61 "rev-parse",
62 "--verify",
63 &format!("{commit_hash}^{{commit}}"),
64 ])
65 .output()
66 .map_err(|_| "Failed to validate commit hash")?;
67
68 if !output.status.success() {
69 return Err("Commit hash does not exist");
70 }
71
72 Ok(())
73}
74
75fn check_for_changes() -> Result<bool, &'static str> {
77 let output = Command::new("git")
78 .args(["diff", "--cached", "--quiet"])
79 .status()
80 .map_err(|_| "Failed to check for staged changes")?;
81
82 if !output.success() {
84 return Ok(true);
85 }
86
87 let output = Command::new("git")
89 .args(["diff", "--quiet"])
90 .status()
91 .map_err(|_| "Failed to check for unstaged changes")?;
92
93 if !output.success() {
95 return Err("You have unstaged changes. Please stage them first with 'git add'");
96 }
97
98 Ok(false)
99}
100
101fn get_short_commit_hash(commit_hash: &str) -> Result<String, &'static str> {
103 let output = Command::new("git")
104 .args(["rev-parse", "--short", commit_hash])
105 .output()
106 .map_err(|_| "Failed to get short commit hash")?;
107
108 if !output.status.success() {
109 return Err("Failed to resolve commit hash");
110 }
111
112 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
113}
114
115fn create_fixup_commit(commit_hash: &str) -> Result<(), &'static str> {
117 let status = Command::new("git")
118 .args(["commit", &format!("--fixup={commit_hash}")])
119 .status()
120 .map_err(|_| "Failed to create fixup commit")?;
121
122 if !status.success() {
123 return Err("Failed to create fixup commit");
124 }
125
126 Ok(())
127}
128
129fn run_autosquash_rebase(commit_hash: &str) -> Result<(), &'static str> {
131 let output = Command::new("git")
133 .args(["rev-parse", &format!("{commit_hash}^")])
134 .output()
135 .map_err(|_| "Failed to find parent commit")?;
136
137 if !output.status.success() {
138 return Err("Cannot rebase - commit has no parent");
139 }
140
141 let parent_hash_string = String::from_utf8_lossy(&output.stdout);
142 let parent_hash = parent_hash_string.trim();
143
144 let status = Command::new("git")
145 .args(["rebase", "-i", "--autosquash", parent_hash])
146 .status()
147 .map_err(|_| "Failed to start interactive rebase")?;
148
149 if !status.success() {
150 return Err("Interactive rebase failed");
151 }
152
153 Ok(())
154}
155
156pub fn get_git_fixup_args() -> [&'static str; 2] {
158 ["commit", "--fixup"]
159}
160
161pub fn get_git_rebase_args() -> [&'static str; 3] {
163 ["rebase", "-i", "--autosquash"]
164}
165
166pub fn format_error_message(msg: &str) -> String {
168 format!("❌ {msg}")
169}
170
171pub fn format_no_changes_message() -> &'static str {
173 "❌ No staged changes found. Please stage your changes first with 'git add'"
174}
175
176pub fn format_creating_fixup_message(short_hash: &str) -> String {
178 format!("🔧 Creating fixup commit for {short_hash}...")
179}
180
181pub fn format_fixup_created_message(short_hash: &str) -> String {
183 format!("✅ Fixup commit created for {short_hash}")
184}
185
186pub fn format_starting_rebase_message() -> &'static str {
188 "🔄 Starting interactive rebase with autosquash..."
189}
190
191pub fn format_rebase_success_message() -> &'static str {
193 "✅ Interactive rebase completed successfully"
194}
195
196pub fn format_manual_rebase_hint(commit_hash: &str) -> String {
198 format!("💡 To squash the fixup commit, run: git rebase -i --autosquash {commit_hash}^")
199}
200
201pub fn is_valid_commit_hash_format(hash: &str) -> bool {
203 if hash.is_empty() {
204 return false;
205 }
206
207 if hash.len() < 4 || hash.len() > 40 {
209 return false;
210 }
211
212 hash.chars().all(|c| c.is_ascii_hexdigit())
214}
215
216pub fn get_commit_hash_validation_rules() -> &'static [&'static str] {
218 &[
219 "Must be 4-40 characters long",
220 "Must contain only hex characters (0-9, a-f)",
221 "Must reference an existing commit",
222 ]
223}