next-version-code.sh: derive the candidate from the commit, not the build clock
fdroid-repo-ci#6's "Superseded" sections change the rule: the candidate is now the committer date of the commit being built, clamped on both sides, rather than whatever minute the runner happened to pick the job up in. Backwards: rebase, amend and cherry-pick can produce a commit older than one already published, so the clamp keys off the committer date, not the author date -- author date survives a rebase unchanged and would replay a stale, already-superseded number. Forwards: a commit's timestamp is attacker- and typo-controlled and the number only ever rises, so a bad one can never be walked back. A value placed just below the int32 ceiling would still publish and then no later build could ever exceed it -- the app becomes permanently un-updatable. TOLERANCE absorbs honest clock skew and the clamp warns loudly rather than rejecting, since rejecting would block a legitimate build over a few minutes of skew while buying nothing the clamp doesn't already cover. next_version_code.py now takes the committer timestamp as a third argument (Unix seconds, what `git log -1 --format=%ct` prints) instead of reading the system clock, keeping it pure and testable without a git checkout. next-version-code.sh, README.md and action.yml updated to match. Granularity stays minutes: concurrency is handled separately (a workflow-level concurrency group, plus publish.sh's refuse-on-existing), so it is now a pure headroom choice. Tests (test_next_version_code.py, test_next_version_code.sh): a normal commit yields its own minute, a commit older than the published maximum still yields published+1, a future-dated commit within tolerance passes through untouched while one beyond it is clamped and warns, a commit dated near the int32 ceiling does not produce a near-int32 result, and a dedicated property test checks the result always strictly exceeds every versionCode in the fixture across all of the above. Each was verified by injecting the corresponding fault (removing the backwards clamp, removing the forwards clamp, and ignoring the commit's date entirely) and confirming the relevant test failed, then reverting. 98 assertions offline (0 failed, 4 skipped -- pre-existing, live-bucket only), up from 91. Co-authored-by: bit <bit@das-labor.org>
This commit is contained in:
+17
-5
@@ -5,11 +5,20 @@
|
||||
# whole failure mode this avoids, so both paths call this one script rather
|
||||
# than each computing their own.
|
||||
#
|
||||
# next-version-code.sh <index-v2.json URL or local path> <application-id>
|
||||
# next-version-code.sh <index-v2.json URL or local path> <application-id> \
|
||||
# <committer-timestamp>
|
||||
#
|
||||
# <committer-timestamp> is the COMMITTER date (not author date -- a rebase
|
||||
# leaves author date unchanged, which would defeat the backwards clamp) of
|
||||
# the commit being built, as Unix seconds: `git log -1 --format=%ct`. This
|
||||
# script takes it as an argument rather than reading git itself, on purpose
|
||||
# -- the caller already knows its own commit, and it keeps the underlying
|
||||
# Python pure and testable without a git checkout.
|
||||
#
|
||||
# Prints the integer to stdout and nothing else:
|
||||
#
|
||||
# versionCode=$(bash next-version-code.sh "$INDEX_URL" "$APPLICATION_ID")
|
||||
# versionCode=$(bash next-version-code.sh "$INDEX_URL" "$APPLICATION_ID" \
|
||||
# "$(git log -1 --format=%ct)")
|
||||
#
|
||||
# The index source may be an http(s) URL -- what both CI and a manual build
|
||||
# use in practice, and needs no credentials, since this repo and the F-Droid
|
||||
@@ -22,7 +31,10 @@
|
||||
# index cannot be fetched or parsed -- see next_version_code.py for why that
|
||||
# is never allowed to fall back to a guess. An index with no entry at all for
|
||||
# this application id is NOT an error: that is a normal first-ever publish
|
||||
# for a channel, and yields the bare candidate.
|
||||
# for a channel, and yields the bare (clamped) candidate. A commit dated
|
||||
# implausibly far in the future is also NOT a hard failure -- it is clamped
|
||||
# to roughly now and a warning is printed on stderr; see next_version_code.py
|
||||
# for why rejecting it outright would be worse.
|
||||
#
|
||||
# The rule itself, and why each part of it is shaped this way, lives in
|
||||
# next_version_code.py's own docstring -- read that, not this file, for the
|
||||
@@ -36,9 +48,9 @@ set -euo pipefail
|
||||
|
||||
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
if [ "$#" -ne 3 ]; then
|
||||
cat >&2 <<'USAGE'
|
||||
usage: next-version-code.sh <index-v2.json URL or local path> <application-id>
|
||||
usage: next-version-code.sh <index-v2.json URL or local path> <application-id> <committer-timestamp>
|
||||
USAGE
|
||||
exit 2
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user