From 4e765e5c642f00333dad5e8c87e79c8fcbfce33e Mon Sep 17 00:00:00 2001 From: Deac Date: Fri, 21 Aug 2026 14:57:03 -0400 Subject: [PATCH] add-promotion-gate-action: Add git/promotion-gate composite action Enforces which source branches may merge into which target branches, configured via a rules input (one target:allowed,allowed per line, glob-capable). Empty rules (default) skips enforcement entirely, so repos can adopt it without immediately gating on rules they have not configured. Extracted for reuse across repos moving to a gitflow-style branch promotion model, starting with stat-tackler-api. --- Makefile | 1 + README.md | 1 + git/promotion-gate/README.md | 21 ++++++++++ git/promotion-gate/action.yml | 75 +++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 git/promotion-gate/README.md create mode 100644 git/promotion-gate/action.yml diff --git a/Makefile b/Makefile index c4c9f05..020d753 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ ACTIONS := \ docker \ node \ git/create_tag \ + git/promotion-gate \ helm/diff \ helm/template \ helm/upgrade \ diff --git a/README.md b/README.md index b3f8b1e..b4aa160 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Reusable composite actions for Gitea CI/CD pipelines. | Action | Description | |---|---| | [git/create_tag](git/create_tag/README.md) | Create and push a git tag in the current repository | +| [git/promotion-gate](git/promotion-gate/README.md) | Enforce which source branches may merge into which target branches | ### Helm diff --git a/git/promotion-gate/README.md b/git/promotion-gate/README.md new file mode 100644 index 0000000..be86dba --- /dev/null +++ b/git/promotion-gate/README.md @@ -0,0 +1,21 @@ +# Promotion Gate + + +## Description + +Enforce which source branches may merge into which target branches + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `rules` |

One rule per line: "target:allowed1,allowed2,…". Targets not listed are unrestricted. Allowed entries may use a trailing glob, e.g. hotfix-*. Leave empty (default) to skip entirely.

| `false` | `""` | + + + +## Runs + +This action is a `composite` action. + diff --git a/git/promotion-gate/action.yml b/git/promotion-gate/action.yml new file mode 100644 index 0000000..284e760 --- /dev/null +++ b/git/promotion-gate/action.yml @@ -0,0 +1,75 @@ +name: Promotion Gate +description: Enforce which source branches may merge into which target branches +inputs: + rules: + description: | + One rule per line: "target:allowed1,allowed2,...". Targets not + listed are unrestricted. Allowed entries may use a trailing glob, + e.g. `hotfix-*`. Leave empty (default) to skip entirely. + default: "" + +runs: + using: composite + steps: + - name: Check merge source is allowed for target branch + shell: sh + env: + BASE: ${{ gitea.event.pull_request.base.ref }} + HEAD: ${{ gitea.event.pull_request.head.ref }} + RULES: ${{ inputs.rules }} + run: | + if [ -z "$RULES" ]; then + echo "No promotion rules configured — skipping." + exit 0 + fi + + rc=0 + found=0 + + oldIFS=$IFS + IFS=' + ' + set -f + set -- $RULES + set +f + IFS=$oldIFS + + for line in "$@"; do + if [ -z "$line" ]; then + continue + fi + target=${line%%:*} + allowed=${line#*:} + if [ "$target" = "$line" ]; then + continue + fi + if [ "$BASE" = "$target" ]; then + found=1 + matched=0 + innerIFS=$IFS + IFS=',' + for pat in $allowed; do + IFS=$innerIFS + case "$HEAD" in + $pat) + matched=1 + ;; + esac + IFS=',' + done + IFS=$innerIFS + + if [ "$matched" -eq 1 ]; then + echo "Allowed: '$HEAD' -> '$BASE' (matches: $allowed)" + else + echo "::error::$BASE only accepts merges from: $allowed — got '$HEAD'" + rc=1 + fi + fi + done + + if [ "$found" -eq 0 ]; then + echo "No promotion-source restriction configured for base '$BASE'" + fi + + exit $rc