Repository navigation
ci: lint the pull request diff instead of the whole module - #17
Merged
Merged
Conversation
The pre-commit workflow fetches full history and passes the pull request
base and head to pre-commit, but the golangci-lint hook never used either.
The upstream dnephin hook declares pass_filenames: false and hard-codes:
golangci-lint run --new-from-rev HEAD --fix
In CI the checkout is already at the pull request head with a clean tree, so
--new-from-rev HEAD diffs HEAD against itself. The baseline collapses and
pre-existing findings anywhere in the module are attributed to whichever
pull request happens to touch a Go file. Two contributor pull requests were
blocked this way by a finding in api/drive that neither of them touched.
Replace the hook with a local one calling lint_diff.py, which takes its
baseline from PRE_COMMIT_FROM_REF. pre-commit exports that variable when
invoked with --from-ref, so CI compares against the merge target and a pull
request is judged solely on its own diff. Run from a git hook the variable
is unset and the script falls back to HEAD, which is the correct baseline
there because the developer is linting uncommitted work.
This keeps one hook behaving correctly in both environments rather than
linting every source file on every commit, and it makes the refs the
workflow already sets up actually load-bearing.
The hook is Python rather than shell. pre-commit requires Python, so it is
guaranteed present, whereas a POSIX shell is not: this module is built for
windows/amd64 and windows/arm64 and contributors may be developing there.
shutil.which resolves the .exe suffix, so no platform special casing is
needed.
The hook fails loudly when the baseline revision cannot be resolved, which
is what a shallow clone would produce, rather than silently degrading to a
whole-module lint.
Ignore Python bytecode, since the hook scripts now live in the repository.
Assisted-by: Kiro <noreply@kiro.dev> (1.0.116)
major0
force-pushed
the
ci/lint-diff-against-pr-base
branch
from
September 23, 2026 13:39
a8f09df to
7e3e2cb
Compare
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
The Pre-commit workflow already does the hard part: it checks out with
fetch-depth: 0, fetchesmain, and passes the pull request base and head topre-commit. The golangci-lint hook then ignored both.
The upstream dnephin hook declares
pass_filenames: falseand hard-codes:In CI the checkout already sits at the pull request head with a clean tree, so
--new-from-rev HEADdiffsHEADagainst itself. The baseline collapses, andpre-existing findings anywhere in the module get attributed to whichever pull
request happens to touch a Go file. #12 and #13 were both blocked this way by a
finding in
api/drivethat neither touched.That flag is correct for the hook's intended use — a local
pre-commitrun,where
HEADis the right baseline for uncommitted work. It is simply wrong inCI.
Changes
Replace the hook with a local one calling
.github/scripts/lint_diff.py, whichtakes its baseline from
PRE_COMMIT_FROM_REF. pre-commit exports that variablewhen invoked with
--from-ref, so:--from-ref <base>)HEAD(fallback)One hook, correct in both environments. This deliberately does not switch to
linting every file on every commit — the goal is that the refs the workflow
already sets up become load-bearing.
This follows the existing precedent in
.pre-commit-config.yaml, wherego-unit-testswas already replaced by a local hook for the same class ofreason: an upstream hard-coded flag that does not suit this repository.
Also ignore Python bytecode, now that hook scripts live in the repository.
Why Python rather than shell
pre-commit requires Python, so it is guaranteed present. A POSIX shell is not:
this module builds for
windows/amd64andwindows/arm64, so contributors maybe developing on Windows.
shutil.whichresolves the.exesuffix, so there isno platform special casing.
Failure behaviour
The hook fails loudly when the baseline revision cannot be resolved — what a
shallow clone would produce — rather than silently degrading to a whole-module
lint, which is the behaviour it exists to prevent.
Testing
Reproduced CI against #12: checked out
origin/pr/12, applied this commit ontop, and ran pre-commit exactly as the workflow does.
The hook ran and passed rather than being skipped, so this is not a vacuous
result. #12's own new code also lints clean under golangci-lint 2.13.2.
Also verified that
language: pythonbuilds its hook environment correctly, andthat the hook still respects
types: [go]— it is skipped on commits touchingno Go files.
Related
This is what unblocks #12 and #13. #16 removes the
api/drivedead code on itsown merit, but is not required for either to go green.
Still outstanding: golangci-lint is pinned in two places that disagree —
lint.ymlrequests v2.11.4 whileinstall-go-tools.shpins v2.13.2 — which iswhy
LintandPre-commitcan reach opposite verdicts on identical code. Thatis a separate PR.