Skip to content

perf(cicd): shallow checkouts for release build and prepare phases - #37177

Closed
wezell wants to merge 4 commits into
mainfrom
ci-shallow-release-checkouts
Closed

perf(cicd): shallow checkouts for release build and prepare phases#37177
wezell wants to merge 4 commits into
mainfrom
ci-shallow-release-checkouts

Conversation

@wezell

@wezell wezell commented Aug 24, 2026

Copy link
Copy Markdown
Member

Closes #37178

Problem

Release runs spend most of their wall-clock time in git fetch, not building. From run 32759840677 (Release 26.08.24-01):

Job Total Git fetch
Prepare Release 22m04s ~20m33s
Build / Initial Artifact Build 34m22s ~19m13s

Root cause: every checkout uses fetch-depth: 0, which fetches all refs — all ~2,100+ branches plus full history (~1.1 GB pack). The build fetch log even shows it downloading unrelated branches (e.g. revert-pubsub-checkout) that have nothing to do with the release tag.

Changes

  1. cicd_comp_build-phase.yml — the ref-based checkout (release tag, LTS tag, manual-deploy ref, nightly ref) now uses fetch-depth: 1. The build only needs the ref snapshot; buildnumber-maven-plugin (scmRevision) reads HEAD only, which works fine on a shallow clone. The no-ref path used by PR checks (git diff origin/main...HEAD, git status clean-tree check) keeps fetch-depth: 0, so PR workflows are unaffected.
  2. cicd_comp_release-prepare-phase.yml — shallow checkout of the default branch HEAD. When an explicit release_commit is supplied and isn't already present locally, it is fetched with git fetch --depth=1 origin <sha> (GitHub enables uploadpack.allowAnySHA1InWant) before git reset --hard.
  3. cicd_comp_test-phase.yml — the same fix, multiplied: the test phase fans out to up to 26 jobs (setup-matrix + 25 suites from .github/test-matrix.yml), each doing a full fetch-depth: 0 checkout (~19-20 min per job). Nothing in these jobs reads git history (no git commands after checkout, the maven-job action has no git usage, Sonar runs in a separate workflow), so both checkouts are now fetch-depth: 1. This speeds up PR, merge-queue, trunk, and nightly test cycles.
  4. cicd_comp_deployment-phase.yml, cicd_6-release.yml (bump-min-sdk-version), and cicd_4-nightly.yml (setup) — completes the audit of every fetch-depth: 0 in .github/. The semantics were misread: fetch-depth: 0 means unlimited history for all refs (every one of ~2,100+ branches, ~1.1 GB pack), not "no history". Deployment and the SDK bump only need the snapshot/HEAD of main (fetch-depth: 1); nightly's setup walks git log on main back to midnight UTC, so it gets a bounded fetch-depth: 100 (max observed on main is ~19 commits/day). Two spots intentionally keep fetch-depth: 0 because they genuinely read history: the build-phase PR path (git diff origin/main...HEAD merge base) and ai_claude-backend-reviewer.yml (agent runs git diff/log/show between base and head SHAs). The deprecated legacy-release_maven-release-process.yml is left untouched.

Measurements (cold clone, tag v26.08.24-01)

Method Time
Full-ref fetch (current, fetch-depth: 0) ~19 min in CI
Single-tag full-depth fetch 9m41s
Single-tag shallow fetch (this PR) 77s
Tarball snapshot (codeload) 24s

The tarball was rejected: no .git means scmRevision silently falls back to UNKNOWN in the shipped artifacts.

Expected savings: ~38 of the ~66 min profiled in the release run.

Validation

  • YAML syntax validated for both workflows
  • Audited every git consumer in the release path: buildnumber-maven-plugin (works shallow), PR-check steps (gated off, run-pr-checks: false), docker/npm scripts (no git usage)
  • Callers passing ref: release, LTS, java-variant, manual-deploy, CLI, nightly — all have run-pr-checks: false

