Repository navigation
Fix inverted stage_changes in CodeOwnership.validate! - #187
Merged
Merged
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.
The fix makes every real validate! call with default arguments run git add, not only the new staging specs. Two cli_spec examples do that, and under a hook's GIT_DIR they staged .github/CODEOWNERS into the enclosing repository, since the scrub only wrapped the staging context. Move it to a before(:suite) hook so every example's git runs against its own temp project. Split the empty package.yml example in two, so validation and the for_file parse-error check pass or fail separately, without nesting one expect inside another.
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. Abefore(:suite)hook inspec/spec_helper.rbfirst 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. It covers the whole suite, not just this context, because the fix makes every realvalidate!call with default arguments stage, including two incli_spec.rb..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. Validation and thefor_filecheck are separate examples, so each fails on its own. They pass on v0.5.0 and on that PR's head. With serde-saphyr 2.1.0 patched in, they fail ifpackage.ymlis read as a plain struct. They passstage_changes: falsebecause staging is not what they test.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): 104 examples, 0 failures, 1 pending, with the freshly built extension loaded. On main (8ac6a59): 96 examples, 0 failures, 1 pending. The eight new examples are the two staging ones and two for each of the threepackage.ymlcases.bundle exec rspecagainst the tracked bundle as committed, with no compile: 104 examples, 0 failures, 1 pending.GIT_DIR,GIT_INDEX_FILEandGIT_WORK_TREEpointing at a separate repository, as in a hook, the whole suite passes and that repository's index is unchanged. Before the scrub moved tospec_helper.rb, the twocli_spec.rbexamples that run the realvalidate!staged.github/CODEOWNERSinto it.package.yml(owner: [) added to the empty-file cases, both its examples fail, 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.bundle exec rspecgives the same 104 examples, 0 failures, 1 pending,bundle exec srb tcreports no errors, and the command CD uses to read the version prints 2.2.1, which RubyGems doesn't have yet.Behavior change
From 2.2.1,
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 runvalidate --skip-autocorrect, which raises when CODEOWNERS is stale, or pass--skip-stageor compare againstHEAD.Release
This bumps the gem to 2.2.1, a patch release because this is a bug fix. CD publishes any version RubyGems doesn't have once CI passes on main, so merging releases 2.2.1. The release also ships what main has gained since 2.2.0, including #186's updates to the extension's Rust dependencies, among them rb-sys 0.9.130 and serde_magnus 0.11.