-
Notifications
You must be signed in to change notification settings - Fork 20
fix: sync-main-to-experimental action correct base branch #479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||
| name: Merge sync PR into experimental | ||||||||||||
|
|
||||||||||||
| # The repo allows only squash merges, and squashing the sync PR would drop main's | ||||||||||||
| # commits and freeze the merge base. This lands it as a real merge commit instead; | ||||||||||||
| # GitHub closes the sync PR as merged once its commits are reachable from experimental. | ||||||||||||
| on: | ||||||||||||
| workflow_dispatch: | ||||||||||||
|
|
||||||||||||
| concurrency: | ||||||||||||
| group: merge-sync-to-experimental | ||||||||||||
| cancel-in-progress: false | ||||||||||||
|
|
||||||||||||
| permissions: | ||||||||||||
| contents: write | ||||||||||||
| pull-requests: read | ||||||||||||
|
|
||||||||||||
| jobs: | ||||||||||||
| merge: | ||||||||||||
| runs-on: ubuntu-latest | ||||||||||||
|
|
||||||||||||
| steps: | ||||||||||||
| - name: Check out the code | ||||||||||||
| uses: actions/checkout@v4 | ||||||||||||
| with: | ||||||||||||
| fetch-depth: 0 | ||||||||||||
|
|
||||||||||||
| - name: Configure git identity | ||||||||||||
| run: | | ||||||||||||
| git config user.name "github-actions[bot]" | ||||||||||||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||||||||||||
|
|
||||||||||||
| - name: Merge chore/sync into experimental | ||||||||||||
| env: | ||||||||||||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||
| run: | | ||||||||||||
| set -euo pipefail | ||||||||||||
|
|
||||||||||||
| git config --replace-all remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" | ||||||||||||
| git fetch --no-tags --force --prune origin | ||||||||||||
|
|
||||||||||||
| if ! git rev-parse --verify --quiet refs/remotes/origin/chore/sync >/dev/null; then | ||||||||||||
| echo "::error::chore/sync does not exist on the remote; there is no sync to land." | ||||||||||||
| exit 1 | ||||||||||||
| fi | ||||||||||||
|
|
||||||||||||
| if git merge-base --is-ancestor origin/chore/sync origin/experimental; then | ||||||||||||
| echo "experimental already contains chore/sync; nothing to merge." | ||||||||||||
| exit 0 | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] When
Suggested change
This handles the partial-failure recovery case (prior run pushed experimental but failed on branch delete). |
||||||||||||
| fi | ||||||||||||
|
|
||||||||||||
| PR_NUMBER="$(gh pr list --base experimental --head chore/sync --state open --json number --jq '.[0].number // empty')" | ||||||||||||
|
|
||||||||||||
| git checkout -B experimental origin/experimental | ||||||||||||
| # --no-ff so the sync always shows up as one labelled merge commit, the way the | ||||||||||||
| # PR button would have recorded it. | ||||||||||||
| if ! git merge --no-ff -m "chore: sync main to experimental${PR_NUMBER:+ (#$PR_NUMBER)}" origin/chore/sync; then | ||||||||||||
| git merge --abort || true | ||||||||||||
| echo "::error::Merging chore/sync into experimental failed. Resolve it on chore/sync, push, and re-run this workflow." | ||||||||||||
| exit 1 | ||||||||||||
| fi | ||||||||||||
|
|
||||||||||||
| git push origin experimental | ||||||||||||
|
|
||||||||||||
| # A PR closed by a push is not covered by delete_branch_on_merge, so remove the | ||||||||||||
| # now fully-merged branch here: the sync job treats an existing chore/sync as a | ||||||||||||
| # sync still in flight. | ||||||||||||
| git push origin --delete chore/sync | ||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,35 +5,112 @@ on: | |
| branches: [ main ] | ||
| workflow_dispatch: | ||
|
|
||
| # Two pushes to main must not race on pushing chore/sync. | ||
| concurrency: | ||
| group: sync-main-to-experimental | ||
| cancel-in-progress: false | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| create-pr: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Check out the code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| # full history, otherwise the merges below have no common ancestor | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Configure git identity | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
|
|
||
| - name: Create or update sync branch | ||
| run: | | ||
| git fetch origin experimental | ||
| git checkout -B chore/sync | ||
| git push --force --set-upstream origin chore/sync | ||
| set -euo pipefail | ||
|
|
||
| # The wildcard is what the bare fetch below and --force-with-lease resolve | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] With |
||
| # branch names through. | ||
| git config --replace-all remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" | ||
| git fetch --no-tags --force --prune origin | ||
|
|
||
| # An existing chore/sync is a sync still in flight: keep it, so commits | ||
| # arriving on main accumulate on it and a manual conflict resolution pushed | ||
| # to it survives. The branch is deleted when its PR merges, so the next sync | ||
| # starts from experimental again. | ||
| BASE=origin/experimental | ||
| if git rev-parse --verify --quiet refs/remotes/origin/chore/sync >/dev/null; then | ||
| BASE=origin/chore/sync | ||
| fi | ||
| git checkout -B chore/sync "$BASE" | ||
|
|
||
| # chore/sync must be experimental + main: anything that only exists on | ||
| # experimental has to survive the sync, or the package built from the branch | ||
| # ships main's protos alone. | ||
| for REF in origin/experimental origin/main; do | ||
| if ! git merge -m "chore: merge $REF into chore/sync" "$REF"; then | ||
| git merge --abort || true | ||
| echo "::error::Merging $REF into chore/sync failed. To recover: branch chore/sync off experimental (or check out the existing one), merge main into it, resolve, push it, and open a PR against experimental. Later runs merge on top of that resolution." | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| # Push whenever the recomputed branch differs from the remote, so a stale | ||
| # chore/sync can never stay the head of an open PR. | ||
| REMOTE_HEAD="$(git rev-parse --verify --quiet refs/remotes/origin/chore/sync || echo absent)" | ||
| if [ "$(git rev-parse HEAD)" = "$REMOTE_HEAD" ]; then | ||
| echo "chore/sync on the remote is already up to date." | ||
| else | ||
| git push --force-with-lease origin chore/sync | ||
| fi | ||
|
|
||
| - name: Create or update PR | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| # Backticks here are data, not shell source: they reach gh through the | ||
| # variable and are never re-evaluated. | ||
| PR_BODY: | | ||
| :crown: *An automated PR to keep experimental in sync with main* | ||
|
|
||
| > [!IMPORTANT] | ||
| > **Do not merge this PR with the Squash button.** | ||
| > | ||
| > Squashing replaces `main`'s commits with a single new one, so `main` stops | ||
| > being an ancestor of `experimental` and the merge base freezes. Every later | ||
| > sync then re-applies changes `experimental` already has, and starts | ||
| > conflicting within a couple of runs. | ||
| > | ||
| > Land it by running the **Merge sync PR into experimental** workflow | ||
| > (Actions -> Merge sync PR into experimental -> Run workflow). It pushes a | ||
| > real merge commit, and this PR closes as merged. | ||
| > | ||
| > If it does get squashed, restore the ancestry with | ||
| > `git merge -s ours origin/main` on `experimental`. | ||
| run: | | ||
| PR_NUMBER=$(gh pr list --base experimental --head chore/sync --state open --json number --jq '.[0].number') | ||
| set -euo pipefail | ||
|
|
||
| # gh pr create fails with "No commits between ..." when nothing is ahead. | ||
| if git merge-base --is-ancestor HEAD origin/experimental; then | ||
| echo "experimental already contains chore/sync; nothing to propose." | ||
| exit 0 | ||
| fi | ||
|
|
||
| PR_NUMBER="$(gh pr list --base experimental --head chore/sync --state open --json number --jq '.[0].number // empty')" | ||
|
|
||
| if [ -n "$PR_NUMBER" ]; then | ||
| echo "PR #$PR_NUMBER already exists, adding a comment..." | ||
| gh pr comment $PR_NUMBER --body "🔄 Updated with latest changes from **main** on $(date -u '+%Y-%m-%d %H:%M UTC')" | ||
| gh pr comment "$PR_NUMBER" --body "🔄 Updated with latest changes from **main** on $(date -u '+%Y-%m-%d %H:%M UTC')" | ||
| else | ||
| echo "Creating new PR..." | ||
| gh pr create \ | ||
| --base experimental \ | ||
| --head chore/sync \ | ||
| --title "chore: sync main to experimental" \ | ||
| --body ":crown: *An automated PR to keep experimental in sync with main*" \ | ||
| --body "$PR_BODY" \ | ||
| --label "auto-pr" | ||
| fi | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Same as the sync workflow —
git config --replace-allis redundant withfetch-depth: 0.