1+ use std:: process:: Command ;
2+
3+ pub fn run ( commit_hash : String , rebase : bool ) {
4+ // Validate the commit hash exists
5+ if let Err ( msg) = validate_commit_hash ( & commit_hash) {
6+ eprintln ! ( "{}" , format_error_message( msg) ) ;
7+ return ;
8+ }
9+
10+ // Get current staged and unstaged changes
11+ 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+ // Get the short commit hash for better UX
25+ 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+ // Create the fixup commit
36+ 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+ // Optionally run interactive rebase with autosquash
44+ 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+
57+ // Helper function to validate commit hash exists
58+ fn 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+
71+ // Helper function to check for changes to commit
72+ fn 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 staged changes exist, we're good
79+ if !output. success ( ) {
80+ return Ok ( true ) ;
81+ }
82+
83+ // Check for unstaged changes
84+ let output = Command :: new ( "git" )
85+ . args ( [ "diff" , "--quiet" ] )
86+ . status ( )
87+ . map_err ( |_| "Failed to check for unstaged changes" ) ?;
88+
89+ // If unstaged changes exist, we need to stage them
90+ if !output. success ( ) {
91+ return Err ( "You have unstaged changes. Please stage them first with 'git add'" ) ;
92+ }
93+
94+ Ok ( false )
95+ }
96+
97+ // Helper function to get short commit hash
98+ fn 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+
111+ // Helper function to create fixup commit
112+ fn 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+
125+ // Helper function to run autosquash rebase
126+ fn run_autosquash_rebase ( commit_hash : & str ) -> Result < ( ) , & ' static str > {
127+ // Find the parent of the target commit for rebase
128+ 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+
152+ // Helper function to get git commit args for fixup
153+ pub fn get_git_fixup_args ( ) -> [ & ' static str ; 2 ] {
154+ [ "commit" , "--fixup" ]
155+ }
156+
157+ // Helper function to get git rebase args
158+ pub fn get_git_rebase_args ( ) -> [ & ' static str ; 3 ] {
159+ [ "rebase" , "-i" , "--autosquash" ]
160+ }
161+
162+ // Helper function to format error message
163+ pub fn format_error_message ( msg : & str ) -> String {
164+ format ! ( "❌ {msg}" )
165+ }
166+
167+ // Helper function to format no changes message
168+ pub fn format_no_changes_message ( ) -> & ' static str {
169+ "❌ No staged changes found. Please stage your changes first with 'git add'"
170+ }
171+
172+ // Helper function to format creating fixup message
173+ pub fn format_creating_fixup_message ( short_hash : & str ) -> String {
174+ format ! ( "🔧 Creating fixup commit for {short_hash}..." )
175+ }
176+
177+ // Helper function to format fixup created message
178+ pub fn format_fixup_created_message ( short_hash : & str ) -> String {
179+ format ! ( "✅ Fixup commit created for {short_hash}" )
180+ }
181+
182+ // Helper function to format starting rebase message
183+ pub fn format_starting_rebase_message ( ) -> & ' static str {
184+ "🔄 Starting interactive rebase with autosquash..."
185+ }
186+
187+ // Helper function to format rebase success message
188+ pub fn format_rebase_success_message ( ) -> & ' static str {
189+ "✅ Interactive rebase completed successfully"
190+ }
191+
192+ // Helper function to format manual rebase hint
193+ pub 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+
197+ // Helper function to check if commit hash is valid format
198+ pub fn is_valid_commit_hash_format ( hash : & str ) -> bool {
199+ if hash. is_empty ( ) {
200+ return false ;
201+ }
202+
203+ // Must be 4-40 characters long (short to full hash)
204+ if hash. len ( ) < 4 || hash. len ( ) > 40 {
205+ return false ;
206+ }
207+
208+ // Must contain only hex characters
209+ hash. chars ( ) . all ( |c| c. is_ascii_hexdigit ( ) )
210+ }
211+
212+ // Helper function to format commit validation rules
213+ pub 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+ }
0 commit comments