From d66e22237adb94095e03351e683826a8dc5eda36 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 21 Jul 2026 09:59:09 -0700 Subject: [PATCH 1/3] fix: don't let git ignore rules override tracked-file inclusion `generate`'s directory walk honored .gitignore (including a personal global core.excludesFile), .git/info/exclude, and local .gitignore in addition to the tool's own `tracked_files` (git ls-files) check. If a directory is force-tracked despite matching one of those rules, the walker pruned it entirely before the tracked-files check ever ran, silently dropping its ownership entries from CODEOWNERS -- and only on whatever machine's git config happens to match, since a personal global gitignore has nothing to do with what's committed to the project. Disable git_global/git_ignore/git_exclude on the WalkBuilder; inclusion is already handled precisely by tracked_files. `.ignore` files are left enabled since they're this tool's own opt-in exclusion mechanism, unrelated to git tracking status (see tests/fixtures/valid_project/.ignore). --- src/project_builder.rs | 12 ++++++++ tests/global_gitignore_test.rs | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/global_gitignore_test.rs diff --git a/src/project_builder.rs b/src/project_builder.rs index 518702f..e717b65 100644 --- a/src/project_builder.rs +++ b/src/project_builder.rs @@ -58,6 +58,18 @@ impl<'a> ProjectBuilder<'a> { let mut builder = WalkBuilder::new(&self.base_path); builder.hidden(false); builder.follow_links(false); + // Inclusion is determined by `tracked_files` (git ls-files) below, not by + // git's own ignore semantics. Without disabling these, a *global* + // .gitignore (core.excludesFile), local .gitignore, or .git/info/exclude + // rule can prune a whole directory from the walk even when its contents + // are force-tracked in git, silently dropping otherwise-owned files from + // the generated CODEOWNERS depending on the machine's git config. + // `.ignore` files are left enabled: they're this tool's own opt-in + // exclusion mechanism (see tests/fixtures/valid_project/.ignore) and are + // unrelated to git tracking status. + builder.git_global(false); + builder.git_ignore(false); + builder.git_exclude(false); // Prune traversal early: skip heavy and irrelevant directories let ignore_dirs = self.config.ignore_dirs.clone(); diff --git a/tests/global_gitignore_test.rs b/tests/global_gitignore_test.rs new file mode 100644 index 0000000..3d864bd --- /dev/null +++ b/tests/global_gitignore_test.rs @@ -0,0 +1,50 @@ +use assert_cmd::prelude::*; +use std::{error::Error, fs, path::Path, process::Command}; + +mod common; +use common::{git_add_all_files, setup_fixture_repo}; + +const FIXTURE: &str = "tests/fixtures/valid_project"; + +/// A directory can be force-tracked in git even when it matches a +/// gitignore-style rule that lives outside the tracked tree (a personal +/// global `core.excludesFile`, or a local, uncommitted `.git/info/exclude` +/// entry). `generate` must still emit ownership for such directories: +/// inclusion is decided by `tracked_files` (git ls-files), so also honoring +/// git's own ignore semantics at the directory-walk level can silently prune +/// tracked, owned files depending on the machine's git config alone -- +/// nothing committed to the repo. `.git/info/exclude` is used here as a +/// reliable stand-in for a developer's global gitignore, which is what +/// originally surfaced this bug (see PR discussion). +#[test] +fn test_generate_ignores_local_exclude_rules_for_tracked_directories() -> Result<(), Box> { + let temp_dir = setup_fixture_repo(Path::new(FIXTURE)); + let project_root = temp_dir.path(); + git_add_all_files(project_root); + + let exclude_path = project_root.join(".git/info/exclude"); + let mut existing = fs::read_to_string(&exclude_path).unwrap_or_default(); + existing.push_str("\nruby/app/payroll/\n"); + fs::write(&exclude_path, existing)?; + + let codeowners_path = project_root.join("tmp/CODEOWNERS"); + fs::create_dir_all(codeowners_path.parent().unwrap())?; + + Command::cargo_bin("codeowners")? + .arg("--project-root") + .arg(project_root) + .arg("--codeowners-file-path") + .arg(&codeowners_path) + .arg("--no-cache") + .arg("generate") + .assert() + .success(); + + let actual_codeowners = fs::read_to_string(&codeowners_path)?; + assert!( + actual_codeowners.contains("/ruby/app/payroll/**/** @PayrollTeam"), + "expected ruby/app/payroll ownership to survive a local .git/info/exclude rule matching it, got:\n{actual_codeowners}" + ); + + Ok(()) +} From 04a420db3ca1928095a6354dd8c02afe3312ff63 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 21 Jul 2026 11:18:15 -0700 Subject: [PATCH 2/3] fix: replace archived actions-rs/audit-check with rustsec/audit-check The audit job was failing to install cargo-audit: the archived actions-rs/audit-check@v1 has no toolchain pin, and the latest cargo-audit's dependency graph (kstring 2.0.4) now requires a newer rustc than this repo's pinned rust-toolchain.toml (1.89.0) provides. Switch to the actively maintained fork, rustsec/audit-check, and override RUSTUP_TOOLCHAIN to stable for just this job so `cargo install cargo-audit` isn't stuck on the pin used to build the actual crate. Also bump version to 0.3.4 for release. --- .github/workflows/audit.yml | 8 +++++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 07ccb2e..9945216 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -12,8 +12,14 @@ on: jobs: audit: runs-on: ubuntu-latest + # cargo-audit's dependency tree can require a newer rustc than this repo's + # pinned rust-toolchain.toml (used to build the actual crate). Override + # with the runner's default stable toolchain just for this job so + # `cargo install cargo-audit` isn't stuck on our pin. + env: + RUSTUP_TOOLCHAIN: stable steps: - uses: actions/checkout@v4 - - uses: actions-rs/audit-check@v1 + - uses: rustsec/audit-check@v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 386702e..17062df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,7 +179,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "codeowners" -version = "0.3.3" +version = "0.3.4" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 7eb1479..0a83a15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codeowners" -version = "0.3.3" +version = "0.3.4" edition = "2024" [profile.release] From b335c0f40ec6530829333ca884e3299348445885 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 21 Jul 2026 11:29:13 -0700 Subject: [PATCH 3/3] fix: update crossbeam-epoch and tracing-subscriber to patched versions cargo-audit flagged both as RUSTSEC advisories once the audit job could actually run (RUSTSEC-2026-0204: invalid pointer dereference in crossbeam-epoch <0.9.20; RUSTSEC-2025-0055: ANSI escape injection in tracing-subscriber <0.3.20). --- Cargo.lock | 77 +++++++++++++----------------------------------------- 1 file changed, 18 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17062df..46f20b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,7 +118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.4.8", + "regex-automata", "serde", ] @@ -237,15 +237,11 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] @@ -403,8 +399,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.8", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] @@ -438,7 +434,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.8", + "regex-automata", "same-file", "walkdir", "winapi-util", @@ -526,11 +522,11 @@ dependencies = [ [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -539,15 +535,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - [[package]] name = "memoize" version = "0.5.1" @@ -585,12 +572,11 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -608,12 +594,6 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "path-clean" version = "1.0.1" @@ -718,17 +698,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -739,15 +710,9 @@ checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -803,12 +768,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "semver" version = "1.0.17" @@ -992,14 +951,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local",