git_policy_cli/
commit_args.rs1const PATHSPEC_SEPARATOR: &str = "--";
25use super::transaction_journal::TransactionMode;
27
28const REDIRECTING_VALUE_OPTIONS: [&str; 3] = ["-C", "--git-dir", "--work-tree"];
30
31const VALUE_TAKING_GLOBAL_OPTIONS: [&str; 7] = [
33 "-c",
34 "-C",
35 "--git-dir",
36 "--work-tree",
37 "--namespace",
38 "--super-prefix",
39 "--attr-source",
40];
41
42const COMMIT_VALUE_OPTIONS: [&str; 20] = [
44 "-m",
45 "-F",
46 "-C",
47 "-c",
48 "-t",
49 "-U",
50 "--message",
51 "--file",
52 "--reuse-message",
53 "--reedit-message",
54 "--squash",
55 "--fixup",
56 "--author",
57 "--date",
58 "--cleanup",
59 "--trailer",
60 "--template",
61 "--unified",
62 "--inter-hunk-context",
63 "--pathspec-from-file",
64];
65
66const SHORT_VALUE_OPTIONS: [&str; 6] = ["-m", "-F", "-C", "-c", "-t", "-U"];
68
69const OPTIONAL_VALUE_LETTERS: [char; 2] = ['S', 'u'];
71
72fn find_value_option_index(letters: &[char]) -> Option<usize> {
82 for (index, letter) in letters.iter().enumerate().skip(1) {
83 let option: String = format!("-{letter}");
84 if SHORT_VALUE_OPTIONS.contains(&option.as_str()) {
85 return Some(index);
86 }
87 }
88 return None;
89}
90
91fn normalise_inline_short(token: &str) -> Vec<String> {
101 if !token.starts_with('-') || token.starts_with("--") || token.len() <= 2 {
102 return vec![String::from(token)];
103 }
104 let letters: Vec<char> = token.chars().collect();
105 let Some(value_index) = find_value_option_index(letters.as_slice()) else {
106 return vec![String::from(token)];
107 };
108 let mut tokens: Vec<String> = letters[1..value_index]
109 .iter()
110 .map(|letter| format!("-{letter}"))
111 .collect();
112 tokens.push(format!("-{}", letters[value_index]));
113 let inline: String = letters[value_index + 1..].iter().collect();
114 if !inline.is_empty() {
115 tokens.push(inline);
116 }
117 return tokens;
118}
119
120pub fn normalise_commit_args(args: &[String]) -> Vec<String> {
130 let separator: Option<usize> = args.iter().position(|token| { return token == PATHSPEC_SEPARATOR; });
131 let (region, tail): (&[String], &[String]) = match separator {
132 Some(index) => (&args[..index], &args[index..]),
133 None => (args, &[]),
134 };
135 let mut normalised: Vec<String> = region
136 .iter()
137 .flat_map(|token| { return normalise_inline_short(token); })
138 .collect();
139 normalised.extend(tail.iter().cloned());
140 return normalised;
141}
142
143pub fn split_commit_invocation(args: &[String], subcommand_index: usize) -> (Vec<String>, Vec<String>) {
154 let globals: &[String] = &args[..subcommand_index];
155 let mut kept: Vec<String> = Vec::new();
156 let mut index: usize = 0;
157 while index < globals.len() {
158 let token: &str = globals[index].as_str();
159 let name: &str = if token.starts_with("--") && token.contains('=') {
160 &token[..token.find('=').unwrap_or(token.len())]
161 } else {
162 token
163 };
164 let separated: bool = VALUE_TAKING_GLOBAL_OPTIONS.contains(&token);
165 if REDIRECTING_VALUE_OPTIONS.contains(&name) || token == "--bare" {
166 if separated {
167 index += 1;
168 }
169 index += 1;
170 continue;
171 }
172 kept.push(String::from(token));
173 if separated {
174 if let Some(value) = globals.get(index + 1) {
175 kept.push(value.clone());
176 }
177 index += 1;
178 }
179 index += 1;
180 }
181 return (kept, args[subcommand_index + 1..].to_vec());
182}
183
184pub fn without_all_flag(commit_args: &[String]) -> Vec<String> {
194 let normalised: Vec<String> = normalise_commit_args(commit_args);
195 let mut kept: Vec<String> = Vec::new();
196 let mut value_next: bool = false;
197 let mut after_separator: bool = false;
198 for token in normalised {
199 if after_separator || value_next {
200 kept.push(token);
201 value_next = false;
202 continue;
203 }
204 if token == PATHSPEC_SEPARATOR {
205 after_separator = true;
206 kept.push(token);
207 continue;
208 }
209 if token == "--all" {
210 continue;
211 }
212 if token.starts_with('-') && !token.starts_with("--") && token.len() > 1 {
213 let letters: Vec<char> = token[1..].chars().collect();
214 let value_start: Option<usize> = letters
215 .iter()
216 .position(|letter| { return OPTIONAL_VALUE_LETTERS.contains(letter); });
217 let flags: &[char] = match value_start {
218 Some(index) => &letters[..index],
219 None => &letters[..],
220 };
221 let mut cluster: String = String::from("-");
222 for letter in flags.iter().filter(|letter| { return **letter != 'a'; }) {
223 cluster.push(*letter);
224 }
225 if let Some(index) = value_start {
226 for letter in &letters[index..] {
227 cluster.push(*letter);
228 }
229 }
230 if cluster == "-" {
231 continue;
232 }
233 value_next = COMMIT_VALUE_OPTIONS.contains(&cluster.as_str());
234 kept.push(cluster);
235 continue;
236 }
237 value_next = COMMIT_VALUE_OPTIONS.contains(&token.as_str());
238 kept.push(token);
239 }
240 return kept;
241}
242
243pub fn private_explicit_commit_args(args: &[String], pathspecs: &[String]) -> Vec<String> {
255 let mut without_file: Vec<String> = Vec::new();
256 let mut index: usize = 0;
257 while index < args.len() {
258 let token: &str = args[index].as_str();
259 if token == "--pathspec-from-file" {
260 index += 2;
261 continue;
262 }
263 if token.starts_with("--pathspec-from-file=") || token == "--pathspec-file-nul" {
264 index += 1;
265 continue;
266 }
267 without_file.push(String::from(token));
268 index += 1;
269 }
270 for pathspec in pathspecs.iter().rev() {
271 if let Some(position) = without_file.iter().rposition(|token| { return token == pathspec; }) {
272 without_file.remove(position);
273 }
274 }
275 return without_file
276 .into_iter()
277 .filter(|token| { return token != "--" && token != "-o" && token != "--only"; })
278 .collect();
279}
280
281pub fn private_selection_commit_args(args: &[String], pathspecs: &[String]) -> Vec<String> {
291 return private_explicit_commit_args(args, pathspecs)
292 .into_iter()
293 .filter(|token| { return !["-i", "-p", "--include", "--interactive", "--patch"].contains(&token.as_str()); })
294 .collect();
295}
296
297pub fn resolve_private_commit_args(
308 args: &[String],
309 pathspecs: &[String],
310 mode: TransactionMode,
311 selected_privately: bool,
312) -> Vec<String> {
313 if mode == TransactionMode::ExplicitPath {
314 return private_explicit_commit_args(args, pathspecs);
315 }
316 if selected_privately {
317 return private_selection_commit_args(args, pathspecs);
318 }
319 return args.to_vec();
320}
321
322#[cfg(test)]
324#[path = "commit_args_tests.rs"]
325mod tests;