Skip to content

Commit 4d56eb5

Browse files
committed
Comparison: normalize man page header, content-based tarball diff, adopt CI man page on normalized match
1 parent d1211b3 commit 4d56eb5

File tree

2 files changed

+102
-11
lines changed

2 files changed

+102
-11
lines changed

docs/release-steps.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ make release # for first publication
5959
You will see:
6060
1. Local build presence check (or build via prerequisites).
6161
2. CI artifact fetch (MANDATORY). Failure to retrieve artifacts aborts; you must wait for the workflow to finish.
62-
3. sha256 comparison (local vs CI). If any differ, release ABORTS by default (no prompt) to enforce deterministic provenance.
62+
3. Normalized comparison phase:
63+
* `keychain` – raw sha256 digest compare.
64+
* `keychain.1` – compare raw hash; if different, re-compare with the Pod::Man auto-generated first line stripped. If normalized content matches, we ADOPT the CI man page and treat as match.
65+
* `keychain-<version>.tar.gz` – unpack both tarballs; compare sorted file list and per-file sha256. If all internal files match, we treat the tarballs as equivalent even if gzip/tar metadata differ (timestamp, owner, compression). Any real content divergence aborts.
6366
- To force using local artifacts: `KEYCHAIN_FORCE_LOCAL=1 make release`
6467
- To adopt CI artifacts: `KEYCHAIN_ADOPT_CI=1 make release`
6568
(Use corresponding `... make release-refresh` for refresh mode.)

scripts/release-orchestrate.sh

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,109 @@ echo "CI artifacts retrieved." >&2
3535
calc_sha256() { sha256sum "$1" | awk '{print $1}'; }
3636

3737
diff_flag=0
38-
echo "Digest comparison (sha256):"
39-
for artifact in keychain-$VER.tar.gz keychain keychain.1; do
40-
if [ -f "$CI_DIR/$artifact" ]; then
41-
L=$(calc_sha256 "$artifact")
42-
R=$(calc_sha256 "$CI_DIR/$artifact")
43-
if [ "$L" = "$R" ]; then
44-
printf ' %-20s %s (match)\n' "$artifact" "$L"
45-
else
46-
printf ' %-20s LOCAL %s != CI %s *DIFF*\n' "$artifact" "$L" "$R"
47-
diff_flag=1
38+
echo "Digest comparison (normalized where applicable):"
39+
40+
normalize_man() {
41+
# Strip the auto-generated Pod::Man first line so version differences don't cause false mismatch.
42+
# Output to stdout.
43+
sed '1{/^\\." Automatically generated by Pod::Man /d;}' "$1"
44+
}
45+
46+
compare_tar_content() {
47+
local local_tar=$1 ci_tar=$2
48+
local tmp_local tmp_ci
49+
tmp_local=$(mktemp -d)
50+
tmp_ci=$(mktemp -d)
51+
# Extract quietly
52+
tar xzf "$local_tar" -C "$tmp_local" 2>/dev/null || return 2
53+
tar xzf "$ci_tar" -C "$tmp_ci" 2>/dev/null || return 2
54+
# Determine root (expect exactly one directory named keychain-$VER)
55+
local root="keychain-$VER"
56+
if [ ! -d "$tmp_local/$root" ] || [ ! -d "$tmp_ci/$root" ]; then
57+
echo " keychain-$VER.tar.gz: unexpected directory layout inside tar" >&2
58+
return 3
59+
fi
60+
# List files (regular only) relative to root
61+
local lf cf
62+
lf=$( (cd "$tmp_local/$root" && find . -type f -print | LC_ALL=C sort) )
63+
cf=$( (cd "$tmp_ci/$root" && find . -type f -print | LC_ALL=C sort) )
64+
if [ "$lf" != "$cf" ]; then
65+
echo " keychain-$VER.tar.gz: file list differs" >&2
66+
return 4
67+
fi
68+
# Hash each file
69+
local mismatch=0
70+
while IFS= read -r rel; do
71+
[ -z "$rel" ] && continue
72+
local h1 h2
73+
h1=$(sha256sum "$tmp_local/$root/$rel" | awk '{print $1}')
74+
h2=$(sha256sum "$tmp_ci/$root/$rel" | awk '{print $1}')
75+
if [ "$h1" != "$h2" ]; then
76+
echo " keychain-$VER.tar.gz: content mismatch in $rel" >&2
77+
mismatch=1
4878
fi
79+
done <<EOF
80+
$lf
81+
EOF
82+
if [ $mismatch -eq 0 ]; then
83+
# Even if tarball blob hashes differ, content matches.
84+
return 0
4985
else
86+
return 5
87+
fi
88+
}
89+
90+
# Process artifacts with specialized logic
91+
for artifact in keychain keychain.1 keychain-$VER.tar.gz; do
92+
if [ ! -f "$CI_DIR/$artifact" ]; then
5093
printf ' %-20s CI copy missing; comparison failed (abort)\n' "$artifact"
5194
diff_flag=1
95+
continue
5296
fi
97+
case "$artifact" in
98+
keychain)
99+
L=$(calc_sha256 "$artifact"); R=$(calc_sha256 "$CI_DIR/$artifact")
100+
if [ "$L" = "$R" ]; then
101+
printf ' %-20s %s (match)\n' "$artifact" "$L"
102+
else
103+
printf ' %-20s LOCAL %s != CI %s *DIFF*\n' "$artifact" "$L" "$R"
104+
diff_flag=1
105+
fi
106+
;;
107+
keychain.1)
108+
# Direct hash first
109+
L=$(calc_sha256 "$artifact"); R=$(calc_sha256 "$CI_DIR/$artifact")
110+
if [ "$L" = "$R" ]; then
111+
printf ' %-20s %s (match)\n' "$artifact" "$L"
112+
else
113+
# Normalize and compare ignoring Pod::Man header line.
114+
if diff -u <(normalize_man "$artifact") <(normalize_man "$CI_DIR/$artifact") >/dev/null 2>&1; then
115+
printf ' %-20s (normalized match ignoring Pod::Man header)\n' "$artifact"
116+
# Adopt CI version so released man page reflects builder environment deterministically.
117+
cp -f "$CI_DIR/$artifact" "$artifact"
118+
else
119+
printf ' %-20s LOCAL %s != CI %s *DIFF* (content mismatch beyond header)\n' "$artifact" "$L" "$R"
120+
diff_flag=1
121+
fi
122+
fi
123+
;;
124+
keychain-$VER.tar.gz)
125+
if compare_tar_content "$artifact" "$CI_DIR/$artifact"; then
126+
# If tar blob hash matches display it; else note normalized match.
127+
L=$(calc_sha256 "$artifact"); R=$(calc_sha256 "$CI_DIR/$artifact")
128+
if [ "$L" = "$R" ]; then
129+
printf ' %-20s %s (match)\n' "$artifact" "$L"
130+
else
131+
printf ' %-20s (content match; tar/gzip metadata differ)\n' "$artifact"
132+
# Optional: adopt CI tarball for consistency (uncomment if desired)
133+
# cp -f "$CI_DIR/$artifact" "$artifact"
134+
fi
135+
else
136+
printf ' %-20s *CONTENT DIFF* (see above messages)\n' "$artifact"
137+
diff_flag=1
138+
fi
139+
;;
140+
esac
53141
done
54142

55143
if [ $diff_flag -ne 0 ]; then

0 commit comments

Comments
 (0)