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(["rev-parse", "--verify", &format!("{commit_hash}^{{commit}}")])
61 .output()
62 .map_err(|_| "Failed to validate commit hash")?;
63
64 if !output.status.success() {
65 return Err("Commit hash does not exist");
66 }
67
68 Ok(())
69}
70
71fn check_for_changes() -> Result<bool, &'static str> {
73 let output = Command::new("git")
74 .args(["diff", "--cached", "--quiet"])
75 .status()
76 .map_err(|_| "Failed to check for staged changes")?;
77
78 if !output.success() {
80 return Ok(true);
81 }
82
83 let output = Command::new("git")
85 .args(["diff", "--quiet"])
86 .status()
87 .map_err(|_| "Failed to check for unstaged changes")?;
88
89 if !output.success() {
91 return Err("You have unstaged changes. Please stage them first with 'git add'");
92 }
93
94 Ok(false)
95}
96
97fn get_short_commit_hash(commit_hash: &str) -> Result<String, &'static str> {
99 let output = Command::new("git")
100 .args(["rev-parse", "--short", commit_hash])
101 .output()
102 .map_err(|_| "Failed to get short commit hash")?;
103
104 if !output.status.success() {
105 return Err("Failed to resolve commit hash");
106 }
107
108 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
109}
110
111fn create_fixup_commit(commit_hash: &str) -> Result<(), &'static str> {
113 let status = Command::new("git")
114 .args(["commit", &format!("--fixup={commit_hash}")])
115 .status()
116 .map_err(|_| "Failed to create fixup commit")?;
117
118 if !status.success() {
119 return Err("Failed to create fixup commit");
120 }
121
122 Ok(())
123}
124
125fn run_autosquash_rebase(commit_hash: &str) -> Result<(), &'static str> {
127 let output = Command::new("git")
129 .args(["rev-parse", &format!("{commit_hash}^")])
130 .output()
131 .map_err(|_| "Failed to find parent commit")?;
132
133 if !output.status.success() {
134 return Err("Cannot rebase - commit has no parent");
135 }
136
137 let parent_hash_string = String::from_utf8_lossy(&output.stdout);
138 let parent_hash = parent_hash_string.trim();
139
140 let status = Command::new("git")
141 .args(["rebase", "-i", "--autosquash", parent_hash])
142 .status()
143 .map_err(|_| "Failed to start interactive rebase")?;
144
145 if !status.success() {
146 return Err("Interactive rebase failed");
147 }
148
149 Ok(())
150}
151
152pub fn get_git_fixup_args() -> [&'static str; 2] {
154 ["commit", "--fixup"]
155}
156
157pub fn get_git_rebase_args() -> [&'static str; 3] {
159 ["rebase", "-i", "--autosquash"]
160}
161
162pub fn format_error_message(msg: &str) -> String {
164 format!("❌ {msg}")
165}
166
167pub fn format_no_changes_message() -> &'static str {
169 "❌ No staged changes found. Please stage your changes first with 'git add'"
170}
171
172pub fn format_creating_fixup_message(short_hash: &str) -> String {
174 format!("🔧 Creating fixup commit for {short_hash}...")
175}
176
177pub fn format_fixup_created_message(short_hash: &str) -> String {
179 format!("✅ Fixup commit created for {short_hash}")
180}
181
182pub fn format_starting_rebase_message() -> &'static str {
184 "🔄 Starting interactive rebase with autosquash..."
185}
186
187pub fn format_rebase_success_message() -> &'static str {
189 "✅ Interactive rebase completed successfully"
190}
191
192pub fn format_manual_rebase_hint(commit_hash: &str) -> String {
194 format!("💡 To squash the fixup commit, run: git rebase -i --autosquash {commit_hash}^")
195}
196
197pub fn is_valid_commit_hash_format(hash: &str) -> bool {
199 if hash.is_empty() {
200 return false;
201 }
202
203 if hash.len() < 4 || hash.len() > 40 {
205 return false;
206 }
207
208 hash.chars().all(|c| c.is_ascii_hexdigit())
210}
211
212pub fn get_commit_hash_validation_rules() -> &'static [&'static str] {
214 &[
215 "Must be 4-40 characters long",
216 "Must contain only hex characters (0-9, a-f)",
217 "Must reference an existing commit",
218 ]
219}