1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! What:
//! The pathspec modes and the pathspec magic that make a conflict-side line unreadable to the guard,
//! and the sentences that
//! tell a person the same line without them would be saved and then allowed.
//! Why:
//! A mode (`GIT_ICASE_PATHSPECS`,
//! `--glob-pathspecs`,
//! ...) changes which files a pathspec names,
//! and magic (`:(icase)`,
//! `:!`,
//! `:/`) does the same for one token,
//! so the guard cannot say which files Git would replace and keeps the rejection.
//! The
//! rejection alone does not say what to change;
//! when the line would be allowed without the mode or magic,
//! the message does.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! modesInEffect(['--icase-pathspecs'], overlay); // ['--icase-pathspecs']
//! modeNote(['--icase-pathspecs'], 'merge'); // 'This merge is in progress ... the same command without `--icase-pathspecs` ...'
//! ```
use std::ffi::OsString;
/// The global options that set a pathspec mode,
/// the ones `git.c` accepts before the subcommand.
pub const MODE_OPTIONS: [&str; 5] = ["--icase-pathspecs", "--glob-pathspecs", "--noglob-pathspecs", "--literal-pathspecs", "--no-literal-pathspecs"];
/// The variables that set a pathspec mode.
pub const MODE_VARIABLES: [&str; 4] = ["GIT_ICASE_PATHSPECS", "GIT_GLOB_PATHSPECS", "GIT_NOGLOB_PATHSPECS", "GIT_LITERAL_PATHSPECS"];
/// What:
/// The spellings of the pathspec modes a command runs under:
/// each global option present,
/// each variable present in the
/// process environment or the child overlay.
/// Why:
/// The note names exactly what the person set,
/// so the way out is to remove exactly that.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function modesInEffect(globalPrefix: string[], overlay: [string, string][]): string[];
/// ```
pub fn modes_in_effect(global_prefix: &[OsString], overlay: &[(OsString, OsString)]) -> Vec<String> {
let mut found: Vec<String> = Vec::new();
for option in MODE_OPTIONS {
for each in global_prefix {
if each.as_encoded_bytes() == option.as_bytes() {
found.push(String::from(option));
break;
}
}
}
for variable in MODE_VARIABLES {
let mut set: bool = std::env::var_os(variable).is_some();
for (name, _) in overlay {
set |= name.as_encoded_bytes() == variable.as_bytes();
}
if set {
found.push(String::from(variable));
}
}
return found;
}
/// The items in backticks,
/// joined with commas.
fn backticked<'item>(items: impl Iterator<Item = &'item str>) -> String {
let mut text: String = String::new();
for (position, item) in items.enumerate() {
if position > 0 {
text.push_str(", ");
}
text.push('`');
text.push_str(item);
text.push('`');
}
return text;
}
/// The magic of a magic-and-file pair.
fn first_of(pair: &(String, String)) -> &str {
return pair.0.as_str();
}
/// The file of a magic-and-file pair.
fn second_of(pair: &(String, String)) -> &str {
return pair.1.as_str();
}
/// The word for an operation in a sentence.
fn operation_word(operation: super::conflict_side_state::Operation) -> &'static str {
use super::conflict_side_state::Operation;
return match operation {
Operation::Merge => "merge",
Operation::Rebase => "rebase",
Operation::CherryPick => "cherry-pick",
Operation::Revert => "revert",
};
}
/// What:
/// The sentence added to a rejection when the line names only conflicted files and a pathspec mode is the only reason it
/// is not allowed.
/// Why:
/// The person is told which mode to drop,
/// and that the same command without it is saved first and then allowed.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function modeNote(modes: string[], operation: Operation): string;
/// ```
pub fn mode_note(modes: &[String], operation: super::conflict_side_state::Operation) -> String {
let named: String = backticked(modes.iter().map(String::as_str));
return format!(
" This {} is in progress and every path named is a conflicted file, so the same command without {named} would be saved first \
and then allowed. {named} changes which files a pathspec names, so the line is rejected as it stands: run it again without \
{named} (drop the option or unset the variable).",
operation_word(operation)
);
}
/// What:
/// The sentence added to a rejection when every token that is not a plain conflicted file is pathspec magic on a conflicted
/// file's name.
/// Why:
/// The person is told which magic to drop and which file to name,
/// and that the line is then saved first and then allowed.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function magicNote(magic: [string, string][], operation: Operation): string;
/// ```
pub fn magic_note(magic: &[(String, String)], operation: super::conflict_side_state::Operation) -> String {
let spellings: String = backticked(magic.iter().map(first_of));
let names: String = backticked(magic.iter().map(second_of));
return format!(
" This {} is in progress and every path named is a conflicted file once the pathspec magic is left off ({spellings}). Pathspec \
magic changes which files a pathspec names, so name each conflicted file itself ({names}) and the line is saved first and \
then allowed.",
operation_word(operation)
);
}
/// Both notes,
/// and the modes a command runs under.
#[cfg(test)]
#[path = "conflict_side_modes_tests.rs"]
mod tests;