diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 480e7d6..e3b20e6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,11 @@ # want: a known advisory beats an unaudited release, which is the same # exception `scripts/dependency-age-exceptions.json` records for a human. # +# Both ecosystems target `develop`, not `main`. A dependency bump is ordinary +# work, and ordinary work enters through `develop` -- see Branching in +# CONTRIBUTING.md. Opening these against `main` put them on the release branch +# and left the back-merge to carry them the wrong way round. +# # See .agents/skills/check-dependencies/SKILL.md for the reasoning, and # AGENTS.md for how a dependency change reaches a release. @@ -18,6 +23,7 @@ updates: - package-ecosystem: npm # The workspace root: one pnpm-lock.yaml covers all four manifests. directory: / + target-branch: develop schedule: interval: weekly day: monday @@ -46,6 +52,7 @@ updates: - package-ecosystem: github-actions directory: / + target-branch: develop schedule: interval: weekly day: monday diff --git a/.github/workflows/backmerge.yml b/.github/workflows/backmerge.yml index e47c8f7..69f1980 100644 --- a/.github/workflows/backmerge.yml +++ b/.github/workflows/backmerge.yml @@ -1,8 +1,19 @@ -# After anything lands on main, keep develop in sync by opening (or refreshing) -# a pull request that merges main back into develop. +# After anything lands on main, bring develop up to it. # -# A PR rather than a direct push, so the merge is visible and CI runs on it. -# Conflicts are resolved on the PR. +# A pull request that merges ITSELF. `develop` is protected -- pushes to it are +# refused with "Changes must be made through a pull request" -- so the PR is not +# optional. What is optional is the person, and the person was the problem: the +# first back-merge PR was merged with "squash", which copied main's content into +# develop as one ordinary commit and dropped its history. Git then had no way to +# know develop already held those changes, so every later back-merge tried to +# re-apply 236 commits whose content was already there: 67 conflicting files on +# a merge that should have been a formality. +# +# So the workflow opens the PR and turns on auto-merge with the MERGE method. +# GitHub merges it once the required checks pass, nobody chooses a method, and +# a squash cannot happen by accident. Auto-merge has to be enabled on the +# repository for this (`allow_auto_merge`); if it is off, the run says so and +# leaves the PR for a person, who must merge it with a merge commit. # # While the project is pre-release the flow is inverted from the eventual one: # work happens on main, and develop follows it. Once releases start, features @@ -16,6 +27,8 @@ on: branches: [main] permissions: + # Read is enough: nothing here pushes. The merge is GitHub'''s, done on the + # pull request once its checks pass. contents: read pull-requests: write @@ -24,60 +37,68 @@ concurrency: cancel-in-progress: false jobs: - open-backmerge-pr: + back-merge: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: fetch-depth: 0 - - name: Skip if develop already contains main + - name: Decide what is needed id: check run: | set -uo pipefail # Said plainly rather than dying on `fatal: Not a valid object name`, # which is what the first run did before develop existed. if ! git rev-parse --verify --quiet origin/develop >/dev/null; then - echo "needs_pr=false" >> "$GITHUB_OUTPUT" + echo "action=none" >> "$GITHUB_OUTPUT" echo "::notice::there is no develop branch, so there is nothing to back-merge into." exit 0 fi if git merge-base --is-ancestor origin/main origin/develop; then - echo "needs_pr=false" >> "$GITHUB_OUTPUT" + echo "action=none" >> "$GITHUB_OUTPUT" echo "develop already contains main; nothing to do." else - echo "needs_pr=true" >> "$GITHUB_OUTPUT" + echo "action=pr" >> "$GITHUB_OUTPUT" fi - - name: Open or update back-merge PR - if: steps.check.outputs.needs_pr == 'true' + - name: Open the back-merge PR and let it merge itself + if: steps.check.outputs.action == 'pr' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} WORKFLOW_NAME: ${{ github.workflow }} RUN_ID: ${{ github.run_id }} run: | + set -uo pipefail MAIN_SHA="$(git rev-parse --short origin/main)" MAIN_SUBJECT="$(git log -1 --format='%s' origin/main)" - EXISTING="$(gh pr list --head main --base develop --state open --json number --jq '.[0].number')" - if [ -n "$EXISTING" ]; then - echo "PR #$EXISTING already open for main -> develop; it will auto-update with the new push." - exit 0 - fi - + NUMBER="$(gh pr list --head main --base develop --state open --json number --jq '.[0].number')" BODY_FILE="$(mktemp)" cat > "$BODY_FILE" <