Files
nvm/test/fast/Unit tests/release-notes.sh replaces control characters in commit subjects
T
Jordan Harband 013b291d36 [meta] release-notes.sh: replace control characters in commit subjects
Commit subjects were printed as-is,
so escape sequences, carriage returns,
and other control characters in a subject reached the terminal during `make release-notes` and `make release`,
where they could hide or rewrite lines of the notes being reviewed,
and were then written verbatim into the bump commit and the release tag.
A tab in a subject also corrupted the tab-separated intermediate file.

Subjects are now filtered as they are read from `git log`,
replacing C0 controls, DEL,
and UTF-8-encoded C1 controls with `?`.
They are replaced rather than removed so that the bytes on either side cannot join into a new control character,
and so the change stays visible in the notes.
The section `grep` now matches bytes,
because in a UTF-8 locale GNU grep stops printing matches at a line that is not valid UTF-8,
which silently dropped bullets.
Lowercasing a subject to find its category now works on bytes too,
because in a UTF-8 locale BSD `tr` stops at a byte that is not valid UTF-8.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-INF3JLG6; the scanner correctly classified it as not a vulnerability.
2026-10-23 08:53:58 -07:00

88 lines
2.7 KiB
Bash
Executable File

#!/bin/sh
RELEASE_NOTES="${PWD}/../../../release-notes.sh"
WORK="$(mktemp -d)" || exit 1
cleanup() {
cd / && rm -rf "${WORK}"
}
die() { echo "$@" ; cleanup ; exit 1; }
export HOME="${WORK}/home"
export GIT_CONFIG_NOSYSTEM=1
unset XDG_CONFIG_HOME GIT_DIR GIT_WORK_TREE
export NVM_RELEASE_NOTES_SKIP_PRS=1
export GIT_COMMITTER_NAME=nvm GIT_COMMITTER_EMAIL=nvm@example.com
IDENT='contributor <contributor@example.com> 0 +0000'
# writes the commit object byte for byte; `git commit` would repair a subject that is not valid UTF-8
commit() {
PARENT=''
if git rev-parse --quiet --verify HEAD >/dev/null; then
PARENT="parent $(git rev-parse HEAD)
"
fi
COMMIT="$(
{
printf 'tree %s\n%sauthor %s\ncommitter %s\n\n' "${EMPTY_TREE}" "${PARENT}" "${IDENT}" "${IDENT}"
# shellcheck disable=SC2059
printf "$1\n"
} | git hash-object -t commit -w --stdin
)" || die 'failed to write a commit'
git update-ref HEAD "${COMMIT}" || die 'failed to update HEAD'
}
mkdir -p "${HOME}" "${WORK}/repo" || die 'failed to create the scratch dirs'
cd "${WORK}/repo" || die 'failed to enter the scratch repo'
git init --quiet . || die 'failed to create the scratch repo'
EMPTY_TREE="$(git hash-object -t tree -w /dev/null)" || die 'failed to write the empty tree'
commit 'v0.0.1'
PREV_REF="$(git rev-parse HEAD)"
commit '[Fix] older bullet'
commit '[New] \342\200\234quoted\342\200\235 bullet'
commit '[Fix] stray \233 byte'
commit '[Fix] U+009B once the escape is gone: \302\033\2332K'
commit '[Fix] U+009B: \302\2332K'
commit '[Fix] tab\tand delete\177'
commit '[Fix] escapes\033]0;title\007\r\033[2K\033[1A'
commit 'no prefix\033[2K'
EXPECTED_OUTPUT="$(printf '%b\n' \
'v0.0.2' \
'' \
'New Stuff' \
'' \
' - \0342\0200\0234quoted\0342\0200\0235 bullet' \
'' \
'Bug Fixes' \
'' \
' - escapes?]0;title???[2K?[1A' \
' - tab?and delete?' \
' - U+009B: ?2K' \
' - U+009B once the escape is gone: \0302?\02332K' \
' - stray \0233 byte' \
' - older bullet' \
'' \
'Misc' \
'' \
' - no prefix?[2K')"
EXPECTED_STDERR='release-notes: no category prefix, filing under Misc: no prefix?[2K'
for LOCALE in C C.UTF-8 en_US.UTF-8; do
case "${LOCALE}" in
*.UTF-8) [ "$(LC_ALL="${LOCALE}" locale charmap 2>/dev/null)" = 'UTF-8' ] || continue ;;
esac
OUTPUT="$(LC_ALL="${LOCALE}" "${RELEASE_NOTES}" v0.0.2 "${PREV_REF}" 2>/dev/null)"
[ "_${OUTPUT}" = "_${EXPECTED_OUTPUT}" ] \
|| die "${LOCALE}: expected >${EXPECTED_OUTPUT}<, got >$(printf '%s' "${OUTPUT}" | od -c)<"
STDERR="$(LC_ALL="${LOCALE}" "${RELEASE_NOTES}" v0.0.2 "${PREV_REF}" 2>&1 >/dev/null)"
[ "_${STDERR}" = "_${EXPECTED_STDERR}" ] \
|| die "${LOCALE}: expected stderr >${EXPECTED_STDERR}<, got >$(printf '%s' "${STDERR}" | od -c)<"
done
cleanup