ok/jj
1
0
Fork 0
forked from mirrors/jj

gitignore: any character can be backslash-escaped

You may use "abc\\" in .gitignore to ignore a file named "abc\". In this
case, removing training spaces on "abc\\ " must result in "abc\\" as the
trailing space is not escaped, the preceeding backslash being part of
the previous "\\" escaping sequence.
This commit is contained in:
Samuel Tardieu 2023-01-14 18:56:33 +01:00
parent 60d1537731
commit 84fc66fe50
2 changed files with 17 additions and 21 deletions

View file

@ -74,6 +74,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the jj repo from a Git repo. They will now be considered as non-existent if the jj repo from a Git repo. They will now be considered as non-existent if
referenced explicitly instead of crashing. referenced explicitly instead of crashing.
* Fixed handling of escaped characters in .gitignore (only keep trailing spaces
if escaped properly).
### Contributors ### Contributors
Thanks to the people who made this release happen! Thanks to the people who made this release happen!

View file

@ -27,33 +27,23 @@ struct GitIgnoreLine {
} }
impl GitIgnoreLine { impl GitIgnoreLine {
// Remove trailing spaces (unless backslash-escaped) // Remove trailing spaces (unless backslash-escaped). Any character
// can be backslash-escaped as well.
fn remove_trailing_space(input: &str) -> &str { fn remove_trailing_space(input: &str) -> &str {
let mut trimmed_len = 0;
let mut non_space_seen = false;
let mut prev_was_space = false;
let mut in_escape = false;
let input = input.strip_suffix('\r').unwrap_or(input); let input = input.strip_suffix('\r').unwrap_or(input);
for (i, c) in input.char_indices() { let mut it = input.char_indices().rev().peekable();
if !prev_was_space && non_space_seen { while let Some((i, c)) = it.next() {
trimmed_len = i; if c != ' ' {
return &input[..i + c.len_utf8()];
} }
if c == ' ' { if matches!(it.peek(), Some((_, '\\'))) {
if in_escape { if it.skip(1).take_while(|(_, b)| *b == '\\').count() % 2 == 1 {
in_escape = false; return &input[..i];
} else {
prev_was_space = true;
} }
} else { return &input[..i + 1];
non_space_seen = true;
prev_was_space = false;
in_escape = c == '\\'
} }
} }
if !prev_was_space && non_space_seen { ""
trimmed_len = input.len();
}
input.split_at(trimmed_len).0
} }
fn parse(prefix: &str, input: &str) -> Option<GitIgnoreLine> { fn parse(prefix: &str, input: &str) -> Option<GitIgnoreLine> {
@ -347,6 +337,9 @@ mod tests {
fn test_gitignore_whitespace() { fn test_gitignore_whitespace() {
assert!(!matches_file(b" \n", " ")); assert!(!matches_file(b" \n", " "));
assert!(matches_file(b"\\ \n", " ")); assert!(matches_file(b"\\ \n", " "));
assert!(matches_file(b"\\\\ \n", "\\"));
assert!(!matches_file(b"\\\\ \n", " "));
assert!(matches_file(b"\\\\\\ \n", "\\ "));
assert!(matches_file(b" a\n", " a")); assert!(matches_file(b" a\n", " a"));
assert!(matches_file(b"a b\n", "a b")); assert!(matches_file(b"a b\n", "a b"));
assert!(matches_file(b"a b \n", "a b")); assert!(matches_file(b"a b \n", "a b"));