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 if self.show_history || self.pick_specific_commits || self.simulate {
93 return Ok(());
94 }
95
96 if self.range {
98 return Ok(());
99 }
100
101 if self.email.is_none() {
102 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 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 if self.range {
140 if self.edit_author || self.edit_time || self.edit_message {
141 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 (true, true, true, true)
149 }
150 } else {
151 (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}