Repository navigation
Conversation
validate! is documented to stage the regenerated .github/CODEOWNERS by default and to leave it unstaged with stage_changes: false, which is what bin/codeownership validate --skip-stage passes. It did the opposite. validate! passed !stage_changes to the extension, whose generate_and_validate took it as skip_stage, as the RBI declared, but handed it unchanged to codeowners-rs's runner::generate_and_validate, whose third parameter is git_stage. That has been the case since the extension was added in #126: the codeowners-rs it pinned already took git_stage (rubyatscale/codeowners-rs#65). codeowners-rs ignores the result of its git add, so nothing ever failed. Pass stage_changes through, and rename the extension's parameter and the RBI's to git_stage to match codeowners-rs, so the flag means the same thing at every layer. The extension's behavior is unchanged, so the tracked bundle needs no rebuild: the fix works with it as committed, and the specs that call RustCodeOwners.generate_and_validate(nil, false) to write a CODEOWNERS file still don't stage it. Add specs that validate! stages CODEOWNERS by default in a git repository and leaves it unstaged with stage_changes: false. Both fail without the fix. They clear git's repo-local environment variables first, so a run from a hook or a linked worktree can't stage into the enclosing repository. The CLI specs only check the arguments that reach validate!, so nothing covered what it does to the index. Add a regression guard for an empty package.yml. rubyatscale/codeowners-rs#142 moves YAML parsing to serde-saphyr, whose unreleased 2.1.0 no longer reads a null document as an empty struct (the fix for bourumir-wyngs/serde-saphyr#233). That PR reads package.yml as an Option so an empty file still means no owner. The spec covers an empty file, a comment-only file and a bare "---". validate! raises if such a file fails to parse, while for_file only prints the error and falls back to the file's annotation, so the spec also checks that for_file prints nothing. It passes today.
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.
CodeOwnership.validate!stages the regenerated.github/CODEOWNERSonly when it is told not to.The bug
validate!is documented to stage the regenerated file by default and to leave it unstaged withstage_changes: false(lib/code_ownership.rb:184-192,README.md:189). The CLI's--skip-stagemaps tostage_changes: false(lib/code_ownership/cli.rb:52and:85). Both are inverted:validate!,bin/codeownership validatevalidate!(stage_changes: false),bin/codeownership validate --skip-stageRoot cause
lib/code_ownership.rb:235called::RustCodeOwners.generate_and_validate(files, !stage_changes), so the second argument meant "skip staging", assorbet/rbi/manual.rbi:12declared:def generate_and_validate(files, skip_stage).skip_stageand passed it unchanged as the third argument of codeowners-rs'srunner::generate_and_validate(ext/code_ownership/src/lib.rs:88-91).git_stage, which means "stage it" (src/runner/api.rs:30andsrc/runner.rs:199-232at v0.5.0, the tagext/code_ownership/Cargo.tomlpins).codeowners-rs added
git_stagein rubyatscale/codeowners-rs#65, and v0.2.7, which the extension pinned when it landed in #126, already had it. So the inversion is as old as the extension. Nothing ever failed: codeowners-rs discards the result of itsgit add(let _ = Command::new("git").arg("add")...output()), and the CLI specs stubvalidate!and only check the arguments it receives.The fix
validate!now passesstage_changesstraight through, and the extension's parameter and the RBI's are renamedgit_stage, matching codeowners-rs, so the flag means "stage it" at every layer. The flip at the extension boundary is what hid this bug, so the fix removes it rather than adding a second negation.The extension's behavior is unchanged; only its parameter name is. So:
lib/code_ownership/code_ownership.bundleneeds no rebuild. The fix works with the bundle as committed.beforeblocks that callRustCodeOwners.generate_and_validate(nil, false)to write a CODEOWNERS file keep their meaning:falsestill doesn't stage.Specs
.validate!when the project is a git repository. Runsgit initin the spec's temp project, then checksgit diff --cached --name-onlyaftervalidate!(expects.github/CODEOWNERS) and aftervalidate!(stage_changes: false)(expects nothing, with the file written). Both examples fail without the fix: the first gets[], the second[".github/CODEOWNERS"]. git runs with the temp project as its working directory and needs no identity, since nothing is committed. The context first clears the variablesgit rev-parse --local-env-varslists, because hooks and linked worktrees exportGIT_DIRandGIT_INDEX_FILE, which would otherwise aim git, including the extension'sgit add, at the repository running the suite..validate!when a package.yml is"","# no keys yet\n"or"---\n". Each must read as a package with no owner:validate!must not raise, which it does when apackage.ymlfails to parse, andfor_file(..., from_codeowners: false)must return the file's annotated team without printing anything. On a parse failurefor_fileonly prints the error and falls back to the annotation, so the spec checks stderr too. This guards the upgrade to a codeowners-rs release with Replace serde_yaml with serde-saphyr to drop unsafe-libyaml codeowners-rs#142, which moves YAML parsing to serde-saphyr. serde-saphyr's unreleased 2.1.0 no longer reads a null document as an empty struct (serde-saphyr#233, fixed by Fix null deserialization for collection targets bourumir-wyngs/serde-saphyr#240), so that PR readspackage.ymlas anOptionto keep an empty file meaning no owner. The spec passes on v0.5.0 and on that PR's head. With serde-saphyr 2.1.0 patched in, it fails ifpackage.ymlis read as a plain struct. It passesstage_changes: falsebecause staging is not what it tests.Verification
Ruby 4.0.7, Rust 1.98.1, git 2.54, macOS arm64.
rake compileoverwrites the tracked bundle, so each run checked which extension$LOADED_FEATURESactually loaded.bundle exec rake(CI's compile and spec step): 101 examples, 0 failures, 1 pending, with the freshly built extension loaded. On main (8ac6a59): 96 examples, 0 failures, 1 pending. The five new examples are the two staging ones and the threepackage.ymlones.bundle exec rspecagainst the tracked bundle as committed, with no compile: 101 examples, 0 failures, 1 pending.GIT_DIRandGIT_INDEX_FILEpointing at a separate repository, as in a hook, the staging examples pass and that repository's index is unchanged.package.yml(owner: [) added to the empty-file cases, itsfor_filecheck fails, so the spec would catch a file that stops parsing.bundle exec srb tc: no errors. RuboCop, which CI doesn't run: no new offenses. The oneStyle/RegexpLiteralatspec/lib/code_ownership_spec.rb:600is already on main.git diff --cached --name-onlyis empty andgit status --porcelainlists at mostlib/code_ownership/code_ownership.bundle, whichrake compilecan overwrite with a dev build. The specs create and stage nothing.Behavior change
Once released,
bin/codeownership validateandCodeOwnership.validate!stage.github/CODEOWNERSby default again, as documented and as the Ruby implementation did before #126, and--skip-stageorstage_changes: falseno longer stages it. A CI step that runsvalidateand thengit diff --exit-codeto catch a stale CODEOWNERS will stop seeing the change, because it is staged; such a step should pass--skip-stageor compare againstHEAD.No version bump is included, so merging releases nothing.