Loading
Fix exists: rule to match ** recursively, files-only (#95)
Summary
Fixes #95 (closed). exists: rule evaluation used Go's filepath.Glob, whose ** is not recursive — it matches exactly one directory level. As a result exists: ['**/*.php'] matched a .php file one directory deep but missed root-level files and files nested two or more levels deep, diverging both from GitLab (where ** matches at any depth) and from glci's own changes: evaluation (which already uses the recursive matchGlob matcher).
evalExists now matches each project file against the pattern with matchGlob, so ** behaves recursively and consistently across exists:/changes:.
Hardening from review
- Files-only — like GitLab (and
changes:),exists:matches regular files, never directories. Applied to both the glob and the literal fast path. - Path traversal — literal patterns that escape
ProjectDirvia..(e.g.../../etc/passwd) are rejected, so a.gitlab-ci.ymlcannot probe the host filesystem through anexists:rule. - Single walk —
NewProjectFileCachememoizes the tree walk so a planning run with manyexists:jobs walks the disk once, not once per job/rule (wired into the planner filter and the dependency planner). - Truncation warning — the 10,000-file cap now emits a warning instead of silently reporting "no match";
existsFileLimitis a var so tests can lower it. .gitskipped — GitLab matches tracked files, never repository internals.
Tests
- Planner-level test: multiple jobs with divergent
**globs evaluated through the shared cache. - Unit: recursive
**at root/one/two levels, directory-no-match, path-traversal (outside + in-bounds..), symlink-to-file match, all three input shapes (list/map/bare string), multiple globs, mixed literal+glob, nonexistent project dir, cap truncation + warning assertion,isLiteralPathtable.
Verified with go build ./..., go vet ./..., make test, make test-e2e-parse, make test-e2e-golden.
Out of scope (follow-ups)
- Brace expansion (
*.{php,inc}) is unsupported in bothexists:andchanges:(sharedmatchGlob/filepath.Matchlimitation). Worth a separate issue. - A comprehensive e2e fixture job exercising recursive
exists:(the existing fixture uses only a literalexists:).