Tweak actions to only update on changes
diff --git a/.github/actions/bazel-docker/action.yml b/.github/actions/bazel-docker/action.yml
index c3bc865..0890900 100644
--- a/.github/actions/bazel-docker/action.yml
+++ b/.github/actions/bazel-docker/action.yml
@@ -47,7 +47,7 @@
 
     - name: Hook up repository Cache
       shell: bash
-      run: echo "BAZEL_FLAGS=$BAZEL_FLAGS --repository_cache='/workspace/${{ steps.bazel.outputs.repository-cache }}'" >> $GITHUB_ENV
+      run: echo "BAZEL_FLAGS=$BAZEL_FLAGS --repository_cache='/workspace/${{ env.REPOSITORY_CACHE_PATH }}'" >> $GITHUB_ENV
 
     - name: Validate inputs
       if: ${{ (inputs.bash && inputs.bazel) || (!inputs.bash && !inputs.bazel) }}
@@ -69,12 +69,9 @@
         image: ${{ inputs.image }}
         command: ${{ inputs.bazel }} ${{ env.BAZEL_FLAGS }}
 
-    # Create a new cache for each push event.  This will be seeded by the
-    # latest cache on that branch, but can include any updates.
     - name: Save Bazel repository cache
+      # Only allow repository cache updates during post-submits.
       if: ${{ github.event_name == 'push' }}
-      uses: actions/cache/save@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
+      uses: ./.github/actions/internal/repository-cache-save
       with:
-        path: ${{ github.workspace }}/${{ steps.output.outputs.repository-cache }}
-        key: bazel-repository-cache-${{ github.ref_name }}-${{ runner.os }}-${{ github.sha }}
-        #restore-keys: bazel-repository-cache-${{ github.ref_name }}-${{ runner.os }}
+        bazel-cache: ${{ inputs.bazel-cache }}
diff --git a/.github/actions/bazel/action.yml b/.github/actions/bazel/action.yml
index 7d01437..5386b06 100644
--- a/.github/actions/bazel/action.yml
+++ b/.github/actions/bazel/action.yml
@@ -78,7 +78,7 @@
 
     - name: Hook up repository Cache
       shell: bash
-      run: echo "BAZEL_FLAGS=$BAZEL_FLAGS --repository_cache=$(pwd)/${{ steps.bazel.outputs.repository-cache }}" >> $GITHUB_ENV
+      run: echo "BAZEL_FLAGS=$BAZEL_FLAGS --repository_cache=$(pwd)/${{ env.REPOSITORY_CACHE_PATH }}" >> $GITHUB_ENV
 
     - name: Validate inputs
       if: ${{ (inputs.bash && inputs.bazel) || (!inputs.bash && !inputs.bazel) }}
@@ -105,12 +105,9 @@
         ${{ inputs.bazel }} $BAZEL_FLAGS
       shell: bash
 
-    # Create a new cache for each push event.  This will be seeded by the
-    # latest cache on that branch, but can include any updates.
     - name: Save Bazel repository cache
+      # Only allow repository cache updates during post-submits.
       if: ${{ github.event_name == 'push' }}
-      uses: actions/cache/save@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
+      uses: ./.github/actions/internal/repository-cache-save
       with:
-        path: ${{ github.workspace }}/${{ steps.output.outputs.repository-cache }}
-        key: bazel-repository-cache-${{ github.ref_name }}-${{ runner.os }}-${{ github.sha }}
-        #restore-keys: bazel-repository-cache-${{ github.ref_name }}-${{ runner.os }}
+        bazel-cache: ${{ inputs.bazel-cache }}
diff --git a/.github/actions/internal/bazel-setup/action.yml b/.github/actions/internal/bazel-setup/action.yml
index 6b3bb42..20e7241 100644
--- a/.github/actions/internal/bazel-setup/action.yml
+++ b/.github/actions/internal/bazel-setup/action.yml
@@ -17,9 +17,6 @@
   bazel-startup-flags:
     description: Bazel startup flags that should be sent to all Bazel invocations
     value: ${{ steps.output.outputs.bazel-startup-flags }}
-  repository-cache:
-    description: The location of our cached Bazel repository cache.
-    value: ${{ steps.output.outputs.repository-cache }}
 
 runs:
   using: 'composite'
@@ -69,10 +66,8 @@
       run: |
         echo "bazel-flags=$BAZEL_FLAGS" >> $GITHUB_OUTPUT
         echo "bazel-startup-flags=$BAZEL_STARTUP_FLAGS" >> $GITHUB_OUTPUT
-        echo "repository-cache=.repository_cache" >> $GITHUB_OUTPUT
 
     - name: Restore Bazel repository cache
-      uses: actions/cache/restore@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
+      uses: ./.github/actions/internal/repository-cache-restore
       with:
