patchette

fix: a repeated STACK key is an error

commit 14dda3a13d7e2fe821cb86d930e27c0250dec7e9 · parent 9cd6edf32e · browse this tree
Ximianth, 2026-10-21 16:11:50 UTC

parse_stack let a later `ref`, `base`, or `tip` line silently replace an
earlier one, so a STACK carrying two different refs was read as the
last, and verify passed on it.  Which value was meant can't be known,
so FORMAT.md now says each key appears at most once, and parse_stack
refuses a repeat by name.

The check sits ahead of the per-key arms, so it covers every key
without repeating itself per arm; an unrecognized key still dies on
its first occurrence as before.  The new fail-closed case failed
before this change.
diff --git a/bin/patchette b/bin/patchette
index cd4762e..54189a1 100755
--- a/bin/patchette
+++ b/bin/patchette
@@ -90,9 +90,9 @@ absdir() {
 ## - @param $1 path to the STACK file
 ## - @state STACK_REF, STACK_BASE, STACK_TIP, STACK_SERIES[] -- the parsed fields
 parse_stack() {
-	local f=$1 line mode=head
+	local f=$1 line mode=head key
 	local oid='^[0-9a-f]{40}([0-9a-f]{24})?$' # SHA-1 or SHA-256, full and lowercase
-	declare -A seen=()
+	declare -A seen=() keys_seen=()
 	STACK_REF='' STACK_BASE='' STACK_TIP='' STACK_SERIES=()
 	while IFS= read -r line; do
 		if [[ $mode == series ]]; then
@@ -102,6 +102,9 @@ parse_stack() {
 			STACK_SERIES+=("$line")
 			continue
 		fi
+		key=${line%% *}
+		[[ -z ${keys_seen[$key]:-} ]] || die "$f: duplicate $key line"
+		keys_seen[$key]=1
 		case $line in
 		"ref "*) STACK_REF=${line#ref } ;;
 		"base "*)
diff --git a/docs/hacking/FORMAT.md b/docs/hacking/FORMAT.md
index 99d14dc..07f9df8 100644
--- a/docs/hacking/FORMAT.md
+++ b/docs/hacking/FORMAT.md
@@ -97,6 +97,7 @@ fix-the-frobnicator-2
 Several pieces of `key value` format data start the file,
 then a literal `series` line denotes the end of that section,
 and then one patch file name is per line, until EOF.
+Each key appears at most once.
 
 Of the key value entries:
 
diff --git a/tests/test-failclosed.sh b/tests/test-failclosed.sh
index 92754d6..31a0164 100755
--- a/tests/test-failclosed.sh
+++ b/tests/test-failclosed.sh
@@ -86,6 +86,12 @@ sed -i 's/^tip .*/tip /' "$TAMPER/$fancy/STACK"
 EXPECT refusal "a tip line with no value is an error, not an absent tip" "tip is not a full commit ID" -- \
 	"$PATCHETTE" verify "$work/corpus/dev" "$TAMPER"
 
+# A repeated STACK key is an error: which value is meant can't be known.
+clone_layout dupkey
+sed -i '1a ref refs/heads/other' "$TAMPER/$fancy/STACK"
+EXPECT refusal "a repeated STACK key is an error" "duplicate ref line" -- \
+	"$PATCHETTE" verify "$work/corpus/dev" "$TAMPER"
+
 # A duplicate series entry is an error.
 clone_layout dupseries
 echo "note-to-self" >>"$TAMPER/$fancy/STACK"