Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

134 lines
5.7 KiB
C

/*
* repro_issue510.c — Reproduce-first case for OPEN bug #510.
*
* Issue: #510 — ".gitignore (non repo root) gaps and overrides"
*
* Root cause (discovered via discover.c):
* cbm_discover_ex() loads the root .gitignore ONLY when a .git directory is
* present at repo_path (is_git_repo gate, ~line 777). For a non-git-root
* call (e.g. indexing pkg/ directly), is_git_repo = false and gitignore =
* NULL. The nested-gitignore fallback also fails: try_load_nested_gitignore()
* has the guard "if (frame->local_gi || frame->prefix[0] == '\0') return NULL"
* (line 630). The initial walk frame always has prefix == "" (empty), so
* prefix[0] == '\0' is true and the function returns NULL without even
* stat-ing the .gitignore file. Result: the .gitignore sitting at the root
* of the indexed directory is completely silently ignored, so every file
* that it excludes gets indexed anyway.
*
* Expected (correct) behaviour:
* When cbm_discover() is called on a directory that is NOT a git repo root
* but DOES contain a .gitignore, that .gitignore MUST be honoured.
* A file matching a pattern in that .gitignore must NOT appear in the
* discovered file list.
*
* Actual (buggy) behaviour:
* cbm_discover() returns the excluded file as a normal discovered file
* because try_load_nested_gitignore() refuses to load .gitignore when
* the walk frame prefix is empty (i.e. the indexed directory itself).
*
* Why RED on current code:
* The fixture creates a directory WITHOUT a .git sub-directory (so the
* is_git_repo gate stays false), writes a .gitignore containing "secret.py",
* and writes secret.py + keep.py. After cbm_discover(), the loop below
* checks that secret.py is NOT in the result. On the current code the
* check FAILS because secret.py is present in the discovered list.
*
* Fix location (not implemented here):
* src/discover/discover.c, function try_load_nested_gitignore():
* Remove (or invert) the "frame->prefix[0] == '\0'" early-return guard so
* that the function also loads .gitignore from the root indexed directory.
* Additionally, cbm_discover_ex() should attempt to load a root .gitignore
* even when the directory is not a git repo.
*/
#include <foundation/compat.h>
#include "test_framework.h"
#include "test_helpers.h"
#include "discover/discover.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* ── Fixture ────────────────────────────────────────────────────────────────
*
* Directory layout (NOT a git repo — no .git/ subdir):
*
* <tmpdir>/
* .gitignore <- contains "secret.py"
* secret.py <- should be EXCLUDED by .gitignore
* keep.py <- should be INCLUDED (not matched by any pattern)
*
* Precondition check (to isolate the discovery layer from extraction):
* The root .gitignore is parseable and matches "secret.py".
* cbm_gitignore_matches(gi, "secret.py", false) == true.
* This GREEN precondition proves the matcher itself is correct; if it
* turns RED instead, the bug is in the matcher, not discovery.
*
* Primary assertion (RED on buggy code):
* After cbm_discover(), "secret.py" must NOT appear in the file list.
*
* The test does NOT create a .git directory, mirroring the exact scenario
* from issue #510 Repro 1-A: indexing a sub-package directly rather than
* the repo root.
*/
TEST(repro_issue510_nested_gitignore_honored) {
/* --- set up temp directory --- */
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "%s/cbm_repro510_XXXXXX", cbm_tmpdir());
ASSERT_NOT_NULL(cbm_mkdtemp(tmpdir));
/* Write fixture files */
ASSERT_EQ(0, th_write_file(TH_PATH(tmpdir, ".gitignore"), "secret.py\n"));
ASSERT_EQ(0, th_write_file(TH_PATH(tmpdir, "secret.py"),
"def secret(): return \"SECRET_TOKEN_111\"\n"));
ASSERT_EQ(0, th_write_file(TH_PATH(tmpdir, "keep.py"),
"def ok(): return 1\n"));
/* --- Precondition: matcher itself handles the pattern correctly --- */
cbm_gitignore_t *gi = cbm_gitignore_parse("secret.py\n");
ASSERT_NOT_NULL(gi);
/* If this assertion fails, the bug is in the gitignore matcher, not
* in discovery — a different bug, not #510. */
ASSERT_TRUE(cbm_gitignore_matches(gi, "secret.py", false));
cbm_gitignore_free(gi);
/* --- Run discovery on the directory (no .git present) --- */
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(tmpdir, NULL, &files, &count);
ASSERT_EQ(0, rc);
/* --- Primary assertion: secret.py must NOT be discovered --- */
bool secret_found = false;
bool keep_found = false;
for (int i = 0; i < count; i++) {
if (strcmp(files[i].rel_path, "secret.py") == 0) {
secret_found = true;
}
if (strcmp(files[i].rel_path, "keep.py") == 0) {
keep_found = true;
}
}
cbm_discover_free(files, count);
th_rmtree(tmpdir);
/* keep.py is a valid Python file and MUST be discovered. */
ASSERT_TRUE(keep_found);
/*
* RED assertion: secret.py matches the root .gitignore pattern and
* must be excluded. On buggy code try_load_nested_gitignore() skips
* the root frame (prefix == ""), so secret.py IS discovered and this
* ASSERT_FALSE fires RED.
*/
ASSERT_FALSE(secret_found);
PASS();
}
/* ── Suite ──────────────────────────────────────────────────────────────── */
SUITE(repro_issue510) {
RUN_TEST(repro_issue510_nested_gitignore_honored);
}