Retry publishMainSite on concurrent asf-site-production pushes - #541
Conversation
Keep Gradle publish instead of deploy-github-pages: that action writes docs/<snapshot|version>, skips root dotfiles, and would wipe grails-core docs. Copy the PR apache#110 non-fast-forward rebase retry into publishMainSite and drop contents:write since this workflow only pushes with GRAILS_GHTOKEN.
There was a problem hiding this comment.
🟡 Changes recommended
The new retry implementation introduces avoidable secret-leak risk in logs and has a few determinism/test-isolation issues that should be fixed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a retry/rebase loop to publishMainSite so concurrent pushes to apache/grails-website’s asf-site-production (e.g., from Grails Core docs publishing) are handled safely without force-pushing, and documents/validates the behavior with tests.
Changes:
- Implement
pushWithRetryfor non-fast-forward / fetch-first push rejections (fetch + descendant check + rebase + retry). - Add Spock coverage for success, disjoint rebase, conflict abort, non-retryable failures, and rewritten-history fail-closed behavior.
- Update workflow/README to reflect the approach and tighten job
contentspermission toread.
File summaries
| File | Description |
|---|---|
| README.md | Documents the cross-repo non-fast-forward retry behavior and links to upstream precedent. |
| buildSrc/src/main/groovy/website/gradle/tasks/PublishMainSiteTask.groovy | Adds push retry logic with fetch/descendant check/rebase and standardizes git invocations. |
| buildSrc/src/test/groovy/website/gradle/tasks/PublishMainSiteTaskSpec.groovy | Introduces fixture-based tests covering retry/rebase and failure modes. |
| .github/workflows/publish.yml | Updates comments and reduces permissions.contents to read-only. |
Review details
Suppressed comments (2)
buildSrc/src/main/groovy/website/gradle/tasks/PublishMainSiteTask.groovy:309
git rebasecan run hooks (e.g., pre-rebase). This command currently omits-c core.hooksPath=even though other git invocations explicitly disable hooks. Add the flag here to keep CI behavior deterministic.
spec.commandLine = ['git', 'rebase', '--onto', newRemoteTip, observedTip]
buildSrc/src/main/groovy/website/gradle/tasks/PublishMainSiteTask.groovy:315
git rebase --abortshould also disable hooks for the same reason as the main rebase command, to avoid surprises from any globalcore.hooksPathconfiguration.
spec.commandLine = ['git', 'rebase', '--abort']
- Files reviewed: 4/4 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Redact authenticated URLs before logging push output, restore the skip-backoff test property after the spec, disable hooks on merge-base, and include both tips in the ancestor-failure message.
The action is not hard-wired to docs/; grails-core sets TARGET_FOLDER to docs. Keep Gradle because a purged root overlay would wipe that tree and git add globs skip new root dotfiles.
Why not switch this workflow to
deploy-github-pagesapache/grails-github-actionsdeploy-github-pages(asf after #110) can publish toapache/grails-websiteasf-site-production. Grails Core already does that withGH_TOKEN: ${{ secrets.GRAILS_GHTOKEN }},TARGET_REPOSITORY: apache/grails-website, andDOCUMENTATION_BRANCH: asf-site-production. The only formal action input istoken; destination layout is env vars, not a hard-codeddocs/path.TARGET_FOLDERdefaults to.. Grails Core setsTARGET_FOLDER: docsbecause its output belongs underdocs/.That is the wrong shape for this site:
build/dist/onto the branch root and leave grails-core'sdocs/tree (and ASF files such as.asf.yaml) in place.PURGE_EXISTINGdefaults totrue. PointingTARGET_FOLDERat.with purge wouldgit rmthe destination checkout, includingdocs/.git add --verbose "${PUBLISH_PATH}"/*. Source copy includes dotfiles, but that glob does not stage new root.htaccessor.well-known.GRADLE_PUBLISH_RELEASE/LAST_SNAPSHOT_FOLDER/latestis a versioned-docs layout. This site is not a snapshot-or-version docs tree.#110 added a five-attempt porcelain
non-fast-forward/fetch firstrebase retry (no force-push). Sequential site-then-docs action runs are not inherently racy after that change. This PR copies that retry into:publishMainSiteinstead of adopting the action.GITHUB_TOKENstill cannot writeapache/grails-website. A PAT (GRAILS_GHTOKEN) is required either way.What this PR does
publishMainSite: fetch, require the new tip to descend from the cloned tip, rebase the unpublished commit, retry up to 5 times. Rewritten history and rebase conflicts fail closed.permissions.contentstoread(this job does not push this repo).secrets.GRAILS_GHTOKEN,GITHUB_SLUG: apache/grails-website,GH_BRANCH: asf-site-production, and intra-repoconcurrencywithcancel-in-progress: false.Tests
PublishMainSiteTaskSpec: first push, disjoint rebase keeps both trees, conflict aborts, unrelated failures are not retried, rewritten history fails closed, credential URLs are redacted.