diff options
Diffstat (limited to 'vendor/gix-pathspec/tests')
14 files changed, 1450 insertions, 0 deletions
diff --git a/vendor/gix-pathspec/tests/defaults.rs b/vendor/gix-pathspec/tests/defaults.rs new file mode 100644 index 000000000..a30cbb7af --- /dev/null +++ b/vendor/gix-pathspec/tests/defaults.rs @@ -0,0 +1,95 @@ +use gix_pathspec::{Defaults, MagicSignature, SearchMode}; +use serial_test::serial; + +#[test] +#[serial] +fn literal_only_combines_with_icase() -> gix_testtools::Result { + { + let _env = gix_testtools::Env::new() + .set("GIT_LITERAL_PATHSPECS", "true") + .set("GIT_ICASE_PATHSPECS", "1") + .set("GIT_NOGLOB_PATHSPECS", "yes"); + assert_eq!( + Defaults::from_environment(&mut |n| std::env::var_os(n))?, + Defaults { + signature: MagicSignature::ICASE, + search_mode: SearchMode::Literal, + literal: true, + } + ); + } + { + let _env = gix_testtools::Env::new() + .set("GIT_LITERAL_PATHSPECS", "true") + .set("GIT_ICASE_PATHSPECS", "false") + .set("GIT_GLOB_PATHSPECS", "yes"); + assert_eq!( + Defaults::from_environment(&mut |n| std::env::var_os(n))?, + Defaults { + signature: MagicSignature::default(), + search_mode: SearchMode::Literal, + literal: true, + } + ); + } + Ok(()) +} +#[test] +#[serial] +fn nothing_is_set_then_it_is_like_the_default_impl() -> gix_testtools::Result { + assert_eq!( + Defaults::from_environment(&mut |n| std::env::var_os(n))?, + Defaults::default() + ); + Ok(()) +} + +#[test] +#[serial] +fn glob_and_noglob_cause_error() -> gix_testtools::Result { + let _env = gix_testtools::Env::new() + .set("GIT_GLOB_PATHSPECS", "1") + .set("GIT_NOGLOB_PATHSPECS", "yes"); + assert_eq!( + Defaults::from_environment(&mut |n| std::env::var_os(n)) + .unwrap_err() + .to_string(), + "Glob and no-glob settings are mutually exclusive" + ); + + Ok(()) +} + +#[test] +#[serial] +fn noglob_works() -> gix_testtools::Result { + let _env = gix_testtools::Env::new() + .set("GIT_GLOB_PATHSPECS", "0") + .set("GIT_NOGLOB_PATHSPECS", "true"); + assert_eq!( + Defaults::from_environment(&mut |n| std::env::var_os(n))?, + Defaults { + signature: MagicSignature::default(), + search_mode: SearchMode::Literal, + literal: false, + }, + "it's OK to set only one of them, and only no-glob has an interesting, non-default effect" + ); + Ok(()) +} + +#[test] +#[serial] +fn glob_works() -> gix_testtools::Result { + let _env = gix_testtools::Env::new().set("GIT_GLOB_PATHSPECS", "yes"); + assert_eq!( + Defaults::from_environment(&mut |n| std::env::var_os(n))?, + Defaults { + signature: MagicSignature::default(), + search_mode: SearchMode::PathAwareGlob, + literal: false, + }, + "docs here are strange but they mean to use pathaware globbing when this variable is set" + ); + Ok(()) +} diff --git a/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline.tar.xz b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline.tar.xz Binary files differnew file mode 100644 index 000000000..2121b5579 --- /dev/null +++ b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline.tar.xz diff --git a/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_dirs.tar.xz b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_dirs.tar.xz Binary files differnew file mode 100644 index 000000000..158b91b11 --- /dev/null +++ b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_dirs.tar.xz diff --git a/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_files.tar.xz b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_files.tar.xz Binary files differnew file mode 100644 index 000000000..3cd83f3fc --- /dev/null +++ b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_files.tar.xz diff --git a/vendor/gix-pathspec/tests/fixtures/generated-archives/parse_baseline.tar.xz b/vendor/gix-pathspec/tests/fixtures/generated-archives/parse_baseline.tar.xz Binary files differnew file mode 100644 index 000000000..1b2ffb32c --- /dev/null +++ b/vendor/gix-pathspec/tests/fixtures/generated-archives/parse_baseline.tar.xz diff --git a/vendor/gix-pathspec/tests/fixtures/match_baseline_dirs.sh b/vendor/gix-pathspec/tests/fixtures/match_baseline_dirs.sh new file mode 100644 index 000000000..37af54e36 --- /dev/null +++ b/vendor/gix-pathspec/tests/fixtures/match_baseline_dirs.sh @@ -0,0 +1,97 @@ +#!/bin/bash +set -eu -o pipefail + +git init; +git init sub +(cd sub + : >empty + git add empty + git commit -m init-for-submodule +) + +git init parent +(cd parent + function baseline() { + local args="" + local specs="" + + for arg in "$@"; do + if [[ $arg == *"+"* ]]; then + echo "BUG: Argument '$arg' contains a space - we use that for separating pathspecs right now" >&2 + exit 1 + fi + args="$args -c submodule.active=$arg" + specs="${specs}+${arg}" + done + + { + echo "$specs" + git $args submodule + echo -n ';' + } >> baseline.git + } + + cat <<EOF >.gitattributes +bb bb-file +bb/ bb-dir +/bb/ bb-dir-from-top +EOF + + for p in a bb dir/b dir/bb dir/nested/c cc; do + git submodule add ../sub $p + git config --unset submodule.$p.active + done + git commit -m "init" + + git submodule > paths + + baseline ':' + baseline ':!' + baseline 'a' + baseline ':(attr:bb-file)' + # :(attr:bb-dir) - ["bb", "dir/bb"] == [] + baseline ':(attr:bb-dir)' git-inconsistency # bb/ matches recursively, git doesn't get it + # :(attr:bb-dir-from-top) - ["bb"] == [] + baseline ':(attr:bb-dir-from-top)' git-inconsistency # probably git doesn't really care about correctness here + baseline ':(icase)A' + baseline ':(icase,exclude)A' + baseline ':(icase,exclude)*/B*' + baseline ':(icase,exclude)*/B?' + baseline 'di' + baseline 'di?' + baseline 'di?/' + baseline 'dir*' + baseline 'dir/*' + baseline ':(glob)dir*' + baseline ':(glob,icase,exclude)dir*' + baseline ':(glob)dir/*' + baseline 'dir' + baseline 'dir/' + baseline ':(literal)dir' + baseline ':(literal)dir/' + baseline 'dir/nested' + baseline 'dir/nested/' + baseline ':(exclude)dir/' + baseline ':(icase)DIR' + baseline ':(icase)DIR/' + baseline ':!a' + baseline ':' ':!bb' + baseline ':!bb' + baseline 'a/' + baseline 'bb' + baseline 'dir/b' + baseline 'dir/b/' + # ["dir/b"] == [] + baseline '*/b/' git-inconsistency + baseline '*b' + baseline '*b*' + baseline '*b?' + baseline '*/b' + baseline '*/b?' + baseline '*/b*' + baseline '*c' + baseline '*/c' + baseline ':(glob)**/c' + baseline ':(glob)**/c?' + baseline ':(glob)**/c*' +) diff --git a/vendor/gix-pathspec/tests/fixtures/match_baseline_files.sh b/vendor/gix-pathspec/tests/fixtures/match_baseline_files.sh new file mode 100644 index 000000000..b8422ca11 --- /dev/null +++ b/vendor/gix-pathspec/tests/fixtures/match_baseline_files.sh @@ -0,0 +1,94 @@ +#!/bin/bash +set -eu -o pipefail + +git init; + +function baseline() { + local specs="" + for arg in "$@"; do + if [[ $arg == *"+"* ]]; then + echo "BUG: Argument '$arg' contains a space - we use that for separating pathspecs right now" >&2 + exit 1 + fi + specs="${specs}+${arg}" + done + + { + echo "$specs" + git ls-files "$@" + echo -n ';' + } >> baseline.git +} + +: >goo +: >'g[o][o]' +: >bar +echo 'goo a !b c=v -d' > .gitattributes +mkdir sub && :>sub/bar +git add . && git commit -m init +# this is to avoid issues on windows, which might not be able to manifest these files. +git -c core.protectNTFS=false update-index --add --cacheinfo 100644 "$(git rev-parse HEAD:goo)" "g*" +git update-index --add --cacheinfo 100644 "$(git rev-parse HEAD:goo)" "!a" +for p in bar bAr BAR foo/bar foo/bAr foo/BAR fOo/bar fOo/bAr fOo/BAR FOO/bar FOO/bAr FOO/BAR; do + git -c core.ignoreCase=false update-index --add --cacheinfo 100644 "$(git rev-parse HEAD:goo)" "$p" +done +git -c core.ignoreCase=false update-index --add --cacheinfo 100644 "$(git rev-parse HEAD:goo)" " " # 4 x space +git -c core.ignoreCase=false update-index --add --cacheinfo 100644 "$(git rev-parse HEAD:goo)" " hi " # 2 x space hi 2 x space + +git ls-files > paths + +baseline ':(attr:a)goo' +baseline ':(attr:a)Goo' +baseline ':(icase,attr:a)Goo' +baseline ':(attr:!b)goo' +baseline ':(attr:c=v)goo' +baseline ':(attr:-d)goo' +baseline ':(attr:a !b c=v -d)goo' +baseline ':(icase,attr:a !b c=v -d)GOO' +baseline ':(attr:a !b c=v -d)g*' +baseline ':(attr:none)goo' +baseline ':(literal)g*' +baseline 'sub/' +baseline 'sub' +baseline 'sub/*' +baseline 'sub*' +baseline ':(literal)g*' +baseline ':(glob)g*' +baseline ':(exclude,literal)g*' +baseline 'g*' +baseline ':(exclude)g*' +baseline ':(literal)?*' +baseline ':(exclude,literal)?*' +baseline '?*' +baseline ':(exclude)?*' +baseline 'g[o][o]' +# ["g[o][o]", "goo"] == ["g[o][o]"] +baseline ':(icase)G[O][o]' git-inconsistency +baseline ':(literal)g[o][o]' +baseline ':(literal,icase)G[o][O]' +baseline ':(glob)g[o][o]' +# ["g[o][o]", "goo"] == ["g[o][o]"] +baseline ':(glob,icase)g[o][O]' git-inconsistency +baseline ':(glob)**/bar' +baseline ':(literal)**/bar' +baseline '**/bar' +baseline '*/bar' +baseline ':(glob)*bar' +baseline ':(glob)**bar' +baseline '*bar' +baseline '*bar*' +baseline 'bar' +baseline 'bar/' +baseline 'sub/bar/' +baseline 'sub/bar' +baseline '!a' +baseline '?a' +baseline 'foo/' +baseline 'foo' +baseline 'foo/*' +baseline 'foo*' +baseline ':(icase)foo/' +baseline ':(icase)foo' +baseline ':(icase)foo/*' +baseline ':(icase)foo*' +baseline ':(icase)foo/bar' diff --git a/vendor/gix-pathspec/tests/fixtures/parse_baseline.sh b/vendor/gix-pathspec/tests/fixtures/parse_baseline.sh new file mode 100755 index 000000000..56525e9eb --- /dev/null +++ b/vendor/gix-pathspec/tests/fixtures/parse_baseline.sh @@ -0,0 +1,158 @@ +#!/bin/bash +set -eu -o pipefail + +git init; + +function baseline() { + local pathspec=$1 # first argument is the pathspec to test + + git ls-files "$pathspec" && status=0 || status=$? + { + echo "$pathspec" + echo "$status" + } >> baseline.git +} + +# success + +# special 'there is no pathspec' spec +baseline ':' + +# repeated_matcher_keywords +baseline ':(glob,glob)' +baseline ':(literal,literal)' +baseline ':(top,top)' +baseline ':(icase,icase)' +baseline ':(attr,attr)' +baseline ':!^(exclude,exclude)' + +# empty_signatures +baseline '.' +baseline ':' +baseline 'some/path' +baseline ':some/path' +baseline ':()some/path' +baseline '::some/path' +baseline ':::some/path' +baseline ':():some/path' + +# whitespace_in_pathspec +baseline ' some/path' +baseline 'some/ path' +baseline 'some/path ' +baseline ': some/path' +baseline ': !some/path' +baseline ': :some/path' +baseline ': ()some/path' +baseline ':! some/path' + +# short_signatures +baseline ':/some/path' +baseline '://some/path' +baseline ':^some/path' +baseline ':^^some/path' +baseline ':!some/path' +baseline ':!!some/path' +baseline ':/!some/path' +baseline ':!/^/:some/path' + +# signatures_and_searchmodes +baseline ':(top)' +baseline ':(icase)' +baseline ':(attr)' +baseline ':(exclude)' +baseline ':(literal)' +baseline ':(glob)' +baseline ':(top,exclude)' +baseline ':(icase,literal)' +baseline ':!(literal)some/*path' +baseline ':(top,literal,icase,attr,exclude)some/path' +baseline ':(top,glob,icase,attr,exclude)some/path' + +# attributes_in_signature +baseline ':(attr:someAttr)' +baseline ':(attr:!someAttr)' +baseline ':(attr:-someAttr)' +baseline ':(attr:someAttr=value)' +baseline ':(attr:a=one b=)' +baseline ':(attr:a= b=two)' +baseline ':(attr:a=one b=two)' +baseline ':(attr:a=one b=two)' +baseline ':(attr:someAttr anotherAttr)' + +# attributes_with_escape_chars_in_state_values +baseline ':(attr:v=one\-)' +baseline ':(attr:v=one\_)' +baseline ':(attr:v=one\,)' +baseline ':(attr:v=one\,two\,three)' +baseline ':(attr:a=\d b= c=\d)' + +# failing + +#empty_input +baseline "" + +# invalid_short_signatures +baseline ':"()' +baseline ':#()' +baseline ':%()' +baseline ':&()' +baseline ":'()" +baseline ':,()' +baseline ':-()' +baseline ':;()' +baseline ':<()' +baseline ':=()' +baseline ':>()' +baseline ':@()' +baseline ':_()' +baseline ':`()' +baseline ':~()' + +# invalid_keywords +baseline ':( )some/path' +baseline ':(tp)some/path' +baseline ':(top, exclude)some/path' +baseline ':(top,exclude,icse)some/path' + +# invalid_attributes +baseline ':(attr:+invalidAttr)some/path' +baseline ':(attr:validAttr +invalidAttr)some/path' +baseline ':(attr:+invalidAttr,attr:valid)some/path' +baseline ':(attr:inva\lid)some/path' + +# invalid_attribute_values +baseline ':(attr:v=inva#lid)some/path' +baseline ':(attr:v=inva\\lid)some/path' +baseline ':(attr:v=invalid\\)some/path' +baseline ':(attr:v=invalid\#)some/path' +baseline ':(attr:v=inva\=lid)some/path' +baseline ':(attr:a=valid b=inva\#lid)some/path' +baseline ':(attr:v=val��)' +baseline ':(attr:pr=pre��x:,)�' + +# escape_character_at_end_of_attribute_value +baseline ':(attr:v=invalid\)some/path' +baseline ':(attr:v=invalid\ )some/path' +baseline ':(attr:v=invalid\ valid)some/path' + +# empty_attribute_specification +baseline ':(attr:)' + +# multiple_attribute_specifications +baseline ':(attr:one,attr:two)some/path' + +# missing_parentheses +baseline ':(top' + +# glob_and_literal_keywords_present +baseline ':(glob,literal)some/path' +# trailing slash +baseline ':(glob,literal)some/path/' +baseline 'some/path/' +baseline 'path/' + +baseline 'a/b/' +baseline 'a/' +baseline '!a' +baseline '\!a' diff --git a/vendor/gix-pathspec/tests/normalize/mod.rs b/vendor/gix-pathspec/tests/normalize/mod.rs new file mode 100644 index 000000000..2fe54f664 --- /dev/null +++ b/vendor/gix-pathspec/tests/normalize/mod.rs @@ -0,0 +1,116 @@ +use std::path::Path; + +#[test] +fn removes_relative_path_components() -> crate::Result { + for (input_path, expected_path, expected_prefix) in [ + ("c", "a/b/c", "a/b"), + ("../c", "a/c", "a"), + ("../b/c", "a/b/c", "a"), // this is a feature - prefix components once consumed by .. are lost. Important as paths can contain globs + ("../*c/d", "a/*c/d", "a"), + ("../../c/d", "c/d", ""), + ("../../c/d/", "c/d", ""), + ("./c", "a/b/c", "a/b"), + ("../../c", "c", ""), + ("../..", ".", ""), + ("../././c", "a/c", "a"), + ("././/./c", "a/b/c", "a/b"), + ("././/./c/", "a/b/c", "a/b"), + ("././/./../c/d/", "a/c/d", "a"), + ] { + let spec = normalized_spec(input_path, "a/b", "")?; + assert_eq!(spec.path(), expected_path); + assert_eq!( + spec.prefix_directory(), + expected_prefix, + "{input_path} -> {expected_path}" + ); + } + Ok(()) +} + +#[test] +fn single_dot_is_special_and_directory_is_implied_without_trailing_slash() -> crate::Result { + for (input_path, expected) in [(".", "."), ("./", ".")] { + let spec = normalized_spec(input_path, "", "/repo")?; + assert_eq!(spec.path(), expected); + assert_eq!(spec.prefix_directory(), ""); + } + Ok(()) +} + +#[test] +fn absolute_path_made_relative() -> crate::Result { + for (input_path, expected, prefix_dir) in [ + ("/repo/a", "a", ""), + ("/repo/a/..//.///b", "b", ""), + ("/repo/a/", "a", "a"), + ("/repo/*/", "*", "*"), + ("/repo/a/b", "a/b", "a"), + ("/repo/*/b", "*/b", "*"), // we assume literal paths if specs are absolute + ("/repo/a/*/", "a/*", "a/*"), + ("/repo/a/b/", "a/b", "a/b"), + ("/repo/a/b/*", "a/b/*", "a/b"), + ("/repo/a/b/c/..", "a/b", "a"), + ] { + let spec = normalized_spec(input_path, "", "/repo")?; + assert_eq!(spec.path(), expected); + assert_eq!(spec.prefix_directory(), prefix_dir, "{input_path}"); + } + Ok(()) +} + +#[test] +fn relative_top_patterns_ignore_the_prefix() -> crate::Result { + let spec = normalized_spec(":(top)c", "a/b", "")?; + assert_eq!(spec.path(), "c"); + assert_eq!(spec.prefix_directory(), ""); + Ok(()) +} + +#[test] +fn absolute_top_patterns_ignore_the_prefix_but_are_made_relative() -> crate::Result { + let spec = normalized_spec(":(top)/a/b", "prefix-ignored", "/a")?; + assert_eq!(spec.path(), "b"); + assert_eq!(spec.prefix_directory(), ""); + Ok(()) +} + +#[test] +fn relative_path_breaks_out_of_working_tree() { + let err = normalized_spec("../a", "", "").unwrap_err(); + assert_eq!(err.to_string(), "The path '../a' leaves the repository"); + let err = normalized_spec("../../b", "a", "").unwrap_err(); + assert_eq!( + err.to_string(), + format!( + "The path '{}' leaves the repository", + if cfg!(windows) { "a\\../../b" } else { "a/../../b" } + ) + ); +} + +#[test] +fn absolute_path_breaks_out_of_working_tree() { + let err = normalized_spec("/path/to/repo/..///./a", "", "/path/to/repo").unwrap_err(); + assert_eq!(err.to_string(), "The path '..///./a' leaves the repository"); + let err = normalized_spec("/path/to/repo/../../../dev", "", "/path/to/repo").unwrap_err(); + assert_eq!(err.to_string(), "The path '../../../dev' leaves the repository"); +} + +#[test] +fn absolute_path_escapes_worktree() { + assert_eq!( + normalized_spec("/dev", "", "/path/to/repo").unwrap_err().to_string(), + "The path '/dev' is not inside of the worktree '/path/to/repo'" + ); +} + +fn normalized_spec( + path: &str, + prefix: &str, + root: &str, +) -> Result<gix_pathspec::Pattern, gix_pathspec::normalize::Error> { + let mut spec = gix_pathspec::parse(path.as_bytes(), Default::default()).expect("valid"); + spec.normalize(Path::new(prefix), Path::new(root))?; + Ok(spec) +} diff --git a/vendor/gix-pathspec/tests/parse/invalid.rs b/vendor/gix-pathspec/tests/parse/invalid.rs new file mode 100644 index 000000000..d080f4afc --- /dev/null +++ b/vendor/gix-pathspec/tests/parse/invalid.rs @@ -0,0 +1,152 @@ +use gix_pathspec::parse::Error; + +use crate::parse::check_against_baseline; + +#[test] +fn empty_input() { + let input = ""; + + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::EmptyString)); +} + +#[test] +fn invalid_short_signatures() { + let inputs = vec![ + ":\"()", ":#()", ":%()", ":&()", ":'()", ":,()", ":-()", ":;()", ":<()", ":=()", ":>()", ":@()", ":_()", + ":`()", ":~()", + ]; + + for input in inputs.into_iter() { + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::Unimplemented { .. })); + } +} + +#[test] +fn invalid_keywords() { + let inputs = vec![ + ":( )some/path", + ":(tp)some/path", + ":(top, exclude)some/path", + ":(top,exclude,icse)some/path", + ]; + + for input in inputs.into_iter() { + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); + } +} + +#[test] +fn invalid_attributes() { + let inputs = vec![ + ":(attr:+invalidAttr)some/path", + ":(attr:validAttr +invalidAttr)some/path", + ":(attr:+invalidAttr,attr:valid)some/path", + r":(attr:inva\lid)some/path", + ]; + + for input in inputs { + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err(), "This pathspec did not produce an error {input}"); + assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + } +} + +#[test] +fn invalid_attribute_values() { + let inputs = vec![ + r":(attr:v=inva#lid)some/path", + r":(attr:v=inva\\lid)some/path", + r":(attr:v=invalid\\)some/path", + r":(attr:v=invalid\#)some/path", + r":(attr:v=inva\=lid)some/path", + r":(attr:a=valid b=inva\#lid)some/path", + ":(attr:v=val��)", + ":(attr:pr=pre��x:,)�", + ]; + + for input in inputs { + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err(), "This pathspec did not produce an error {input}"); + assert!( + matches!(output.unwrap_err(), Error::InvalidAttributeValue { .. }), + "Errors did not match for pathspec: {input}" + ); + } +} + +#[test] +fn escape_character_at_end_of_attribute_value() { + let inputs = vec![ + r":(attr:v=invalid\)some/path", + r":(attr:v=invalid\ )some/path", + r":(attr:v=invalid\ valid)some/path", + ]; + + for input in inputs { + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err(), "This pathspec did not produce an error {input}"); + assert!(matches!(output.unwrap_err(), Error::TrailingEscapeCharacter)); + } +} + +#[test] +fn empty_attribute_specification() { + let input = ":(attr:)"; + + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::EmptyAttribute)); +} + +#[test] +fn multiple_attribute_specifications() { + let input = ":(attr:one,attr:two)some/path"; + + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MultipleAttributeSpecifications)); +} + +#[test] +fn missing_parentheses() { + let input = ":(top"; + + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); +} + +#[test] +fn glob_and_literal_keywords_present() { + let input = ":(glob,literal)some/path"; + + assert!(!check_against_baseline(input), "This pathspec is valid in git: {input}"); + + let output = gix_pathspec::parse(input.as_bytes(), Default::default()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); +} diff --git a/vendor/gix-pathspec/tests/parse/mod.rs b/vendor/gix-pathspec/tests/parse/mod.rs new file mode 100644 index 000000000..6ee363d6b --- /dev/null +++ b/vendor/gix-pathspec/tests/parse/mod.rs @@ -0,0 +1,93 @@ +use std::collections::HashMap; + +use bstr::{BStr, BString, ByteSlice}; +use gix_attributes::State; +use gix_pathspec::{MagicSignature, Pattern, SearchMode}; +use once_cell::sync::Lazy; + +#[test] +fn baseline() { + for (pattern, exit_code) in BASELINE.iter() { + let res = gix_pathspec::parse(pattern, Default::default()); + assert_eq!( + res.is_ok(), + *exit_code == 0, + "{pattern:?} disagrees with baseline: {res:?}" + ); + if let Ok(pat) = res { + let actual = pat.to_bstring(); + assert_eq!( + pat, + gix_pathspec::parse(actual.as_ref(), Default::default()).expect("still valid"), + "{pattern} != {actual}: display must roundtrip into actual pattern" + ); + } + let p = gix_pathspec::Pattern::from_literal(pattern, Default::default()); + assert!(matches!(p.search_mode, SearchMode::Literal)); + } +} + +mod invalid; +mod valid; + +/// A way to specify expectations more easily by simplifying assignments. +#[derive(Debug, Clone, PartialEq, Eq)] +struct NormalizedPattern { + path: BString, + signature: MagicSignature, + search_mode: SearchMode, + attributes: Vec<(BString, State)>, +} + +impl From<Pattern> for NormalizedPattern { + fn from(p: Pattern) -> Self { + NormalizedPattern { + path: p.path().to_owned(), + signature: p.signature, + search_mode: p.search_mode, + attributes: p + .attributes + .into_iter() + .map(|attr| (attr.name.as_str().into(), attr.state)) + .collect(), + } + } +} + +static BASELINE: Lazy<HashMap<BString, usize>> = Lazy::new(|| { + let base = gix_testtools::scripted_fixture_read_only("parse_baseline.sh").unwrap(); + + (|| -> crate::Result<_> { + let mut map = HashMap::new(); + let baseline = std::fs::read(base.join("baseline.git"))?; + let mut lines = baseline.lines(); + while let Some(spec) = lines.next() { + let exit_code = lines.next().expect("two lines per baseline").to_str()?.parse()?; + map.insert(spec.into(), exit_code); + } + Ok(map) + })() + .unwrap() +}); + +fn check_valid_inputs<'a>(inputs: impl IntoIterator<Item = (&'a str, NormalizedPattern)>) { + for (input, expected) in inputs.into_iter() { + assert!( + check_against_baseline(input), + "This pathspec is invalid in git: {input}" + ); + + let pattern = gix_pathspec::parse(input.as_bytes(), Default::default()) + .unwrap_or_else(|_| panic!("parsing should not fail with pathspec {input}")); + let pattern: NormalizedPattern = pattern.into(); + assert_eq!(pattern, expected, "while checking input: \"{input}\""); + } +} + +fn check_against_baseline(pathspec: &str) -> bool { + let key: &BStr = pathspec.into(); + let base = BASELINE + .get(key) + .unwrap_or_else(|| panic!("missing baseline for pathspec: {pathspec:?}")); + *base == 0 +} diff --git a/vendor/gix-pathspec/tests/parse/valid.rs b/vendor/gix-pathspec/tests/parse/valid.rs new file mode 100644 index 000000000..ce0b05d88 --- /dev/null +++ b/vendor/gix-pathspec/tests/parse/valid.rs @@ -0,0 +1,359 @@ +use gix_attributes::State; +use gix_pathspec::{MagicSignature, SearchMode}; + +use crate::parse::{check_against_baseline, check_valid_inputs, NormalizedPattern}; + +#[test] +fn repeated_matcher_keywords() { + let input = vec![ + (":(glob,glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), + (":(literal,literal)", pat_with_search_mode(SearchMode::Literal)), + (":(top,top)", pat_with_sig(MagicSignature::TOP)), + (":(icase,icase)", pat_with_sig(MagicSignature::ICASE)), + (":(attr,attr)", pat_with_attrs(vec![])), + (":!^(exclude,exclude)", pat_with_sig(MagicSignature::EXCLUDE)), + ]; + + check_valid_inputs(input); +} + +#[test] +fn glob_negations_are_always_literal() { + check_valid_inputs([("!a", pat_with_path("!a")), ("\\!a", pat_with_path("\\!a"))]); +} + +#[test] +fn literal_default_prevents_parsing() { + let pattern = gix_pathspec::parse( + ":".as_bytes(), + gix_pathspec::Defaults { + signature: MagicSignature::EXCLUDE, + search_mode: SearchMode::PathAwareGlob, + literal: true, + }, + ) + .expect("valid"); + assert!(!pattern.is_nil()); + assert_eq!(pattern.path(), ":"); + assert!(matches!(pattern.search_mode, SearchMode::Literal)); + + let input = ":(literal)f[o][o]"; + let pattern = gix_pathspec::parse( + input.as_bytes(), + gix_pathspec::Defaults { + signature: MagicSignature::TOP, + search_mode: SearchMode::Literal, + literal: true, + }, + ) + .expect("valid"); + assert_eq!(pattern.path(), input, "no parsing happens at all"); + assert!(matches!(pattern.search_mode, SearchMode::Literal)); + + let pattern = gix_pathspec::parse( + input.as_bytes(), + gix_pathspec::Defaults { + signature: MagicSignature::TOP, + search_mode: SearchMode::Literal, + literal: false, + }, + ) + .expect("valid"); + assert_eq!(pattern.path(), "f[o][o]", "in literal default mode, we still parse"); + assert!(matches!(pattern.search_mode, SearchMode::Literal)); +} + +#[test] +fn there_is_no_pathspec_pathspec() { + check_against_baseline(":"); + let pattern = gix_pathspec::parse(":".as_bytes(), Default::default()).expect("valid"); + assert!(pattern.is_nil()); + + let actual: NormalizedPattern = pattern.into(); + assert_eq!(actual, pat_with_path("")); + + let pattern = gix_pathspec::parse( + ":".as_bytes(), + gix_pathspec::Defaults { + signature: MagicSignature::EXCLUDE, + search_mode: SearchMode::PathAwareGlob, + literal: false, + }, + ) + .expect("valid"); + assert!(pattern.is_nil()); +} + +#[test] +fn defaults_are_used() -> crate::Result { + let defaults = gix_pathspec::Defaults { + signature: MagicSignature::EXCLUDE, + search_mode: SearchMode::Literal, + literal: false, + }; + let p = gix_pathspec::parse(".".as_bytes(), defaults)?; + assert_eq!(p.path(), "."); + assert_eq!(p.signature, defaults.signature); + assert_eq!(p.search_mode, defaults.search_mode); + assert!(p.attributes.is_empty()); + assert!(!p.is_nil()); + Ok(()) +} + +#[test] +fn literal_from_defaults_is_overridden_by_element_glob() -> crate::Result { + let defaults = gix_pathspec::Defaults { + search_mode: SearchMode::Literal, + ..Default::default() + }; + let p = gix_pathspec::parse(":(glob)*override".as_bytes(), defaults)?; + assert_eq!(p.path(), "*override"); + assert_eq!(p.signature, MagicSignature::default()); + assert_eq!(p.search_mode, SearchMode::PathAwareGlob, "this is the element override"); + assert!(p.attributes.is_empty()); + assert!(!p.is_nil()); + Ok(()) +} + +#[test] +fn glob_from_defaults_is_overridden_by_element_glob() -> crate::Result { + let defaults = gix_pathspec::Defaults { + search_mode: SearchMode::PathAwareGlob, + ..Default::default() + }; + let p = gix_pathspec::parse(":(literal)*override".as_bytes(), defaults)?; + assert_eq!(p.path(), "*override"); + assert_eq!(p.signature, MagicSignature::default()); + assert_eq!(p.search_mode, SearchMode::Literal, "this is the element override"); + assert!(p.attributes.is_empty()); + assert!(!p.is_nil()); + Ok(()) +} + +#[test] +fn empty_signatures() { + let inputs = vec![ + (".", pat_with_path(".")), + ("some/path", pat_with_path("some/path")), + (":some/path", pat_with_path("some/path")), + (":()some/path", pat_with_path("some/path")), + ("::some/path", pat_with_path("some/path")), + (":::some/path", pat_with_path(":some/path")), + (":():some/path", pat_with_path(":some/path")), + ]; + + check_valid_inputs(inputs) +} + +#[test] +fn whitespace_in_pathspec() { + let inputs = vec![ + (" some/path", pat_with_path(" some/path")), + ("some/ path", pat_with_path("some/ path")), + ("some/path ", pat_with_path("some/path ")), + (": some/path", pat_with_path(" some/path")), + (": !some/path", pat_with_path(" !some/path")), + (": :some/path", pat_with_path(" :some/path")), + (": ()some/path", pat_with_path(" ()some/path")), + ( + ":! some/path", + pat_with_path_and_sig(" some/path", MagicSignature::EXCLUDE), + ), + ( + ":!!some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ]; + + check_valid_inputs(inputs) +} + +#[test] +fn short_signatures() { + let inputs = vec![ + (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), + ( + ":^some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":!some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":/!some/path", + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ( + ":!/^/:some/path", + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ]; + + check_valid_inputs(inputs) +} + +#[test] +fn trailing_slash_is_turned_into_magic_signature_and_removed() { + check_valid_inputs([ + ("a/b/", pat_with_path_and_sig("a/b", MagicSignature::MUST_BE_DIR)), + ("a/", pat_with_path_and_sig("a", MagicSignature::MUST_BE_DIR)), + ]); +} + +#[test] +fn signatures_and_searchmodes() { + let inputs = vec![ + (":(top)", pat_with_sig(MagicSignature::TOP)), + (":(icase)", pat_with_sig(MagicSignature::ICASE)), + (":(attr)", pat_with_path("")), + (":(exclude)", pat_with_sig(MagicSignature::EXCLUDE)), + (":(literal)", pat_with_search_mode(SearchMode::Literal)), + (":(glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), + ( + ":(top,exclude)", + pat_with_sig(MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ( + ":(icase,literal)", + pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), + ), + ( + ":!(literal)some/*path", + pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), + ), + ( + ":(top,literal,icase,attr,exclude)some/path", + pat( + "some/path", + MagicSignature::TOP | MagicSignature::EXCLUDE | MagicSignature::ICASE, + SearchMode::Literal, + vec![], + ), + ), + ( + ":(top,glob,icase,attr,exclude)some/path", + pat( + "some/path", + MagicSignature::TOP | MagicSignature::EXCLUDE | MagicSignature::ICASE, + SearchMode::PathAwareGlob, + vec![], + ), + ), + ]; + + check_valid_inputs(inputs); +} + +#[test] +fn attributes_in_signature() { + let inputs = vec![ + (":(attr:someAttr)", pat_with_attrs(vec![("someAttr", State::Set)])), + ( + ":(attr:!someAttr)", + pat_with_attrs(vec![("someAttr", State::Unspecified)]), + ), + (":(attr:-someAttr)", pat_with_attrs(vec![("someAttr", State::Unset)])), + ( + ":(attr:someAttr=value)", + pat_with_attrs(vec![("someAttr", State::Value("value".into()))]), + ), + ( + ":(attr:a=one b=)", + pat_with_attrs(vec![("a", State::Value("one".into())), ("b", State::Value("".into()))]), + ), + ( + ":(attr:a= b=two)", + pat_with_attrs(vec![("a", State::Value("".into())), ("b", State::Value("two".into()))]), + ), + ( + ":(attr:a=one b=two)", + pat_with_attrs(vec![ + ("a", State::Value("one".into())), + ("b", State::Value("two".into())), + ]), + ), + ( + ":(attr:a=one b=two)", + pat_with_attrs(vec![ + ("a", State::Value("one".into())), + ("b", State::Value("two".into())), + ]), + ), + ( + ":(attr:someAttr anotherAttr)", + pat_with_attrs(vec![("someAttr", State::Set), ("anotherAttr", State::Set)]), + ), + ]; + + check_valid_inputs(inputs) +} + +#[test] +fn attributes_with_escape_chars_in_state_values() { + let inputs = vec![ + ( + r":(attr:v=one\-)", + pat_with_attrs(vec![("v", State::Value(r"one-".into()))]), + ), + ( + r":(attr:v=one\_)", + pat_with_attrs(vec![("v", State::Value(r"one_".into()))]), + ), + ( + r":(attr:v=one\,)", + pat_with_attrs(vec![("v", State::Value(r"one,".into()))]), + ), + ( + r":(attr:v=one\,two\,three)", + pat_with_attrs(vec![("v", State::Value(r"one,two,three".into()))]), + ), + ( + r":(attr:a=\d b= c=\d)", + pat_with_attrs(vec![ + ("a", State::Value(r"d".into())), + ("b", State::Value(r"".into())), + ("c", State::Value(r"d".into())), + ]), + ), + ]; + + check_valid_inputs(inputs) +} + +fn pat_with_path(path: &str) -> NormalizedPattern { + pat_with_path_and_sig(path, MagicSignature::empty()) +} + +fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> NormalizedPattern { + pat(path, signature, SearchMode::ShellGlob, vec![]) +} + +fn pat_with_sig(signature: MagicSignature) -> NormalizedPattern { + pat("", signature, SearchMode::ShellGlob, vec![]) +} + +fn pat_with_attrs(attrs: Vec<(&'static str, State)>) -> NormalizedPattern { + pat("", MagicSignature::empty(), SearchMode::ShellGlob, attrs) +} + +fn pat_with_search_mode(search_mode: SearchMode) -> NormalizedPattern { + pat("", MagicSignature::empty(), search_mode, vec![]) +} + +fn pat( + path: &str, + signature: MagicSignature, + search_mode: SearchMode, + attributes: Vec<(&str, State)>, +) -> NormalizedPattern { + NormalizedPattern { + path: path.into(), + signature, + search_mode, + attributes: attributes + .into_iter() + .map(|(attr, state)| (attr.into(), state)) + .collect(), + } +} diff --git a/vendor/gix-pathspec/tests/pathspec.rs b/vendor/gix-pathspec/tests/pathspec.rs new file mode 100644 index 000000000..d83fefbcb --- /dev/null +++ b/vendor/gix-pathspec/tests/pathspec.rs @@ -0,0 +1,5 @@ +pub use gix_testtools::Result; + +mod normalize; +mod parse; +mod search; diff --git a/vendor/gix-pathspec/tests/search/mod.rs b/vendor/gix-pathspec/tests/search/mod.rs new file mode 100644 index 000000000..638709c35 --- /dev/null +++ b/vendor/gix-pathspec/tests/search/mod.rs @@ -0,0 +1,281 @@ +use std::path::Path; + +#[test] +fn directories() -> crate::Result { + baseline::run("directory", true, baseline::directories) +} + +#[test] +fn no_pathspecs_match_everything() -> crate::Result { + let mut search = gix_pathspec::Search::from_specs([], None, Path::new(""))?; + assert_eq!(search.patterns().count(), 0, "nothing artificial is added"); + let m = search + .pattern_matching_relative_path("hello".into(), None, &mut |_, _, _, _| { + unreachable!("must not be called") + }) + .expect("matches"); + assert_eq!(m.pattern.prefix_directory(), "", "there is no prefix as none was given"); + + Ok(()) +} + +#[test] +fn init_with_exclude() -> crate::Result { + let search = gix_pathspec::Search::from_specs(pathspecs(&["tests/", ":!*.sh"]), None, Path::new(""))?; + assert_eq!(search.patterns().count(), 2, "nothing artificial is added"); + assert!( + search.patterns().next().expect("first of two").is_excluded(), + "re-orded so that excluded are first" + ); + assert_eq!(search.common_prefix(), "tests"); + Ok(()) +} + +#[test] +fn no_pathspecs_respect_prefix() -> crate::Result { + let mut search = gix_pathspec::Search::from_specs([], Some(Path::new("a")), Path::new(""))?; + assert_eq!( + search.patterns().count(), + 1, + "we get an artificial pattern to get the prefix" + ); + assert!( + search + .pattern_matching_relative_path("hello".into(), None, &mut |_, _, _, _| unreachable!( + "must not be called" + )) + .is_none(), + "not the right prefix" + ); + let m = search + .pattern_matching_relative_path("a/b".into(), None, &mut |_, _, _, _| unreachable!("must not be called")) + .expect("match"); + assert_eq!( + m.pattern.prefix_directory(), + "a", + "the prefix directory matched verbatim" + ); + + Ok(()) +} + +#[test] +fn prefixes_are_always_case_insensitive() -> crate::Result { + let path = gix_testtools::scripted_fixture_read_only("match_baseline_files.sh")?.join("paths"); + let items = baseline::parse_paths(path)?; + + for (spec, prefix, common_prefix, expected) in [ + (":(icase)bar", "FOO", "FOO", &["FOO/BAR", "FOO/bAr", "FOO/bar"] as &[_]), + (":(icase)bar", "F", "F", &[]), + (":(icase)bar", "FO", "FO", &[]), + (":(icase)../bar", "fOo", "", &["BAR", "bAr", "bar"]), + ("../bar", "fOo", "bar", &["bar"]), + (" ", "", " ", &[" "]), // whitespace can match verbatim + (" hi*", "", " hi", &[" hi "]), // whitespace can match with globs as well + (":(icase)../bar", "fO", "", &["BAR", "bAr", "bar"]), // prefixes are virtual, and don't have to exist at all. + ( + ":(icase)../foo/bar", + "FOO", + "", + &[ + "FOO/BAR", "FOO/bAr", "FOO/bar", "fOo/BAR", "fOo/bAr", "fOo/bar", "foo/BAR", "foo/bAr", "foo/bar", + ], + ), + ("../foo/bar", "FOO", "foo/bar", &["foo/bar"]), + ( + ":(icase)../foo/../fOo/bar", + "FOO", + "", + &[ + "FOO/BAR", "FOO/bAr", "FOO/bar", "fOo/BAR", "fOo/bAr", "fOo/bar", "foo/BAR", "foo/bAr", "foo/bar", + ], + ), + ("../foo/../fOo/BAR", "FOO", "fOo/BAR", &["fOo/BAR"]), + ] { + let mut search = gix_pathspec::Search::from_specs( + gix_pathspec::parse(spec.as_bytes(), Default::default()), + Some(Path::new(prefix)), + Path::new(""), + )?; + assert_eq!(search.common_prefix(), common_prefix, "{spec} {prefix}"); + let actual: Vec<_> = items + .iter() + .filter(|relative_path| { + search + .pattern_matching_relative_path(relative_path.as_str().into(), Some(false), &mut |_, _, _, _| false) + .is_some() + }) + .collect(); + assert_eq!(actual, expected, "{spec} {prefix}"); + } + Ok(()) +} + +#[test] +fn common_prefix() -> crate::Result { + for (specs, prefix, expected) in [ + (&["foo/bar", ":(icase)foo/bar"] as &[_], None, ""), + (&["foo/bar", "foo"], None, "foo"), + (&["foo/bar/baz", "foo/bar/"], None, "foo/bar"), // directory trailing slashes are ignored, but that prefix shouldn't care anyway + (&[":(icase)bar", ":(icase)bart"], Some("foo"), "foo"), // only case-sensitive portions count + (&["bar", "bart"], Some("foo"), "foo/bar"), // otherwise everything that matches counts + (&["bar", "bart", "ba"], Some("foo"), "foo/ba"), + ] { + let search = gix_pathspec::Search::from_specs( + specs + .iter() + .map(|s| gix_pathspec::parse(s.as_bytes(), Default::default()).expect("valid")), + prefix.map(Path::new), + Path::new(""), + )?; + assert_eq!(search.common_prefix(), expected, "{specs:?} {prefix:?}"); + } + Ok(()) +} + +#[test] +fn files() -> crate::Result { + baseline::run("file", false, baseline::files) +} + +fn pathspecs(input: &[&str]) -> Vec<gix_pathspec::Pattern> { + input + .iter() + .map(|pattern| gix_pathspec::parse(pattern.as_bytes(), Default::default()).expect("known to be valid")) + .collect() +} + +mod baseline { + use std::path::{Path, PathBuf}; + + use bstr::{BString, ByteSlice}; + + pub fn run( + name: &str, + items_are_dirs: bool, + init: impl FnOnce() -> crate::Result<(PathBuf, Vec<String>, Vec<Expected>)>, + ) -> crate::Result { + let (root, items, expected) = init()?; + let mut collection = Default::default(); + let attrs = + gix_attributes::Search::new_globals(Some(root.join(".gitattributes")), &mut Vec::new(), &mut collection)?; + let tests = expected.len(); + for expected in expected { + let mut search = gix_pathspec::Search::from_specs(expected.pathspecs, None, Path::new(""))?; + let actual: Vec<_> = items + .iter() + .filter(|path| { + search + .pattern_matching_relative_path( + path.as_str().into(), + Some(items_are_dirs), + &mut |rela_path, case, is_dir, out| { + out.initialize(&collection); + attrs.pattern_matching_relative_path(rela_path, case, Some(is_dir), out) + }, + ) + .map_or(false, |m| !m.is_excluded()) + }) + .cloned() + .collect(); + let matches_expectation = actual == expected.matches; + assert_eq!( + matches_expectation, + expected.is_consistent, + "{} - {actual:?} == {:?}", + search.patterns().map(|p| format!("{p}")).collect::<Vec<_>>().join(", "), + expected.matches + ); + } + eprintln!("{tests} {name} matches OK"); + Ok(()) + } + + #[derive(Debug)] + pub struct Expected { + pub pathspecs: Vec<gix_pathspec::Pattern>, + pub matches: Vec<String>, + /// If true, this means that the baseline is different from what we get, and that our solution is consistent with the rules. + pub is_consistent: bool, + } + + pub fn parse_paths(path: PathBuf) -> std::io::Result<Vec<String>> { + let buf = std::fs::read(path)?; + Ok(buf.lines().map(BString::from).map(|s| s.to_string()).collect()) + } + + fn parse_blocks(input: &[u8], parse_matches: impl Fn(&[u8]) -> Vec<String>) -> Vec<Expected> { + input + .split(|b| *b == b';') + .filter(|b| !b.is_empty()) + .map(move |block| { + let mut lines = block.lines(); + let mut is_inconsistent = false; + let pathspecs = lines + .next() + .expect("pathspec") + .split(|b| *b == b'+') + .filter(|spec| { + is_inconsistent = spec.as_bstr() == "git-inconsistency"; + !is_inconsistent + }) + .filter_map(|s| (!s.trim().is_empty()).then(|| s.trim())) + .map(|pathspec| gix_pathspec::parse(pathspec, Default::default()).expect("valid pathspec")) + .collect(); + Expected { + pathspecs, + matches: parse_matches(lines.as_bytes()), + is_consistent: !is_inconsistent, + } + }) + .collect() + } + + mod submodule { + use bstr::ByteSlice; + + pub fn matches_from_status(input: &[u8]) -> impl Iterator<Item = (bool, String)> + '_ { + input.lines().map(|line| { + let matches = line[0] == b' '; + assert_eq!(!matches, line[0] == b'-'); + let mut tokens = line[1..].split(|b| *b == b' ').skip(1); + let path = tokens.next().expect("path").to_str().expect("valid UTF-8"); + (matches, path.to_owned()) + }) + } + + pub fn parse_expected(input: &[u8]) -> Vec<super::Expected> { + super::parse_blocks(input, |block| { + matches_from_status(block) + .filter_map(|(matches, module_path)| matches.then_some(module_path)) + .collect() + }) + } + } + + mod files { + use bstr::{BString, ByteSlice}; + pub fn parse_expected(input: &[u8]) -> Vec<super::Expected> { + super::parse_blocks(input, |block| { + block.lines().map(BString::from).map(|s| s.to_string()).collect() + }) + } + } + + pub fn directories() -> crate::Result<(PathBuf, Vec<String>, Vec<Expected>)> { + let root = gix_testtools::scripted_fixture_read_only("match_baseline_dirs.sh")?.join("parent"); + let buf = std::fs::read(root.join("paths"))?; + let items = submodule::matches_from_status(&buf) + .map(|(_matches, path)| path) + .collect(); + let expected = submodule::parse_expected(&std::fs::read(root.join("baseline.git"))?); + Ok((root, items, expected)) + } + + pub fn files() -> crate::Result<(PathBuf, Vec<String>, Vec<Expected>)> { + let root = gix_testtools::scripted_fixture_read_only("match_baseline_files.sh")?; + let items = parse_paths(root.join("paths"))?; + let expected = files::parse_expected(&std::fs::read(root.join("baseline.git"))?); + Ok((root, items, expected)) + } +} |