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
67impl Args {
68    pub fn is_help_request(&self) -> bool {
69        !self.show_history
70            && !self.pick_specific_commits
71            && !self.range
72            && !self.simulate
73            && self.email.is_none()
74            && self.name.is_none()
75            && self.start.is_none()
76            && self.end.is_none()
77    }
78
79    pub fn ensure_all_args_present(&mut self) -> crate::utils::types::Result<()> {
80        use crate::utils::prompt::prompt_for_missing_arg;
81
82        if self.repo_path.is_none() {
83            self.repo_path = Some(String::from("./"));
84        }
85
86        // Skip prompting for email, name, start, and end if using show_history, pick_specific_commits, or simulation modes
87        if self.show_history || self.pick_specific_commits || self.simulate {
88            return Ok(());
89        }
90
91        // Range mode will prompt for its own parameters interactively
92        if self.range {
93            return Ok(());
94        }
95
96        if self.email.is_none() {
97            self.email = Some(prompt_for_missing_arg("email")?);
98        }
99
100        if self.name.is_none() {
101            self.name = Some(prompt_for_missing_arg("name")?);
102        }
103
104        if self.start.is_none() {
105            self.start = Some(prompt_for_missing_arg("start date (YYYY-MM-DD HH:MM:SS)")?);
106        }
107
108        if self.end.is_none() {
109            self.end = Some(prompt_for_missing_arg("end date (YYYY-MM-DD HH:MM:SS)")?);
110        }
111
112        Ok(())
113    }
114
115    pub fn validate_simulation_args(&self) -> crate::utils::types::Result<()> {
116        if self.show_diff && !self.simulate {
117            return Err("--show-diff requires --simulate to be enabled".into());
118        }
119        Ok(())
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_args_default_values() {
129        let args = Args {
130            repo_path: None,
131            email: None,
132            name: None,
133            start: None,
134            end: None,
135            show_history: false,
136            pick_specific_commits: false,
137            range: false,
138            simulate: false,
139            show_diff: false,
140        };
141
142        assert_eq!(args.repo_path, None);
143        assert_eq!(args.email, None);
144        assert_eq!(args.name, None);
145        assert_eq!(args.start, None);
146        assert_eq!(args.end, None);
147        assert!(!args.show_history);
148        assert!(!args.pick_specific_commits);
149        assert!(!args.range);
150    }
151
152    #[test]
153    fn test_args_with_show_history() {
154        let args = Args {
155            repo_path: Some("/test/repo".to_string()),
156            email: None,
157            name: None,
158            start: None,
159            end: None,
160            show_history: true,
161            pick_specific_commits: false,
162            range: false,
163            simulate: false,
164            show_diff: false,
165        };
166
167        assert_eq!(args.repo_path, Some("/test/repo".to_string()));
168        assert!(args.show_history);
169        assert!(!args.pick_specific_commits);
170    }
171
172    #[test]
173    fn test_args_with_pick_specific_commits() {
174        let args = Args {
175            repo_path: Some("/test/repo".to_string()),
176            email: None,
177            name: None,
178            start: None,
179            end: None,
180            show_history: false,
181            pick_specific_commits: true,
182            range: false,
183            simulate: false,
184            show_diff: false,
185        };
186
187        assert_eq!(args.repo_path, Some("/test/repo".to_string()));
188        assert!(!args.show_history);
189        assert!(args.pick_specific_commits);
190    }
191
192    #[test]
193    fn test_args_full_rewrite() {
194        let args = Args {
195            repo_path: Some("/test/repo".to_string()),
196            email: Some("test@example.com".to_string()),
197            name: Some("Test User".to_string()),
198            start: Some("2023-01-01 00:00:00".to_string()),
199            end: Some("2023-01-02 00:00:00".to_string()),
200            show_history: false,
201            pick_specific_commits: false,
202            range: false,
203            simulate: false,
204            show_diff: false,
205        };
206
207        assert_eq!(args.repo_path, Some("/test/repo".to_string()));
208        assert_eq!(args.email, Some("test@example.com".to_string()));
209        assert_eq!(args.name, Some("Test User".to_string()));
210        assert_eq!(args.start, Some("2023-01-01 00:00:00".to_string()));
211        assert_eq!(args.end, Some("2023-01-02 00:00:00".to_string()));
212    }
213
214    #[test]
215    fn test_args_with_range() {
216        let args = Args {
217            repo_path: Some("/test/repo".to_string()),
218            email: None,
219            name: None,
220            start: None,
221            end: None,
222            show_history: false,
223            pick_specific_commits: false,
224            range: true,
225            simulate: false,
226            show_diff: false,
227        };
228
229        assert_eq!(args.repo_path, Some("/test/repo".to_string()));
230        assert!(!args.show_history);
231        assert!(!args.pick_specific_commits);
232        assert!(args.range);
233    }
234
235    #[test]
236    fn test_is_help_request() {
237        // Default args should trigger help
238        let args = Args {
239            repo_path: None,
240            email: None,
241            name: None,
242            start: None,
243            end: None,
244            show_history: false,
245            pick_specific_commits: false,
246            range: false,
247            simulate: false,
248            show_diff: false,
249        };
250        assert!(args.is_help_request());
251
252        // Args with repo_path only should still trigger help
253        let args = Args {
254            repo_path: Some("/test/repo".to_string()),
255            email: None,
256            name: None,
257            start: None,
258            end: None,
259            show_history: false,
260            pick_specific_commits: false,
261            range: false,
262            simulate: false,
263            show_diff: false,
264        };
265        assert!(args.is_help_request());
266
267        // Args with show_history should NOT trigger help
268        let args = Args {
269            repo_path: None,
270            email: None,
271            name: None,
272            start: None,
273            end: None,
274            show_history: true,
275            pick_specific_commits: false,
276            range: false,
277            simulate: false,
278            show_diff: false,
279        };
280        assert!(!args.is_help_request());
281
282        // Args with email should NOT trigger help
283        let args = Args {
284            repo_path: None,
285            email: Some("test@example.com".to_string()),
286            name: None,
287            start: None,
288            end: None,
289            show_history: false,
290            pick_specific_commits: false,
291            range: false,
292            simulate: false,
293            show_diff: false,
294        };
295        assert!(!args.is_help_request());
296
297        // Args with range should NOT trigger help
298        let args = Args {
299            repo_path: None,
300            email: None,
301            name: None,
302            start: None,
303            end: None,
304            show_history: false,
305            pick_specific_commits: false,
306            range: true,
307            simulate: false,
308            show_diff: false,
309        };
310        assert!(!args.is_help_request());
311
312        // Args with pick_specific_commits should NOT trigger help
313        let args = Args {
314            repo_path: None,
315            email: None,
316            name: None,
317            start: None,
318            end: None,
319            show_history: false,
320            pick_specific_commits: true,
321            range: false,
322            simulate: false,
323            show_diff: false,
324        };
325        assert!(!args.is_help_request());
326    }
327
328    #[test]
329    fn test_args_with_simulate() {
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: true,
340            show_diff: false,
341        };
342
343        assert!(!args.is_help_request());
344        assert!(args.simulate);
345        assert!(!args.show_diff);
346    }
347
348    #[test]
349    fn test_validate_simulation_args_valid() {
350        let args = Args {
351            repo_path: Some("/test/repo".to_string()),
352            email: None,
353            name: None,
354            start: None,
355            end: None,
356            show_history: false,
357            pick_specific_commits: false,
358            range: false,
359            simulate: true,
360            show_diff: true,
361        };
362
363        let result = args.validate_simulation_args();
364        assert!(result.is_ok());
365    }
366
367    #[test]
368    fn test_validate_simulation_args_invalid() {
369        let args = Args {
370            repo_path: Some("/test/repo".to_string()),
371            email: None,
372            name: None,
373            start: None,
374            end: None,
375            show_history: false,
376            pick_specific_commits: false,
377            range: false,
378            simulate: false,
379            show_diff: true,
380        };
381
382        let result = args.validate_simulation_args();
383        assert!(result.is_err());
384        assert!(result
385            .unwrap_err()
386            .to_string()
387            .contains("--show-diff requires --simulate"));
388    }
389}