git_editor/
args.rs

1use clap::Parser;
2
3#[derive(Parser)]
4#[command(author, version, about)]
5pub struct Args {
6    #[arg(
7        short = 'r',
8        long = "repo-path",
9        help = "Path or URI to the repository"
10    )]
11    pub repo_path: Option<String>,
12
13    #[arg(long, help = "Email associated with the commits")]
14    pub email: Option<String>,
15
16    #[arg(short = 'n', long = "name", help = "Name associated with the commits")]
17    pub name: Option<String>,
18
19    #[arg(
20        short = 'b',
21        long = "begin",
22        help = "Start date for the commits in YYYY-MM-DD format"
23    )]
24    pub start: Option<String>,
25
26    #[arg(
27        short = 'e',
28        long = "end",
29        help = "End date for the commits in YYYY-MM-DD format"
30    )]
31    pub end: Option<String>,
32
33    #[arg(
34        short = 's',
35        long = "show-history",
36        help = "Show updated commit history after rewriting"
37    )]
38    pub show_history: bool,
39
40    #[arg(
41        short = 'p',
42        long = "pick-specific-commits",
43        help = "Pick specific commits to rewrite. Provide a comma-separated list of commit hashes."
44    )]
45    pub pick_specific_commits: bool,
46
47    #[arg(
48        short = 'x',
49        long = "range",
50        help = "Edit a range of commits (e.g., --range to interactively select range)"
51    )]
52    pub range: bool,
53
54    #[arg(
55        long = "simulate",
56        help = "Show what changes would be made without applying them (dry-run mode)"
57    )]
58    pub simulate: bool,
59
60    #[arg(
61        long = "show-diff",
62        help = "Show detailed diff preview in simulation mode (requires --simulate)"
63    )]
64    pub show_diff: bool,
65
66    #[arg(
67        long = "message",
68        help = "Edit only commit messages in range mode (-x)"
69    )]
70    pub edit_message: bool,
71
72    #[arg(
73        long = "author",
74        help = "Edit only author name and email in range mode (-x)"
75    )]
76    pub edit_author: bool,
77
78    #[arg(long = "time", help = "Edit only timestamps in range mode (-x)")]
79    pub edit_time: bool,
80}
81
82impl Args {
83    pub fn ensure_all_args_present(&mut self) -> crate::utils::types::Result<()> {
84        use crate::utils::git_config::{get_git_user_email, get_git_user_name};
85        use crate::utils::prompt::{prompt_for_missing_arg, prompt_with_default};
86
87        if self.repo_path.is_none() {
88            self.repo_path = Some(String::from("./"));
89        }
90
91        // Skip prompting for email, name, start, and end if using show_history, pick_specific_commits, or simulation modes
92        if self.show_history || self.pick_specific_commits || self.simulate {
93            return Ok(());
94        }
95
96        // Range mode will prompt for its own parameters interactively
97        if self.range {
98            return Ok(());
99        }
100
101        if self.email.is_none() {
102            // Try to get email from git config first
103            if let Some(git_email) = get_git_user_email() {
104                self.email = Some(prompt_with_default("Email", &git_email)?);
105            } else {
106                self.email = Some(prompt_for_missing_arg("email")?);
107            }
108        }
109
110        if self.name.is_none() {
111            // Try to get name from git config first
112            if let Some(git_name) = get_git_user_name() {
113                self.name = Some(prompt_with_default("Name", &git_name)?);
114            } else {
115                self.name = Some(prompt_for_missing_arg("name")?);
116            }
117        }
118
119        if self.start.is_none() {
120            self.start = Some(prompt_for_missing_arg("start date (YYYY-MM-DD HH:MM:SS)")?);
121        }
122
123        if self.end.is_none() {
124            self.end = Some(prompt_for_missing_arg("end date (YYYY-MM-DD HH:MM:SS)")?);
125        }
126
127        Ok(())
128    }
129
130    pub fn validate_simulation_args(&self) -> crate::utils::types::Result<()> {
131        if self.show_diff && !self.simulate {
132            return Err("--show-diff requires --simulate to be enabled".into());
133        }
134        Ok(())
135    }
136
137    pub fn get_editable_fields(&self) -> (bool, bool, bool, bool) {
138        // (author_name, author_email, timestamp, message)
139        if self.range {
140            if self.edit_author || self.edit_time || self.edit_message {
141                // Selective editing - only edit specified fields
142                let edit_author = self.edit_author;
143                let edit_time = self.edit_time;
144                let edit_message = self.edit_message;
145                (edit_author, edit_author, edit_time, edit_message)
146            } else {
147                // Default: edit all fields when no specific flags are provided
148                (true, true, true, true)
149            }
150        } else {
151            // Not in range mode - this shouldn't be called
152            (false, false, false, false)
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_args_default_values() {
163        let args = Args {
164            repo_path: None,
165            email: None,
166            name: None,
167            start: None,
168            end: None,
169            show_history: false,
170            pick_specific_commits: false,
171            range: false,
172            simulate: false,
173            show_diff: false,
174            edit_message: false,
175            edit_author: false,
176            edit_time: false,
177        };
178
179        assert_eq!(args.repo_path, None);
180        assert_eq!(args.email, None);
181        assert_eq!(args.name, None);
182        assert_eq!(args.start, None);
183        assert_eq!(args.end, None);
184        assert!(!args.show_history);
185        assert!(!args.pick_specific_commits);
186        assert!(!args.range);
187    }
188
189    #[test]
190    fn test_args_with_show_history() {
191        let args = Args {
192            repo_path: Some("/test/repo".to_string()),
193            email: None,
194            name: None,
195            start: None,
196            end: None,
197            show_history: true,
198            pick_specific_commits: false,
199            range: false,
200            simulate: false,
201            show_diff: false,
202            edit_message: false,
203            edit_author: false,
204            edit_time: false,
205        };
206
207        assert_eq!(args.repo_path, Some("/test/repo".to_string()));
208        assert!(args.show_history);
209        assert!(!args.pick_specific_commits);
210    }
211
212    #[test]
213    fn test_args_with_pick_specific_commits() {
214        let args = Args {
215            repo_path: Some("/test/repo".to_string()),
216            email: None,
217            name: None,
218            start: None,
219            end: None,
220            show_history: false,
221            pick_specific_commits: true,
222            range: false,
223            simulate: false,
224            show_diff: false,
225            edit_message: false,
226            edit_author: false,
227            edit_time: false,
228        };
229
230        assert_eq!(args.repo_path, Some("/test/repo".to_string()));
231        assert!(!args.show_history);
232        assert!(args.pick_specific_commits);
233    }
234
235    #[test]
236    fn test_args_full_rewrite() {
237        let args = Args {
238            repo_path: Some("/test/repo".to_string()),
239            email: Some("test@example.com".to_string()),
240            name: Some("Test User".to_string()),
241            start: Some("2023-01-01 00:00:00".to_string()),
242            end: Some("2023-01-02 00:00:00".to_string()),
243            show_history: false,
244            pick_specific_commits: false,
245            range: false,
246            simulate: false,
247            show_diff: false,
248            edit_message: false,
249            edit_author: false,
250            edit_time: false,
251        };
252
253        assert_eq!(args.repo_path, Some("/test/repo".to_string()));
254        assert_eq!(args.email, Some("test@example.com".to_string()));
255        assert_eq!(args.name, Some("Test User".to_string()));
256        assert_eq!(args.start, Some("2023-01-01 00:00:00".to_string()));
257        assert_eq!(args.end, Some("2023-01-02 00:00:00".to_string()));
258    }
259
260    #[test]
261    fn test_args_with_range() {
262        let args = Args {
263            repo_path: Some("/test/repo".to_string()),
264            email: None,
265            name: None,
266            start: None,
267            end: None,
268            show_history: false,
269            pick_specific_commits: false,
270            range: true,
271            simulate: false,
272            show_diff: false,
273            edit_message: false,
274            edit_author: false,
275            edit_time: false,
276        };
277
278        assert_eq!(args.repo_path, Some("/test/repo".to_string()));
279        assert!(!args.show_history);
280        assert!(!args.pick_specific_commits);
281        assert!(args.range);
282    }
283
284    #[test]
285    fn test_args_with_simulate() {
286        let args = Args {
287            repo_path: Some("/test/repo".to_string()),
288            email: None,
289            name: None,
290            start: None,
291            end: None,
292            show_history: false,
293            pick_specific_commits: false,
294            range: false,
295            simulate: true,
296            show_diff: false,
297            edit_message: false,
298            edit_author: false,
299            edit_time: false,
300        };
301
302        assert!(args.simulate);
303        assert!(!args.show_diff);
304    }
305
306    #[test]
307    fn test_validate_simulation_args_valid() {
308        let args = Args {
309            repo_path: Some("/test/repo".to_string()),
310            email: None,
311            name: None,
312            start: None,
313            end: None,
314            show_history: false,
315            pick_specific_commits: false,
316            range: false,
317            simulate: true,
318            show_diff: true,
319            edit_message: false,
320            edit_author: false,
321            edit_time: false,
322        };
323
324        let result = args.validate_simulation_args();
325        assert!(result.is_ok());
326    }
327
328    #[test]
329    fn test_validate_simulation_args_invalid() {
330        let args = Args {
331            repo_path: Some("/test/repo".to_string()),
332            email: None,
333            name: None,
334            start: None,
335            end: None,
336            show_history: false,
337            pick_specific_commits: false,
338            range: false,
339            simulate: false,
340            show_diff: true,
341            edit_message: false,
342            edit_author: false,
343            edit_time: false,
344        };
345
346        let result = args.validate_simulation_args();
347        assert!(result.is_err());
348        assert!(result
349            .unwrap_err()
350            .to_string()
351            .contains("--show-diff requires --simulate"));
352    }
353}