From f1ee1351c594c8dea28b515796485ddf9b1852a8 Mon Sep 17 00:00:00 2001 From: Alexander Olzem Date: Fri, 7 Aug 2026 12:26:52 +0200 Subject: [PATCH] fix: keep merge options in sync and add option to enforce review of latest push --- README.md | 5 +++-- common.mk | 6 +++++- docs/NEW_REPO.md | 2 +- scripts/repo-settings.sh | 28 +++++++++++++++++++++++++--- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5c42905..b6a6812 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,9 @@ Run `make repo-settings` to reconcile your GitHub repository with the organizati It configures: - **Labels** — creates/updates the standard set of issue and PR labels -- **Merge strategy** — merge commits only by default (configurable via `REPO_ALLOW_MERGE_COMMIT`, `REPO_ALLOW_SQUASH_MERGE`, `REPO_ALLOW_REBASE_MERGE`), auto-merge enabled, delete branch on merge +- **Merge strategy** — merge commits only by default, configurable via `REPO_ALLOW_MERGE_COMMIT`, `REPO_ALLOW_SQUASH_MERGE`, `REPO_ALLOW_REBASE_MERGE` (these also restrict the ruleset's allowed merge methods; at least one must be enabled or `repo-settings` fails); auto-merge enabled, delete branch on merge - **Secret scanning** — enabled -- **Branch protection** — a "protect-main" ruleset on the default branch (and any `REPO_RULESET_BRANCHES` patterns): requires PRs with 1 approval, dismisses stale reviews, requires review thread resolution, enforces commit signatures, prevents direct pushes/deletions/non-fast-forwards +- **Branch protection** — a "protect-main" ruleset on the default branch (and any `REPO_RULESET_BRANCHES` patterns): requires PRs with 1 approval, dismisses stale reviews, requires review thread resolution, enforces commit signatures, requires approval of the latest push when `REPO_REQUIRE_LAST_PUSH_APPROVAL` is `true`, prevents direct pushes/deletions/non-fast-forwards The repository settings are configurable via make variables (set them in your `Makefile` or pass them on the command line, e.g. `make repo-settings REPO_STATUS_CHECKS='["CI","lint"]' REPO_RULESET_BRANCHES='["release/*"]'`): @@ -111,6 +111,7 @@ The repository settings are configurable via make variables (set them in your `M | `REPO_ALLOW_MERGE_COMMIT` | `true` | Allow merge commits in the merge strategy | | `REPO_ALLOW_SQUASH_MERGE` | `false` | Allow squash merging | | `REPO_ALLOW_REBASE_MERGE` | `false` | Allow rebase merging | +| `REPO_REQUIRE_LAST_PUSH_APPROVAL` | `false` | Require the most recent push to be approved before merging | | `REPO_ADMIN_BYPASS` | `true` | When `false`, org admins cannot bypass the ruleset | | `REPO_REQUIRED_APPROVING_REVIEW_COUNT` | `1` | Number of approving reviews required to merge | | `REPO_REQUIRE_CODE_OWNER_REVIEW` | `false` | Require an approving review from code owners | diff --git a/common.mk b/common.mk index 6c9d6f0..7a2fe0f 100644 --- a/common.mk +++ b/common.mk @@ -57,7 +57,9 @@ SETUP_ENVTEST ?= $(LOCALGOBIN)/setup-envtest # adds further branch patterns (a JSON array, e.g. '["release/*"]'; short names are # normalized to refs/heads/...). REPO_STATUS_CHECKS is a JSON array of status-check # contexts that must pass (each job name is used as-is, so contexts with spaces work). -# REPO_ALLOW_MERGE_COMMIT/SQUASH_MERGE/REBASE_MERGE configure the merge strategy. +# REPO_ALLOW_MERGE_COMMIT/SQUASH_MERGE/REBASE_MERGE configure the merge strategy and +# the ruleset's allowed merge methods; at least one must be `true` (repo-settings fails otherwise). +# REPO_REQUIRE_LAST_PUSH_APPROVAL requires the most recent push to be approved before merging. # Booleans must be `true` or `false`. Example: # make repo-settings REPO_ADMIN_BYPASS=false REPO_STATUS_CHECKS='["CI","Check action pins"]' REPO_RULESET_BRANCHES='["release/*"]' REPO_ADMIN_BYPASS ?= true @@ -69,6 +71,7 @@ REPO_RULESET_BRANCHES ?= [] REPO_ALLOW_MERGE_COMMIT ?= true REPO_ALLOW_SQUASH_MERGE ?= false REPO_ALLOW_REBASE_MERGE ?= false +REPO_REQUIRE_LAST_PUSH_APPROVAL ?= false .PHONY: repo-settings repo-settings: ## Reconcile GitHub repository settings (labels, merge strategy, branch protection, security) @@ -81,6 +84,7 @@ repo-settings: ## Reconcile GitHub repository settings (labels, merge strategy, REPO_REQUIRE_BRANCH_UP_TO_DATE='$(REPO_REQUIRE_BRANCH_UP_TO_DATE)' \ REPO_REQUIRE_CODE_OWNER_REVIEW='$(REPO_REQUIRE_CODE_OWNER_REVIEW)' \ REPO_REQUIRED_APPROVING_REVIEW_COUNT='$(REPO_REQUIRED_APPROVING_REVIEW_COUNT)' \ + REPO_REQUIRE_LAST_PUSH_APPROVAL='$(REPO_REQUIRE_LAST_PUSH_APPROVAL)' \ REPO_RULESET_BRANCHES='$(REPO_RULESET_BRANCHES)' \ REPO_STATUS_CHECKS='$(REPO_STATUS_CHECKS)' \ DEV_KIT_VERSION='$(DEV_KIT_VERSION)' \ diff --git a/docs/NEW_REPO.md b/docs/NEW_REPO.md index 1796e52..951169c 100644 --- a/docs/NEW_REPO.md +++ b/docs/NEW_REPO.md @@ -33,7 +33,7 @@ Run: make repo-settings ``` -This reconciles labels, merge strategy (merge commits only by default, configurable via the `REPO_ALLOW_*` variables; auto-merge enabled, delete branch on merge), secret scanning, and the `protect-main` branch ruleset. See `make help` for details. +This reconciles labels, merge strategy (merge commits only by default, configurable via the `REPO_ALLOW_*` variables, which also restrict the ruleset's allowed merge methods; at least one must be enabled; auto-merge enabled, delete branch on merge), last-push-approval requirements (`REPO_REQUIRE_LAST_PUSH_APPROVAL`), secret scanning, and the `protect-main` branch ruleset. See `make help` for details. The branch protection ruleset is configurable via make variables, e.g.: diff --git a/scripts/repo-settings.sh b/scripts/repo-settings.sh index 4e06e55..4103919 100755 --- a/scripts/repo-settings.sh +++ b/scripts/repo-settings.sh @@ -14,9 +14,10 @@ set -euo pipefail # REPO_REQUIRE_BRANCH_UP_TO_DATE require branches up to date (requires status checks) # REPO_STATUS_CHECKS JSON array of required status-check contexts # REPO_RULESET_BRANCHES JSON array of additional branch patterns -# REPO_ALLOW_MERGE_COMMIT allow merge commits in the merge strategy +# REPO_ALLOW_MERGE_COMMIT allow merge commits in the merge strategy and ruleset # REPO_ALLOW_SQUASH_MERGE allow squash merging # REPO_ALLOW_REBASE_MERGE allow rebase merging +# REPO_REQUIRE_LAST_PUSH_APPROVAL require the most recent push to be approved before merging # DEV_KIT_VERSION dev-kit version to fetch the workflow from # GH, JQ commands used to talk to GitHub and build JSON @@ -43,6 +44,25 @@ if [ "$REPO_REQUIRE_BRANCH_UP_TO_DATE" = "true" ] && exit 1 fi +allowed_merge_methods=$( + # shellcheck disable=SC2016 # $variables belong to jq, not the shell + "$JQ" -cn \ + --argjson merge "$REPO_ALLOW_MERGE_COMMIT" \ + --argjson squash "$REPO_ALLOW_SQUASH_MERGE" \ + --argjson rebase "$REPO_ALLOW_REBASE_MERGE" \ + '[ + if $merge then "merge" else empty end, + if $squash then "squash" else empty end, + if $rebase then "rebase" else empty end + ] + ' +) + +if [ "$allowed_merge_methods" = "[]" ]; then + echo "error: at least one merge method must be allowed" >&2 + exit 1 +fi + echo "Reconciling settings for $REPO..." echo " Syncing labels..." @@ -94,6 +114,8 @@ RULESET_JSON=$( --argjson codeOwner "$REPO_REQUIRE_CODE_OWNER_REVIEW" \ --argjson adminBypass "$REPO_ADMIN_BYPASS" \ --argjson upToDate "$REPO_REQUIRE_BRANCH_UP_TO_DATE" \ + --argjson allowedMergeMethods "$allowed_merge_methods" \ + --argjson requireLastPushApproval "$REPO_REQUIRE_LAST_PUSH_APPROVAL" \ '{ name: "protect-main", target: "branch", enforcement: "active", conditions: { ref_name: { include: $branches, exclude: [] } }, rules: ([ @@ -106,9 +128,9 @@ RULESET_JSON=$( dismiss_stale_reviews_on_push: true, required_reviewers: [], require_code_owner_review: $codeOwner, - require_last_push_approval: false, + require_last_push_approval: $requireLastPushApproval, required_review_thread_resolution: true, - allowed_merge_methods: ["squash", "rebase", "merge"] + allowed_merge_methods: $allowedMergeMethods } } ] + (if ($checks | length > 0) then [{ type: "required_status_checks", parameters: {