-
-
Notifications
You must be signed in to change notification settings - Fork 311
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
eol::convert_to_worktree()
.
It's the inverse of `eol::convert_to_git()` to re-add CRLF where there were LF only.
- Loading branch information
Showing
5 changed files
with
309 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
use crate::clear_and_set_capacity; | ||
use crate::eol::{AttributesDigest, Configuration, Mode, Stats}; | ||
use bstr::{ByteSlice, ByteVec}; | ||
|
||
/// Convert all `\n` in `src` to `crlf` if `digest` and `config` indicate it, returning `true` if `buf` holds the result, or `false` | ||
/// if no change was made after all. | ||
pub fn convert_to_worktree(src: &[u8], digest: AttributesDigest, buf: &mut Vec<u8>, config: Configuration) -> bool { | ||
if src.is_empty() || digest.to_eol(config) != Some(Mode::CrLf) { | ||
return false; | ||
} | ||
let stats = Stats::from_bytes(src); | ||
if !stats.will_convert_lf_to_crlf(digest, config) { | ||
return false; | ||
} | ||
|
||
clear_and_set_capacity(buf, src.len() + stats.lone_lf); | ||
|
||
let mut ofs = 0; | ||
while let Some(pos) = src[ofs..].find_byteset(b"\r\n") { | ||
match src[ofs + pos] { | ||
b'\r' => { | ||
if src.get(ofs + pos + 1) == Some(&b'\n') { | ||
buf.push_str(&src[ofs..][..pos + 2]); | ||
ofs += pos + 2; | ||
} else { | ||
buf.push_str(&src[ofs..][..pos + 1]); | ||
ofs += pos + 1; | ||
} | ||
} | ||
b'\n' => { | ||
buf.push_str(&src[ofs..][..pos]); | ||
buf.push_str(b"\r\n"); | ||
ofs += pos + 1; | ||
} | ||
_ => unreachable!("would only find one of two possible values"), | ||
} | ||
} | ||
buf.push_str(&src[ofs..]); | ||
true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
use bstr::{ByteSlice, ByteVec}; | ||
use gix_filter::eol; | ||
use gix_filter::eol::AttributesDigest; | ||
use std::path::Path; | ||
|
||
#[test] | ||
fn with_binary_attribute_is_never_converted() { | ||
let mut buf = Vec::new(); | ||
let changed = eol::convert_to_git( | ||
b"hi\r\nho", | ||
AttributesDigest::Binary, | ||
&mut buf, | ||
no_call, | ||
Default::default(), | ||
) | ||
.expect("no error"); | ||
assert!(!changed, "the user marked it as binary so it's never being touched"); | ||
} | ||
|
||
#[test] | ||
fn no_crlf_means_no_work() -> crate::Result { | ||
let mut buf = Vec::new(); | ||
let changed = eol::convert_to_git(b"hi", AttributesDigest::TextCrlf, &mut buf, no_call, Default::default()) | ||
.expect("no error"); | ||
assert!(!changed); | ||
|
||
let changed = eol::convert_to_git( | ||
b"hi", | ||
AttributesDigest::TextAutoCrlf, | ||
&mut buf, | ||
no_object_in_index, | ||
Default::default(), | ||
) | ||
.expect("no error"); | ||
assert!(!changed, "in auto-mode, the object is queried in the index as well."); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn detected_as_binary() -> crate::Result { | ||
let mut buf = Vec::new(); | ||
let changed = eol::convert_to_git( | ||
b"hi\0zero makes it binary", | ||
AttributesDigest::TextAuto, | ||
&mut buf, | ||
no_call, | ||
Default::default(), | ||
) | ||
.expect("no error"); | ||
assert!( | ||
!changed, | ||
"in auto-mode, we have a heuristic to see if the buffer is binary" | ||
); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn fast_conversion_by_stripping_cr() -> crate::Result { | ||
let mut buf = Vec::new(); | ||
let changed = eol::convert_to_git( | ||
b"a\r\nb\r\nc", | ||
AttributesDigest::TextCrlf, | ||
&mut buf, | ||
no_call, | ||
Default::default(), | ||
) | ||
.expect("no error"); | ||
assert!(changed); | ||
assert_eq!(buf.as_bstr(), "a\nb\nc", "here carriage returns can just be stripped"); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn slower_conversion_due_to_lone_cr() -> crate::Result { | ||
let mut buf = Vec::new(); | ||
let changed = eol::convert_to_git( | ||
b"\r\ra\r\nb\r\nc", | ||
AttributesDigest::TextCrlf, | ||
&mut buf, | ||
no_call, | ||
Default::default(), | ||
) | ||
.expect("no error"); | ||
assert!(changed); | ||
assert_eq!( | ||
buf.as_bstr(), | ||
"\r\ra\nb\nc", | ||
"here carriage returns cannot be stripped but must be handled in pairs" | ||
); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn crlf_in_index_prevents_conversion_to_lf() -> crate::Result { | ||
let mut buf = Vec::new(); | ||
let mut called = false; | ||
let changed = eol::convert_to_git( | ||
b"elligible\n", | ||
AttributesDigest::TextAutoInput, | ||
&mut buf, | ||
|buf| { | ||
called = true; | ||
buf.clear(); | ||
buf.push_str("with CRLF\r\n"); | ||
Ok::<_, std::convert::Infallible>(Some(())) | ||
}, | ||
Default::default(), | ||
) | ||
.expect("no error"); | ||
assert!(called, "in auto mode, the index is queried as well"); | ||
assert!( | ||
!changed, | ||
"we saw the CRLF is present in the index, so it's unsafe to make changes" | ||
); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn round_trip_check() -> crate::Result { | ||
let mut buf = Vec::new(); | ||
for (input, expected) in [ | ||
(&b"lone-nl\nhi\r\nho"[..], "LF would be replaced by CRLF in 'hello.txt'"), | ||
// despite trying, I was unable to get into the other branch | ||
(b"lone-cr\nhi\r\nho", "LF would be replaced by CRLF in 'hello.txt'"), | ||
] { | ||
let err = eol::convert_to_git( | ||
input, | ||
AttributesDigest::TextCrlf, | ||
&mut buf, | ||
no_call, | ||
eol::convert_to_git::Context { | ||
round_trip_check: Some(gix_filter::eol::convert_to_git::RoundTripCheck::Fail { | ||
rela_path: Path::new("hello.txt"), | ||
}), | ||
config: Default::default(), | ||
}, | ||
) | ||
.unwrap_err(); | ||
assert_eq!(err.to_string(), expected); | ||
|
||
let changed = eol::convert_to_git( | ||
input, | ||
AttributesDigest::TextCrlf, | ||
&mut buf, | ||
no_call, | ||
eol::convert_to_git::Context { | ||
round_trip_check: Some(gix_filter::eol::convert_to_git::RoundTripCheck::Warn { | ||
rela_path: Path::new("hello.txt"), | ||
}), | ||
config: Default::default(), | ||
}, | ||
)?; | ||
assert!( | ||
changed, | ||
"in warn mode, we will get a result even though it won't round-trip" | ||
) | ||
} | ||
Ok(()) | ||
} | ||
|
||
#[allow(clippy::ptr_arg)] | ||
fn no_call(_buf: &mut Vec<u8>) -> std::io::Result<Option<()>> { | ||
unreachable!("index function will not be called") | ||
} | ||
|
||
#[allow(clippy::ptr_arg)] | ||
fn no_object_in_index(_buf: &mut Vec<u8>) -> std::io::Result<Option<()>> { | ||
Ok(Some(())) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use bstr::ByteSlice; | ||
use gix_filter::eol; | ||
use gix_filter::eol::{AttributesDigest, Configuration, Mode}; | ||
|
||
#[test] | ||
fn no_conversion_if_attribute_digest_does_not_allow_it() { | ||
let mut buf = Vec::new(); | ||
for digest in [ | ||
AttributesDigest::Binary, | ||
AttributesDigest::TextInput, | ||
AttributesDigest::TextAutoInput, | ||
] { | ||
let changed = eol::convert_to_worktree(b"hi\nho", digest, &mut buf, Default::default()); | ||
assert!(!changed, "the digest doesn't allow for CRLF changes"); | ||
} | ||
} | ||
|
||
#[test] | ||
fn no_conversion_if_configuration_does_not_allow_it() { | ||
let mut buf = Vec::new(); | ||
for digest in [AttributesDigest::Text, AttributesDigest::TextAuto] { | ||
for config in [ | ||
Configuration { | ||
auto_crlf: None, | ||
eol: Some(Mode::CrLf), | ||
}, | ||
Configuration { | ||
auto_crlf: Some(false), | ||
eol: Some(Mode::Lf), | ||
}, | ||
] { | ||
let changed = eol::convert_to_worktree(b"hi\nho", digest, &mut buf, config); | ||
assert!(!changed, "the configuration doesn't allow for changes"); | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn no_conversion_if_nothing_to_do() { | ||
let mut buf = Vec::new(); | ||
for (input, digest, msg) in [ | ||
( | ||
&b"hi\r\nho"[..], | ||
AttributesDigest::TextCrlf, | ||
"no lone line feed to handle", | ||
), | ||
( | ||
&b"binary\0linefeed\nho"[..], | ||
AttributesDigest::TextAutoCrlf, | ||
"binary in auto-mode is never handled", | ||
), | ||
( | ||
&b"binary\nlinefeed\r\nho"[..], | ||
AttributesDigest::TextAutoCrlf, | ||
"mixed crlf and lf is avoided", | ||
), | ||
( | ||
&b"elligible-but-disabled\nhere"[..], | ||
AttributesDigest::Binary, | ||
"designated binary is never handled", | ||
), | ||
] { | ||
let changed = eol::convert_to_worktree(input, digest, &mut buf, Default::default()); | ||
assert!(!changed, "{msg}"); | ||
} | ||
} | ||
|
||
#[test] | ||
fn each_nl_is_replaced_with_crnl() { | ||
let mut buf = Vec::new(); | ||
let changed = eol::convert_to_worktree( | ||
b"hi\n\nho\nend", | ||
AttributesDigest::TextCrlf, | ||
&mut buf, | ||
Default::default(), | ||
); | ||
assert!( | ||
changed, | ||
"the buffer has to be changed as it is explicitly demanded and has newlines to convert" | ||
); | ||
assert_eq!(buf.as_bstr(), "hi\r\n\r\nho\r\nend"); | ||
} | ||
|
||
#[test] | ||
fn existing_crnl_are_not_replaced_for_safety_nor_are_lone_cr() { | ||
let mut buf = Vec::new(); | ||
let changed = eol::convert_to_worktree( | ||
b"hi\r\n\nho\r\nend\r", | ||
AttributesDigest::TextCrlf, | ||
&mut buf, | ||
Default::default(), | ||
); | ||
assert!(changed); | ||
assert_eq!(buf.as_bstr(), "hi\r\n\r\nho\r\nend\r"); | ||
} |
Oops, something went wrong.