Skip to content

Instantly share code, notes, and snippets.

@sebmellen
Last active January 16, 2024 03:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sebmellen/639904445118be908cbe3c23d9797d46 to your computer and use it in GitHub Desktop.
Save sebmellen/639904445118be908cbe3c23d9797d46 to your computer and use it in GitHub Desktop.
Protected GitHub fast-forward commit action with Mergefreeze

Protected GitHub fast-forward commit action with Mergefreeze

Introduction

See screenshots of the action... in action

In case anyone is having issues with the lack of -ff merge options in GitHub's web UI, this protected GitHub fast-forward commit action with Mergefreeze may help.

This is not an ideal solution, and it will cost your organization an additional $40/month for the Mergefreeze subscription, but it works well. We spent a lot of time figuring it out. Mergefreeze, combined with the action, prevents you from accidentally merging via the default web UI options by blocking them using a branch protection rule. The fast-forward action blocks all commits to your desired branch that don't use the action.

When run by adding a /fast-forward comment to your PR, the action temporarily suspends the required Mergefreeze branch protection rule, merges the commit to your desired branch through the CLI, and then adds back the branch protection rule. This avoids any mistaken merge/rebase commits to the protected branch, and it works just as fast as merging using the traditional web UI options.

Hope this helps someone!

Instructions

  1. Setup a Mergefreeze account at https://www.mergefreeze.com/. You need to be on the proactive plan to setup your web API.
  2. Generate an Organization access token for Mergefreeze under your organization settings.
  3. Add your repositories and desired protected braches to your Mergefreeze account, using the "Toggle a branch protection rule" freeze method. a. Note that this action has support for demo, staging, and production branch protections. You can easily edit these branches by editing the actions.
  4. Create a repo that is accessible organization-wide called fast-forward-action. Add the fast-forward-action.yml and check-comment-action.yml files to this repository in the instructed locations (see comments at top of file) Ensure that reusable workflows are enabled and can be accessed (https://docs.github.com/en/actions/using-workflows/reusing-workflows#access-to-reusable-workflows).
  5. Add the fast-forward.yml action file to every repo that you want to enable these protections on.
# PLACE THIS FILE IN A REPOSITORY THAT LIVES WITHIN YOUR ORGANIZATION TITLED "fast-forward-action"
# IN THE .github/workflows/check-comment-action.yml PATH
on:
workflow_call:
inputs:
repo_name:
required: true
type: string
pr_number:
required: true
type: number
secrets:
GITHUB_AUTH_TOKEN:
required: true
jobs:
check-for-comment:
runs-on: ubuntu-20.04
steps:
- uses: octokit/request-action@v2.x
id: get_pull_request_info
with:
route: GET /repos/{repo}/pulls/{pr}
repo: ${{ inputs.repo_name }}
pr: ${{ inputs.pr_number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_AUTH_TOKEN }}
- name: Parse pull request using jq and checking mergeable state
id: mergeable_pr
run: |
cat <<"EOF" > pr.json
${{ steps.get_pull_request_info.outputs.data }}
EOF
export BASE_BRANCH=$(jq -r '.base.ref' pr.json)
echo "baseBranch=$BASE_BRANCH" >> $GITHUB_OUTPUT
echo "BASE_BRANCH=$BASE_BRANCH" >> $GITHUB_ENV
- uses: octokit/request-action@v2.x
id: get_pull_request_comments
with:
route: GET /repos/{repo}/issues/{pr}/comments
repo: ${{ inputs.repo_name }}
pr: ${{ inputs.pr_number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_AUTH_TOKEN }}
- name: Check base ref, parse comments and check if fast-forward comment exists
run: |
if [ "$BASE_BRANCH" != "demo" ] && [ "$BASE_BRANCH" != "staging" ] && [ "$BASE_BRANCH" != "production" ]; then
exit 0
fi
cat <<"EOF" > comments.json
${{ steps.get_pull_request_comments.outputs.data }}
EOF
COMMENT_EXISTS=$(jq -r '.[] | select(.body=="/fast-forward") | .body' < comments.json)
echo $COMMENT_EXISTS | grep -q "/fast-forward" 2> /dev/null
exit $?
# PLACE THIS FILE IN A REPOSITORY THAT LIVES WITHIN YOUR ORGANIZATION TITLED "fast-forward-action"
# IN THE .github/workflows/fast-forward-action.yml PATH
on:
workflow_call:
inputs:
repo_name:
required: true
type: string
pr_number:
required: true
type: number
comment_body:
required: true
type: string
secrets:
GITHUB_AUTH_TOKEN:
required: true
MERGEFREEZE_TOKEN:
required: true
jobs:
main:
if: ${{ inputs.comment_body == '/fast-forward' }}
runs-on: ubuntu-20.04
steps:
- uses: octokit/request-action@v2.x
id: get_pull_request_info
with:
route: GET /repos/{repo}/pulls/{pr}
repo: ${{ inputs.repo_name }}
pr: ${{ inputs.pr_number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_AUTH_TOKEN }}
- name: Parse pull request using jq and checking mergeable state
id: mergeable_pr
run: |
cat <<EOF > pr.js
${{ steps.get_pull_request_info.outputs.data }}
EOF
export IS_MERGEABLE_PR=$(jq -r '.mergeable' pr.js)
export BASE_BRANCH=$(jq -r '.base.ref' pr.js)
export COMPARE_BRANCH=$(jq -r '.head.ref' pr.js)
echo "mergeable=$IS_MERGEABLE_PR" >> $GITHUB_OUTPUT
echo "baseBranch=$BASE_BRANCH" >> $GITHUB_OUTPUT
echo "BASE_BRANCH=$BASE_BRANCH" >> $GITHUB_ENV
echo "compareBranch=$COMPARE_BRANCH" >> $GITHUB_OUTPUT
- name: Check if the base ref is demo, staging or production
run: |
if [ "${BASE_BRANCH}" != "demo" ] && [ "${BASE_BRANCH}" != "staging" ] && [ "${BASE_BRANCH}" != "production" ]; then
exit 1
fi
- uses: actions/checkout@v3
if: steps.mergeable_pr.outputs.mergeable
with:
fetch-depth: 0
ref: ${{ steps.mergeable_pr.outputs.compareBranch }}
token: ${{ secrets.GITHUB_AUTH_TOKEN }}
- name: Unfreeze Branch
if: steps.mergeable_pr.outputs.mergeable
uses: fjogeleit/http-request-action@v1
with:
url: "https://app.mergefreeze.com/api/branches/${{ inputs.repo_name }}/${{ env.BASE_BRANCH }}/?access_token=${{ secrets.MERGEFREEZE_TOKEN }}"
method: "POST"
customHeaders: '{"Content-Type": "application/x-www-form-urlencoded"}'
data: "frozen=false&user_name=MergeBot"
- name: Merge the PR
if: steps.mergeable_pr.outputs.mergeable
run: |
git config --global user.email "<USER_USED_TO_MERGE_PR>"
git config --global user.name "<USER_USED_TO_MERGE_PR>"
# git remote set-url origin https://<USER_USED_TO_MERGE_PR>:${{ secrets.GITHUB_AUTH_TOKEN }}@github.com/${{ inputs.repo_name }}.git
git checkout ${{ steps.mergeable_pr.outputs.baseBranch }}
git merge --ff-only ${{ steps.mergeable_pr.outputs.compareBranch }}
git push origin
- name: Freeze Branch
if: steps.mergeable_pr.outputs.mergeable
uses: fjogeleit/http-request-action@v1
with:
url: "https://app.mergefreeze.com/api/branches/${{ inputs.repo_name }}/${{ env.BASE_BRANCH }}/?access_token=${{ secrets.MERGEFREEZE_TOKEN }}"
method: "POST"
customHeaders: '{"Content-Type": "application/x-www-form-urlencoded"}'
data: "frozen=true&user_name=MergeBot"
# THIS SETUP IS BUILT TO LEVERAGE A RESUABLE WORKFLOW (THAT IS AVAILABLE IN THE fast-forward-action.yml FILE BELOW
# THIS FILE GOES IN THE REPO THAT YOU'RE FAST FORWARDING IN THE .github/workflows/fast-forward.yml PATH
on:
pull_request:
types: ["opened", "synchronize"]
issue_comment:
jobs:
check-comment:
uses: <ORG_NAME_HERE>/fast-forward-action/.github/workflows/comment-check-action.yml@master
with:
repo_name: ${{ github.repository }}
pr_number: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.event.issue.number }}
secrets:
GITHUB_AUTH_TOKEN: ${{ secrets.YOUR_GITHUB_AUTH_TOKEN }}
fast-forward:
if: github.event_name == 'issue_comment' && github.event.issue.pull_request
needs: check-comment
uses: <ORG_NAME_HERE>/fast-forward-action/.github/workflows/fast-forward-action.yml@master
with:
repo_name: ${{ github.repository }}
pr_number: ${{ github.event.issue.number }}
comment_body: ${{ github.event.comment.body }}
secrets:
GITHUB_AUTH_TOKEN: ${{ secrets.YOUR_GITHUB_AUTH_TOKEN }}
MERGEFREEZE_TOKEN: ${{ secrets.YOUR_MERGEFREEZE_TOKEN }}
@sebmellen
Copy link
Author

In action

Mergefreeze blocking the merge, even after all other branch protection rules have been met

Screenshot 2024-01-15 at 9 00 51 PM

/fast-forward comment triggers the merge action

Screenshot 2024-01-15 at 9 01 14 PM

The branch is merged into your target using -ff

Screenshot 2024-01-15 at 9 02 02 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment