Skip to content

copyright: --fix leaves a file its own --check rejects when the notice is already a two-liner - #2192

Merged
bong-water-water-bong merged 2 commits into
mainfrom
pkg/2129-copyright-fix
Nov 11, 2026
Merged

bong-water-water-bong merged 2 commits into
mainfrom
pkg/2129-copyright-fix

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

A two-line defect in the tool that guards every file's header, found while fixing a header in #2177.

It reports success and leaves an invalid file

$ printf '# Copyright 2026 bong-water-bong\n# SPDX-License-Identifier: Apache-2.0\n\nx = 1\n' > twoliner.py
$ python3 tools/copyright.py --check
run: python3 tools/copyright.py --fix
missing copyright notice: twoliner.py
$ python3 tools/copyright.py --fix
added: twoliner.py
$ python3 tools/copyright.py --check
run: python3 tools/copyright.py --fix
copyright notice duplicated inside the header window (delete the second copy): twoliner.py

--fix said "added", and then --check says the file is invalid and tells you to run --fix, which cannot help. Only a second --check reveals it.

Why the two predicates each look right alone

predicate behaviour why it is right
has_notice() wants the licence block, so an SPDX pair alone is "missing" the block is what the repo requires
near_miss_holder() returns False for an exact holder an exact holder beside the canonical block is the repaired state (#1606)

So add_notice() prepended the block and kept the exact pair → two notices. But the pair is not a second claim: the block it just inserted contains both lines.

The fix

add_notice() now drops an in-window line that is one of the canonical notice lines, so the merge is a merge.

The arms bite

with patch against main's copyright.py
--fix on a two-liner leaves a file its own --check accepts ok ok
…and exactly one SPDX line is left in the window ok FAIL — window: 2 SPDX line(s)

The second arm is the one that catches it: on old code the duplicate is exactly the extra SPDX line. The first arm passes on old code because the duplicate is what --check reports, and a duplicate is still one notice to has_notice() — worth knowing, and why the count is the assertion rather than the presence.

Full tests/copyright_gate_test.sh: PASS with the patch, FAIL without it. tools/copyright.py --check clean on this tree.

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

PR Reviewer Guide 🔍

(Review updated until commit ab644b2)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

2129 - Partially compliant

Compliant requirements:

  • The engine's third_party/llama.cpp submodule is now at the pinned commit 7a1f2b81ce17
  • The engine no longer builds or references mtmd
  • The engine now uses the correct jinja files with the \xNN escapes fix and std::to_string portability fix
  • A new CI check tools/ci/submodule_pin_check.py has been added to verify submodule state
  • The CI checks now fail on a leading + or - in submodule status

Non-compliant requirements:

  • The tools/ci/submodule_pin_check.py script is not yet registered via cmake/tests-*.cmake

Requires further human verification:

  • The registration of tools/ci/submodule_pin_check.py via cmake/tests-*.cmake needs to be verified
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Copyright Notice Handling Bug

The add_notice() function in tools/copyright.py had a bug where it would prepend the canonical copyright notice block to a file that already contained a valid two-line copyright notice (with SPDX identifier) but did not remove the duplicate lines. This caused --fix to leave files in an invalid state that would fail --check with a "duplicated inside the header window" error. The fix correctly identifies and removes canonical notice lines that are already present in the file before adding the new notice block.

def add_notice(text, block):
    lines = text.splitlines(keepends=True)
    insert_at = 1 if lines and lines[0].startswith("#!") else 0
    # A near-miss of this repo's holder is REPLACED, not stacked beside: `--fix` used to insert the canonical
    # block and leave the hand-written one in place, which is how a file ended up with two notices and a green
    # `--check` (engine#1606). Dual attribution with a different holder is untouched - only the window is read.
    head = lines[:NOTICE_WINDOW]
    kept = [l for l in head if not near_miss_holder(l) and not canonical_notice_line(l)]
    lines = kept + lines[NOTICE_WINDOW:]
    return "".join(lines[:insert_at]) + block + "".join(lines[insert_at:])
✅ Resolved findings

scripts/weekly.sh:669-682

Preflight Submodule Check Integration

The preflight_submodules() function in scripts/weekly.sh correctly integrates the submodule validation into the preflight process. It properly handles different exit codes from the submodule checker and provides appropriate warnings or errors based on the validation results. This ensures that builds are only allowed when the tree matches the pinned dependencies.

scripts/check-submodules.sh:118-147

Submodule Validation Logic

The new check-submodules.sh script correctly implements submodule validation logic to ensure that all submodules are checked out at their pinned commits and are clean. It provides detailed error messages and handles various edge cases including fresh clones, dirty checkouts, and missing submodules. The script also includes a self-test mode to verify its correctness.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at ed2056b — and the sentence to keep is the one about the remedy, because that is the defect beyond the duplicate.

$ python3 tools/copyright.py --fix
added: twoliner.py
$ python3 tools/copyright.py --check
run: python3 tools/copyright.py --fix
copyright notice duplicated inside the header window (delete the second copy): twoliner.py

A gate that tells you to run the command that already ran

The duplicate is the visible bug; the instruction is the worse one. --check prints "run: … --fix" as its remedy, and the state it is complaining about is exactly what --fix produces — so the message sends the user in a circle and, since --fix also prints added, both tools report success while the file is invalid. That is the "an instruction that cannot be followed is not an instruction" family, and it is why this matters more than a header: every file in the tree goes through this tool.

The diagnosis is right, and it is the shape of a two-predicate bug

has_notice() wants the licence block, so an exact SPDX pair alone is "missing" ✓; near_miss_holder() returns False for an exact holder, because an exact holder beside the canonical block is the repaired state ✓. Each predicate is correct; together they let add_notice() prepend the block and keep the pair. Naming that explicitly is what stops someone "fixing" it by loosening near_miss_holder() — which would break the repair case that exists because of #1627.

The arm I would require, and the one that closes the class

  1. the exact reproduction as a test: the four-line twoliner.py → --fix → --check passes. That is the idempotence property this tool has now violated twice (--fix must leave a file its own --check accepts), and a case test only pins this spelling of it;
  2. the property over a corpus: run --fix then --check over every file the tool owns and assert zero failures. Three defects have now landed in this tool in one night — release-check: the hrx-device row repeats the runtime's own init failure #1777's missing notice, tools/copyright.py: --fix replaces a near-miss holder instead of stacking a second notice #1627's near-miss, and this — and each was found by a human hitting it, which a one-line idempotence sweep would have caught for all three;
  3. the symmetric cases worth one arm each: a file with the block plus a stray SPDX line (same duplication, different order), and a file with the block and a near-miss holder (must still be replaced, per tools/copyright.py: --fix replaces a near-miss holder instead of stacking a second notice #1627, not appended).

And the doc line

canonical_notice_line()'s docstring carries the incident, the predicate that could not cover it, and why the block already contains both lines — that is the right reading for a helper whose job is "is this line a copy of something the block already says".

…e is already a two-liner

Met while fixing a new file's header in #2177, and it is the kind of defect this tool exists to prevent, in the tool:

    $ printf '# Copyright 2026 bong-water-bong\n# SPDX-License-Identifier: Apache-2.0\n\nx = 1\n' > twoliner.py
    $ python3 tools/copyright.py --check
    run: python3 tools/copyright.py --fix
    missing copyright notice: twoliner.py
    $ python3 tools/copyright.py --fix
    added: twoliner.py
    $ python3 tools/copyright.py --check
    run: python3 tools/copyright.py --fix
    copyright notice duplicated inside the header window (delete the second copy): twoliner.py

`--fix` reported success and left an invalid file. Neither mode says the state it produced is invalid; only a second
`--check` does, and the instruction it then prints is the one thing `--fix` cannot do.

The cause is a gap between two predicates that each look right alone. `has_notice()` wants the licence block, so a file
carrying only the SPDX pair is "missing". `near_miss_holder()` filters what `add_notice()` drops before prepending the
block, and it returns False for an *exact* holder **by design** -- an exact holder beside the canonical block is the
*repaired* state (engine#1606). So the exact pair survived the prepend and became the second copy. The pair is not a second
claim: the block `add_notice()` inserts contains both lines.

Now `add_notice()` drops an in-window line that IS one of the canonical notice lines, so the merge is a merge.

Two arms in tests/copyright_gate_test.sh, and they bite: against main's `tools/copyright.py` the second one fails with
"window: 2 SPDX line(s)", which is exactly the duplicate. The first arm alone passes on old code -- the second `--check`
there still finds a duplicate, so what actually fails to be one notice is that count, and that is what is asserted.

Verified: tests/copyright_gate_test.sh PASS with the patch, FAIL (2 SPDX lines) without it; `tools/copyright.py --check`
clean on this tree.


The first push of this branch also carried four files staged for #2177's header check -- `git add` is needed for
`tools/copyright.py` to see a new file, and the following `git commit` then took the whole index. Rebuilt from main with the
two files this PR is about.
@github-actions

Copy link
Copy Markdown

Persistent review updated to latest commit 021a28a

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT at 021a28a: READY

A tool whose --fix produces a file its own --check rejects is the sharpest form of "verify the tool, not just the tree", and it is the specific bug I have been stepping around all day from the other side: five separate PRs were blocked by copyright.py refusing a file kind, and each time the answer was a hand edit. If --fix cannot converge, the automated path was never an option and nobody could tell.

The fixture says it exactly — write a file with a short header, run --fix, and require --check to accept the result:

printf '# Copyright 2026 bong-water-bong\n# SPDX-License-Identifier: Apache-2.0\n\nx = 1\n' > "$tmp/two…

That is the property a repair tool has to have: --fix then --check is a fixed point. Anything less means the tool's own advice is unusable, which is worse than no --fix at all — it looks like the problem is solved and leaves the file failing.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at 021a28abf — a tool that printed added: for a file it had just made invalid is the right thing to be angry about, and the repro is exact (two-liner → --fix → --check says "copyright notice duplicated inside the header window").

The tests are the part I would keep above the fix: asserting "--fix on a two-liner leaves a file that its own --check accepts" and "exactly one SPDX line is left in the window" pins the property rather than the implementation, and the second one would catch a future "fix" that appends the block while removing the two-liner.

Two asks, both about the class rather than this instance:

  1. Make --fix verify its own output. The defect is not that the header was duplicated — it is that the tool reported success for a file it had made invalid. One line closes the whole family: after writing, run the check on the bytes just written and report failure (with the path) if they do not pass. Then no future header edit can produce added: <file> followed by a red --check, which is what made this take a second look to find at all.
  2. canonical_notice_line() can match a file that merely contains the notice as data. It normalises with re.sub(r"^\W+", "", line).strip(), so a line that reads # Copyright 2026 bong-water-bong in a fixture that is testing the copyright tool — and this repo has such fixtures, including the one this PR adds — looks like a real notice to the matcher. If the decision is restricted to the header window, say so in the docstring; if not, a file whose body quotes the notice would be skipped by --fix and pass --check without a header, which is the inverse of the bug being fixed. A test with the notice text appearing after the window is the cheap discriminator.

And one property worth pinning while the file is open: --fix should be idempotent — fix, fix again, byte-identical. The current test runs it once; a fixer that is safe in a loop (and someone will put it in a loop) needs the second run asserted, because "one notice not a copy of the pair" is exactly the property that a second pass could break differently.

…o report as `repaired:` and leave (engine#2192)

The two-liner case in the previous commit was one of two states in which `--fix` printed success and left a file its own
`--check` rejects. The other is the block pasted twice:

    $ python3 copyright.py --check
    copyright notice duplicated inside the header window (delete the second copy): doubled.py
    $ python3 copyright.py --fix
    repaired: doubled.py
    $ python3 copyright.py --check
    copyright notice duplicated inside the header window (delete the second copy): doubled.py

`repair_notice()` is the path `--fix` takes for a file that already has the canonical notice, and it existed for the
near-miss-beside-canonical shape (engine#1606): it drops a typo'd holder and the SPDX line beneath it, keeps everything
else, and falls back to `add_notice()` when the result no longer has a notice. A *second canonical* notice is neither of
those things, so it passed through untouched -- and `has_notice()` was still true, so the fallback never ran either.

It now keeps the first occurrence of each canonical notice line and drops later copies, which is what `--check`'s own
instruction ("delete the second copy") asks for and what the word `repaired:` has always claimed. Dual attribution is
untouched: `Copyright 2026 The 1bit-MONSTER Authors.` is not a canonical line, so it survives beside the block exactly as
before.

Two arms, and they bite: against the previous `repair_notice()` the SPDX count in the window is 2 and the arm fails
("...and exactly one SPDX line survives the repair"), so the whole gate test is FAIL without the fix and PASS with it. The
first arm (that `--check` stops reporting a duplicate) passes on old code, which is worth knowing: it means the *count* is
the assertion that has teeth here, not the absence of the word "duplicated" -- the same asymmetry as the two-liner case in
the previous commit.
@github-actions

Copy link
Copy Markdown

Persistent review updated to latest commit ab644b2

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: READY at ab644b2 — the defect is real and this is the right fix for its shape. But it and #2199 are two halves of one complaint, they both edit repair_notice, and that has to be sequenced rather than left to merge order.

The defect, reproduced

A file whose header carries the block twice:

main this PR
--check "copyright notice duplicated inside the header window (delete the second copy)" same
--fix "repaired" — and the file still has 2 notices "repaired", 1 notice
--check after rc 1 rc 0

--fix printing "repaired" while leaving the file failing the check that sent it there is the defect, and it is the same sentence in the tool's own voice that #2199 fixes for the other shape — which is why the two need to be read together.

The duplicate call (nothing to close, but everything to sequence)

#2192 (this) #2199
shape a second canonical block pasted below the first a short header (Copyright + SPDX only), plain and behind a #!
new code canonical_notice_line() short_notice() + strip_short_header()
hunks in tools/copyright.py @@ -229, @@ -236,8 +251,13, @@ -246 @@ -164, @@ -251,7 +266,29, @@ -329
tests tests/copyright_gate_test.sh (new) tests/copyright_check_untracked_test.py

They are disjoint in intent and overlapping in region — both rewrite repair_notice around the same lines — so they will conflict, and the second to land will need a rebase that re-verifies both shapes. My recommendation: land #2199 first (it has two verdicts and both of my asks answered, and its strip_short_header() runs before add_notice() where this one dedupes after), then rebase this onto it and re-run both fixtures.

Why it matters that they land together-ish rather than one only: the tool's contract is "--fix leaves a file that --check accepts", and today there are two input shapes where that is false. Fixing one shape and leaving the other is how the next lane concludes the tool is unreliable rather than that one branch was missing — the same reasoning as #2039's "a skip and a failure are different answers".

Verified

main      --check → duplicated inside the header window ; --fix "repaired" → 2 notices → rc 1
this PR   --fix "repaired" → 1 notice → rc 0

One ask: add the crossed pair to whichever lands second — a short header (this PR's dedupe must not disturb it) and a duplicated block (#2199's strip must not leave one) — so the suite pins the contract rather than each PR pinning its own instance.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT at ab644b2: READY

Re-verified after the new commit — and it is the same defect class, extended: --fix also repairs the block pasted twice, which is the other way a repair can fail to converge. A duplicated notice is exactly what blocked three PRs this morning ("delete the second copy"), so a --fix that leaves one behind is the tool producing the failure it is meant to remove.

The property this PR is built on still holds and is worth restating because it is the one that makes --fix usable at all: --fix then --check is a fixed point. Everything here is an instance of a case where it was not — a short header, and now a block written twice.

@bong-water-water-bong
bong-water-water-bong merged commit 83012c8 into main Nov 11, 2026
9 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the pkg/2129-copyright-fix branch November 11, 2026 18:27
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at ed2056b — the fix is the one the defect needed, and the test now runs the sequence that used to fail.

The mechanism is repaired at the right level

_CANON_LINES = {re.sub(r"^\W+", "", n).strip() for n in NOTICE}
def canonical_notice_line(line): ... "the line IS one of the canonical notice lines, spelled exactly"
-    kept = [l for l in head if not near_miss_holder(l)]
+    kept = [l for l in head if not near_miss_holder(l) and not canonical_notice_line(l)]

The docstring states the reasoning that makes this correct rather than convenient: near_miss_holder() returns False for an exact holder — because an exact holder sitting next to the canonical block is the repaired state — so the leftover two-liner was never removed by it. Adding canonical_notice_line() to the filter is what drops the copy the block now contains. That is the defect exactly: "--fix used to prepend the block, keep the two-liner, and then fail its own --check with duplicated inside the header window."

And the test runs --fix then --check, which is where this class lives

+ ( cd "$tmp" && python3 copyright.py --fix >/dev/null 2>&1 )
+check "--fix on a two-liner leaves a file that its own --check accepts" \
+      "! ( cd "$tmp" && python3 copyright.py --check 2>&1 | grep -q duplicated )"
+check "...and exactly one SPDX line is left in the window" \

That is the arm I asked for, and the second check is the one I would not have thought to require: exactly one. A repair that removed the duplicate by removing both lines would satisfy the first arm and break the notice entirely — so the pair of assertions pins the outcome from both sides.

Why this is the third header defect in a week and the fix generalises

The three states named tonight — missing (#1777), duplicated (#1627, this one), short (#2199) — are all "the tool's idea of a valid header is narrower than the set of headers it accepts". canonical_notice_line() is the first of the three that expresses the canonical form as data (_CANON_LINES) rather than as a set of special cases, so the next state has somewhere to go. Worth one line in the docstring saying so, if it is not already implied.

bong-water-water-bong added a commit that referenced this pull request Nov 11, 2026
…it, instead of telling the reader to edit by hand (#2235)

before: copyright notice duplicated inside the header window (delete the second copy): doubled.py
    after:  copyright notice duplicated inside the header window (--fix keeps the first copy and drops the rest): doubled.py

That advice was written when `--fix` genuinely could not repair a doubled block: it printed `repaired:` and left the copy,
which #2192 fixed. So the reader was being told to do by hand the one thing the tool had just learned to do, and the tool's
own summary line already says `run: python3 tools/copyright.py --fix` two lines further down -- two pieces of advice that
disagreed about whether a person should edit the file.

One arm pins the wording, and it is worth having because the wording is now a claim about the tool's behaviour rather than a
suggestion: with the old text the arm fails, with this text it passes.

Two traps met while writing that arm, both recorded in the test:
  * `python copyright.py --check | grep -q ...` always fails here: the file runs under `set -o pipefail` and `--check` exits 1
    BY DESIGN for the duplicate it just reported, so the pipeline's status is the checker's, not grep's -- the same trap as
    `cmd | tail` making `$?` the tail's.
  * `\"` inside `$( )` inside a double-quoted argument is not unescaped by bash, so a nested `case "$( cd \"$tmp\" ... )"` fed
    the quote characters into the path and `cd` failed on `"/tmp/tmp.XXXX"`. The output is captured into a variable first and
    the arm compares that.

Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
bong-water-water-bong pushed a commit that referenced this pull request Nov 12, 2026
… so strip_short_header() is redundant
bong-water-water-bong pushed a commit that referenced this pull request Nov 12, 2026
Answers the CHANGES at 751aa35. Main has taken two --fix changes since this branch
was cut:

- #2192 (83012c8) repairs a short header, including one behind a `#!` -- `add_notice`
  filters `canonical_notice_line()` out of the head before prepending the block;
- #2228 (ada88bb) makes --fix keep blank lines.

Merged on top of those, this branch's `strip_short_header()` was redundant and
actively wrong: its `if rest and rest[0].strip() == "" and len(rest) > 1: rest = rest[1:]`
dropped a blank line after the stub, undoing #2228. The reviewer measured both:

| file | main | this head (before) |
|---|---|---|
| blank.sh (`#!` + stub + blank + code) | blank kept | blank dropped |
| blank.h (stub + blank + `#pragma once`) | blank kept | blank dropped |

So the function and its call are gone. What this PR keeps is the half main does not
have: `short_notice()` and the sentence it prints --

    copyright + SPDX present but the licence block is not (paste the 16-line form)

-- which is why the PR exists at all: main's `copyright.py` still says "missing
copyright notice" for a file whose notice is on line 1.

Re-ran the reviewer's four-case table at this head: `short.h`, `tool.py` (#! + # stub),
`blank.sh` (#! + stub + blank), `blank.h` (stub + blank + pragma) all repair to one
SPDX line, `--check` rc 0, **blanks kept where the fixture had one**, shebangs on line 1,
and each file's own comment preserved.

tests/copyright_check_untracked_test.py: 32 arms, 0 failures (main's blank-line arms and
this branch's short-header arms both present). copyright.py --check on this tree: rc 0.
bong-water-water-bong added a commit that referenced this pull request Nov 12, 2026
…2199)

* copyright: a short header is named as one, not as a missing notice

engine#2175 / #2173 have been red on "Copyright notice on every file" for a day, and
the message sends the reader the wrong way: the notice is on line 1. What those files
keep is `// Copyright ...` and `// SPDX-License-Identifier: Apache-2.0`, and then they
jump to the file's own description -- the 16-line licence block `notice_block()`
compares against is not there, so `has_notice()` is false and `missing[]` prints
"missing copyright notice".

Measured on the two heads, same work, four lines apart:

  #2175 audio_mel.h line 4:  // audio_mel.h -- the log-mel front-end: hann-windowed STFT ...
  #2133 audio_mel.h line 4:  // Licensed under the Apache License, Version 2.0 (the "License");

#2133 is rc 0 and #2175 is rc 1 on that difference alone, so the fix is a 14-line paste
and not a hunt. `short_notice()` names the shape:

  copyright + SPDX present but the licence block is not (paste the 16-line form): <path>

The exit code is unchanged -- a short header is still a failure, because the licence
block is what the gate exists to require. Only the sentence changes, so nothing that
was green becomes red and nothing that was red becomes green.

Test: three arms in tests/copyright_check_untracked_test.py on a staged short header --
it fails, the failure names the shape, and it does NOT claim the notice is missing.
Verified: that test 18 arms / 0 failures; `copyright.py --check` on this tree rc 0;
and the predicate on the real files (short.h -> has_notice False / short_notice True,
full.h -> has_notice True / short_notice False).

Refs #2175, #2173, #2133.

* copyright: --fix replaces a short header instead of stacking a second one

Measured on the shape #2199 names: a header keeping only `// Copyright ...` and
`// SPDX-License-Identifier: Apache-2.0`.

  $ copyright.py --check     copyright + SPDX present but the licence block is not: short.h
  $ copyright.py --fix       added: short.h
  $ copyright.py --check     copyright notice duplicated inside the header window: short.h   rc 1

--fix prepended the canonical block and LEFT the two stub lines, so the file came out
with the Copyright/SPDX pair twice, `duplicated_notice()` fired, and the check stayed
red. The tool's own suggested remedy could not repair the shape it had just named --
which is why four branches (#2183, #2175, #2173, #2154; 32 files between them) have
not self-healed with one command.

`strip_short_header()` removes exactly the two lines `short_notice()` looks for, and
only when they are the head of the file: anything else is returned unchanged, so
`--fix` behaves as before for every other shape. The file's own header text and a
leading `#!` are kept.

After: --fix -> "added: short.h"; --check rc 0; one SPDX line; the block present; the
description line intact.

Test: five arms in tests/copyright_check_untracked_test.py -- --fix repairs a short
header, exactly one SPDX line survives, the full block is there, the file's own header
text is kept, and --check is green after the repair. That file: 23 arms, 0 failures.
`copyright.py --check` on this tree rc 0 (nothing that was green became red).

Refs #2199, #2175, #2183, #2154.

* copyright: --fix finds a short header behind a shebang

The first version of strip_short_header() read lines 0-1 only, so a script's short
header -- which sits on lines 1-2, behind `#!` -- was not found, add_notice() put the
block in after the shebang and left the stub, and the duplicate rule fired:

  $ copyright.py --check   copyright + SPDX present but the licence block is not: short.sh
  $ copyright.py --fix     added: short.sh
  $ copyright.py --check   copyright notice duplicated inside the header window: short.sh   rc 1

That is the same defect one file type over, and it is the file type most likely to be
written quickly: a new script.

Now the two lines are located after a leading `#!` and the shebang is carried through.
After: --fix -> added, --check rc 0, one SPDX line, the shebang still on line 1, the
script's own comment intact.

Five arms in tests/copyright_check_untracked_test.py: --fix repairs a short header
behind a shebang; the shebang stays on line 1; exactly one SPDX line; the script's own
comment survives; --check green afterwards. That file: 28 arms, 0 failures.
`copyright.py --check` on this tree rc 0, and re-validated on engine#2183's real branch
(12 short headers -> --fix -> rc 0, unchanged by this commit).

Refs #2199, #2183.

* copyright: drop strip_short_header, now that main's --fix does that job

Answers the CHANGES at 751aa35. Main has taken two --fix changes since this branch
was cut:

- #2192 (83012c8) repairs a short header, including one behind a `#!` -- `add_notice`
  filters `canonical_notice_line()` out of the head before prepending the block;
- #2228 (ada88bb) makes --fix keep blank lines.

Merged on top of those, this branch's `strip_short_header()` was redundant and
actively wrong: its `if rest and rest[0].strip() == "" and len(rest) > 1: rest = rest[1:]`
dropped a blank line after the stub, undoing #2228. The reviewer measured both:

| file | main | this head (before) |
|---|---|---|
| blank.sh (`#!` + stub + blank + code) | blank kept | blank dropped |
| blank.h (stub + blank + `#pragma once`) | blank kept | blank dropped |

So the function and its call are gone. What this PR keeps is the half main does not
have: `short_notice()` and the sentence it prints --

    copyright + SPDX present but the licence block is not (paste the 16-line form)

-- which is why the PR exists at all: main's `copyright.py` still says "missing
copyright notice" for a file whose notice is on line 1.

Re-ran the reviewer's four-case table at this head: `short.h`, `tool.py` (#! + # stub),
`blank.sh` (#! + stub + blank), `blank.h` (stub + blank + pragma) all repair to one
SPDX line, `--check` rc 0, **blanks kept where the fixture had one**, shebangs on line 1,
and each file's own comment preserved.

tests/copyright_check_untracked_test.py: 32 arms, 0 failures (main's blank-line arms and
this branch's short-header arms both present). copyright.py --check on this tree: rc 0.

---------

Co-authored-by: bong-water-water-bong <bong@water>
Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant