From f1575908b79c070607a741dfd4202bc37ee502c7 Mon Sep 17 00:00:00 2001 From: "yousef.sadr" Date: Thu, 16 Apr 2026 15:29:06 -0400 Subject: [PATCH 1/5] Add GitHub Actions workflow for syncing upstream repository - Introduced a new workflow `sync-upstream.yaml` that automates the process of syncing the main branch with an upstream repository on a scheduled basis and via manual triggers. - The workflow includes steps for fast-forwarding the main branch, checking for updates on the cohere branch, and creating pull requests for any new commits. - Enhanced error handling for merge conflicts and provided detailed instructions for conflict resolution in the generated pull requests. --- .github/workflows/sync-upstream.yaml | 196 +++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 .github/workflows/sync-upstream.yaml diff --git a/.github/workflows/sync-upstream.yaml b/.github/workflows/sync-upstream.yaml new file mode 100644 index 0000000000..ceb575f897 --- /dev/null +++ b/.github/workflows/sync-upstream.yaml @@ -0,0 +1,196 @@ +name: Sync upstream + +on: + schedule: + # Every Monday at 09:00 UTC + - cron: "0 9 * * 1" + workflow_dispatch: + inputs: + upstream_ref: + description: "Upstream ref to sync from (default: main)" + required: false + default: "main" + cohere_branch: + description: "Cohere branch to merge into" + required: false + default: "cohere" + +permissions: + contents: write + pull-requests: write + +env: + UPSTREAM_REPO: https://github.com/confidential-containers/cloud-api-adaptor.git + UPSTREAM_REF: ${{ inputs.upstream_ref || 'main' }} + COHERE_BRANCH: ${{ inputs.cohere_branch || 'cohere' }} + +jobs: + # ── Step 1: fast-forward origin/main to match upstream/main ────────────── + sync-main: + runs-on: ubuntu-latest + outputs: + new_commits: ${{ steps.ff.outputs.new_commits }} + upstream_sha: ${{ steps.ff.outputs.upstream_sha }} + steps: + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Fast-forward main to upstream + id: ff + run: | + git remote add upstream "$UPSTREAM_REPO" || true + git fetch upstream "$UPSTREAM_REF" + + BEHIND=$(git rev-list --count HEAD..upstream/$UPSTREAM_REF) + UPSTREAM_SHA=$(git rev-parse --short upstream/$UPSTREAM_REF) + echo "upstream_sha=$UPSTREAM_SHA" >> "$GITHUB_OUTPUT" + + if [ "$BEHIND" -eq 0 ]; then + echo "main is already up to date with upstream." + echo "new_commits=0" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Verify this is a clean fast-forward (main should have no extra commits). + AHEAD=$(git rev-list --count upstream/$UPSTREAM_REF..HEAD) + if [ "$AHEAD" -ne 0 ]; then + echo "::error::origin/main is $AHEAD commits AHEAD of upstream — cannot fast-forward." + echo "::error::Remove the extra commits first (rebase or reset) before syncing." + exit 1 + fi + + git merge --ff-only upstream/$UPSTREAM_REF + git push origin main + echo "new_commits=$BEHIND" >> "$GITHUB_OUTPUT" + echo "Fast-forwarded main by $BEHIND commits to $UPSTREAM_SHA" + + # ── Step 2: merge updated main into the cohere branch via PR ───────────── + sync-cohere: + needs: sync-main + runs-on: ubuntu-latest + steps: + - name: Checkout cohere branch + uses: actions/checkout@v4 + with: + ref: ${{ env.COHERE_BRANCH }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Check if cohere is behind main + id: check + run: | + git fetch origin main + BEHIND=$(git rev-list --count HEAD..origin/main) + echo "behind=$BEHIND" >> "$GITHUB_OUTPUT" + echo "Cohere branch is $BEHIND commits behind origin/main" + + DATE=$(date +%Y-%m-%d) + SHORT_SHA=$(git rev-parse --short origin/main) + echo "sync_branch=sync/upstream-${DATE}-${SHORT_SHA}" >> "$GITHUB_OUTPUT" + + - name: Skip if up to date + if: steps.check.outputs.behind == '0' + run: echo "Cohere branch is already up to date with main. Nothing to do." + + - name: Create sync branch and attempt merge + if: steps.check.outputs.behind != '0' + id: merge + run: | + git checkout -b "${{ steps.check.outputs.sync_branch }}" + + if git merge origin/main --no-edit \ + -m "Merge main (${{ needs.sync-main.outputs.upstream_sha }}) into $COHERE_BRANCH"; then + echo "conflict=false" >> "$GITHUB_OUTPUT" + else + echo "conflict=true" >> "$GITHUB_OUTPUT" + echo "" + echo "=== CONFLICT: listing conflicted files ===" + git diff --name-only --diff-filter=U + # Abort so the branch stays at the cohere HEAD — the reviewer + # will run the merge locally and get native Git conflict tooling. + git merge --abort + fi + + - name: Push sync branch + if: steps.check.outputs.behind != '0' + run: git push origin "${{ steps.check.outputs.sync_branch }}" + + - name: Create pull request (clean merge) + if: steps.check.outputs.behind != '0' && steps.merge.outputs.conflict == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BEHIND=${{ steps.check.outputs.behind }} + UPSTREAM_SHA=${{ needs.sync-main.outputs.upstream_sha }} + + gh pr create \ + --base "$COHERE_BRANCH" \ + --head "${{ steps.check.outputs.sync_branch }}" \ + --title "Sync upstream ($UPSTREAM_SHA) — $BEHIND new commits" \ + --body "$(cat < Date: Thu, 16 Apr 2026 15:48:44 -0400 Subject: [PATCH 2/5] Refactor GitHub Actions workflow for upstream syncing - Updated `sync-upstream.yaml` to deny all permissions at the workflow level, requiring explicit opt-in for jobs. - Implemented concurrency control to prevent overlapping sync workflows. - Enhanced security by ensuring the GITHUB_TOKEN is not persisted in the git configuration. - Improved error handling and clarity in the fast-forwarding and merging processes, including better management of merge conflicts. - Updated action versions for consistency and reliability. --- .github/workflows/sync-upstream.yaml | 103 ++++++++++++++++++--------- 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/.github/workflows/sync-upstream.yaml b/.github/workflows/sync-upstream.yaml index ceb575f897..557930b8f4 100644 --- a/.github/workflows/sync-upstream.yaml +++ b/.github/workflows/sync-upstream.yaml @@ -15,9 +15,14 @@ on: required: false default: "cohere" -permissions: - contents: write - pull-requests: write +# Deny all permissions at the workflow level; jobs opt in explicitly below. +permissions: {} + +# Only one sync workflow runs at a time — prevents racing pushes to main or +# duplicate sync PRs if a manual dispatch overlaps the cron schedule. +concurrency: + group: sync-upstream + cancel-in-progress: false env: UPSTREAM_REPO: https://github.com/confidential-containers/cloud-api-adaptor.git @@ -27,17 +32,22 @@ env: jobs: # ── Step 1: fast-forward origin/main to match upstream/main ────────────── sync-main: + name: Fast-forward main from upstream runs-on: ubuntu-latest + permissions: + contents: write # push the fast-forwarded main branch outputs: new_commits: ${{ steps.ff.outputs.new_commits }} upstream_sha: ${{ steps.ff.outputs.upstream_sha }} steps: - name: Checkout main - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: main fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + # Don't persist GITHUB_TOKEN in .git/config — pushes auth explicitly + # via extraheader below so the token can't leak to later steps. + persist-credentials: false - name: Configure git run: | @@ -46,12 +56,14 @@ jobs: - name: Fast-forward main to upstream id: ff + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git remote add upstream "$UPSTREAM_REPO" || true git fetch upstream "$UPSTREAM_REF" - BEHIND=$(git rev-list --count HEAD..upstream/$UPSTREAM_REF) - UPSTREAM_SHA=$(git rev-parse --short upstream/$UPSTREAM_REF) + BEHIND=$(git rev-list --count "HEAD..upstream/$UPSTREAM_REF") + UPSTREAM_SHA=$(git rev-parse --short "upstream/$UPSTREAM_REF") echo "upstream_sha=$UPSTREAM_SHA" >> "$GITHUB_OUTPUT" if [ "$BEHIND" -eq 0 ]; then @@ -61,29 +73,34 @@ jobs: fi # Verify this is a clean fast-forward (main should have no extra commits). - AHEAD=$(git rev-list --count upstream/$UPSTREAM_REF..HEAD) + AHEAD=$(git rev-list --count "upstream/$UPSTREAM_REF..HEAD") if [ "$AHEAD" -ne 0 ]; then echo "::error::origin/main is $AHEAD commits AHEAD of upstream — cannot fast-forward." echo "::error::Remove the extra commits first (rebase or reset) before syncing." exit 1 fi - git merge --ff-only upstream/$UPSTREAM_REF - git push origin main + git merge --ff-only "upstream/$UPSTREAM_REF" + git -c "http.https://github.com/.extraheader=AUTHORIZATION: bearer $GH_TOKEN" \ + push origin main echo "new_commits=$BEHIND" >> "$GITHUB_OUTPUT" echo "Fast-forwarded main by $BEHIND commits to $UPSTREAM_SHA" # ── Step 2: merge updated main into the cohere branch via PR ───────────── sync-cohere: + name: Merge main into cohere via PR needs: sync-main runs-on: ubuntu-latest + permissions: + contents: write # push the sync/* branch + pull-requests: write # create the sync PR via `gh pr create` steps: - name: Checkout cohere branch - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ env.COHERE_BRANCH }} fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Configure git run: | @@ -106,45 +123,58 @@ jobs: if: steps.check.outputs.behind == '0' run: echo "Cohere branch is already up to date with main. Nothing to do." - - name: Create sync branch and attempt merge + - name: Create sync branch and check for conflicts if: steps.check.outputs.behind != '0' id: merge + env: + SYNC_BRANCH: ${{ steps.check.outputs.sync_branch }} run: | - git checkout -b "${{ steps.check.outputs.sync_branch }}" - - if git merge origin/main --no-edit \ - -m "Merge main (${{ needs.sync-main.outputs.upstream_sha }}) into $COHERE_BRANCH"; then + # The sync branch is created at origin/main so it's always ahead of + # cohere by $BEHIND commits. The PR merges this into cohere via the + # GitHub UI, which handles clean merges and surfaces conflicts + # natively — the merge commit is never pushed from CI. + # (If we instead tried to pre-merge in CI and aborted on conflict, + # the sync branch would equal cohere HEAD and `gh pr create` would + # fail with "No commits between cohere and ".) + git branch "$SYNC_BRANCH" origin/main + + # Preflight merge — purely to report conflict status in the PR body. + # Never committed or pushed; aborted either way. + if git merge --no-commit --no-ff origin/main; then echo "conflict=false" >> "$GITHUB_OUTPUT" else echo "conflict=true" >> "$GITHUB_OUTPUT" echo "" echo "=== CONFLICT: listing conflicted files ===" git diff --name-only --diff-filter=U - # Abort so the branch stays at the cohere HEAD — the reviewer - # will run the merge locally and get native Git conflict tooling. - git merge --abort fi + git merge --abort 2>/dev/null || true - name: Push sync branch if: steps.check.outputs.behind != '0' - run: git push origin "${{ steps.check.outputs.sync_branch }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SYNC_BRANCH: ${{ steps.check.outputs.sync_branch }} + run: | + git -c "http.https://github.com/.extraheader=AUTHORIZATION: bearer $GH_TOKEN" \ + push origin "$SYNC_BRANCH" - name: Create pull request (clean merge) if: steps.check.outputs.behind != '0' && steps.merge.outputs.conflict == 'false' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BEHIND: ${{ steps.check.outputs.behind }} + UPSTREAM_SHA: ${{ needs.sync-main.outputs.upstream_sha }} + SYNC_BRANCH: ${{ steps.check.outputs.sync_branch }} run: | - BEHIND=${{ steps.check.outputs.behind }} - UPSTREAM_SHA=${{ needs.sync-main.outputs.upstream_sha }} - gh pr create \ --base "$COHERE_BRANCH" \ - --head "${{ steps.check.outputs.sync_branch }}" \ + --head "$SYNC_BRANCH" \ --title "Sync upstream ($UPSTREAM_SHA) — $BEHIND new commits" \ --body "$(cat < Date: Thu, 16 Apr 2026 16:19:15 -0400 Subject: [PATCH 3/5] Update sync-upstream workflow for feature branch testing - Added a temporary push trigger for the `feat/sync-upstream-workflow` branch to facilitate testing before merging. - Maintained the existing scheduled sync functionality for the main branch. --- .github/workflows/sync-upstream.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/sync-upstream.yaml b/.github/workflows/sync-upstream.yaml index 557930b8f4..aadb2f0182 100644 --- a/.github/workflows/sync-upstream.yaml +++ b/.github/workflows/sync-upstream.yaml @@ -1,6 +1,10 @@ name: Sync upstream on: + # TEMPORARY: testing on feature branch — remove before merging to cohere. + push: + branches: + - feat/sync-upstream-workflow schedule: # Every Monday at 09:00 UTC - cron: "0 9 * * 1" From 815c6369df24a82d46139609339cebeea75d7d63 Mon Sep 17 00:00:00 2001 From: "yousef.sadr" Date: Thu, 16 Apr 2026 16:29:35 -0400 Subject: [PATCH 4/5] Refactor sync-upstream workflow for improved security and clarity - Updated `sync-upstream.yaml` to explicitly set the GITHUB_TOKEN environment variable, enhancing security by preventing token persistence in git configuration. - Simplified the push command by removing the extraheader for authentication, streamlining the push process. - Added comments for clarity regarding token handling and configuration steps. --- .github/workflows/sync-upstream.yaml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/sync-upstream.yaml b/.github/workflows/sync-upstream.yaml index aadb2f0182..c1f7a32a50 100644 --- a/.github/workflows/sync-upstream.yaml +++ b/.github/workflows/sync-upstream.yaml @@ -49,19 +49,20 @@ jobs: with: ref: main fetch-depth: 0 - # Don't persist GITHUB_TOKEN in .git/config — pushes auth explicitly - # via extraheader below so the token can't leak to later steps. + # zizmor: token is set up explicitly below via `gh auth setup-git` + # so it isn't persisted in .git/config after this step. persist-credentials: false - name: Configure git + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + gh auth setup-git - name: Fast-forward main to upstream id: ff - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git remote add upstream "$UPSTREAM_REPO" || true git fetch upstream "$UPSTREAM_REF" @@ -76,7 +77,6 @@ jobs: exit 0 fi - # Verify this is a clean fast-forward (main should have no extra commits). AHEAD=$(git rev-list --count "upstream/$UPSTREAM_REF..HEAD") if [ "$AHEAD" -ne 0 ]; then echo "::error::origin/main is $AHEAD commits AHEAD of upstream — cannot fast-forward." @@ -85,8 +85,7 @@ jobs: fi git merge --ff-only "upstream/$UPSTREAM_REF" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: bearer $GH_TOKEN" \ - push origin main + git push origin main echo "new_commits=$BEHIND" >> "$GITHUB_OUTPUT" echo "Fast-forwarded main by $BEHIND commits to $UPSTREAM_SHA" @@ -104,12 +103,17 @@ jobs: with: ref: ${{ env.COHERE_BRANCH }} fetch-depth: 0 + # zizmor: token is set up explicitly below via `gh auth setup-git` + # so it isn't persisted in .git/config after this step. persist-credentials: false - name: Configure git + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + gh auth setup-git - name: Check if cohere is behind main id: check @@ -157,11 +161,8 @@ jobs: - name: Push sync branch if: steps.check.outputs.behind != '0' env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SYNC_BRANCH: ${{ steps.check.outputs.sync_branch }} - run: | - git -c "http.https://github.com/.extraheader=AUTHORIZATION: bearer $GH_TOKEN" \ - push origin "$SYNC_BRANCH" + run: git push origin "$SYNC_BRANCH" - name: Create pull request (clean merge) if: steps.check.outputs.behind != '0' && steps.merge.outputs.conflict == 'false' From 5b75df502f7f037b3a1ef6e09cf5c5cf031cad7c Mon Sep 17 00:00:00 2001 From: "yousef.sadr" Date: Thu, 16 Apr 2026 16:32:06 -0400 Subject: [PATCH 5/5] ci: set GH_TOKEN at job level for gh credential helper Made-with: Cursor --- .github/workflows/sync-upstream.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sync-upstream.yaml b/.github/workflows/sync-upstream.yaml index c1f7a32a50..207601e9fb 100644 --- a/.github/workflows/sync-upstream.yaml +++ b/.github/workflows/sync-upstream.yaml @@ -43,6 +43,11 @@ jobs: outputs: new_commits: ${{ steps.ff.outputs.new_commits }} upstream_sha: ${{ steps.ff.outputs.upstream_sha }} + # GH_TOKEN must be available to every step — `gh auth setup-git` + # registers `gh` as a git credential helper, and that helper shells + # out to `gh` at push-time, which reads $GH_TOKEN from the environment. + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout main uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -54,8 +59,6 @@ jobs: persist-credentials: false - name: Configure git - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" @@ -97,6 +100,11 @@ jobs: permissions: contents: write # push the sync/* branch pull-requests: write # create the sync PR via `gh pr create` + # GH_TOKEN must be available to every step — `gh auth setup-git` + # registers `gh` as a git credential helper, and that helper shells + # out to `gh` at push-time, which reads $GH_TOKEN from the environment. + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout cohere branch uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -108,8 +116,6 @@ jobs: persist-credentials: false - name: Configure git - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com"