-        path: ${{ github.workspace }}/${{ steps.output.outputs.repository-cache }}
-        key: bazel-repository-cache-${{ github.base_ref || github.ref_name }}-${{ runner.os }}
+        bazel-cache: ${{ inputs.bazel-cache }}
diff --git a/.github/actions/internal/repository-cache-restore/action.yml b/.github/actions/internal/repository-cache-restore/action.yml
new file mode 100644
index 0000000..a5b4a0a
--- /dev/null
+++ b/.github/actions/internal/repository-cache-restore/action.yml
@@ -0,0 +1,41 @@
+name: Restore Repository Cache
+description: Restore the Bazel repository cache from our github action cache
+inputs:
+  bazel-cache:
+    required: true
+    description: A unique path for the Bazel cache.
+    type: string
+
+# By design, these actions will restore the latest cache for this branch/os,
+# and only save a new version if something has changed.  Initially this will
+# cause a lot of churn, since each test has a slightly different set of
+# repositories to download.  Over time though, since we don't upload no-op
+# changes, this should converge to a stable set of 3 caches per branch.  Every
+# run will update the current cache with a new test's repositories, until there
+# are no unique ones left.
+#
+# This saves asymptotic space, since each one of these can get up to ~500 MB
+# and Github prunes the cache after 10 GB.
+runs:
+  using: 'composite'
+  steps:
+    - name: Setup Bazel repository cache variables
+      shell: bash
+      run: |
+        REPOSITORY_CACHE_BASE=repository-cache-${{ github.base_ref || github.ref_name }}-${{ runner.os }}
+        echo "REPOSITORY_CACHE_BASE=$REPOSITORY_CACHE_BASE" >> $GITHUB_ENV
+        echo "REPOSITORY_CACHE_NAME=$REPOSITORY_CACHE_BASE-${{ inputs.bazel-cache}}-${{ github.sha }}" >> $GITHUB_ENV
+        echo "REPOSITORY_CACHE_PATH=.repository-cache" >> $GITHUB_ENV
+
+    - name: Restore Bazel repository cache
+      id: restore-cache
+      uses: actions/cache/restore@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
+      with:
+        path: ${{ github.workspace }}/${{ env.REPOSITORY_CACHE_PATH }}
+        key: ${{ env.REPOSITORY_CACHE_NAME }}
+        restore-keys: ${{ env.REPOSITORY_CACHE_BASE }}
+
+    - name: Initialize BAZEL environment variable
+      if: ${{ steps.restore-cache.cache-hit }}
+      shell: bash
+      run: echo "REPOSITORY_CACHE_HASH=${{ hashFiles(format('{0}/**', env.REPOSITORY_CACHE_PATH)) }}" >> $GITHUB_ENV
diff --git a/.github/actions/internal/repository-cache-save/action.yml b/.github/actions/internal/repository-cache-save/action.yml
new file mode 100644
index 0000000..a5bd98b
--- /dev/null
+++ b/.github/actions/internal/repository-cache-save/action.yml
@@ -0,0 +1,24 @@
+name: Restore Repository Cache
+description: Restore the Bazel repository cache from our github action cache
+inputs:
+  bazel-cache:
+    required: true
+    description: A unique path for the Bazel cache.
+    type: string
+
+# Note: this action will only work if repository-cache-restore has already
+# been called.  All bazel actions should specify the repository_cache parameter
+# using REPOSITORY_CACHE_PATH.
+#
+# We intentionally upload to REPOSITORY_CACHE_BASE to prevent a flood of new
+# caches on any change.  Only 1 job per os in each test run will be allowed to
+# update the cache because they're all trying to write to the same location.
+runs:
+  using: 'composite'
+  steps:
+    - name: Save modified Bazel repository cache
+      if: ${{ env.REPOSITORY_CACHE_HASH != hashFiles(format('{0}/**', env.REPOSITORY_CACHE_PATH)) }}
+      uses: actions/cache/save@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
+      with:
+        path: ${{ github.workspace }}/${{ env.REPOSITORY_CACHE_PATH }}
+        key: ${{ env.REPOSITORY_CACHE_BASE }}
diff --git a/.github/workflows/clear_caches.yml b/.github/workflows/clear_caches.yml
index f27e00a..339385f 100644
--- a/.github/workflows/clear_caches.yml
+++ b/.github/workflows/clear_caches.yml
@@ -20,8 +20,8 @@
       - uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
         with:
           path: ${{ github.workspace }}/${{ steps.output.outputs.repository-cache }}
-          key: bazel-repository-cache-${{ github.ref_name }}-${{ runner.os }}-reset-${{ github.sha }}
+          key: repository-cache-${{ github.ref_name }}-${{ runner.os }}-reset-${{ github.sha }}
 
       - run: |
-        mkdir -p '${{ github.workspace }}/.repository_cache'
-        touch '${{ github.workspace }}/.repository_cache/reset_file'
+        mkdir -p '${{ github.workspace }}/.repository-cache'
+        touch '${{ github.workspace }}/.repository-cache/reset_file'