From a56c3bae4ed071897564bef65f13e131f3e8f1ad Mon Sep 17 00:00:00 2001 From: Narendran Raghavan Date: Fri, 14 Aug 2026 14:30:41 -0700 Subject: [PATCH] ci: update PR branches after main changes Signed-off-by: Narendran Raghavan --- .github/workflows/ci.yml | 9 ++ .github/workflows/update-pr-branches.yml | 101 +++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 .github/workflows/update-pr-branches.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23fae662a..a79867f6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,6 +129,15 @@ jobs: # commit pass. A for-loop over the SHA list checks every commit. status=0 for sha in $(git log --format=%H "${BASE}..${HEAD}"); do + parents=$(git show -s --format=%P "$sha") + committer_email=$(git show -s --format=%ce "$sha") + # GitHub's update-branch API creates a merge commit without a DCO + # trailer. Its parents were already checked, so exempt only those + # GitHub-generated merge commits; contributor commits still require + # sign-off. + if [[ "$parents" == *" "* && "$committer_email" == "noreply@github.com" ]]; then + continue + fi if ! git log -1 --format="%B" "$sha" | grep -q "^Signed-off-by:"; then echo " missing Signed-off-by: $sha $(git log -1 --format=%s "$sha")" status=1 diff --git a/.github/workflows/update-pr-branches.yml b/.github/workflows/update-pr-branches.yml new file mode 100644 index 000000000..b376397d1 --- /dev/null +++ b/.github/workflows/update-pr-branches.yml @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Update PR branches + +on: + push: + branches: ["main"] + workflow_dispatch: + +# The update-branch API merges main into each eligible PR head branch. +permissions: + contents: write + pull-requests: write + +# Serialize runs so rapid merges to main cannot race while updating PR heads. +concurrency: + group: update-pr-branches + cancel-in-progress: false + +jobs: + update: + name: Update eligible PR branches + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + BASE_BRANCH: main + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + steps: + - name: Merge main into open PR branches + shell: bash + run: | + set -euo pipefail + + pulls="$({ + gh api --paginate --slurp \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2026-03-10" \ + "repos/$GH_REPO/pulls?state=open&base=$BASE_BRANCH&per_page=100" + } | jq -r '.[][] | [.number, .head.sha, (.head.repo.full_name // "")] | @tsv')" + + if [[ -z "$pulls" ]]; then + echo "No open pull requests target $BASE_BRANCH." | tee -a "$GITHUB_STEP_SUMMARY" + exit 0 + fi + + updated=0 + skipped=0 + failed=0 + + while IFS=$'\t' read -r number head_sha head_repo; do + if [[ "$head_repo" != "$GH_REPO" ]]; then + echo "::notice title=PR #$number not updated::The head branch is in a fork, which the repository GITHUB_TOKEN cannot modify." + skipped=$((skipped + 1)) + continue + fi + + if response="$({ + gh api --method PUT \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2026-03-10" \ + "repos/$GH_REPO/pulls/$number/update-branch" \ + -f "expected_head_sha=$head_sha" + } 2>&1)"; then + echo "Queued update for PR #$number." + updated=$((updated + 1)) + elif [[ "$response" == *"HTTP 422"* ]]; then + # GitHub returns 422 when the branch is already current, has a + # merge conflict, or changed after it was listed. + echo "::notice title=PR #$number not changed::The branch is current, conflicted, or changed while this workflow was running." + skipped=$((skipped + 1)) + else + echo "::error title=PR #$number update failed::The update-branch API request failed." + failed=$((failed + 1)) + fi + done <<< "$pulls" + + { + echo "### Pull request branch updates" + echo + echo "- Queued: $updated" + echo "- Skipped: $skipped" + echo "- Failed: $failed" + } >> "$GITHUB_STEP_SUMMARY" + + if (( failed > 0 )); then + exit 1 + fi