mirror of
https://github.com/awfixers-stuff/src.git
synced 2026-03-26 20:46:00 +00:00
create src
This commit is contained in:
38
src-worktree/tests/Cargo.toml
Normal file
38
src-worktree/tests/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
||||
lints.workspace = true
|
||||
|
||||
[package]
|
||||
name = "src-worktree-tests"
|
||||
version = "0.0.0"
|
||||
repository = "https://github.com/GitoxideLabs/gitoxide"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "A crate for testing the src-worktree crate with feature toggles"
|
||||
authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
publish = false
|
||||
|
||||
[[test]]
|
||||
name = "integrate"
|
||||
path = "integrate.rs"
|
||||
|
||||
[features]
|
||||
src-features-parallel = ["src-features/parallel"]
|
||||
|
||||
[dev-dependencies]
|
||||
src-worktree = { path = "..", features = ["attributes"] }
|
||||
src-index = { path = "../../src-index" }
|
||||
src-fs = { path = "../../src-fs" }
|
||||
src-hash = { path = "../../src-hash", features = ["sha256"] }
|
||||
src-object = { path = "../../src-object" }
|
||||
src-glob = { path = "../../src-glob" }
|
||||
src-path = { path = "../../src-path" }
|
||||
src-attributes = { path = "../../src-attributes" }
|
||||
src-ignore = { path = "../../src-ignore" }
|
||||
src-features = { path = "../../src-features" }
|
||||
src-discover = { path = "../../src-discover" }
|
||||
|
||||
bstr = { version = "1.12.0", default-features = false }
|
||||
|
||||
src-testtools = { path = "../../tests/tools" }
|
||||
src-odb = { path = "../../src-odb" }
|
||||
symlink = "0.1.0"
|
||||
3
src-worktree/tests/fixtures/generated-archives/.gitignore
vendored
Normal file
3
src-worktree/tests/fixtures/generated-archives/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
make_ignore_and_attributes_setup.tar
|
||||
make_attributes_baseline.tar
|
||||
symlink_stack.tar
|
||||
BIN
src-worktree/tests/fixtures/generated-archives/make_special_exclude_case.tar
vendored
Normal file
BIN
src-worktree/tests/fixtures/generated-archives/make_special_exclude_case.tar
vendored
Normal file
Binary file not shown.
108
src-worktree/tests/fixtures/make_attributes_baseline.sh
Executable file
108
src-worktree/tests/fixtures/make_attributes_baseline.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu -o pipefail
|
||||
|
||||
mkdir basics;
|
||||
|
||||
function baseline() {
|
||||
{
|
||||
echo "$1"
|
||||
GIT_ATTR_NOSYSTEM=1 git -c core.attributesFile=$PWD/user.attributes check-attr -a "$1"
|
||||
echo
|
||||
} >> baseline
|
||||
|
||||
{
|
||||
echo "$1"
|
||||
GIT_ATTR_NOSYSTEM=1 git -c core.attributesFile=$PWD/user.attributes check-attr info test -- "$1"
|
||||
echo
|
||||
} >> baseline.selected
|
||||
}
|
||||
|
||||
|
||||
(cd basics
|
||||
git init
|
||||
|
||||
# based on https://github.com/git/git/blob/140b9478dad5d19543c1cb4fd293ccec228f1240/t/t0003-attributes.sh#L45
|
||||
mkdir -p a/b/d a/c b
|
||||
(
|
||||
echo "[attr]notest !test"
|
||||
echo "\" d \" test=d"
|
||||
echo " e test=e"
|
||||
echo " e\" test=e"
|
||||
echo "f test=f"
|
||||
echo "a/i test=a/i"
|
||||
echo "onoff test -test"
|
||||
echo "offon -test test"
|
||||
echo "no notest"
|
||||
echo "A/e/F test=A/e/F"
|
||||
echo "\!escaped test-escaped"
|
||||
echo "**/recursive test-double-star-slash"
|
||||
echo "a**f test-double-star-no-slash"
|
||||
echo "dir-slash/ never"
|
||||
echo "dir/** always"
|
||||
) > .gitattributes
|
||||
(
|
||||
echo "g test=a/g"
|
||||
echo "b/g test=a/b/g"
|
||||
) > a/.gitattributes
|
||||
(
|
||||
echo "h test=a/b/h"
|
||||
echo "d/* test=a/b/d/*"
|
||||
echo "d/yes notest"
|
||||
) > a/b/.gitattributes
|
||||
(
|
||||
echo "global test=global"
|
||||
echo "z/x/a global-no-wildcard-case-test"
|
||||
echo "z/x/* global-wildcard-case-test"
|
||||
) > user.attributes
|
||||
|
||||
git add . && git commit -qm c1
|
||||
(
|
||||
echo "global test=global"
|
||||
echo "* info=attributes"
|
||||
) > .git/info/attributes
|
||||
|
||||
|
||||
baseline z/x/a
|
||||
baseline Z/x/a
|
||||
baseline z/x/A
|
||||
baseline Z/X/a
|
||||
baseline Z/x/a
|
||||
baseline " d "
|
||||
baseline e
|
||||
baseline f
|
||||
baseline dir-slash
|
||||
baseline dir-slash/a
|
||||
baseline dir
|
||||
baseline dir/a
|
||||
baseline recursive
|
||||
baseline a/recursive
|
||||
baseline a/b/recursive
|
||||
baseline a/b/c/recursive
|
||||
baseline "!escaped"
|
||||
baseline af
|
||||
baseline axf
|
||||
baseline a/b/d/no
|
||||
baseline a/e/f
|
||||
baseline a/f
|
||||
baseline a/b/d/g
|
||||
baseline a/B/D/g
|
||||
baseline b/g
|
||||
baseline a/c/f
|
||||
baseline "e\""
|
||||
baseline a/i
|
||||
baseline A/b/h
|
||||
baseline A/B/D/NO
|
||||
baseline subdir/a/i
|
||||
baseline onoff
|
||||
baseline offon
|
||||
baseline no
|
||||
baseline A/e/F
|
||||
baseline a/e/F
|
||||
baseline a/e/f
|
||||
baseline a/g
|
||||
baseline a/b/g
|
||||
baseline a/b/h
|
||||
baseline a/b/d/ANY
|
||||
baseline a/b/d/yes
|
||||
baseline global
|
||||
)
|
||||
132
src-worktree/tests/fixtures/make_ignore_and_attributes_setup.sh
Executable file
132
src-worktree/tests/fixtures/make_ignore_and_attributes_setup.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu -o pipefail
|
||||
|
||||
cat <<EOF >user.exclude
|
||||
# a custom exclude configured per user
|
||||
user-file-anywhere
|
||||
/user-file-from-top
|
||||
|
||||
user-Dir-anywhere/
|
||||
/user-dir-from-top
|
||||
|
||||
user-subdir/file
|
||||
**/user-subdir-anywhere/file
|
||||
a/b/*
|
||||
z/x
|
||||
EOF
|
||||
|
||||
mkdir repo;
|
||||
(cd repo
|
||||
git init -q
|
||||
git config core.excludesFile ../user.exclude
|
||||
|
||||
cat <<EOF >.git/info/exclude
|
||||
# a sample .git/info/exclude
|
||||
file-anywhere
|
||||
/file-from-top
|
||||
|
||||
dir-anywhere/
|
||||
/dir-from-top
|
||||
|
||||
subdir/file
|
||||
**/subdir-anywhere/file
|
||||
EOF
|
||||
|
||||
cat <<EOF >.gitignore
|
||||
# a sample .gitignore
|
||||
top-level-local-file-anywhere
|
||||
d/e/*
|
||||
e/f
|
||||
EOF
|
||||
|
||||
mkdir dir-with-ignore
|
||||
cat <<EOF >dir-with-ignore/.gitignore
|
||||
# a sample .gitignore
|
||||
sub-level-local-file-anywhere
|
||||
sub-Level-dir-anywhere/
|
||||
!/negated
|
||||
/negated-dir/
|
||||
!/negated-dir/
|
||||
EOF
|
||||
|
||||
git add .gitignore dir-with-ignore
|
||||
git commit --allow-empty -m "init"
|
||||
|
||||
# just add this git-ignore file, so it's a new file that doesn't exist on disk.
|
||||
mkdir other-dir-with-ignore
|
||||
skip_worktree_ignore=other-dir-with-ignore/.gitignore
|
||||
cat <<EOF >"$skip_worktree_ignore"
|
||||
# a sample .gitignore
|
||||
other-sub-level-local-file-anywhere
|
||||
other-sub-level-dir-anywhere/
|
||||
EOF
|
||||
git add $skip_worktree_ignore && git update-index --skip-worktree $skip_worktree_ignore && rm $skip_worktree_ignore
|
||||
|
||||
mkdir user-dir-anywhere user-dir-from-top dir-anywhere dir-from-top
|
||||
mkdir -p dir/user-dir-anywhere dir/dir-anywhere
|
||||
|
||||
git check-ignore -vn --stdin 2>&1 <<EOF >git-check-ignore.baseline || :
|
||||
dir-with-ignore/sub-level-dir-anywhere/
|
||||
dir-with-ignore/foo/Sub-level-dir-anywhere/
|
||||
dir-with-ignore/Sub-level-dir-anywhere
|
||||
user-file-anywhere
|
||||
dir/user-file-anywhere
|
||||
user-file-from-top
|
||||
no-match/user-file-from-top
|
||||
USER-dir-anywhere
|
||||
user-dir-from-top
|
||||
no-match/user-dir-from-top
|
||||
user-subdir/file
|
||||
subdir/user-subdir-anywhere/file
|
||||
user-dir-anywhere/hello
|
||||
dir/user-dir-anywhere/hello
|
||||
file-anywhere
|
||||
dir/file-anywhere
|
||||
file-from-top
|
||||
no-match/file-from-top
|
||||
dir-anywhere
|
||||
dir/dir-anywhere
|
||||
dir-from-top
|
||||
no-match/dir-from-top
|
||||
subdir/file
|
||||
subdir/subdir-anywhere/file
|
||||
top-level-local-file-anywhere
|
||||
dir/top-level-local-file-anywhere
|
||||
no-match/sub-level-local-file-anywhere
|
||||
dir-with-ignore/sub-level-local-file-anywhere
|
||||
dir-with-ignore/sub-dir/sub-level-local-file-anywhere
|
||||
other-dir-with-ignore/other-sub-level-local-file-anywhere
|
||||
other-dir-with-ignore/sub-level-local-file-anywhere
|
||||
other-dir-with-ignore/sub-dir/other-sub-level-local-file-anywhere
|
||||
other-dir-with-ignore/no-match/sub-level-local-file-anywhere
|
||||
non-existing/dir-anywhere
|
||||
dir-anywhere/hello
|
||||
dir/dir-anywhere/hello
|
||||
no-match/sub-level-dir-anywhere/hello
|
||||
no-match/other-sub-level-dir-anywhere/hello
|
||||
dir-with-ignore/sub-level-dir-anywhere/hello
|
||||
dir-with-ignore/sub-level-dir-anywhere/
|
||||
other-dir-with-ignore/sub-level-dir-anywhere/hello
|
||||
other-dir-with-ignore/other-sub-level-dir-anywhere/hello
|
||||
other-dir-with-ignore/other-sub-level-dir-anywhere/
|
||||
dir-with-ignore/negated
|
||||
dir-with-ignore/negated-dir/hello
|
||||
User-file-ANYWHERE
|
||||
User-Dir-ANYWHERE
|
||||
a/b/C
|
||||
a/B/c
|
||||
A/B/C
|
||||
z/x
|
||||
Z/x
|
||||
z/X
|
||||
Z/X
|
||||
d/e/F
|
||||
d/e/f
|
||||
D/e/F
|
||||
D/E/F
|
||||
e/f
|
||||
e/F
|
||||
E/f
|
||||
E/F
|
||||
EOF
|
||||
)
|
||||
28
src-worktree/tests/fixtures/make_special_exclude_case.sh
Executable file
28
src-worktree/tests/fixtures/make_special_exclude_case.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu -o pipefail
|
||||
|
||||
git init -q
|
||||
|
||||
mkdir -p tld tld/sd
|
||||
cat <<EOF >.gitignore
|
||||
# directory exclude
|
||||
tld/
|
||||
|
||||
!tld/file
|
||||
EOF
|
||||
|
||||
cat <<EOF >tld/.gitignore
|
||||
sd/
|
||||
!sd/
|
||||
|
||||
!file
|
||||
EOF
|
||||
|
||||
git check-ignore -vn --stdin 2>&1 <<EOF >git-check-ignore.baseline || :
|
||||
tld
|
||||
tld/
|
||||
tld/file
|
||||
tld/sd
|
||||
tld/sd/
|
||||
EOF
|
||||
|
||||
26
src-worktree/tests/fixtures/symlink_stack.sh
Executable file
26
src-worktree/tests/fixtures/symlink_stack.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu -o pipefail
|
||||
|
||||
git init base;
|
||||
(cd base
|
||||
touch file
|
||||
mkdir dir
|
||||
touch dir/file-in-dir
|
||||
(cd dir
|
||||
ln -s file-in-dir filelink
|
||||
mkdir subdir
|
||||
ln -s subdir dirlink
|
||||
)
|
||||
|
||||
ln -s file root-filelink
|
||||
ln -s dir root-dirlink
|
||||
|
||||
cat <<EOF > .gitattributes
|
||||
/file file-attr
|
||||
/dir/file-in-dir dir-file-attr
|
||||
EOF
|
||||
git add . && git commit -m "init"
|
||||
)
|
||||
|
||||
ln -s base symlink-base
|
||||
|
||||
2
src-worktree/tests/integrate.rs
Normal file
2
src-worktree/tests/integrate.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod worktree;
|
||||
use worktree::*;
|
||||
9
src-worktree/tests/worktree/mod.rs
Normal file
9
src-worktree/tests/worktree/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use gix_hash::ObjectId;
|
||||
|
||||
mod stack;
|
||||
|
||||
pub use gix_testtools::Result;
|
||||
|
||||
pub fn hex_to_id(hex: &str) -> ObjectId {
|
||||
ObjectId::from_hex(hex.as_bytes()).expect("40 bytes hex")
|
||||
}
|
||||
139
src-worktree/tests/worktree/stack/attributes.rs
Normal file
139
src-worktree/tests/worktree/stack/attributes.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use bstr::ByteSlice;
|
||||
use gix_attributes::search::Outcome;
|
||||
use gix_worktree::stack::state;
|
||||
|
||||
use crate::worktree::stack::probe_case;
|
||||
|
||||
#[test]
|
||||
fn baseline() -> crate::Result {
|
||||
// Due to the way our setup differs from gits dynamic stack (which involves trying to read files from disk
|
||||
// by path) we can only test one case baseline, so we require multiple platforms (or filesystems) to run this.
|
||||
let case = probe_case()?;
|
||||
let dir = gix_testtools::scripted_fixture_read_only_standalone("make_attributes_baseline.sh")?;
|
||||
let base = dir.join("basics");
|
||||
let git_dir = base.join(".git");
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut collection = gix_attributes::search::MetadataCollection::default();
|
||||
let state = gix_worktree::stack::State::for_checkout(
|
||||
false,
|
||||
gix_worktree::validate::path::component::Options {
|
||||
protect_windows: false,
|
||||
protect_ntfs: false,
|
||||
..Default::default()
|
||||
},
|
||||
state::Attributes::new(
|
||||
gix_attributes::Search::new_globals([base.join("user.attributes")], &mut buf, &mut collection)?,
|
||||
Some(git_dir.join("info").join("attributes")),
|
||||
gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
|
||||
collection,
|
||||
),
|
||||
);
|
||||
|
||||
let mut cache = gix_worktree::Stack::new(&base, state, case, buf, vec![]);
|
||||
|
||||
let mut actual = cache.attribute_matches();
|
||||
let input = std::fs::read(base.join("baseline"))?;
|
||||
for (rela_path, expected) in (baseline::Expectations { lines: input.lines() }) {
|
||||
let entry = cache.at_entry(rela_path, None, &gix_object::find::Never)?;
|
||||
let has_match = entry.matching_attributes(&mut actual);
|
||||
|
||||
assert_eq!(
|
||||
has_match,
|
||||
!expected.is_empty(),
|
||||
"matches are reported when git reports them, too"
|
||||
);
|
||||
assert_references(&actual);
|
||||
assert_eq!(actual.iter_selected().count(), 0, "no selection made yet");
|
||||
let actual: Vec<_> = actual
|
||||
.iter()
|
||||
.filter_map(|m| (!m.assignment.state.is_unspecified()).then_some(m.assignment))
|
||||
.collect();
|
||||
assert_eq!(actual, expected, "we have the same matches: {rela_path:?}");
|
||||
assert_eq!(has_match, !actual.is_empty());
|
||||
}
|
||||
|
||||
let mut actual = cache.selected_attribute_matches(["info", "test"]);
|
||||
let input = std::fs::read(base.join("baseline.selected"))?;
|
||||
for (rela_path, expected) in (baseline::Expectations { lines: input.lines() }) {
|
||||
let entry = cache.at_entry(rela_path, None, &gix_object::find::Never)?;
|
||||
let has_match = entry.matching_attributes(&mut actual);
|
||||
|
||||
assert_eq!(
|
||||
has_match,
|
||||
!expected.is_empty(),
|
||||
"matches are reported when git reports them, too"
|
||||
);
|
||||
assert_references(&actual);
|
||||
let actual: Vec<_> = actual.iter_selected().map(|m| m.assignment).collect();
|
||||
assert_eq!(actual, expected, "we have the same matches: {rela_path:?}");
|
||||
assert_eq!(has_match, !actual.is_empty());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn assert_references(out: &Outcome) {
|
||||
for m in out.iter() {
|
||||
if let Some(source) = m.kind.source_id() {
|
||||
let sm = out
|
||||
.match_by_id(source)
|
||||
.expect("sources are always available in the outcome");
|
||||
assert_ne!(
|
||||
sm.assignment.name, m.assignment.name,
|
||||
"it's impossible to resolve to ourselves"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod baseline {
|
||||
use bstr::{BStr, ByteSlice};
|
||||
use gix_attributes::{AssignmentRef, StateRef};
|
||||
|
||||
pub struct Expectations<'a> {
|
||||
pub lines: bstr::Lines<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Expectations<'a> {
|
||||
type Item = (
|
||||
&'a BStr,
|
||||
// Names might refer to attributes or macros
|
||||
Vec<AssignmentRef<'a>>,
|
||||
);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let path = self.lines.next()?;
|
||||
let mut assignments = Vec::new();
|
||||
loop {
|
||||
let line = self.lines.next()?;
|
||||
if line.is_empty() {
|
||||
return Some((path.as_bstr(), assignments));
|
||||
}
|
||||
|
||||
let mut prev = None;
|
||||
let mut tokens = line.splitn(3, |b| {
|
||||
let is_match = *b == b' ' && prev.take() == Some(b':');
|
||||
prev = Some(*b);
|
||||
is_match
|
||||
});
|
||||
|
||||
if let Some(((_path, attr), info)) = tokens.next().zip(tokens.next()).zip(tokens.next()) {
|
||||
let state = match info {
|
||||
b"set" => StateRef::Set,
|
||||
b"unset" => StateRef::Unset,
|
||||
b"unspecified" => StateRef::Unspecified,
|
||||
_ => StateRef::from_bytes(info),
|
||||
};
|
||||
let attr = attr.trim_end_with(|b| b == ':');
|
||||
assignments.push(AssignmentRef {
|
||||
name: gix_attributes::NameRef::try_from(attr.as_bstr()).expect("valid attributes"),
|
||||
state,
|
||||
});
|
||||
} else {
|
||||
unreachable!("invalid line format: {line:?}", line = line.as_bstr())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src-worktree/tests/worktree/stack/create_directory.rs
Normal file
139
src-worktree/tests/worktree/stack/create_directory.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use std::path::Path;
|
||||
|
||||
use gix_testtools::tempfile::{tempdir, TempDir};
|
||||
use gix_worktree::{stack, Stack};
|
||||
|
||||
const IS_FILE: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::FILE);
|
||||
const IS_DIR: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::DIR);
|
||||
|
||||
#[test]
|
||||
fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() -> crate::Result {
|
||||
let dir = tempdir()?;
|
||||
let mut cache = Stack::new(
|
||||
dir.path().join("non-existing-root"),
|
||||
stack::State::for_checkout(false, Default::default(), Default::default()),
|
||||
Default::default(),
|
||||
Vec::new(),
|
||||
Default::default(),
|
||||
);
|
||||
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 0);
|
||||
|
||||
let path = cache.at_path("hello", IS_FILE, &gix_object::find::Never)?.path();
|
||||
assert!(!path.parent().unwrap().exists(), "prefix itself is never created");
|
||||
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_paths_are_created_in_full() {
|
||||
let (mut cache, _tmp) = new_cache();
|
||||
|
||||
for (name, mode) in [
|
||||
("dir", IS_DIR),
|
||||
("submodule", IS_DIR),
|
||||
("file", IS_FILE),
|
||||
("exe", IS_FILE),
|
||||
("link", None),
|
||||
] {
|
||||
let path = cache
|
||||
.at_path(Path::new("dir").join(name), mode, &gix_object::find::Never)
|
||||
.unwrap()
|
||||
.path();
|
||||
assert!(path.parent().unwrap().is_dir(), "dir exists");
|
||||
}
|
||||
|
||||
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_directories_are_fine() -> crate::Result {
|
||||
let (mut cache, tmp) = new_cache();
|
||||
std::fs::create_dir(tmp.path().join("dir"))?;
|
||||
|
||||
let path = cache.at_path("dir/file", IS_FILE, &gix_object::find::Never)?.path();
|
||||
assert!(path.parent().unwrap().is_dir(), "directory is still present");
|
||||
assert!(!path.exists(), "it won't create the file");
|
||||
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_to_each_component() -> crate::Result {
|
||||
let (mut cache, tmp) = new_cache();
|
||||
|
||||
let err = cache
|
||||
.at_path("valid/.gIt", IS_FILE, &gix_object::find::Never)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
cache.statistics().delegate.num_mkdir_calls,
|
||||
1,
|
||||
"the valid directory was created"
|
||||
);
|
||||
assert!(tmp.path().join("valid").is_dir(), "it was actually created");
|
||||
assert_eq!(err.to_string(), "The .git name may never be used");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symlinks_or_files_in_path_are_forbidden_or_unlinked_when_forced() -> crate::Result {
|
||||
let (mut cache, tmp) = new_cache();
|
||||
let forbidden = tmp.path().join("forbidden");
|
||||
std::fs::create_dir(&forbidden)?;
|
||||
symlink::symlink_dir(&forbidden, tmp.path().join("link-to-dir"))?;
|
||||
std::fs::write(tmp.path().join("file-in-dir"), [])?;
|
||||
|
||||
for dirname in &["file-in-dir", "link-to-dir"] {
|
||||
if let stack::State::CreateDirectoryAndAttributesStack {
|
||||
unlink_on_collision, ..
|
||||
} = cache.state_mut()
|
||||
{
|
||||
*unlink_on_collision = false;
|
||||
}
|
||||
let relative_path = format!("{dirname}/file");
|
||||
assert_eq!(
|
||||
cache
|
||||
.at_path(&*relative_path, IS_FILE, &gix_object::find::Never)
|
||||
.unwrap_err()
|
||||
.kind(),
|
||||
std::io::ErrorKind::AlreadyExists
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
cache.statistics().delegate.num_mkdir_calls,
|
||||
2,
|
||||
"it tries to create each directory once, but it's a file"
|
||||
);
|
||||
cache.take_statistics();
|
||||
for dirname in &["link-to-dir", "file-in-dir"] {
|
||||
if let stack::State::CreateDirectoryAndAttributesStack {
|
||||
unlink_on_collision, ..
|
||||
} = cache.state_mut()
|
||||
{
|
||||
*unlink_on_collision = true;
|
||||
}
|
||||
let relative_path = format!("{dirname}/file");
|
||||
let path = cache
|
||||
.at_path(&*relative_path, IS_FILE, &gix_object::find::Never)?
|
||||
.path();
|
||||
assert!(path.parent().unwrap().is_dir(), "directory was forcefully created");
|
||||
assert!(!path.exists());
|
||||
}
|
||||
assert_eq!(
|
||||
cache.statistics().delegate.num_mkdir_calls,
|
||||
4,
|
||||
"like before, but it unlinks what's there and tries again"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_cache() -> (Stack, TempDir) {
|
||||
let dir = tempdir().unwrap();
|
||||
let cache = Stack::new(
|
||||
dir.path(),
|
||||
stack::State::for_checkout(false, Default::default(), Default::default()),
|
||||
Default::default(),
|
||||
Vec::new(),
|
||||
Default::default(),
|
||||
);
|
||||
(cache, dir)
|
||||
}
|
||||
186
src-worktree/tests/worktree/stack/ignore.rs
Normal file
186
src-worktree/tests/worktree/stack/ignore.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use std::fs::Metadata;
|
||||
|
||||
use bstr::{BStr, ByteSlice};
|
||||
use gix_fs::stack::ToNormalPathComponents;
|
||||
use gix_index::entry::Mode;
|
||||
use gix_worktree::{stack::state::ignore::Source, Stack};
|
||||
|
||||
use crate::{hex_to_id, worktree::stack::probe_case};
|
||||
|
||||
struct IgnoreExpectations<'a> {
|
||||
lines: bstr::Lines<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IgnoreExpectations<'a> {
|
||||
type Item = (&'a BStr, Option<(&'a BStr, usize, &'a BStr)>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let line = self.lines.next()?;
|
||||
let (left, value) = line.split_at(line.find_byte(b'\t').unwrap());
|
||||
let value = value[1..].as_bstr();
|
||||
|
||||
let source_and_line = if left == b"::" {
|
||||
None
|
||||
} else {
|
||||
let mut tokens = left.split(|b| *b == b':');
|
||||
let source = tokens.next().unwrap().as_bstr();
|
||||
let line_number: usize = tokens.next().unwrap().to_str_lossy().parse().ok().unwrap();
|
||||
let pattern = tokens.next().unwrap().as_bstr();
|
||||
Some((source, line_number, pattern))
|
||||
};
|
||||
Some((value, source_and_line))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_by_dir_is_handled_just_like_git() {
|
||||
let dir = gix_testtools::scripted_fixture_read_only_standalone("make_special_exclude_case.sh").unwrap();
|
||||
let git_dir = dir.join(".git");
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let case = gix_glob::pattern::Case::Sensitive;
|
||||
let state = gix_worktree::stack::State::for_add(
|
||||
Default::default(),
|
||||
gix_worktree::stack::state::Ignore::new(
|
||||
Default::default(),
|
||||
gix_ignore::Search::from_git_dir(&git_dir, None, &mut buf, Default::default()).unwrap(),
|
||||
None,
|
||||
Source::WorktreeThenIdMappingIfNotSkipped,
|
||||
Default::default(),
|
||||
),
|
||||
);
|
||||
let mut cache = Stack::new(&dir, state, case, buf, Default::default());
|
||||
let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline")).unwrap();
|
||||
let expectations = IgnoreExpectations {
|
||||
lines: baseline.lines(),
|
||||
};
|
||||
struct FindError;
|
||||
impl gix_object::Find for FindError {
|
||||
fn try_find<'a>(
|
||||
&self,
|
||||
_id: &gix_hash::oid,
|
||||
_buffer: &'a mut Vec<u8>,
|
||||
) -> Result<Option<gix_object::Data<'a>>, gix_object::find::Error> {
|
||||
Err(std::io::Error::other("unreachable").into())
|
||||
}
|
||||
}
|
||||
for (relative_entry, source_and_line) in expectations {
|
||||
let (source, line, expected_pattern) = source_and_line.expect("every value is matched");
|
||||
let relative_path = gix_path::from_byte_slice(relative_entry);
|
||||
let is_dir = dir.join(relative_path).metadata().ok().map(metadata_to_mode);
|
||||
|
||||
let platform = cache.at_entry(relative_entry, is_dir, &FindError).unwrap();
|
||||
let match_ = platform.matching_exclude_pattern().expect("match all values");
|
||||
let _is_excluded = platform.is_excluded();
|
||||
assert_eq!(
|
||||
match_.pattern.to_string(),
|
||||
expected_pattern,
|
||||
"we perfectly agree with git"
|
||||
);
|
||||
assert_eq!(
|
||||
expected_pattern, "tld/",
|
||||
"each entry matches on the main directory exclude, ignoring negations entirely"
|
||||
);
|
||||
// TODO: adjust baseline to also include precious files.
|
||||
assert_eq!(
|
||||
match_.kind,
|
||||
gix_ignore::Kind::Expendable,
|
||||
"for now all patterns are expendable until precious files are supported by git"
|
||||
);
|
||||
assert_eq!(line, 2);
|
||||
assert_eq!(source, ".gitignore");
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata_to_mode(meta: Metadata) -> Mode {
|
||||
if meta.is_dir() {
|
||||
gix_index::entry::Mode::DIR
|
||||
} else {
|
||||
gix_index::entry::Mode::FILE
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_against_baseline() -> crate::Result {
|
||||
let dir = gix_testtools::scripted_fixture_read_only_standalone("make_ignore_and_attributes_setup.sh")?;
|
||||
let worktree_dir = dir.join("repo");
|
||||
let git_dir = worktree_dir.join(".git");
|
||||
let mut buf = Vec::new();
|
||||
let user_exclude_path = dir.join("user.exclude");
|
||||
assert!(user_exclude_path.is_file());
|
||||
|
||||
// Due to the way our setup differs from gits dynamic stack (which involves trying to read files from disk
|
||||
// by path) we can only test one case baseline, so we require multiple platforms (or filesystems) to run this.
|
||||
let case = probe_case()?;
|
||||
let mut index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, false, Default::default())?;
|
||||
let odb = gix_odb::at(git_dir.join("objects"))?;
|
||||
let parse_ignore = gix_ignore::search::Ignore::default();
|
||||
let state = gix_worktree::stack::State::for_add(
|
||||
Default::default(),
|
||||
gix_worktree::stack::state::Ignore::new(
|
||||
gix_ignore::Search::from_overrides(["!force-include"], parse_ignore),
|
||||
gix_ignore::Search::from_git_dir(&git_dir, Some(user_exclude_path), &mut buf, parse_ignore)?,
|
||||
None,
|
||||
Source::WorktreeThenIdMappingIfNotSkipped,
|
||||
parse_ignore,
|
||||
),
|
||||
);
|
||||
let paths_storage = index.take_path_backing();
|
||||
let attribute_files_in_index = state.id_mappings_from_index(&index, &paths_storage, case);
|
||||
assert_eq!(
|
||||
attribute_files_in_index,
|
||||
vec![(
|
||||
"other-dir-with-ignore/.gitignore".into(),
|
||||
hex_to_id("5c7e0ed672d3d31d83a3df61f13cc8f7b22d5bfd")
|
||||
)]
|
||||
);
|
||||
let mut cache = Stack::new(&worktree_dir, state, case, buf, attribute_files_in_index);
|
||||
|
||||
let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?;
|
||||
let expectations = IgnoreExpectations {
|
||||
lines: baseline.lines(),
|
||||
};
|
||||
for (relative_entry, source_and_line) in expectations {
|
||||
let relative_path = gix_path::from_byte_slice(relative_entry);
|
||||
let is_dir = worktree_dir.join(relative_path).metadata().ok().map(metadata_to_mode);
|
||||
|
||||
let platform = cache.at_entry(relative_entry, is_dir, &odb)?;
|
||||
|
||||
let match_ = platform.matching_exclude_pattern();
|
||||
let is_excluded = platform.is_excluded();
|
||||
match (match_, source_and_line) {
|
||||
(None, None) => {
|
||||
assert!(!is_excluded);
|
||||
}
|
||||
(Some(m), Some((source_file, line, pattern))) => {
|
||||
assert_eq!(m.pattern.to_string(), pattern);
|
||||
assert_eq!(m.sequence_number, line);
|
||||
// TODO: adjust baseline to also include precious files.
|
||||
if !m.pattern.is_negative() {
|
||||
assert_eq!(
|
||||
m.kind,
|
||||
platform.excluded_kind().expect("it matches"),
|
||||
"both values agree, no matter which method is used"
|
||||
);
|
||||
}
|
||||
// Paths read from the index are relative to the repo, and they don't exist locally due tot skip-worktree
|
||||
if m.source.is_some_and(std::path::Path::exists) {
|
||||
assert_eq!(
|
||||
m.source.map(|p| p.canonicalize().unwrap()),
|
||||
Some(worktree_dir.join(source_file.to_str_lossy().as_ref()).canonicalize()?)
|
||||
);
|
||||
}
|
||||
}
|
||||
(Some(actual), None) if actual.pattern.is_negative() => {
|
||||
// OK: we provide negative patterns that matched on paths if there was no other match, while git doesn't.
|
||||
}
|
||||
(actual, expected) => {
|
||||
panic!(
|
||||
"actual {actual:?} didn't match {expected:?} at '{relative_entry}': {components:?}",
|
||||
components = relative_entry.to_normal_path_components().collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
23
src-worktree/tests/worktree/stack/mod.rs
Normal file
23
src-worktree/tests/worktree/stack/mod.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use gix_glob::pattern::Case;
|
||||
|
||||
mod create_directory;
|
||||
|
||||
mod attributes;
|
||||
mod ignore;
|
||||
|
||||
fn probe_case() -> crate::Result<Case> {
|
||||
Ok(
|
||||
if gix_fs::Capabilities::probe(
|
||||
&gix_discover::upwards(".".as_ref())?
|
||||
.0
|
||||
.into_repository_and_work_tree_directories()
|
||||
.0,
|
||||
)
|
||||
.ignore_case
|
||||
{
|
||||
Case::Fold
|
||||
} else {
|
||||
Case::Sensitive
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user