From 4a172b8a21cf6e4dd9cc14512abb4e0064f25858 Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Tue, 5 May 2026 03:51:26 -0500 Subject: [PATCH] ci: restore source mtimes after checkout for stable cargo fingerprints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `actions/checkout@v4` stamps every file with the checkout time. Cargo's freshness check is mtime-based, so workspace crates always look newer than their cached fingerprints and recompile from scratch (~16min on a Test Suite run with the cache otherwise warm). git-restore-mtime walks git history and rewrites each file's mtime to its last commit's timestamp. Identical commit ⇒ identical mtimes ⇒ cargo treats sources as fresh. Binary is pre-installed on the self-hosted runner image; the `command -v` guard keeps the step a no-op on hosted runners. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 168 ++++++++++++++++++++ .github/workflows/upgrade-version-check.yml | 13 ++ 2 files changed, 181 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22f9e0eb3..babd96873 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,20 @@ jobs: with: ref: ${{ env.GIT_REF }} + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -210,6 +224,20 @@ jobs: with: ref: ${{ env.GIT_REF }} + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -308,6 +336,20 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -355,6 +397,20 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -387,6 +443,20 @@ jobs: permissions: read-all steps: - uses: actions/checkout@v3 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -415,6 +485,20 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -554,6 +638,20 @@ jobs: with: ref: ${{ env.GIT_REF }} + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -594,6 +692,20 @@ jobs: with: ref: ${{ env.GIT_REF }} + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -692,6 +804,20 @@ jobs: id: checkout-stdb uses: actions/checkout@v4 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -847,6 +973,20 @@ jobs: id: checkout-stdb uses: actions/checkout@v4 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -1196,6 +1336,20 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash @@ -1241,6 +1395,20 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi + - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash diff --git a/.github/workflows/upgrade-version-check.yml b/.github/workflows/upgrade-version-check.yml index f171a56f4..d7399860d 100644 --- a/.github/workflows/upgrade-version-check.yml +++ b/.github/workflows/upgrade-version-check.yml @@ -12,6 +12,19 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + - name: Restore source mtimes from git history + # `actions/checkout@v4` stamps every file with the checkout time, but + # cargo's freshness check uses mtime. Without this, every workspace + # crate looks newer than its cached fingerprint and gets recompiled. + # git-restore-mtime sets each file's mtime to its last commit time, + # making mtimes deterministic across the cache builder and the runner. + # No-op on hosted runners (binary only exists on the self-hosted image). + if: runner.os == 'Linux' + shell: bash + run: | + if command -v git-restore-mtime >/dev/null 2>&1; then + git restore-mtime --quiet + fi - name: Use cached cargo target if available if: runner.os == 'Linux' shell: bash