Follow-ups (not in this PR)

  • The stale-branch cleanup (backup + delete of ~1,800 branches >6m old) is still worth doing for general repo hygiene
  • bump-min-sdk-version is fixed by this PR. Correction on Check Changed Files: it does not pay the fetch tax — for PRs dorny/paths-filter uses the GitHub REST API for changed files (~1s) and its checkout is already shallow by default (~13s); the 2m26s seen in the release run was runner-queue time (release also passes change-detection: 'disabled', skipping those steps entirely)
  • maven-job action declares a needs-history input that is never consumed (vestigial)

fetch-depth: 0 fetches every branch ref (~2k+ branches, ~1.1 GB pack)
which cost ~19-20 min per job in release runs (see run 32759840677:
Prepare Release 22m of which ~20m was the fetch; Initial Artifact Build
34m of which ~19m was the fetch).

- build-phase: the ref-based checkout only needs the ref snapshot.
  buildnumber-maven-plugin (scmRevision) reads HEAD only, which works on
  a shallow clone. The PR-check steps that need history stay on the
  fetch-depth: 0 no-ref path.
- release-prepare: checkout HEAD of the default branch shallowly; when
  an explicit release commit is supplied, fetch just that commit
  (GitHub enables uploadpack.allowAnySHA1InWant) before reset --hard.

Measured from a cold clone: shallow single-tag fetch ~77s vs ~19min in
CI for the full fetch. Tarball snapshot alternative (~24s) was rejected
because it loses .git and the scmRevision stamp falls back to UNKNOWN.
@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @wezell's task in 1m 34s —— View job


Code Review — shallow checkouts for release build & prepare phases

  • Gather context (diff, all 6 workflow files)
  • Recheck the two prior dotbot findings against the current diff
  • Scan the new commit's changes for new issues

Reviewed the full diff (88af0bad..HEAD) across all 6 workflow files, with focus on the last commit (7c7b9b2b) that addressed the earlier review.

Resolved

  • cicd_comp_build-phase.yml:109 — The P2 finding (depth should key off run-pr-checks, not ref) is fixed: fetch-depth: ${{ inputs.run-pr-checks && '0' || '1' }}. The expression is correct — true && '0' || '1''0', false && '0' || '1''1'. The string literals '0'/'1' correctly sidestep the GH-expression pitfall where numeric 0 is falsy (the inline comment documents exactly this). The hypothetical ref != '' && run-pr-checks: true caller now gets full history for git diff origin/main...HEAD.
  • cicd_4-nightly.yml:100 — The P3 midnight-boundary concern is addressed by raising fetch-depth from 100 → 1000 (~52 days at the observed ~19 commits/day). git log --before=$MIDNIGHT -1 only walks back ~3h of commits from HEAD, so the boundary cannot realistically be overrun, and the empty-result fallback is now unreachable in practice.

New Issues

No new issues.

I verified the two paths most worth scrutinizing in the new code:

  • cicd_comp_release-prepare-phase.yml:153-161release_commit is never empty here: set-version falls back to git log -1 %H (HEAD) when the input is blank (line 124-126), so git cat-file -e / git fetch --depth=1 origin <sha> always receive a real SHA. Default case (HEAD) is present locally → no extra fetch; explicit-commit case fetches just that commit before git reset --hard. Pushing the new release branch from the shallow clone is fine since its base commit already exists on the remote.
  • cicd_4-nightly.yml:106-131 — no git operation in Find Build Commit needs history beyond the bounded window; use-latest-commit reads HEAD only.

All other changes (cicd_6-release.yml, cicd_comp_deployment-phase.yml, cicd_comp_test-phase.yml) are HEAD-only snapshots with no git-history consumers in-job — the fetch-depth: 1 reductions are safe.

LGTM. 🟢

· branch ci-shallow-release-checkouts

wezell added 2 commits August 24, 2026 15:35
Same fix as the release phases, but multiplied: the test phase fans out to
up to 26 jobs (setup-matrix + 25 suites from .github/test-matrix.yml), each
doing a fetch-depth: 0 checkout that pulls every branch ref (~2k+ branches,
~1.1 GB pack, ~19-20 min per job).

Nothing in these jobs reads git history: no git commands run after checkout,
the maven-job action has no git usage, Sonar runs in a separate workflow, and
buildnumber-maven-plugin (scmRevision) reads HEAD only, which works on a
shallow clone.
…nd nightly setup

Completes the audit of every fetch-depth: 0 in .github/: the semantics were
misread — fetch-depth: 0 means UNLIMITED history for ALL refs (every branch,
~2k+ branches, ~1.1 GB pack, ~19-20 min per job), not 'no history'.

- deployment-phase: no git commands after checkout -> fetch-depth: 1
- cicd_6-release bump-min-sdk-version: only needs HEAD of main (it does its
  own  before checkout -B) -> fetch-depth: 1
- cicd_4-nightly setup: Find Build Commit walks git log on main back to
  midnight UTC (max observed ~19 commits/day on main) -> fetch-depth: 100

Left at fetch-depth: 0 intentionally:
- cicd_comp_build-phase.yml no-ref path: diff --git a/.github/workflows/cicd_comp_build-phase.yml b/.github/workflows/cicd_comp_build-phase.yml
index ef92201..3f0fe0d 100644
--- a/.github/workflows/cicd_comp_build-phase.yml
+++ b/.github/workflows/cicd_comp_build-phase.yml
@@ -91,12 +91,18 @@ jobs:
         if: inputs.ref == ''
         with:
           fetch-depth: 0
+      # Shallow checkout (fetch-depth: 1): the build only needs the ref snapshot.
+      # fetch-depth: 0 pulled every branch ref (~2k+ branches, ~1.1 GB pack) and
+      # cost ~19-20 min per job. buildnumber-maven-plugin (scmRevision) only reads
+      # HEAD, which works fine on a shallow clone. The `run-pr-checks` steps that
+      # need real history (git diff origin/main...HEAD, git status) are only used
+      # by the inputs.ref == '' path below, which keeps fetch-depth: 0.
       - name: Checkout code with ref ${{ inputs.ref }}
         if: inputs.ref != ''
         uses: actions/checkout@v4
         with:
           ref: ${{ inputs.ref }}
-          fetch-depth: 0
+          fetch-depth: 1

       # Check if .mvn/maven.config is modified in PR (only for PR checks)
       - name: Check if .mvn/maven.config is in the PR commit
diff --git a/.github/workflows/cicd_comp_release-prepare-phase.yml b/.github/workflows/cicd_comp_release-prepare-phase.yml
index dff9e11..484e5f4 100644
--- a/.github/workflows/cicd_comp_release-prepare-phase.yml
+++ b/.github/workflows/cicd_comp_release-prepare-phase.yml
@@ -93,10 +93,14 @@ jobs:
         env:
           GITHUB_CONTEXT: ${{ toJson(github) }}

+      # Shallow checkout (fetch-depth: 1): this job only needs HEAD of the default
+      # branch (or the explicitly supplied release commit, fetched below) to cut
+      # the release branch from. fetch-depth: 0 pulled every branch ref
+      # (~2k+ branches, ~1.1 GB pack) and cost ~20 min per release run.
       - name: Checkout core
         uses: actions/checkout@v4
         with:
-          fetch-depth: 0
+          fetch-depth: 1
           token: ${{ secrets.CI_MACHINE_TOKEN || github.token }}

       - uses: ./.github/actions/core-cicd/cleanup-runner
@@ -146,7 +150,15 @@ jobs:
             git push origin :refs/tags/${release_tag}
           fi

