wsb 0.255.6379

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
//! Against a real repository, because the point of the flag is that GIT
//! decides — a test using a hand-rolled matcher would only prove the matcher.

use super::*;
use std::fs;

/// A repository with `build/` and `*.log` ignored.
fn repo() -> tempfile::TempDir {
    let dir = tempfile::TempDir::new().expect("temp dir");
    let root = dir.path();
    git(root, &["init", "-q"]);
    git(root, &["config", "user.email", "t@example.com"]);
    git(root, &["config", "user.name", "t"]);
    fs::write(root.join(".gitignore"), "build/\n*.log\n!keep.log\n").expect("gitignore");
    fs::create_dir_all(root.join("build/deep")).expect("build");
    fs::write(root.join("build/deep/artifact.o"), "x").expect("artifact");
    fs::write(root.join("noise.log"), "x").expect("noise");
    fs::write(root.join("keep.log"), "x").expect("keep");
    fs::write(root.join("main.rs"), "x").expect("source");
    dir
}

/// Through the one git constructor in this crate.
///
/// Not `std::process::Command`: an inherited `GIT_DIR` overrides
/// `current_dir`, so a bare git would build the fixture in whichever
/// repository the test runner happened to be launched from — which for a test
/// ABOUT ignore rules would read this workspace's rules and prove nothing.
fn git(root: &Path, args: &[&str]) {
    let output = crate::git_backend::git_command()
        .arg("-C")
        .arg(root)
        .args(args)
        .output()
        .expect("git runs");
    assert!(
        output.status.success(),
        "git {args:?} failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );
}

#[test]
fn test11065_an_ignored_directory_is_ignored_whole() {
    let dir = repo();
    let ignored = Ignored::discover(dir.path());

    // The directory itself and everything under it. This is what makes the
    // flag worth having: the walk is refused at `build/` and never descends.
    assert!(ignored.contains(&dir.path().join("build")));
    assert!(ignored.contains(&dir.path().join("build/deep")));
    assert!(ignored.contains(&dir.path().join("build/deep/artifact.o")));
}

#[test]
fn test11066_a_tracked_file_is_never_ignored() {
    let dir = repo();
    // `*.log` matches it, but git does not ignore a file it is tracking — and
    // a hand-written matcher would get this wrong and skip a file the user
    // very much wants refactored.
    git(dir.path(), &["add", "-f", "noise.log"]);
    let ignored = Ignored::discover(dir.path());
    assert!(
        !ignored.contains(&dir.path().join("noise.log")),
        "a tracked file is not ignored, whatever the pattern says"
    );
}

#[test]
fn test11067_a_negation_is_honoured() {
    let dir = repo();
    let ignored = Ignored::discover(dir.path());
    assert!(ignored.contains(&dir.path().join("noise.log")));
    // `!keep.log` un-ignores it. Negations are exactly the part a reimplemented
    // matcher gets wrong.
    assert!(!ignored.contains(&dir.path().join("keep.log")));
}

#[test]
fn test11068_source_is_untouched() {
    let dir = repo();
    let ignored = Ignored::discover(dir.path());
    assert!(!ignored.contains(&dir.path().join("main.rs")));
    assert!(!ignored.contains(&dir.path().join(".gitignore")));
}

#[test]
fn test11069_a_nested_repository_is_asked_about_its_own_tree() {
    let dir = repo();
    // A submodule's ignores are invisible to its parent: the parent's
    // ls-files stops at the boundary. Without asking the inner repository,
    // its `vendor/` would be walked in full.
    let inner = dir.path().join("inner");
    fs::create_dir_all(inner.join("vendor")).expect("inner");
    git(&inner, &["init", "-q"]);
    fs::write(inner.join(".gitignore"), "vendor/\n").expect("inner gitignore");
    fs::write(inner.join("vendor/dep.rs"), "x").expect("dep");
    fs::write(inner.join("lib.rs"), "x").expect("lib");

    let ignored = Ignored::discover(dir.path());
    assert!(
        ignored.contains(&inner.join("vendor")),
        "the inner repository's own rules decide inside it"
    );
    assert!(!ignored.contains(&inner.join("lib.rs")));
}

#[test]
fn test11070_a_directory_outside_any_repository_ignores_nothing() {
    let dir = tempfile::TempDir::new().expect("temp dir");
    fs::write(dir.path().join("a.log"), "x").expect("file");
    // No repository means nothing is "verifiably ignored", so nothing is
    // skipped — the flag must not invent rules where git has none.
    let ignored = Ignored::discover(dir.path());
    assert!(ignored.is_empty());
    assert!(!ignored.contains(&dir.path().join("a.log")));
}

#[test]
fn test11071_an_empty_set_never_claims_a_path() {
    // The default is what a run without the flag carries, and it must be
    // inert: every path reaches the walk exactly as it did before.
    let ignored = Ignored::default();
    assert!(ignored.is_empty());
    assert!(!ignored.contains(Path::new("/anything/at/all")));
    assert_eq!(ignored.len(), 0);
}