From 7ca43ab314d50b6593f866788a4634058c284526 Mon Sep 17 00:00:00 2001 From: safishamsi Date: Mon, 13 Jul 2026 18:35:09 +0100 Subject: [PATCH] test(detect): harden nested-ignore tests (#1847 followup) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tighten the brittle negation test to use .py files so classification lands in the deterministic `code` bucket (was checking a fuzzy document+unclassified union), and add a composition test asserting a nested `.gitignore` `!` re-include outranks both a root `.gitignore` and `.git/info/exclude` (#1810) — locking the precedence across all three ignore sources. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/test_detect.py | 45 +++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/tests/test_detect.py b/tests/test_detect.py index 6f71fe4..37ffc6f 100644 --- a/tests/test_detect.py +++ b/tests/test_detect.py @@ -269,23 +269,42 @@ def test_gitignore_nested_below_root_prunes_whole_directory(tmp_path): def test_gitignore_nested_negation_overrides_broader_root_rule(tmp_path): """A closer (nested) .gitignore's `!` re-include wins over a root exclude, - matching git's closer-file-wins precedence.""" - (tmp_path / ".gitignore").write_text("*.txt\n") + matching git's closer-file-wins precedence. Uses .py so classification lands + in the deterministic `code` bucket.""" + (tmp_path / ".gitignore").write_text("*.py\n") sub = tmp_path / "vendor" / "sub" sub.mkdir(parents=True) - (sub / ".gitignore").write_text("!important.txt\n") - (tmp_path / "root.txt").write_text("a") - (sub / "important.txt").write_text("b") - (sub / "other.txt").write_text("c") + (sub / ".gitignore").write_text("!important.py\n") + (tmp_path / "root.py").write_text("a = 1") + (sub / "important.py").write_text("b = 1") + (sub / "other.py").write_text("c = 1") result = detect(tmp_path) - # .txt is not a code extension so check the document bucket instead - doc_files = result["files"]["document"] if "document" in result["files"] else [] - unclassified = result.get("unclassified", []) - all_seen = doc_files + unclassified - assert any("important.txt" in f for f in all_seen) - assert not any(f.endswith("root.txt") for f in all_seen) - assert not any(f.endswith("other.txt") for f in all_seen) + code = result["files"]["code"] + # nested `!important.py` re-includes it despite the root `*.py` exclude... + assert any("vendor/sub/important.py" in f for f in code) + # ...while the root-excluded and non-re-included files stay out + assert not any(f.endswith("root.py") for f in code) + assert not any(f.endswith("other.py") for f in code) + + +def test_nested_ignore_overrides_git_info_exclude_and_root(tmp_path): + """Precedence across all three sources: a nested `.gitignore` `!` re-include + outranks both a root `.gitignore` and `.git/info/exclude` (lowest, from + #1810), while an info/exclude-only file with no re-include stays out.""" + (tmp_path / ".git" / "info").mkdir(parents=True) + (tmp_path / ".git" / "info" / "exclude").write_text("*.py\n") + (tmp_path / ".gitignore").write_text("keep.py\n") # root also excludes it + sub = tmp_path / "a" / "b" + sub.mkdir(parents=True) + (sub / ".gitignore").write_text("!keep.py\n") # nearest wins -> re-included + (sub / "keep.py").write_text("x = 1") + (tmp_path / "drop.py").write_text("y = 1") # only info/exclude -> excluded + + result = detect(tmp_path) + code = result["files"]["code"] + assert any("a/b/keep.py" in f for f in code), "nested ! must beat root + info/exclude" + assert not any(f.endswith("drop.py") for f in code) def test_detect_handles_circular_symlinks(tmp_path):