From 445088e6488879a2cf989012112aeedcea27b937 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 9 Aug 2026 05:47:44 -0700 Subject: [PATCH] Use the maintained git-restore-mtime, and assert it actually restored The Ubuntu package is git-tools v2022.12, and that release shells out to `git whatchanged`. Current git refuses to run that without `--i-still-use-this`, a flag there is no way to pass through the tool, so it failed twelve times inside one step, restored nothing, and exited 0. Every release since #65 has been a full copy while CI reported success, which the VPS agent measured from the other end as 0 of 3,275 shared inodes. Upstream fixed it: MestreLion/git-tools 91dc541 replaced whatchanged with `git log`, released as v2025.08. chetan/git-restore-mtime-action v2.3 vendors that exact version, verified by reading the vendored script at the pinned SHA -- `__version__ = "2025.08"` and no whatchanged anywhere in it. Taking the action rather than the tarball also drops the apt round trip the step's own comment called out as a thing that can fail on its own, and it pins by SHA like every other action here. The assertion is the half that matters, because the failure mode was a step that exited 0 having done nothing. 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 always breaches that bound and a restored one never does. It calibrates from the repository, so nothing goes stale as content moves. Counting distinct mtime days was written first 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 and that check would have failed the good case. It was caught only because it was run against a real restored clone instead of being trusted, which is the same discipline this whole change exists to enforce. Measured on two clones of this repository, the assertion run verbatim: restored ok margin 651427s unrestored ERROR margin -33196s And the property the deploy actually needs: two independent clones, restored, produce byte-identical path+mtime sets, so --link-dest can match. 1,052 files updated by the new script, the same 1,052 as before. Closes #74. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/deploy-site-task.yml | 40 ++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-site-task.yml b/.github/workflows/deploy-site-task.yml index 5964693..f0ca4a9 100644 --- a/.github/workflows/deploy-site-task.yml +++ b/.github/workflows/deploy-site-task.yml @@ -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 @@ -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