Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions .github/workflows/deploy-site-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ jobs:

# One update for the job, because each one is a network round trip that can fail on its
# own. REQUIRE_BROTLI later makes a missing brotli fatal, so this keeps the build from
# failing, and git-restore-mtime is what the next step runs.
# failing.
- name: Install build tools step
run: |
set -Eeuo pipefail
sudo apt-get update
sudo apt-get install --yes --no-install-recommends brotli git-restore-mtime
sudo apt-get install --yes --no-install-recommends brotli

# Git stores no mtimes, so a checkout stamps every file with the moment it was written.
# The deploy uploads with --link-dest against the previous release, and a file only links
Expand All @@ -100,12 +100,42 @@ jobs:
# began with, so a media file that acquires a bad one stays present, correctly named and
# unreadable, through every later release. The live check is what notices that, by
# requesting images and failing on the 403.
# `git restore-mtime`, the subcommand form, because the package installs into git's
# exec-path at /usr/lib/git-core rather than onto PATH, so the bare name does not resolve.
# The action rather than the Ubuntu package, because the package is git-tools v2022.12 and
# that release shells out to `git whatchanged`, which current git refuses to run without
# `--i-still-use-this` - a flag there is no way to pass through. It failed twelve times in
# one step, restored nothing, and exited 0, so every release since #65 was a full copy
# while CI reported success. Upstream replaced whatchanged with `git log` in v2025.08, and
# this action vendors exactly that version.
- name: Restore file mtimes step
uses: chetan/git-restore-mtime-action@d186aca54f8760da4dec55313195e51ed3ebb0b3 # v2.3
with:
args: static

# The step above is the second tool to claim it restored these and not have done it, so
# the outcome is asserted rather than the tool trusted. This is the check whose absence
# let #65 ship broken: `git restore-mtime` printed "1,052 files to be processed" and then
# processed none of them, which reads exactly like success.
#
# The discriminator is that a restored file cannot be newer than the commit it was dated
# from, so no file under static/ may be newer than HEAD's own commit time. A checkout
# necessarily happens after the commit it checks out, so an unrestored tree is always
# newer than that bound and a restored one never is. It calibrates itself from the
# repository, so no measured constant goes stale as content moves.
#
# Counting distinct mtime days looks like the obvious check and is wrong: git restores
# the LAST COMMIT time, and static/ arrived in a bulk import, so a correctly restored
# tree here has exactly one distinct day. That version was written first and rejected
# only because it was run against a real restored clone before being trusted.
- name: Assert mtimes were restored step
run: |
set -Eeuo pipefail
git restore-mtime static
bound=$(git log -1 --format=%ct)
newest=$(find static -type f -printf '%T@\n' | sort -n | tail -1 | cut -d. -f1)
echo "static/ newest mtime $newest, HEAD committed $bound, margin $((bound - newest))s"
if [ "$newest" -gt "$bound" ]; then
echo "::error::mtime restore did nothing: static/ carries files newer than HEAD's commit, so they still hold their checkout time and --link-dest will link nothing."
exit 1
fi

# The pin lives in the action, so the deploy and validation cannot install different generators.
- name: Install Hugo step
Expand Down