Files
galaxy/crates/repo_metadata/src/entry_test.rs
T

416 lines
14 KiB
Rust

use super::path_passes_filters;
use ignore::gitignore::Gitignore;
use virtual_fs::{Stub, VirtualFS};
#[cfg(unix)]
#[test]
fn test_path_passes_filters_unix() {
VirtualFS::test("test_path_passes_filters", |dirs, mut sandbox| {
sandbox.mkdir("my_repo");
sandbox.mkdir("my_repo/.git");
sandbox.mkdir("my_repo/.git/refs");
sandbox.mkdir("my_repo/.git/refs/heads");
sandbox.mkdir("my_repo/src");
sandbox.mkdir("my_repo/target");
sandbox.mkdir("my_repo/target/debug");
sandbox.mkdir("outside_of_codebase");
sandbox.with_files(vec![
Stub::EmptyFile("my_repo/README.txt"),
Stub::EmptyFile("my_repo/.git/blob.txt"),
Stub::EmptyFile("my_repo/.git/HEAD"),
Stub::EmptyFile("my_repo/.git/refs/heads/main"),
Stub::EmptyFile("my_repo/.git/refs/heads/feature-branch"),
Stub::EmptyFile("my_repo/src/main.rs"),
Stub::EmptyFile("my_repo/target/debug/a.out"),
Stub::EmptyFile("outside_of_codebase/text.txt"),
]);
sandbox.with_files(vec![Stub::FileWithContent("my_repo/.gitignore", "target")]);
let test_gitignore_entry = dirs.tests().join("my_repo/.gitignore");
let gitignores = vec![Gitignore::new(test_gitignore_entry).0];
// Do NOT ignore a file that does not exist (for deletions)
assert!(path_passes_filters(
dirs.tests().join("my_repo/does_not_exist.txt").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join("my_repo/src").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join("my_repo/src/main.rs").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join("outside_of_codebase/text.txt").as_path(),
&gitignores
));
// Allow .git internal files that provide useful signals
assert!(path_passes_filters(
dirs.tests().join("my_repo/.git/HEAD").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join("my_repo/.git/refs/heads").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join("my_repo/.git/refs/heads/main").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests()
.join("my_repo/.git/refs/heads/feature-branch")
.as_path(),
&gitignores
));
// Non-allowlisted .git/ internal files are filtered out
assert!(!path_passes_filters(
dirs.tests().join("my_repo/.git/index").as_path(),
&gitignores
));
assert!(!path_passes_filters(
dirs.tests().join("my_repo/.git/blob.txt").as_path(),
&gitignores
));
// .git directory itself is still ignored
assert!(!path_passes_filters(
dirs.tests().join("my_repo/.git").as_path(),
&gitignores
));
// Ignore .gitignored paths and their children.
assert!(!path_passes_filters(
dirs.tests().join("my_repo/target/").as_path(),
&gitignores
));
assert!(!path_passes_filters(
dirs.tests().join("my_repo/target/debug").as_path(),
&gitignores
));
assert!(!path_passes_filters(
dirs.tests().join("my_repo/target/debug/a.out").as_path(),
&gitignores
));
// Ignore a .gitignored file that does not exist (for deletions)
assert!(!path_passes_filters(
&dirs.tests().join("my_repo/target/does_not_exist.txt"),
&gitignores
));
// Ensure paths are canonicalized before being matched against gitignores.
assert!(path_passes_filters(
dirs.tests()
.join("outside_of_codebase/../my_repo/README.txt")
.as_path(),
&gitignores
));
assert!(!path_passes_filters(
dirs.tests()
.join("outside_of_codebase/../my_repo/target/debug/a.out")
.as_path(),
&gitignores
));
});
}
#[cfg_attr(
windows,
ignore = "TODO(CODE-312): issue with Gitignore matching on Windows"
)]
#[cfg(windows)]
#[test]
fn test_path_passes_filters_windows() {
VirtualFS::test("test_path_passes_filters", |dirs, mut sandbox| {
sandbox.mkdir("my_repo");
sandbox.mkdir(r"my_repo\.git");
sandbox.mkdir(r"my_repo\.git\refs");
sandbox.mkdir(r"my_repo\.git\refs\heads");
sandbox.mkdir(r"my_repo\src");
sandbox.mkdir(r"my_repo\target");
sandbox.mkdir(r"my_repo\target\debug");
sandbox.mkdir("outside_of_codebase");
sandbox.with_files(vec![
Stub::EmptyFile(r"my_repo\README.txt"),
Stub::EmptyFile(r"my_repo\.git\blob.txt"),
Stub::EmptyFile(r"my_repo\.git\HEAD"),
Stub::EmptyFile(r"my_repo\.git\refs\heads\main"),
Stub::EmptyFile(r"my_repo\.git\refs\heads\feature-branch"),
Stub::EmptyFile(r"my_repo\src\main.rs"),
Stub::EmptyFile(r"my_repo\target\debug\a.out"),
Stub::EmptyFile(r"outside_of_codebase\text.txt"),
]);
sandbox.with_files(vec![Stub::FileWithContent(r"my_repo\.gitignore", "target")]);
let test_gitignore_entry = dirs.tests().join(r"my_repo\.gitignore");
let gitignores = vec![Gitignore::new(test_gitignore_entry).0];
assert!(path_passes_filters(
dirs.tests().join(r"my_repo\src").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join(r"my_repo\src\main.rs").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join(r"outside_of_codebase\text.txt").as_path(),
&gitignores
));
// Allow .git internal files that provide useful signals
assert!(path_passes_filters(
dirs.tests().join(r"my_repo\.git\HEAD").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join(r"my_repo\.git\refs\heads").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests().join(r"my_repo\.git\refs\heads\main").as_path(),
&gitignores
));
assert!(path_passes_filters(
dirs.tests()
.join(r"my_repo\.git\refs\heads\feature-branch")
.as_path(),
&gitignores
));
// .git directory itself is still ignored
assert!(!path_passes_filters(
dirs.tests().join(r"my_repo\.git").as_path(),
&gitignores
));
// Ignore .gitignored paths and their children.
assert!(!path_passes_filters(
dirs.tests().join(r"my_repo\target").as_path(),
&gitignores
));
assert!(!path_passes_filters(
dirs.tests().join(r"my_repo\target\debug").as_path(),
&gitignores
));
assert!(!path_passes_filters(
dirs.tests().join(r"my_repo\target\debug\a.out").as_path(),
&gitignores
));
// Ensure paths are canonicalized before being matched against gitignores.
assert!(path_passes_filters(
dirs.tests()
.join(r"outside_of_codebase\..\my_repo\README.txt")
.as_path(),
&gitignores
));
assert!(!path_passes_filters(
dirs.tests()
.join(r"outside_of_codebase\..\my_repo\target\debug\a.out")
.as_path(),
&gitignores
));
});
}
#[test]
fn test_git_path_filtering_allowlist() {
use super::{is_commit_related_git_file, is_index_lock_file, should_ignore_git_path};
use std::path::Path;
// Non-git paths should not be ignored
assert!(!should_ignore_git_path(Path::new(
"/home/user/project/src/main.rs"
)));
assert!(!should_ignore_git_path(Path::new(
"/home/user/project/README.md"
)));
// .git directory itself should be ignored
assert!(should_ignore_git_path(Path::new("/home/user/project/.git")));
// Allowlisted: commit-related files are NOT ignored
assert!(!should_ignore_git_path(Path::new(
"/home/user/project/.git/HEAD"
)));
assert!(!should_ignore_git_path(Path::new(
"/home/user/project/.git/refs/heads/main"
)));
assert!(!should_ignore_git_path(Path::new(
"/home/user/project/.git/refs/heads/feature-branch"
)));
// Allowlisted: index.lock is NOT ignored
assert!(!should_ignore_git_path(Path::new(
"/home/user/project/.git/index.lock"
)));
// Everything else in .git/ IS ignored
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/index"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/config"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/COMMIT_EDITMSG"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/FETCH_HEAD"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/ORIG_HEAD"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/refs/tags/v1.0"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/refs/remotes/origin/main"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/objects/abc123"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/hooks/pre-commit"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/logs/HEAD"
)));
// Worktree paths: allowlisted patterns under .git/worktrees/<name>/
assert!(!should_ignore_git_path(Path::new(
"/home/user/project/.git/worktrees/my-wt/HEAD"
)));
assert!(!should_ignore_git_path(Path::new(
"/home/user/project/.git/worktrees/my-wt/index.lock"
)));
// Non-allowlisted worktree paths are still ignored
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/worktrees/my-wt/index"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/worktrees/my-wt/COMMIT_EDITMSG"
)));
// worktrees dir itself (no content after worktree name) is ignored
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/worktrees"
)));
assert!(should_ignore_git_path(Path::new(
"/home/user/project/.git/worktrees/my-wt"
)));
// is_commit_related_git_file
assert!(is_commit_related_git_file(Path::new("/repo/.git/HEAD")));
assert!(is_commit_related_git_file(Path::new(
"/repo/.git/refs/heads/main"
)));
assert!(is_commit_related_git_file(Path::new(
"/repo/.git/worktrees/wt/HEAD"
)));
assert!(!is_commit_related_git_file(Path::new(
"/repo/.git/index.lock"
)));
assert!(!is_commit_related_git_file(Path::new(
"/repo/.git/refs/tags/v1"
)));
// is_index_lock_file
assert!(is_index_lock_file(Path::new("/repo/.git/index.lock")));
assert!(is_index_lock_file(Path::new(
"/repo/.git/worktrees/wt/index.lock"
)));
assert!(!is_index_lock_file(Path::new("/repo/.git/HEAD")));
assert!(!is_index_lock_file(Path::new("/repo/.git/index")));
// Test Windows-style paths (only on Windows, as path parsing is platform-specific)
#[cfg(windows)]
{
assert!(!should_ignore_git_path(Path::new(
r"C:\Users\user\project\.git\HEAD"
)));
assert!(!should_ignore_git_path(Path::new(
r"C:\Users\user\project\.git\index.lock"
)));
assert!(should_ignore_git_path(Path::new(
r"C:\Users\user\project\.git\index"
)));
}
}
#[test]
fn test_is_shared_git_ref() {
use super::is_shared_git_ref;
use std::path::Path;
// Shared refs — broadcast to all repos
assert!(is_shared_git_ref(Path::new("/repo/.git/refs/heads/main")));
assert!(is_shared_git_ref(Path::new(
"/repo/.git/refs/heads/feature"
)));
// Repo-specific — NOT shared
assert!(!is_shared_git_ref(Path::new("/repo/.git/HEAD")));
assert!(!is_shared_git_ref(Path::new("/repo/.git/index.lock")));
// Worktree paths — NOT shared
assert!(!is_shared_git_ref(Path::new(
"/repo/.git/worktrees/foo/HEAD"
)));
assert!(!is_shared_git_ref(Path::new(
"/repo/.git/worktrees/foo/refs/heads/main"
)));
// Other .git internals — NOT shared
assert!(!is_shared_git_ref(Path::new("/repo/.git/refs/tags/v1")));
assert!(!is_shared_git_ref(Path::new(
"/repo/.git/refs/remotes/origin/main"
)));
assert!(!is_shared_git_ref(Path::new("/repo/.git/config")));
// Not a git path at all
assert!(!is_shared_git_ref(Path::new("/repo/src/main.rs")));
}
#[test]
fn test_extract_worktree_git_dir() {
use super::extract_worktree_git_dir;
use std::path::{Path, PathBuf};
// Standard worktree path extracts the per-worktree gitdir
assert_eq!(
extract_worktree_git_dir(Path::new("/repo/.git/worktrees/foo/HEAD")),
Some(PathBuf::from("/repo/.git/worktrees/foo"))
);
assert_eq!(
extract_worktree_git_dir(Path::new("/repo/.git/worktrees/bar/index.lock")),
Some(PathBuf::from("/repo/.git/worktrees/bar"))
);
// Non-worktree paths return None
assert_eq!(extract_worktree_git_dir(Path::new("/repo/.git/HEAD")), None);
assert_eq!(
extract_worktree_git_dir(Path::new("/repo/.git/refs/heads/main")),
None
);
assert_eq!(
extract_worktree_git_dir(Path::new("/repo/src/main.rs")),
None
);
// Edge case: not enough depth after worktrees/
assert_eq!(
extract_worktree_git_dir(Path::new("/repo/.git/worktrees")),
None
);
assert_eq!(
extract_worktree_git_dir(Path::new("/repo/.git/worktrees/foo")),
None
);
}