summaryrefslogtreecommitdiffstats
path: root/vendor/gix-pathspec/tests
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-pathspec/tests')
-rw-r--r--vendor/gix-pathspec/tests/defaults.rs95
-rw-r--r--vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline.tar.xzbin0 -> 16928 bytes
-rw-r--r--vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_dirs.tar.xzbin0 -> 16028 bytes
-rw-r--r--vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_files.tar.xzbin0 -> 10700 bytes
-rw-r--r--vendor/gix-pathspec/tests/fixtures/generated-archives/parse_baseline.tar.xzbin0 -> 9528 bytes
-rw-r--r--vendor/gix-pathspec/tests/fixtures/match_baseline_dirs.sh97
-rw-r--r--vendor/gix-pathspec/tests/fixtures/match_baseline_files.sh94
-rwxr-xr-xvendor/gix-pathspec/tests/fixtures/parse_baseline.sh158
-rw-r--r--vendor/gix-pathspec/tests/normalize/mod.rs116
-rw-r--r--vendor/gix-pathspec/tests/parse/invalid.rs152
-rw-r--r--vendor/gix-pathspec/tests/parse/mod.rs93
-rw-r--r--vendor/gix-pathspec/tests/parse/valid.rs359
-rw-r--r--vendor/gix-pathspec/tests/pathspec.rs5
-rw-r--r--vendor/gix-pathspec/tests/search/mod.rs281
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
new file mode 100644
index 000000000..2121b5579
--- /dev/null
+++ b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline.tar.xz
Binary files differ
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
new file mode 100644
index 000000000..158b91b11
--- /dev/null
+++ b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_dirs.tar.xz
Binary files differ
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
new file mode 100644
index 000000000..3cd83f3fc
--- /dev/null
+++ b/vendor/gix-pathspec/tests/fixtures/generated-archives/match_baseline_files.tar.xz
Binary files differ
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
new file mode 100644
index 000000000..1b2ffb32c
--- /dev/null
+++ b/vendor/gix-pathspec/tests/fixtures/generated-archives/parse_baseline.tar.xz
Binary files differ
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))
+ }
+}