History

Merge branch 'worktree-encoding'

Sebastian Thiel committed 3 years ago

Commit 5af2cf32 parents 3fd5e16 d1fed3e11 files · +406 −72Browse files

Unified · Split

11 files
  1. Cargo.lock +1 −0
  2. gix-filter/Cargo.toml +1 −0
  3. gix-filter/src/ident.rs +43 −25
  4. gix-filter/src/lib.rs +4 −2
  5. gix-filter/src/worktree/encode_to_git.rs +90 −0
  6. gix-filter/src/worktree/encode_to_worktree.rs +69 −0
  7. gix-filter/src/worktree/encoding.rs +31 −0
  8. gix-filter/src/worktree/mod.rs +16 −0
  9. gix-filter/tests/filter.rs +1 −0
  10. gix-filter/tests/ident/mod.rs +49 −45
  11. gix-filter/tests/worktree/mod.rs +101 −0
Cargo.lock+1−0
@@ -1650,6 +1650,7 @@ name = "gix-filter" · 1649 unmodified lines
1650version = "0.0.0"1650version = "0.0.0"
1651dependencies = [1651dependencies = [
1652 "bstr",1652 "bstr",
1653 "encoding_rs",
1653 "gix-hash 0.11.3",1654 "gix-hash 0.11.3",
1654 "gix-object 0.31.0",1655 "gix-object 0.31.0",
1655 "gix-trace 0.1.1",1656 "gix-trace 0.1.1",
gix-filter/Cargo.toml+1−0
@@ -16,5 +16,6 @@ gix-hash = { version = "^0.11.3", path = "../gix-hash" } · 15 unmodified lines
16gix-trace = { version = "^0.1.1", path = "../gix-trace" }16gix-trace = { version = "^0.1.1", path = "../gix-trace" }
17gix-object = { version = "^0.31.0", path = "../gix-object" }17gix-object = { version = "^0.31.0", path = "../gix-object" }
18 18
19encoding_rs = "0.8.32"
19bstr = { version = "1.5.0", default-features = false, features = ["std"] }20bstr = { version = "1.5.0", default-features = false, features = ["std"] }
20thiserror = "1.0.38"21thiserror = "1.0.38"
gix-filter/src/ident.rs+43−25
@@ -1,9 +1,10 @@
1use bstr::{BStr, ByteSlice, ByteVec};1use crate::clear_and_set_capacity;
2use std::borrow::Cow;2use bstr::{ByteSlice, ByteVec};
3use std::ops::Range;3use std::ops::Range;
4 4
5/// Undo identifiers like `$Id:<hexsha>$` to `$Id$`. Newlines between dollars are ignored.5/// Undo identifiers like `$Id:<hexsha>$` to `$Id$` in `src` and write to `buf`. Newlines between dollars are ignored.
6pub fn undo(mut input: Cow<'_, BStr>) -> Cow<'_, BStr> {6/// Return `true` if `buf` was written or `false` if `src` was left unaltered (as there was nothing to do).
7pub fn undo(src: &[u8], buf: &mut Vec<u8>) -> bool {
7 fn find_range(input: &[u8]) -> Option<Range<usize>> {8 fn find_range(input: &[u8]) -> Option<Range<usize>> {
8 let mut ofs = 0;9 let mut ofs = 0;
9 loop {10 loop {
@@ -21,37 +22,54 @@ pub fn undo(mut input: Cow<'_, BStr>) -> Cow<'_, BStr> { · 11 unmodified lines
21 }22 }
22 23
23 let mut ofs = 0;24 let mut ofs = 0;
24 while let Some(range) = find_range(&input[ofs..]) {25 let mut initialized = false;
25 input26 while let Some(range) = find_range(&src[ofs..]) {
26 .to_mut()27 if !initialized {
27 .replace_range((range.start + ofs)..(range.end + ofs), b"$Id$");28 clear_and_set_capacity(buf, src.len());
28 ofs += range.start + 4;29 initialized = true;
30 }
31 buf.push_str(&src[ofs..][..range.start]);
32 buf.push_str(b"$Id$");
33 ofs += range.end;
34 }
35 if initialized {
36 buf.push_str(&src[ofs..]);
29 }37 }
30 input38 initialized
31}39}
32 40
33/// Substitute all occurrences of `$Id$` with `$Id: <hexsha-of-input>$` if present and return the changed buffer, with `object_hash`41/// Substitute all occurrences of `$Id$` with `$Id: <hexsha-of-input>$` if present in `src` and write all changes to `buf`,
34/// being used accordingly.42/// with `object_hash` being used accordingly. Return `true` if `buf` was written to or `false` if no change was made
43/// (as there was nothing to do).
35///44///
36/// ### Deviation45/// ### Deviation
37///46///
38/// `Git` also tries to cleanup 'stray' substituted `$Id: <hex>$`, but we don't do that, sticking exactly to what ought to be done.47/// `Git` also tries to cleanup 'stray' substituted `$Id: <hex>$`, but we don't do that, sticking exactly to what ought to be done.
39/// The respective code is up to 16 years old and one might assume that `git` by now handles checking and checkout filters correctly.48/// The respective code is up to 16 years old and one might assume that `git` by now handles checking and checkout filters correctly.
40pub fn apply(mut input: Cow<'_, BStr>, object_hash: gix_hash::Kind) -> Cow<'_, BStr> {49pub fn apply(src: &[u8], object_hash: gix_hash::Kind, buf: &mut Vec<u8>) -> bool {
41 let mut buf: [u8; b": $".len() + gix_hash::Kind::longest().len_in_hex()] = std::array::from_fn(|_| 0);50 const HASH_LEN: usize = ": ".len() + gix_hash::Kind::longest().len_in_hex();
42 let mut id = None;51 let mut id = None;
43 let mut ofs = 0;52 let mut ofs = 0;
44 while let Some(pos) = input[ofs..].find(b"$Id$") {53 while let Some(pos) = src[ofs..].find(b"$Id$") {
45 let id = id.get_or_insert_with(|| gix_object::compute_hash(object_hash, gix_object::Kind::Blob, &input));54 let id = match id {
55 None => {
56 let new_id = gix_object::compute_hash(object_hash, gix_object::Kind::Blob, src);
57 id = new_id.into();
58 clear_and_set_capacity(buf, src.len() + HASH_LEN); // pre-allocate for one ID
59 new_id
60 }
61 Some(id) => id.to_owned(),
62 };
46 63
47 buf[..2].copy_from_slice(b": ");64 buf.push_str(&src[ofs..][..pos + 3]);
48 let _ = id.hex_to_buf(&mut buf[2..][..object_hash.len_in_hex()]);65 buf.push_str(b": ");
49 let replaced_id = &mut buf[..2 + object_hash.len_in_hex() + 1];66 id.write_hex_to(&mut *buf).expect("writes to memory always work");
50 *replaced_id.last_mut().expect("present") = b'$';67 buf.push(b'$');
51 input68
52 .to_mut()69 ofs += pos + 4;
53 .replace_range((ofs + pos + 3)..(ofs + pos + 4), &*replaced_id);70 }
54 ofs += pos + 3 + replaced_id.len();71 if id.is_some() {
72 buf.push_str(&src[ofs..]);
55 }73 }
56 input74 id.is_some()
57}75}
gix-filter/src/lib.rs+4−2
@@ -8,8 +8,7 @@ · 7 unmodified lines
8//!8//!
9//! This crate implements the building blocks in terms of applying and undoing filters, along with logic to decide whether9//! This crate implements the building blocks in terms of applying and undoing filters, along with logic to decide whether
10//! or not to apply such a filter.10//! or not to apply such a filter.
11#![deny(rust_2018_idioms, missing_docs)]11#![deny(rust_2018_idioms, missing_docs, unsafe_code)]
12#![forbid(unsafe_code)]
13 12
14///13///
15pub mod ident;14pub mod ident;
@@ -17,6 +16,9 @@ pub mod ident; · 1 unmodified line
17/// utilities related to handling line endings in buffers16/// utilities related to handling line endings in buffers
18pub mod eol;17pub mod eol;
19 18
19/// Utilities for handling worktree encodings.
20pub mod worktree;
21
20fn clear_and_set_capacity(buf: &mut Vec<u8>, cap: usize) {22fn clear_and_set_capacity(buf: &mut Vec<u8>, cap: usize) {
21 buf.clear();23 buf.clear();
22 if buf.capacity() < cap {24 if buf.capacity() < cap {
gix-filter/src/worktree/encode_to_git.rs+90−0
@@ -0,0 +1,90 @@
1/// Whether or not to perform round-trip checks.
2#[derive(Debug, Copy, Clone)]
3pub enum RoundTrip {
4 /// Assure that we can losslessly convert the UTF-8 result back to the original encoding.
5 Validate,
6 /// Do not check if the encoding is round-trippable.
7 Ignore,
8}
9
10/// The error returned by [`encode_to_git()][super::encode_to_git()].
11#[derive(Debug, thiserror::Error)]
12#[allow(missing_docs)]
13pub enum Error {
14 #[error("Cannot convert input of {input_len} bytes to UTF-8 without overflowing")]
15 Overflow { input_len: usize },
16 #[error("The input was malformed and could not be decoded as '{encoding}'")]
17 Malformed { encoding: &'static str },
18 #[error("Encoding from '{src_encoding}' to '{dest_encoding}' and back is not the same")]
19 RoundTrip {
20 src_encoding: &'static str,
21 dest_encoding: &'static str,
22 },
23}
24
25pub(crate) mod function {
26 use super::{Error, RoundTrip};
27 use crate::clear_and_set_capacity;
28 use encoding_rs::DecoderResult;
29
30 /// Decode `src` according to `src_encoding` to `UTF-8` for storage in git and place it in `buf`.
31 /// Note that the encoding is always applied, there is no conditional even if `src_encoding` already is `UTF-8`.
32 pub fn encode_to_git(
33 src: &[u8],
34 src_encoding: &'static encoding_rs::Encoding,
35 buf: &mut Vec<u8>,
36 round_trip: RoundTrip,
37 ) -> Result<(), Error> {
38 let mut decoder = src_encoding.new_decoder_with_bom_removal();
39 let buf_len = decoder
40 .max_utf8_buffer_length_without_replacement(src.len())
41 .ok_or(Error::Overflow { input_len: src.len() })?;
42 clear_and_set_capacity(buf, buf_len);
43 // SAFETY: `clear_and_set_capacity` assure that we have the given `buf_len` allocated, so setting its length is only making available
44 // what is allocated. Later we will truncate to the amount of actually written bytes.
45 #[allow(unsafe_code)]
46 unsafe {
47 buf.set_len(buf_len);
48 }
49 let (res, read, written) = decoder.decode_to_utf8_without_replacement(src, buf, true);
50 match res {
51 DecoderResult::InputEmpty => {
52 assert!(
53 buf_len >= written,
54 "encoding_rs estimates the maximum amount of bytes written correctly"
55 );
56 assert_eq!(read, src.len(), "input buffer should be fully consumed");
57 // SAFETY: we trust that `encoding_rs` reports this number correctly, and truncate everything else.
58 #[allow(unsafe_code)]
59 unsafe {
60 buf.set_len(written);
61 }
62 }
63 DecoderResult::OutputFull => {
64 unreachable!("we assure that the output buffer is big enough as per the encoder's estimate")
65 }
66 DecoderResult::Malformed(_, _) => {
67 return Err(Error::Malformed {
68 encoding: src_encoding.name(),
69 })
70 }
71 }
72
73 match round_trip {
74 RoundTrip::Validate => {
75 // SAFETY: we trust `encoding_rs` to output valid UTF-8 only if we ask it to.
76 #[allow(unsafe_code)]
77 let str = unsafe { std::str::from_utf8_unchecked(buf) };
78 let (should_equal_src, _actual_encoding, _had_errors) = src_encoding.encode(str);
79 if should_equal_src != src {
80 return Err(Error::RoundTrip {
81 src_encoding: src_encoding.name(),
82 dest_encoding: "UTF-8",
83 });
84 }
85 }
86 RoundTrip::Ignore => {}
87 }
88 Ok(())
89 }
90}
gix-filter/src/worktree/encode_to_worktree.rs+69−0
@@ -0,0 +1,69 @@
1/// The error returned by [`encode_to_worktree()][super::encode_to_worktree()].
2#[derive(Debug, thiserror::Error)]
3#[allow(missing_docs)]
4pub enum Error {
5 #[error("Cannot convert input of {input_len} UTF-8 bytes to target encoding without overflowing")]
6 Overflow { input_len: usize },
7 #[error("Input was not UTF-8 encoded")]
8 InputAsUtf8(#[from] std::str::Utf8Error),
9 #[error("The character '{character}' could not be mapped to the {worktree_encoding}")]
10 Unmappable {
11 character: char,
12 worktree_encoding: &'static str,
13 },
14}
15
16pub(crate) mod function {
17 use super::Error;
18 use crate::clear_and_set_capacity;
19 use encoding_rs::EncoderResult;
20
21 /// Encode `src_utf8`, which is assumed to be UTF-8 encoded, according to `worktree_encoding` for placement in the working directory,
22 /// and write it to `buf`, possibly resizing it.
23 /// Note that the encoding is always applied, there is no conditional even if `worktree_encoding` and the `src` encoding are the same.
24 pub fn encode_to_worktree(
25 src_utf8: &[u8],
26 worktree_encoding: &'static encoding_rs::Encoding,
27 buf: &mut Vec<u8>,
28 ) -> Result<(), Error> {
29 let mut encoder = worktree_encoding.new_encoder();
30 let buf_len = encoder
31 .max_buffer_length_from_utf8_if_no_unmappables(src_utf8.len())
32 .ok_or(Error::Overflow {
33 input_len: src_utf8.len(),
34 })?;
35 clear_and_set_capacity(buf, buf_len);
36 // SAFETY: `clear_and_set_capacity` assure that we have the given `buf_len` allocated, so setting its length is only making available
37 // what is allocated. Later we will truncate to the amount of actually written bytes.
38 #[allow(unsafe_code)]
39 unsafe {
40 buf.set_len(buf_len);
41 }
42 let src = std::str::from_utf8(src_utf8)?;
43 let (res, read, written) = encoder.encode_from_utf8_without_replacement(src, buf, true);
44 match res {
45 EncoderResult::InputEmpty => {
46 assert!(
47 buf_len >= written,
48 "encoding_rs estimates the maximum amount of bytes written correctly"
49 );
50 assert_eq!(read, src_utf8.len(), "input buffer should be fully consumed");
51 // SAFETY: we trust that `encoding_rs` reports this number correctly, and truncate everything else.
52 #[allow(unsafe_code)]
53 unsafe {
54 buf.set_len(written);
55 }
56 }
57 EncoderResult::OutputFull => {
58 unreachable!("we assure that the output buffer is big enough as per the encoder's estimate")
59 }
60 EncoderResult::Unmappable(c) => {
61 return Err(Error::Unmappable {
62 worktree_encoding: worktree_encoding.name(),
63 character: c,
64 })
65 }
66 }
67 Ok(())
68 }
69}
gix-filter/src/worktree/encoding.rs+31−0
@@ -0,0 +1,31 @@
1use bstr::BStr;
2use encoding_rs::Encoding;
3
4///
5pub mod for_label {
6 use bstr::BString;
7
8 /// The error returned by [for_label()][super::for_label()].
9 #[derive(Debug, thiserror::Error)]
10 #[allow(missing_docs)]
11 pub enum Error {
12 #[error("An encoding named '{name}' is not known")]
13 Unknown { name: BString },
14 }
15}
16
17/// Try to produce a new `Encoding` for `label` or report an error if it is not known.
18///
19/// ### Deviation
20///
21/// * There is no special handling of UTF-16LE/BE with checks if data contains a BOM or not, like `git` as we don't expect to have
22/// data available here.
23/// * Special `-BOM` suffixed versions of `UTF-16` encodings are not supported.
24pub fn for_label<'a>(label: impl Into<&'a BStr>) -> Result<&'static Encoding, for_label::Error> {
25 let mut label = label.into();
26 if label == "latin-1" {
27 label = "ISO-8859-1".into();
28 }
29 let enc = Encoding::for_label(label.as_ref()).ok_or_else(|| for_label::Error::Unknown { name: label.into() })?;
30 Ok(enc)
31}
gix-filter/src/worktree/mod.rs+16−0
@@ -0,0 +1,16 @@
1//! Worktree encodings are powered by the `encoding_rs` crate, which has a narrower focus than the `iconv` library. Thus this implementation
2//! is inherently more limited but will handle the common cases.
3//!
4//! Note that for encoding to legacy formats, [additional normalization steps](https://docs.rs/encoding_rs/0.8.32/encoding_rs/#preparing-text-for-the-encoders)
5//! can be taken, which we do not yet take unless there is specific examples or problems to solve.
6
7///
8pub mod encoding;
9
10///
11pub mod encode_to_git;
12pub use encode_to_git::function::encode_to_git;
13
14///
15pub mod encode_to_worktree;
16pub use encode_to_worktree::function::encode_to_worktree;
gix-filter/tests/filter.rs+1−0
@@ -1,4 +1,5 @@
1mod eol;1mod eol;
2mod ident;2mod ident;
3mod worktree;
3 4
4pub type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;5pub type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
gix-filter/tests/ident/mod.rs+49−45
@@ -1,107 +1,111 @@
1use bstr::BStr;
2use std::borrow::Cow;
3
4fn cowstr(input: &str) -> Cow<'_, BStr> {
5 Cow::Borrowed(input.into())
6}
7mod undo {1mod undo {
8 use crate::ident::cowstr;2 use bstr::{ByteSlice, B};
9 use std::borrow::Cow;
10 3
11 #[test]4 #[test]
12 fn no_id_changes_nothing() {5 fn no_id_changes_nothing() {
13 let cow = gix_filter::ident::undo(cowstr("hello"));6 let mut buf = Vec::new();
14 assert!(matches!(cow, Cow::Borrowed(_)), "the buffer is not touched");7 let changed = gix_filter::ident::undo(B("hello"), &mut buf);
15 assert_eq!(cow.as_ref(), "hello");8 assert!(!changed, "the buffer is not touched");
9 assert_eq!(buf.len(), 0);
16 }10 }
17 11
18 #[test]12 #[test]
19 fn empty() {13 fn empty() {
20 assert!(matches!(gix_filter::ident::undo(cowstr("")), Cow::Borrowed(_)));14 let mut buf = Vec::new();
15 assert!(
16 !gix_filter::ident::undo(B(""), &mut buf),
17 "nothing to be done in empty buffer"
18 );
21 }19 }
22 20
23 #[test]21 #[test]
24 fn nothing_if_newline_between_dollars() {22 fn nothing_if_newline_between_dollars() {
25 assert!(matches!(gix_filter::ident::undo(cowstr(" $Id: \n$")), Cow::Borrowed(_)));23 let mut buf = Vec::new();
24 assert!(!gix_filter::ident::undo(B(" $Id: \n$"), &mut buf));
25 assert_eq!(buf.len(), 0);
26 }26 }
27 27
28 #[test]28 #[test]
29 fn nothing_if_it_is_not_id() {29 fn nothing_if_it_is_not_id() {
30 let mut buf = Vec::new();
30 assert!(31 assert!(
31 matches!(gix_filter::ident::undo(cowstr(" $id: something$")), Cow::Borrowed(_)),32 !gix_filter::ident::undo(B(" $id: something$"), &mut buf),
32 "it's matching case-sensitively"33 "it's matching case-sensitively"
33 );34 );
35 assert_eq!(buf.len(), 0);
34 }36 }
35 37
36 #[test]38 #[test]
37 fn anything_between_dollar_id_dollar() {39 fn anything_between_dollar_id_dollar() {
38 assert_eq!(40 let mut buf = Vec::new();
39 gix_filter::ident::undo(cowstr(" $Id: something$\nhello")).as_ref(),41 assert!(gix_filter::ident::undo(B(" $Id: something$\nhello"), &mut buf));
40 " $Id$\nhello"42 assert_eq!(buf.as_bstr(), " $Id$\nhello");
41 );
42 }43 }
43 44
44 #[test]45 #[test]
45 fn multiple() {46 fn multiple() {
46 assert_eq!(47 let mut buf = Vec::new();
47 gix_filter::ident::undo(cowstr(48 assert!(gix_filter::ident::undo(
48 "$Id: a\n$ $Id: something$\nhello$Id: hex$\nlast $Id:other$\n$Id: \n$"49 B("$Id: a\n$ $Id: something$\nhello$Id: hex$\nlast $Id:other$\n$Id: \n$"),
49 ))50 &mut buf
50 .as_ref(),51 ));
51 "$Id: a\n$ $Id$\nhello$Id$\nlast $Id$\n$Id: \n$",52 assert_eq!(buf.as_bstr(), "$Id: a\n$ $Id$\nhello$Id$\nlast $Id$\n$Id: \n$");
52 );53
53 assert_eq!(54 assert!(gix_filter::ident::undo(
54 gix_filter::ident::undo(cowstr("$Id: a\n$$Id:$$Id: hex$\n$Id:other$$Id: $end")).as_ref(),55 B("$Id: a\n$$Id:$$Id: hex$\n$Id:other$$Id: $end"),
55 "$Id: a\n$$Id$$Id$\n$Id$$Id$end",56 &mut buf
56 );57 ));
58 assert_eq!(buf.as_bstr(), "$Id: a\n$$Id$$Id$\n$Id$$Id$end");
57 }59 }
58}60}
59 61
60mod apply {62mod apply {
61 use crate::ident::cowstr;63 use bstr::{ByteSlice, B};
62 use gix_filter::ident;64 use gix_filter::ident;
63 use std::borrow::Cow;
64 65
65 #[test]66 #[test]
66 fn no_change() {67 fn no_change() {
68 let mut buf = Vec::new();
67 for input_no_match in [69 for input_no_match in [
68 "",70 "",
69 "nothing",71 "nothing",
70 "$ID$ case sensitive matching",72 "$ID$ case sensitive matching",
71 "$Id: expanded is ignored$",73 "$Id: expanded is ignored$",
72 ] {74 ] {
73 let res = ident::apply(cowstr(input_no_match), gix_hash::Kind::Sha1);75 let changed = ident::apply(input_no_match.as_bytes(), gix_hash::Kind::Sha1, &mut buf);
74 assert!(76 assert!(!changed, "no substitution happens, nothing to do");
75 matches!(res, Cow::Borrowed(_)),77 assert_eq!(buf.len(), 0);
76 "no substitution happens, so no mutable version of the Cow is created"
77 );
78 assert_eq!(res.as_ref(), input_no_match, "there definitely is no change");
79 }78 }
80 }79 }
81 80
82 #[test]81 #[test]
83 fn simple() {82 fn simple() {
84 assert_eq!(83 let mut buf = Vec::new();
85 ident::apply(cowstr("$Id$"), gix_hash::Kind::Sha1).as_ref(),84 assert!(
86 "$Id: b3f5ebfb5843bc43ceecff6d4f26bb37c615beb1$"85 ident::apply(B("$Id$"), gix_hash::Kind::Sha1, &mut buf),
86 "a change happens"
87 );87 );
88 assert_eq!(buf.as_bstr(), "$Id: b3f5ebfb5843bc43ceecff6d4f26bb37c615beb1$");
88 89
90 assert!(ident::apply(B("$Id$ $Id$ foo"), gix_hash::Kind::Sha1, &mut buf));
89 assert_eq!(91 assert_eq!(
90 ident::apply(cowstr("$Id$ $Id$"), gix_hash::Kind::Sha1).as_ref(),92 buf.as_bstr(),
91 "$Id: f6f3176060328ef7030a8b8eeda57fbf0587b2f9$ $Id: f6f3176060328ef7030a8b8eeda57fbf0587b2f9$"93 "$Id: e230cff7a9624f59eaa28bfb97602c3a03651a49$ $Id: e230cff7a9624f59eaa28bfb97602c3a03651a49$ foo"
92 );94 );
93 }95 }
94 96
95 #[test]97 #[test]
96 fn round_trips() {98 fn round_trips() {
99 let mut buf = Vec::new();
97 for input in [100 for input in [
98 "hi\n$Id$\nho\n\t$Id$$Id$$Id$",101 "hi\n$Id$\nho\n\t$Id$$Id$$Id$",
99 "$Id$",102 "$Id$",
100 "$Id$ and one more $Id$ and done",103 "$Id$ and one more $Id$ and done",
101 ] {104 ] {
102 let res = ident::apply(cowstr(input), gix_hash::Kind::Sha1);105 let changed = ident::apply(B(input), gix_hash::Kind::Sha1, &mut buf);
103 assert_ne!(res.as_ref(), input, "the input was rewritten");106 assert!(changed, "the input was rewritten");
104 assert_eq!(ident::undo(res).as_ref(), input, "the filter can be undone perfectly");107 assert!(ident::undo(&buf.clone(), &mut buf), "undo does something as well");
108 assert_eq!(buf.as_bstr(), input, "the filter can be undone perfectly");
105 }109 }
106 }110 }
107}111}
gix-filter/tests/worktree/mod.rs+101−0
@@ -0,0 +1,101 @@
1mod encoding {
2 mod for_label {
3 use gix_filter::worktree;
4
5 #[test]
6 fn unknown() {
7 assert_eq!(
8 worktree::encoding::for_label("FOO").unwrap_err().to_string(),
9 "An encoding named 'FOO' is not known"
10 );
11 }
12
13 #[test]
14 fn utf32_is_not_supported() {
15 for enc in ["UTF-32BE", "UTF-32LE", "UTF-32", "UTF-32LE-BOM", "UTF-32BE-BOM"] {
16 assert!(
17 matches!(
18 worktree::encoding::for_label(enc).unwrap_err(),
19 worktree::encoding::for_label::Error::Unknown { .. }
20 ),
21 "it's not needed for the web and this crate is meant for use in firefox"
22 );
23 }
24 }
25
26 #[test]
27 fn various_spellings_of_utf_8_are_supported() {
28 for enc in ["UTF8", "UTF-8", "utf-8", "utf8"] {
29 let enc = worktree::encoding::for_label(enc).unwrap();
30 assert_eq!(enc.name(), "UTF-8");
31 }
32 }
33
34 #[test]
35 fn various_utf_16_without_bom_suffix_are_supported() {
36 for label in ["UTF-16BE", "UTF-16LE"] {
37 let enc = worktree::encoding::for_label(label).unwrap();
38 assert_eq!(enc.name(), label);
39 }
40 }
41
42 #[test]
43 fn various_utf_16_with_bom_suffix_are_unsupported() {
44 for label in ["UTF-16BE-BOM", "UTF-16LE-BOM"] {
45 assert!(
46 matches!(
47 worktree::encoding::for_label(label).unwrap_err(),
48 worktree::encoding::for_label::Error::Unknown { .. }
49 ),
50 "git supports these and has special handling, but we have not for now. Git has no tests for that either."
51 );
52 }
53 }
54
55 #[test]
56 fn latin_1_is_supported_with_fallback() {
57 let enc = worktree::encoding::for_label("latin-1").unwrap();
58 assert_eq!(
59 enc.name(),
60 "windows-1252",
61 "the encoding crate has its own fallback for ISO-8859-1 which we try to use"
62 );
63 }
64 }
65}
66
67mod encode_to_git {
68 use bstr::ByteSlice;
69 use gix_filter::worktree;
70 use gix_filter::worktree::encode_to_git::RoundTrip;
71
72 #[test]
73 fn simple() -> crate::Result {
74 let input = &b"hello"[..];
75 for round_trip in [RoundTrip::Ignore, RoundTrip::Validate] {
76 let mut buf = Vec::new();
77 worktree::encode_to_git(input, encoding_rs::UTF_8, &mut buf, round_trip)?;
78 assert_eq!(buf.as_bstr(), input)
79 }
80 Ok(())
81 }
82}
83
84mod encode_to_worktree {
85 use bstr::ByteSlice;
86 use gix_filter::worktree;
87 use gix_filter::worktree::encode_to_git::RoundTrip;
88
89 #[test]
90 fn shift_jis() -> crate::Result {
91 let input = "ハローワールド";
92 let mut buf = Vec::new();
93 worktree::encode_to_worktree(input.as_bytes(), encoding_rs::SHIFT_JIS, &mut buf)?;
94
95 let mut re_encoded = Vec::new();
96 worktree::encode_to_git(&buf, encoding_rs::SHIFT_JIS, &mut re_encoded, RoundTrip::Validate)?;
97
98 assert_eq!(re_encoded.as_bstr(), input, "this should be round-trippable too");
99 Ok(())
100 }
101}