diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index 682bfd4..e7ee7d4 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -85,19 +85,19 @@ jobs: - uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2.4.4 with: version: 2025.7.12 - - name: Cache Go modules with multiple restore keys + - name: Cache Go modules with fallback branch id: cache-go uses: ./ with: path: | ~/go/pkg/mod ~/.cache/go-build - key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} + key: go-${{ runner.os }}-${{ github.run_id }} restore-keys: | - go-${{ runner.os }}-${{ hashFiles('**/go.mod') }} go-${{ runner.os }}- fail-on-cache-miss: false fallback-branch: refs/heads/branch-2 + fallback-to-default-branch: 'true' environment: dev backend: s3 - name: Check Go cache hit result diff --git a/README.md b/README.md index 42909e9..9e53316 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ npm run build:guard-main # build credential-guard main only npm run build:guard-post # build credential-guard post only ``` -Bundled output goes to `credential-setup/dist/` and `credential-guard/dist/`. These must be committed since GitHub Actions runs them directly. +Bundled output goes to `credential-setup/dist/` and `credential-guard/dist/`. +These must be committed since GitHub Actions runs them directly. ## Usage @@ -66,7 +67,7 @@ Bundled output goes to `credential-setup/dist/` and `credential-guard/dist/`. Th | `path` | Files, directories, and wildcard patterns to cache | Yes | | | `key` | Explicit key for restoring and saving cache | Yes | | | `restore-keys` | Ordered list of prefix-matched keys for fallback | No | | -| `fallback-to-default-branch` | Automatically add a fallback restore key pointing to the default branch cache (S3 backend only). Disable if you want strict branch isolation. | No | `true` | +| `fallback-to-default-branch` | Automatically add a fallback restore key pointing to the default branch cache (S3 backend only). Disable if you want strict branch isolation. | No | `false` | | `fallback-branch` | Optional maintenance branch for fallback restore keys (pattern: `branch-*`, S3 backend only). If not set, the repository default branch is used. | No | | | `environment` | Environment to use (dev or prod, S3 backend only) | No | `prod` | | `upload-chunk-size` | Chunk size for large file uploads (bytes) | No | | @@ -119,11 +120,11 @@ A GitHub Action that provides branch-specific caching on AWS S3 with intelligent The action searches for cache entries in this order: -1. **Primary key**: `${BRANCH_NAME}/${key}` -2. **Branch-specific restore keys**: `${BRANCH_NAME}/${restore-key}` (for each restore key provided) -3. **Default branch fallbacks** (when `fallback-to-default-branch: true`, the default): - - If `restore-keys` are provided: `refs/heads/${DEFAULT_BRANCH}/${restore-key}` for each restore key - - If no `restore-keys` are provided: `refs/heads/${DEFAULT_BRANCH}/${key}` (exact-match fallback) +1. **Primary key**: `${BRANCH_NAME}/${key}` (exact match) +2. **Default branch exact-match fallback** (when `fallback-to-default-branch: true`): `refs/heads/${DEFAULT_BRANCH}/${key}` +3. **Branch-specific restore keys** (if `restore-keys` provided): `${BRANCH_NAME}/${restore-key}` for each restore key (prefix match) +4. **Default branch restore key fallbacks** (if `restore-keys` provided): + `refs/heads/${DEFAULT_BRANCH}/${restore-key}` for each restore key (prefix match, lowest priority) #### Example — with restore-keys @@ -138,8 +139,9 @@ The action searches for cache entries in this order: For a feature branch `feature/new-ui`, this will search for: 1. `feature/new-ui/node-linux-abc123...` (exact match) -2. `feature/new-ui/node-linux-` (branch-specific partial match) -3. `refs/heads/main/node-linux-` (default branch fallback, assuming `main` is the repository's default branch) +2. `refs/heads/main/node-linux-abc123...` (default branch fallback, exact match) +3. `feature/new-ui/node-linux-` (branch-specific prefix match) +4. `refs/heads/main/node-linux-` (default branch fallback, prefix match) #### Example — without restore-keys @@ -167,7 +169,8 @@ To disable the automatic default branch fallback: #### Key Differences from Standard Cache Action -- **Automatic default branch fallback**: By default, feature branches fall back to the default branch cache when no branch-specific entry exists +- **Automatic default branch fallback**: By default, feature branches fall back to the default branch cache + when no branch-specific entry exists - **Dynamic default branch detection**: The action detects your default branch using the GitHub API and uses it for fallback - **Branch isolation**: Each branch maintains its own cache namespace, preventing cross-branch cache pollution diff --git a/scripts/prepare-keys.sh b/scripts/prepare-keys.sh index 4f4029c..a5e5518 100755 --- a/scripts/prepare-keys.sh +++ b/scripts/prepare-keys.sh @@ -42,19 +42,6 @@ echo "branch-key=${BRANCH_KEY}" >> "$GITHUB_OUTPUT" RESTORE_KEYS="" -# Process restore keys: add branch-specific keys -if [[ -n $INPUT_RESTORE_KEYS ]]; then - while IFS= read -r line; do - if [ -n "$line" ]; then - if [ -n "$RESTORE_KEYS" ]; then - RESTORE_KEYS="${RESTORE_KEYS}"$'\n'"${BRANCH_NAME}/${line}" - else - RESTORE_KEYS="${BRANCH_NAME}/${line}" - fi - fi - done <<< "$INPUT_RESTORE_KEYS" -fi - # Determine the fallback branch if [[ -n "$INPUT_FALLBACK_BRANCH" ]]; then # Explicit fallback-branch is always honoured, regardless of fallback-to-default-branch @@ -66,23 +53,21 @@ elif [[ $INPUT_FALLBACK_TO_DEFAULT_BRANCH == "true" ]]; then jq -r '.default_branch') fi +# Build restore keys in priority order: +# 1. Fallback branch exact match (primary key re-scoped to fallback branch) +# 2. Branch-specific prefix matches (user's restore-keys scoped to current branch) +# 3. Fallback branch prefix matches (user's restore-keys re-scoped to fallback branch) + +FALLBACK_EXACT_KEY="" +FALLBACK_ACTIVE=false + if [[ -n "${FALLBACK_BRANCH:-}" && "$FALLBACK_BRANCH" != "null" ]]; then - # Skip fallback if we're already on the fallback branch CURRENT_BRANCH="${BRANCH_NAME#refs/heads/}" if [[ "$CURRENT_BRANCH" != "$FALLBACK_BRANCH" ]]; then case "$FALLBACK_BRANCH" in main|master|branch-*) - if [[ -n $INPUT_RESTORE_KEYS ]]; then - # Add fallback branch restore keys for each user-provided restore key - while IFS= read -r line; do - if [[ -n "$line" ]]; then - RESTORE_KEYS="${RESTORE_KEYS}"$'\n'"refs/heads/${FALLBACK_BRANCH}/${line}" - fi - done <<< "$INPUT_RESTORE_KEYS" - else - # No restore keys provided: add exact-match fallback using the primary key - RESTORE_KEYS="refs/heads/${FALLBACK_BRANCH}/${INPUT_KEY}" - fi + FALLBACK_ACTIVE=true + FALLBACK_EXACT_KEY="refs/heads/${FALLBACK_BRANCH}/${INPUT_KEY}" ;; *) echo "::warning::Fallback branch '$FALLBACK_BRANCH' is not supported for cache fallback. Supported branches: main, master, branch-*" @@ -93,6 +78,32 @@ elif [[ -n "$INPUT_FALLBACK_BRANCH" || $INPUT_FALLBACK_TO_DEFAULT_BRANCH == "tru echo "::warning::Unable to determine fallback branch; skipping fallback restore keys." fi +# Add fallback exact-match key first (highest priority restore key) +if [[ -n "$FALLBACK_EXACT_KEY" ]]; then + RESTORE_KEYS="$FALLBACK_EXACT_KEY" +fi + +# Helper: append a prefixed restore key for each user-provided restore-key line +append_restore_keys() { + local prefix="$1" + while IFS= read -r line; do + if [[ -n "$line" ]]; then + RESTORE_KEYS="${RESTORE_KEYS:+${RESTORE_KEYS}$'\n'}${prefix}/${line}" + fi + done <<< "$INPUT_RESTORE_KEYS" + return 0 +} + +# Add branch-specific restore keys (prefix match) +if [[ -n $INPUT_RESTORE_KEYS ]]; then + append_restore_keys "$BRANCH_NAME" +fi + +# Add fallback branch restore keys (prefix match, lowest priority) +if [[ $FALLBACK_ACTIVE == true && -n $INPUT_RESTORE_KEYS ]]; then + append_restore_keys "refs/heads/${FALLBACK_BRANCH}" +fi + if [[ -n "$RESTORE_KEYS" ]]; then { echo "branch-restore-keys<