-          git reset --hard ${{ steps.set-version.outputs.release_commit }}
+          release_commit=${{ steps.set-version.outputs.release_commit }}
+          # Shallow checkout (fetch-depth: 1) only guarantees HEAD of the default
+          # branch. When an explicit release commit was supplied, fetch just that
+          # commit (GitHub enables uploadpack.allowAnySHA1InWant) before resetting.
+          if ! git cat-file -e "${release_commit}^{commit}" 2>/dev/null; then
+            echo "Release commit ${release_commit} not present locally, fetching it"
+            git fetch --depth=1 origin "${release_commit}"
+          fi
+          git reset --hard "${release_commit}"
           release_version=${{ steps.set-version.outputs.release_version }}
           release_branch=${{ steps.set-version.outputs.release_branch }}

diff --git a/.github/workflows/cicd_comp_test-phase.yml b/.github/workflows/cicd_comp_test-phase.yml
index 7c77510..01c020e 100644
--- a/.github/workflows/cicd_comp_test-phase.yml
+++ b/.github/workflows/cicd_comp_test-phase.yml
@@ -96,7 +96,9 @@ jobs:
       - name: Checkout code
         uses: actions/checkout@v4
         with:
-          fetch-depth: 0
+          # Shallow checkout: this job only reads .github/test-matrix.yml from HEAD.
+          # fetch-depth: 0 pulled every branch ref (~2k+ branches, ~1.1 GB pack).
+          fetch-depth: 1

       - name: Parse test configuration
         id: parse-config
@@ -249,7 +251,12 @@ jobs:
       - name: Checkout code
         uses: actions/checkout@v4
         with:
-          fetch-depth: 0
+          # Shallow checkout: test jobs run Maven suites against the checked-out
+          # snapshot only — no git history is read anywhere in this job or in the
+          # maven-job action (buildnumber-maven-plugin reads HEAD, which works on
+          # a shallow clone). fetch-depth: 0 pulled every branch ref (~2k+
+          # branches, ~1.1 GB pack) at ~19-20 min per matrix job.
+          fetch-depth: 1

       # The libvips image engine (IMAGE_API_USE_LIBVIPS) is exercised by VipsParityTest.
       # Install native libvips on the JVM unit-test runner so those tests run instead of
  needs the merge base with main (PR checks)
- ai_claude-backend-reviewer.yml: agent runs git diff/log/show between base
  and head SHAs
