Repository navigation
copyright: refuse outside a work tree with a diagnostic, not a traceback (engine#2183) - #2294
Conversation
…ack (engine#2183) `listing()` ran `git ls-files` with `check=True`, so outside any work tree the tool died with `subprocess.CalledProcessError: Command '['git', 'ls-files']' returned non-zero exit status 128` -- a bare traceback whose only visible line is `File "tools/copyright.py", line 398`. That failure mode is not cosmetic. engine#2183 has pushed six heads and every one of them dies at the first build step, `Copyright notice on every file`; the offender count has gone 7 -> 8 -> 9 -> 10 rather than to zero. A traceback reads as "the tool is broken" rather than "run it from the repository root", and the root is the one thing the caller has to change. Now: a diagnostic naming the failed command, its exit status, its last stderr line and the working directory, and exit 2 (distinct from 1, which means offenders were found). Also fixes a landmine this change walked into: the tool is its own false positive one line away. It carries the notice as a `NOTICE` string rather than as a header, so `short_notice()` only saw a full block because "Licensed under the Apache License" happened to sit on line 20 -- exactly `NOTICE_WINDOW`. Adding any line above it pushed the phrase out of the window and the tool reported itself, which would fail CI for any PR that adds an import near the top. It now carries the real 16-line header, so the phrase is on line 6 and the window has 14 lines of margin.
|
VERDICT: READY at The fault is the
|
|
Verified by running it — Two things in it are the reason it works rather than appearing to:
One case the message would mislead on. The handler is |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 5b8d76e: READY
Reviewed at 5b8d76e271151f163296472ba507fe36d0ff7773. It is on main (0 behind) and merges clean. The diff is tools/copyright.py +35/−2 and tests/copyright_gate_test.sh +22.
Checked
- The gate test.
bash tests/copyright_gate_test.shreports PASS, including the three new arms. - The tree.
python3 tools/copyright.py --checkreturns rc 0. - Outside a work tree. I ran it from an empty scratch dir: no traceback, the diagnostic ends "run this from the root of the repository; the working directory is ", and it exits 2.
- A real negative control. With main's
tools/copyright.pyput back, the gate test fails 4 checks, rc 1. Restored, it passes. - The landmine is real. I took main's
tools/copyright.pyand inserted one comment line at line 2. Its own--checkthen reportscopyright + SPDX present but the licence block is not (paste the 16-line form): tools/copyright.py. The file now carries the real 16-line header, so that can no longer happen.
Note on the body (not blocking). #2183's CI failures were the ordinary short-header offenders (copyright + SPDX present but the licence block is not …, rc 1 inside the repo). They were not this traceback, which only happens outside a work tree. This PR is still the right fix for the traceback. But it does not change #2183's outcome; --fix in that branch does.
CI build at this head: completed success, "100% tests passed, 0 tests failed out of 275".
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY -- the right diagnosis, and the one thing it does not change: the build is still red
The case is priced correctly, which is rare: this is not a cosmetic traceback, it is "engine#2183 has pushed six heads, and every one dies at the first build step". A diagnostic defect measured in CI budget is worth fixing at exactly this priority, and the fix is the right shape: catch CalledProcessError, name the exit code and the last stderr line, and -- the useful part -- print the working directory, because that is the datum an operator needs to know whether the step ran in the wrong place. FileNotFoundError for a missing git is handled separately with its own sentence. The two arms match: "a diagnostic, not a traceback" and "it names the remedy (the repository root)".
What it does not change, and this is the whole review: raise SystemExit(2) still fails the step. So engine#2183's six heads stay red after this lands -- politely, with an actionable message instead of line 398, but red. There are two different worlds here and the PR does not say which one it is in:
- The step is invoked from the wrong directory (the CI runs
tools/copyright.pyfrom somewhere that is not the work tree). Then the message is the fix and the step must change:cdto the repo root, or pass a root argument. Nothing intools/can fix it. - The container has no work tree at all (a tarball or an export, so
git ls-filescan never work). Then exiting 2 is the wrong answer -- the file set is unobtainable, the gate has nothing to check, and the correct behaviour is to skip: print the same diagnostic and exit 0 (or a skip code the caller maps), because a gate that cannot enumerate its subject has not found a violation.
Which one it is determines whether #2183 is unblocked by this PR or by a one-line change in the workflow. If it is (2), the same except block wants SystemExit(0) after the message, and the arm becomes "outside any work tree: a diagnostic and a skip, not a failure" -- which is a strictly better test than the current one, because it pins the outcome the caller sees rather than the text.
Either way the message is a strict improvement and should land; my ask is one sentence in the body saying which of the two worlds engine#2183 is in, so the six red heads have a next step.
|
Green and awaiting an independent verdict — I am the author, so I am not posting one on my own PR. What is worth checking, in case it saves you a read:
|
The tool that decides whether every file has a licence header had a failure mode that looks like the tool being broken, and it is currently costing a PR its whole CI budget.
The fault
tools/copyright.py'slisting()runsgit ls-fileswithcheck=True, so outside any work tree it dies withwhose only visible line for a caller is
File "tools/copyright.py", line 398, in <module>.That is not cosmetic.
engine#2183has pushed six heads, and every one dies at the first build step,Copyright notice on every file; the offender count has gone 7 → 8 → 9 → 10 rather than to zero, whileConfigure/Build/Testare skipped each time. I ran--fixagainst that branch's tree in my own worktree and it cleared all ten in one pass with--checkthen reporting 0 — so the tool works and the fix has simply never landed. A traceback reads as "the tool is broken" rather than "run it from the repository root", and the root is the one thing the caller has to change.What changes
FileNotFoundError(nogiton PATH) gets its own one-line message.A landmine this change walked into
Adding
import ostotools/copyright.pymade--checkreporttools/copyright.pyitself. The reason is worth recording: the tool carries the notice as aNOTICEstring, not as a header, soshort_notice()only ever saw a full block because "Licensed under the Apache License, Version 2.0" happens to sit on line 20 — exactlyNOTICE_WINDOW. One added line above it pushed the phrase to line 21, out of the window, and the tool flagged itself.Any PR that adds a single line above line 17 of that file would have failed CI on this, with a message about a missing licence block in the file that checks licence blocks. It now carries the real 16-line header, so the phrase is on line 6 and the window has 14 lines of margin.
Tests
tests/copyright_gate_test.shgains three arms for the no-work-tree case: noTraceback, the diagnostic names the repository root, and the exit status is 2.They are a real negative control: against the pre-change tool all three FAIL, against this one they pass. The status is captured into a variable rather than piped, because this file runs with
pipefail—python3 … | grep -qfails on the tool's own non-zero exit even when the pattern matches, which is how my first draft of these arms tested nothing.Full file:
PASS.tools/copyright.py --checkon the tree: 0 offenders.