check-attribution-trailers.sh used `|| true` on `git log`'s exit status, so a hard failure (bad range, unborn HEAD) and an empty-but-successful range were indistinguishable — both fell through to the same `-z "$commits"` exit-0 path. Capture the status via the `if` guard (exempt from set -e on purpose) and exit 1 on a real git log failure. check-issue-refs.sh piped `git ls-files | xargs grep | grep -v lint:allow`, then swallowed the final exit code with `|| true`. Worse: xargs itself collapses grep's exit 1 (no match) and exit 2+ (real error, e.g. an unreadable file) into the same xargs(1) status (123 either way), so even capturing that status can't tell them apart. Switched to `git grep`, which runs once over the tracked set and hands back its own exit status untouched (0 matched / 1 no match / 2+ error) — then branch on that status explicitly for both the scan and the lint:allow filter step. Refs #4439, #4442
89 lines
4 KiB
Shell
Executable file
89 lines
4 KiB
Shell
Executable file
#!/bin/sh
|
|
# CI lint: flags tracker references — a `#N` tag or a full `.../issues/N`
|
|
# forge URL — anywhere in tracked text, source or docs. Prose, not tracker
|
|
# references: in code because tags rot; in markdown because the forge's
|
|
# public mirror carries no issue/PR data at all, so bare/qualified/glued
|
|
# `#N` and a full link are equally dead weight for a public reader — a
|
|
# full URL is the same problem spelled out longer, not a safer swap for a
|
|
# short tag. No markdown exemption: one used to exist, dropped once that
|
|
# read as still allowing exactly this.
|
|
#
|
|
# Emits a CI error annotation per hit, exits 1 if any hit is found. Its own
|
|
# required CI job (branch protection) — a hit blocks merge.
|
|
#
|
|
# Scope: every tracked `*.rs *.nix *.js *.mjs *.ts *.tsx *.css *.html *.md
|
|
# *.yml *.yaml`. CI workflow files (`.forgejo/workflows/*.yml`) are explicitly in
|
|
# scope: a step comment is still a comment. The pattern matches a hash,
|
|
# 2-5 digits, then a non-alphanumeric char or end-of-line (skips
|
|
# letter-bearing hex colours and digit-runs-then-letter, e.g. `#24h`;
|
|
# residual: a pure-numeric short hex trips it, write the six-digit form to
|
|
# dodge); or an `/issues/N` path segment, catching a full link via
|
|
# `$HIVE_FORGE_URL` or a literal domain alike.
|
|
#
|
|
# Escape hatch: a line with the marker `lint:allow` is exempt. Reserve it
|
|
# for a genuine non-tag hit (a `#123` heading example, test-input data) and
|
|
# keep a short reason next to it — not for a real reference of either
|
|
# form; rewrite those to prose that stands on its own instead.
|
|
set -eu
|
|
|
|
pattern='#[0-9]{2,5}([^0-9a-zA-Z]|$)|/issues/[0-9]+([^0-9a-zA-Z]|$)'
|
|
|
|
# A lint whose file-list matches nothing passes forever while checking not
|
|
# one file — mirrors the assertion in scripts/check-doc-refs.sh. If a rename
|
|
# or extension-list edit silences `git ls-files` here, that is a failure of
|
|
# this script, not a property of the tree.
|
|
file_count="$(git ls-files '*.rs' '*.nix' '*.js' '*.mjs' '*.ts' '*.tsx' '*.css' '*.html' '*.md' '*.yml' '*.yaml' | wc -l)"
|
|
if [ "$file_count" -eq 0 ]; then
|
|
echo 'check-issue-refs: file-list search matched nothing — pattern or tree changed' >&2
|
|
exit 1
|
|
fi
|
|
|
|
# git grep, not `git ls-files | xargs grep`: grep's exit code is the
|
|
# signal that distinguishes "no tracker refs" (1, clean) from a real
|
|
# scan failure (2+), but piping through xargs collapsed both into
|
|
# xargs(1)'s own 123 either way — indistinguishable, so a real failure
|
|
# (e.g. a file grep can't read) silently read as a clean tree. git grep
|
|
# runs once over the tracked set matching these globs and hands back its
|
|
# own exit status untouched: 0 = matched, 1 = no match, 2+/128 = error.
|
|
#
|
|
# The `if` guard (not `|| true`) is what's exempt from `set -e` here —
|
|
# capture the status, then branch on it explicitly rather than erasing it.
|
|
if matches="$(
|
|
git grep -nE "$pattern" -- '*.rs' '*.nix' '*.js' '*.mjs' '*.ts' '*.tsx' '*.css' '*.html' '*.md' \
|
|
'*.yml' '*.yaml'
|
|
)"; then
|
|
grep_status=0
|
|
else
|
|
grep_status=$?
|
|
fi
|
|
|
|
if [ "$grep_status" -gt 1 ]; then
|
|
printf 'check-issue-refs: git grep failed while scanning for tracker references (exit %s)\n' "$grep_status" >&2
|
|
exit 1
|
|
fi
|
|
|
|
hits=""
|
|
if [ "$grep_status" -eq 0 ]; then
|
|
# Lines carrying the `lint:allow` marker are dropped (legitimate
|
|
# non-tracker hit; see the header). Same exit-code split applies here:
|
|
# 1 means every match was exempted (clean), 2+ means grep itself broke.
|
|
if hits="$(printf '%s\n' "$matches" | grep -v 'lint:allow')"; then
|
|
filter_status=0
|
|
else
|
|
filter_status=$?
|
|
fi
|
|
if [ "$filter_status" -gt 1 ]; then
|
|
printf 'check-issue-refs: grep failed while filtering lint:allow markers (exit %s)\n' "$filter_status" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$hits" ]; then
|
|
echo "$hits" | while IFS=: read -r file lineno _; do
|
|
printf '::error file=%s,line=%s::tracker reference — write prose that stands on its own, not a hash-number tag or a full issue URL (see /knowledge/hive-rules.md)\n' "$file" "$lineno"
|
|
done
|
|
count="$(printf '%s\n' "$hits" | wc -l | tr -d ' ')"
|
|
printf 'check-issue-refs: %s tracker reference(s) found\n' "$count" >&2
|
|
exit 1
|
|
fi
|
|
exit 0
|