Repository navigation
Conversation
`validate <files>`, `gv <files>` and `for-file` matched each path as
typed. The only cleanup was stripping the project root from an absolute
path, and the CLI canonicalizes the root. So `./app/x.rb`, `app//x.rb`,
`app/./x.rb`, `app/../app/x.rb` and an absolute path through a symlink
to the project, as macOS's /tmp is, were matched against owned_globs,
unowned_globs and CODEOWNERS as different strings. With `**/*.rb`, a
file that CODEOWNERS lists was reported as unowned. With
`{app,config}/**/*.rb`, a file with no owner was left out and passed,
and `for-file` ignored the annotation of a file named through a
symlink.
A new path_utils::project_relative_path finds the project-relative path
that a given path names. It joins the path onto the absolute root and
cleans it lexically, as cli.rs does for `--config-path`. If that isn't
under the root, it resolves the root, which a library caller's
RunConfig may give unresolved, and then the path's ancestors from the
shortest until one is in the project, keeping the rest of the path as
written. So a path through a symlink to the project names what its
plain form does. Nothing inside the project is resolved: a symlink
there keeps its own path, and a deleted file, or one in a directory
that doesn't exist or can't be read, is still checked rather than
skipped. A path that names nothing under the root is matched as
before: as given, without the root if it's absolute.
validate_files uses it for the glob filters and the
optionally_owned_globs check, and the CODEOWNERS query for its lookup,
which covers `for-file --from-codeowners` and the code_ownership gem's
lookups. That replaces plain_relative and the second CODEOWNERS lookup
from #135, so a path with a `..` inside the project, or through a
symlink, now gets the same answer there as the plain path rather than
being reported as unowned. find_file_owners uses it for `for-file` and
the library calls behind it.
runner::teams_for_files_from_codeowners is now keyed by each path as
given, so two paths that name the same file stay separate entries. An
absolute path used to come back under its stripped relative form,
although the gem stores each result under the path it passed. Results
and messages still show each path as given. The README's CLI reference
now says that `validate` and `gv` take files, and which path forms
work.
Fixes #145.
This was referenced Nov 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
validate <files>,gv <files>andfor-filenow normalize each path they're given before matching it../app/x.rb,app//x.rb,app/./x.rb,app/../app/x.rband an absolute path through a symlink to the project (macOS's/tmp) all get the answer their plain form gets. Results and messages still show each path as given.Fixes #145.
Why
Paths were matched as typed. The only cleanup was stripping the project root from an absolute path, and the CLI canonicalizes the root. So, with the issue's project:
owned_globs: ["**/*.rb"],validate ./app/valid.rb(and//,/./,.., and the/tmpabsolute path) reported an owned file as unowned.for-file --from-codeowners ./app/valid.rbgave Unowned.{app,config}/**/*.rb,validate ./app/unowned.rband the/tmpabsolute path skipped a file with no owner, so it passed.for-file <tmp abs>/app/valid.rbignored the annotation.optionally_owned_globs,..and symlinked paths were reported as unowned where the plain path was excused.Changes
path_utils::project_relative_pathfinds the project-relative path that a given path names, orNone.cli.rsdoes for--config-path. Most paths stop here, with no file system access.RunConfigmay give it unresolved. Then it resolves the path's ancestors, shortest first, until one is in the project, and keeps the rest of the path as written. Nothing inside the project is resolved, so:Runner::validate_filesuses it for the owned/unowned glob filter and theoptionally_owned_globscheck. That replaces Add optionally_owned_globs to permit files with no owner #135'splain_relativeand its second CODEOWNERS lookup.codeowners_query::teams_for_files_from_codeownerslooks up each path by its normalized form. That coversfor-file --from-codeowners,validate <files>, and the gem'sCodeOwnership.for_fileandteams_for_files.find_file_owners(for-fileandrunner::file_owner_for_file) uses it too.for-file, which the gem uses for files in other gems.runner::teams_for_files_from_codeownersis now keyed by each path exactly as given.result[file_path]under the path it passed.Parser::teams_from_files_paths(crate-private) now returns teams in input order, and the query zips them with the given strings.validateandgvlist[files...], and one line says paths may be relative to the project root or absolute.Relation to #126
#126 attempts the same fix and is open, but it conflicts with main and hasn't moved since its review. This is a fresh implementation that avoids the problems that review found. Keys stay the caller's strings. An IO error while resolving steps up to the next ancestor rather than dropping the path. Nonexistent paths are still checked.
../../acan't be read as an in-projecta. If this lands, #126 can probably be closed.Test plan
cargo fmt --all -- --check,cargo clippy --all-targets --all-features -- -D warningsandcargo testpass (273 passed, the 3 existing ignores).tests/path_normalization_test.rs(13 tests, temp repos built with thetests/commonhelpers) covers:validate,gvandfor-file, including--from-codeownersunowned_globswith./and//.., still reported..after a symlinked directory, resolved lexicallyfor-fileon an annotated file outside the projectteams_for_files_from_codeownerskeyed exactly byx,./x, the absolute path and a symlinked absolute pathproject_relative_pathunit tests cover:./,//,.and..inside and outside the project, including../../optionally_owned_globs_test.rs: the..test now expects the optionally owned heading, listing the path as given. New tests cover a symlinked path and a path outside the project, which is never excused.#[cfg(unix)]and make their own links in temp dirs rather than relying on macOS's/tmp.validate,crosscheck-owners,generateand per-filefor-fileacross every fixture against main. Nothing changed for plain or canonical absolute paths.Follow-ups (not in this PR)
TeamFinder.for_filereturns nil for any path starting with./whenfrom_codeowners: false. That's in the gem.**matches.., so a path that leaves the project with..can still matchowned_globsand CODEOWNERS globs, as on main.for-file .andfor-file <root>pick up a.codeownerfrom above the root (pre-existing).validate_filesstill does one CODEOWNERS lookup per file. Batching them is a separate perf change.