From 8fe239bc5d66c57854f3ceab30ee721c0ea9b2ce Mon Sep 17 00:00:00 2001 From: Marc Riegel Date: Sun, 1 Nov 2026 10:06:29 +0100 Subject: [PATCH 1/2] Keep the commits added to the regeneration pull request A later run rebuilt the branch from main and force-pushed it, dropping a new service's package added on the branch as the pull request asks, and closed the pull request, deleting the branch, when the API had nothing new. It now generates on top of added commits with main merged in, never closes a pull request that has them, and pushes with a lease, so a commit pushed during the run fails it instead of being lost. --- .github/workflows/regenerate.yml | 50 ++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index a50c4d0..d9c715d 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -4,6 +4,10 @@ # the branch `regenerate`, refreshed by each later run until it is merged; # when it changes nothing, an open one is closed. # +# Commits someone adds to that pull request, such as a new service's +# package, are kept: a later run generates on top of them, with main merged +# in, and never closes a pull request that has them. +# # The pull request is opened with the workflow token, which starts no # pull_request run, so this dispatches CI on the branch; its result shows on # the pull request's commit. Opening it needs "Allow GitHub Actions to create @@ -37,6 +41,29 @@ jobs: ref: main fetch-depth: 0 + # The open pull request's branch, when someone has added commits to + # it, with main merged in; main otherwise. + - name: Base + run: | + set -euo pipefail + echo "PREVIOUS=$(cat API_REVISION)" >> "$GITHUB_ENV" + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + base=main + lease="" + if git fetch --quiet origin "${BRANCH}" 2>/dev/null; then + lease="$(git rev-parse FETCH_HEAD)" + open="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number // empty')" + added="$(git log --format=%ae origin/main..FETCH_HEAD | grep -cvxF "$(git config user.email)" || true)" + if [[ -n "${open}" && "${added}" -gt 0 ]]; then + git switch --quiet -C "${BRANCH}" FETCH_HEAD + git merge --quiet --no-edit origin/main + base=branch + fi + fi + echo "BASE=${base}" >> "$GITHUB_ENV" + echo "LEASE=${lease}" >> "$GITHUB_ENV" + # rustup installs the toolchain rust-toolchain.toml names. - name: Rust run: rustup toolchain install @@ -45,16 +72,13 @@ jobs: uses: 'Swatinem/rust-cache@v2' - name: Update - run: | - previous="$(cat API_REVISION)" - scripts/update.sh - echo "PREVIOUS=${previous}" >> "$GITHUB_ENV" + run: scripts/update.sh - name: Pull request run: | set -euo pipefail open="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number // empty')" - if [[ -z "$(git status --porcelain)" ]]; then + if [[ "${BASE}" == main && -z "$(git status --porcelain)" ]]; then echo "already current" if [[ -n "${open}" ]]; then gh pr close "${open}" --delete-branch --comment "main is current with the API." @@ -63,19 +87,21 @@ jobs: fi revision="$(cat API_REVISION)" - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' git switch --quiet -C "${BRANCH}" - git add --all - git commit --quiet -m "Regenerate from origincloudhq/api@${revision:0:12}" \ - -m "API: https://github.com/origincloudhq/api/compare/${PREVIOUS}...${revision}" + if [[ -n "$(git status --porcelain)" ]]; then + git add --all + git commit --quiet -m "Regenerate from origincloudhq/api@${revision:0:12}" \ + -m "API: https://github.com/origincloudhq/api/compare/${PREVIOUS}...${revision}" + fi # Unchanged since the last run: nothing to push or check again. - if git fetch --quiet origin "${BRANCH}" 2>/dev/null && git diff --quiet FETCH_HEAD HEAD; then + if [[ -n "${LEASE}" ]] && git diff --quiet "${LEASE}" HEAD; then echo "pull request #${open:-?} is current" exit 0 fi - git push --quiet --force origin "${BRANCH}" + # Replaces the branch only as this run found it: a commit pushed + # meanwhile fails the run instead of being lost. + git push --quiet --force-with-lease="${BRANCH}:${LEASE}" origin "${BRANCH}" body="Generated from [origincloudhq/api@${revision:0:12}](https://github.com/origincloudhq/api/commit/${revision}) ([changes](https://github.com/origincloudhq/api/compare/${PREVIOUS}...${revision})) by scripts/update.sh. From d48992ba1a2441a31d3f59c147cde9578c000c40 Mon Sep 17 00:00:00 2001 From: Marc Riegel Date: Sun, 1 Nov 2026 10:16:30 +0100 Subject: [PATCH 2/2] Keep added commits through amends, fetch failures and merge conflicts Committers count as well as authors, so an amended commit of the workflow's is kept. An unreadable origin fails the run instead of passing for an absent branch. The branch is deleted only as the run found it, under the lease. Conflicts from merging main resolve in what update.sh writes again; any other fails with a message. A fork's pull request from a branch named regenerate is not taken for the workflow's. --- .github/workflows/regenerate.yml | 65 ++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index d9c715d..facf7cf 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -6,7 +6,10 @@ # # Commits someone adds to that pull request, such as a new service's # package, are kept: a later run generates on top of them, with main merged -# in, and never closes a pull request that has them. +# in, and never closes a pull request that has them. Merging main resolves +# conflicts in what scripts/update.sh writes again anyway (API_REVISION and crates/*/src/generated); +# any other conflict fails the run until someone merges main into the +# branch by hand. # # The pull request is opened with the workflow token, which starts no # pull_request run, so this dispatches CI on the branch; its result shows on @@ -49,20 +52,52 @@ jobs: echo "PREVIOUS=$(cat API_REVISION)" >> "$GITHUB_ENV" git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + bot="$(git config user.email)" base=main lease="" - if git fetch --quiet origin "${BRANCH}" 2>/dev/null; then + # Exit status 2: there is no such branch. Any other failure fails + # the run rather than passing for an absent branch. + found=0 + git ls-remote --exit-code --heads origin "${BRANCH}" >/dev/null || found=$? + if [[ "${found}" -eq 0 ]]; then + git fetch --quiet origin "${BRANCH}" lease="$(git rev-parse FETCH_HEAD)" - open="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number // empty')" - added="$(git log --format=%ae origin/main..FETCH_HEAD | grep -cvxF "$(git config user.email)" || true)" + open="$(gh pr list --head "${BRANCH}" --base main --state open --json number,isCrossRepository --jq 'map(select(.isCrossRepository | not))[0].number // empty')" + # Committers too: an amended or rebased commit of the workflow's + # keeps it as the author. + added="$(git log --format='%ae%n%ce' origin/main..FETCH_HEAD | grep -cvxF "${bot}" || true)" if [[ -n "${open}" && "${added}" -gt 0 ]]; then git switch --quiet -C "${BRANCH}" FETCH_HEAD - git merge --quiet --no-edit origin/main + if ! git merge --quiet --no-edit origin/main; then + for path in $(git diff --name-only --diff-filter=U); do + case "${path}" in + API_REVISION | crates/*/src/generated/*) + if git checkout --quiet --ours -- "${path}" 2>/dev/null; then + git add -- "${path}" + else + git rm --quiet -- "${path}" + fi + ;; + esac + done + conflicts="$(git diff --name-only --diff-filter=U | paste -sd ' ' -)" + if [[ -n "${conflicts}" ]]; then + echo "::error::merging main into ${BRANCH} conflicts in ${conflicts}: merge main into it by hand and push" + exit 1 + fi + git commit --quiet --no-edit + fi base=branch fi + elif [[ "${found}" -ne 2 ]]; then + echo "::error::could not read origin's branches" + exit 1 fi - echo "BASE=${base}" >> "$GITHUB_ENV" - echo "LEASE=${lease}" >> "$GITHUB_ENV" + { + echo "BASE=${base}" + echo "LEASE=${lease}" + echo "FROM=$(cat API_REVISION)" + } >> "$GITHUB_ENV" # rustup installs the toolchain rust-toolchain.toml names. - name: Rust @@ -77,11 +112,19 @@ jobs: - name: Pull request run: | set -euo pipefail - open="$(gh pr list --head "${BRANCH}" --base main --state open --json number --jq '.[0].number // empty')" + open="$(gh pr list --head "${BRANCH}" --base main --state open --json number,isCrossRepository --jq 'map(select(.isCrossRepository | not))[0].number // empty')" if [[ "${BASE}" == main && -z "$(git status --porcelain)" ]]; then echo "already current" - if [[ -n "${open}" ]]; then - gh pr close "${open}" --delete-branch --comment "main is current with the API." + # The branch goes only as the Base step found it, and its pull + # request closes with it; one pushed to meanwhile stays. + if [[ -n "${LEASE}" ]]; then + if git push --quiet --force-with-lease="${BRANCH}:${LEASE}" origin ":${BRANCH}"; then + if [[ -n "${open}" ]]; then + gh pr comment "${open}" --body "main is current with the API." + fi + else + echo "::warning::${BRANCH} changed during the run; left as it is" + fi fi exit 0 fi @@ -91,7 +134,7 @@ jobs: if [[ -n "$(git status --porcelain)" ]]; then git add --all git commit --quiet -m "Regenerate from origincloudhq/api@${revision:0:12}" \ - -m "API: https://github.com/origincloudhq/api/compare/${PREVIOUS}...${revision}" + -m "API: https://github.com/origincloudhq/api/compare/${FROM}...${revision}" fi # Unchanged since the last run: nothing to push or check again.