- legacy-release_maven-release-process.yml: deprecated workflow (follow-up
  tracked in #37178)
@github-actions github-actions Bot added the Area : CI/CD PR changes GitHub Actions/workflows label Aug 24, 2026
@wezell

wezell commented Aug 24, 2026

Copy link
Copy Markdown
Member Author

✅ Validated via Manual Deploy run 32769863759

Dispatched -8 Manual Deploy with Use workflow from: ci-shallow-release-checkouts (this PR's branch) so the run exercised the modified workflows end-to-end.

Build / Initial Artifact Build: ✅ success — the ref-path checkout with fetch-depth: 1 worked, including the Maven build (buildnumber-maven-plugin/scmRevision stamps correctly on the shallow clone).

Measured fetch time (ref-path checkout, this branch)

Fetch duration
Before (fetch-depth: 0, release run 32759840677) ~19m13s
After (fetch-depth: 1, this run) 6m00s (19:49:46 → 19:55:46)

Savings ~13 min per build job. Note the ~77s figure in the PR description was measured from a fast local network; GitHub-hosted runners get ~1 MB/s to the git backend, so ~6 min for the ~360 MB shallow snapshot is the realistic number. The remaining ~6 min is pure snapshot transfer — irreducible without the tarball approach (loses .git/scmRevision).

@wezell wezell added the PR : dotbot review Trigger dotbot AI code review on this PR label Aug 24, 2026
Comment thread .github/workflows/cicd_comp_build-phase.yml Outdated
Comment thread .github/workflows/cicd_4-nightly.yml Outdated
dotbot flagged two items:

1. [P2] build-phase ref checkout depth was keyed on the wrong condition.
   The PR-check steps (.mvn/maven.config guard, clean-tree check) are gated
   on run-pr-checks, not ref == '' as the comment claimed. Drive depth by
   run-pr-checks so a future caller passing both ref and run-pr-checks gets
   full history. Uses string literals: GH expressions treat 0 as falsy, so
   the naive run-pr-checks && 0 || 1 collapses to 1 in the true branch.

2. [P3] nightly setup bounded depth (100) could theoretically miss the
   midnight commit. Raised to 1000 (~52 days of volume at the ~19/day max).
   Rejected dotbot's suggested fetch-depth: 0 - that regresses the nightly
   setup job to a ~19-20 min full fetch for a once-daily schedule when a
   comfortably-bounded fetch is equally correct in practice.
@wezell

wezell commented Aug 24, 2026

Copy link
Copy Markdown
Member Author

👀 Backend reviewer (dotbot) findings addressed

dotbot (deepseek-v4-pro-0813 + glm-latest) reviewed the PR and reported 2 findings. Assessment and resolution:

1️⃣ [P2] build-phase ref-checkout depth keyed on the wrong condition — fixed

The comment claimed the PR-check steps were gated on inputs.ref == '', when they are actually gated on inputs.run-pr-checks. No current caller sets both, so it was latent — but a future ref + run-pr-checks: true combination would have run git diff origin/main...HEAD against a shallow clone. Now the ref-path depth is driven by run-pr-checks.

One correction to dotbot's own suggested fix, though: its proposed expression inputs.run-pr-checks && 0 || 1 is wrong — in GitHub expression semantics 0 is falsy, so true && 0 yields 0, then 0 || 1 yields 1. The true branch collapses to 1 (shallow), the opposite of intent. I used string literals '0'/'1' instead, which are truthy and chain correctly.

2️⃣ [P3] nightly setup bounded depth could miss the midnight commit — hardened

fetch-depth: 100 could theoretically miss the midnight lookup if >100 commits landed between midnight and run time (~3h). Raised to 1000 (~52 days of volume at the ~19/day observed max). I deliberately rejected dotbot's suggested fetch-depth: 0 — it would regress the nightly setup job back to a ~19-20 min full fetch every night for a once-a-day schedule that a comfortably-bounded fetch handles equally well.

Both changes committed as 7c7b9b2b21. Manual Deploy validation (32769863759) remains green end-to-end; the deploy produced dotcms/dotcms:manual, :manual_cb1a752 (immutable), and :manual_ci-shallow-release-checkouts (ref alias).

@github-actions

Copy link
Copy Markdown
Contributor

dotbot code review:

  • Reviewer: deepseek/deepseek-v4-pro-0813 (medium)
  • Overall: patch is correct
  • New findings this run: 0
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 0

The incremental diff directly addresses the prior comments: the build-phase ref checkout now drives fetch-depth from inputs.run-pr-checks using truthy string literals ('0'/'1'), which works correctly in GitHub expressions, and the nightly setup checkout's bounded depth is raised to 1000 with an accurate explanation. No current caller combines ref and run-pr-checks, and the new changes introduce no live bug.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · deepseek/deepseek-v4-pro-0813 · medium

@github-actions

Copy link
Copy Markdown
Contributor

dotbot code review:

  • Reviewer: ~z-ai/glm-latest (medium)
  • Overall: patch is correct
  • New findings this run: 0
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 0

The prior finding is fixed: fetch-depth was raised from 100 to 1000, providing ample headroom over the observed ~19 commits/day. The build-phase fix also correctly gates depth on run-pr-checks using truthy string literals. No remaining issues.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · ~z-ai/glm-latest · medium

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : CI/CD PR changes GitHub Actions/workflows PR : dotbot review Trigger dotbot AI code review on this PR

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Release CI: fetch-depth: 0 checkouts cost ~19-20 min per job (fetch of all ~2k branches)

2 participants