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 if self.show_history || self.pick_specific_commits || self.simulate {
88 return Ok(());
89 }
90
91 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 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 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 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 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 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 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}