Skip to content

Update from Template #37

Update from Template

Update from Template #37

name: Update from Template
# This workflow keeps the repo up to date with changes from the template repo (REMOTE_URL)
# It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into
# this repos default branch (which is checked out here)
# Note that this requires a PAT (Personal Access Token) - at best from a servicing account
# PAT permissions: read:discussion, read:org, repo, workflow
# Also note that you should have at least once merged the template repo into the current repo manually
# otherwise a "refusing to merge unrelated histories" error might occur.
on:
schedule:
- cron: '55 2 * * 1'
workflow_dispatch:
inputs:
no_automatic_merge:
type: boolean
description: 'No automatic merge'
default: false
env:
UPDATE_BRANCH: update-from-template
UPDATE_BRANCH_MERGED: update-from-template-merged
REMOTE_URL: https://github.com/xdev-software/standard-maven-template.git
REMOTE_BRANCH: master
permissions:
contents: write
pull-requests: write
jobs:
update:
runs-on: ubuntu-latest
timeout-minutes: 60
outputs:
update_branch_merged_commit: ${{ steps.manage-branches.outputs.update_branch_merged_commit }}
create_update_branch_merged_pr: ${{ steps.manage-branches.outputs.create_update_branch_merged_pr }}
steps:
- uses: actions/checkout@v4
with:
# Required because otherwise there are always changes detected when executing diff/rev-list
fetch-depth: 0
# If no PAT is used the following error occurs on a push:
# refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission
token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
- name: Init Git
run: |
git config --global user.email "[email protected]"
git config --global user.name "XDEV Bot"
- name: Manage branches
id: manage-branches
run: |
echo "Adding remote template-repo"
git remote add template ${{ env.REMOTE_URL }}
echo "Fetching remote template repo"
git fetch template
echo "Deleting local branches that will contain the updates - if present"
git branch -D ${{ env.UPDATE_BRANCH }} || true
git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true
echo "Checking if the remote template repo has new commits"
git rev-list ..template/${{ env.REMOTE_BRANCH }}
if [ $(git rev-list --count ..template/${{ env.REMOTE_BRANCH }}) -eq 0 ]; then
echo "There are no commits new commits on the template repo"
echo "Deleting origin branch(es) that contain the updates - if present"
git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true
git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true
echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT
echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT
exit 0
fi
echo "Found new commits on the template repo"
echo "Creating update branch"
git branch ${{ env.UPDATE_BRANCH }} template/${{ env.REMOTE_BRANCH }}
git branch --unset-upstream ${{ env.UPDATE_BRANCH }}
echo "Pushing update branch"
git push -f -u origin ${{ env.UPDATE_BRANCH }}
echo "Getting base branch"
base_branch=$(git branch --show-current)
echo "Base branch is $base_branch"
echo "base_branch=$base_branch" >> $GITHUB_OUTPUT
echo "Trying to create auto-merged branch ${{ env.UPDATE_BRANCH_MERGED }}"
git branch ${{ env.UPDATE_BRANCH_MERGED }} ${{ env.UPDATE_BRANCH }}
git checkout ${{ env.UPDATE_BRANCH_MERGED }}
echo "Merging branch $base_branch into ${{ env.UPDATE_BRANCH_MERGED }}"
git merge $base_branch && merge_exit_code=$? || merge_exit_code=$?
if [ $merge_exit_code -ne 0 ]; then
echo "Auto merge failed! Manual merge required"
echo "::notice ::Auto merge failed - Manual merge required"
echo "Cleaning up failed merge"
git merge --abort
git checkout $base_branch
git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true
echo "Deleting auto-merge branch - if present"
git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true
echo "create_update_branch_pr=1" >> $GITHUB_OUTPUT
echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT
exit 0
fi
echo "Post processing: Trying to automatically fill in template variables"
find . -type f \
-not -path "./.git/**" \
-not -path "./.github/workflows/update-from-template.yml" -print0 \
| xargs -0 sed -i "s/template-placeholder/${GITHUB_REPOSITORY#*/}/g"
git status
git add --all
if [[ "$(git status --porcelain)" != "" ]]; then
echo "Filled in template; Committing"
git commit -m "Fill in template"
fi
echo "Pushing auto-merged branch"
git push -f -u origin ${{ env.UPDATE_BRANCH_MERGED }}
echo "update_branch_merged_commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "Restoring base branch $base_branch"
git checkout $base_branch
echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT
echo "create_update_branch_merged_pr=1" >> $GITHUB_OUTPUT
echo "try_close_update_branch_pr=1" >> $GITHUB_OUTPUT
- name: Create/Update PR update_branch
if: steps.manage-branches.outputs.create_update_branch_pr == 1
env:
GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
run: |
gh_pr_up() {
gh pr create -H "${{ env.UPDATE_BRANCH }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH }}" && gh pr edit "$@")
}
gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \
--title "Update from template" \
--body "An automated PR to sync changes from the template into this repo"
# Ensure that only a single PR is open (otherwise confusion and spam)
- name: Close PR update_branch
if: steps.manage-branches.outputs.try_close_update_branch_pr == 1
env:
GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
run: |
gh pr close "${{ env.UPDATE_BRANCH }}" || true
- name: Create/Update PR update_branch_merged
if: steps.manage-branches.outputs.create_update_branch_merged_pr == 1
env:
GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
run: |
gh_pr_up() {
gh pr create -H "${{ env.UPDATE_BRANCH_MERGED }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH_MERGED }}" && gh pr edit "$@")
}
gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \
--title "Update from template (auto-merged)" \
--body "An automated PR to sync changes from the template into this repo"
# Wait a moment so that checks of PR have higher prio than following job
sleep 3
# Split into two jobs to help with executor starvation
auto-merge:
needs: [update]
if: needs.update.outputs.create_update_branch_merged_pr == 1
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with:
# Required because otherwise there are always changes detected when executing diff/rev-list
fetch-depth: 0
# If no PAT is used the following error occurs on a push:
# refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission
token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
- name: Init Git
run: |
git config --global user.email "[email protected]"
git config --global user.name "XDEV Bot"
- name: Checking if auto-merge for PR update_branch_merged can be done
id: auto-merge-check
env:
GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }}
run: |
not_failed_conclusion="skipped|neutral|success"
not_relevant_app_slug="dependabot|github-pages|sonarcloud"
echo "Waiting for checks to start..."
sleep 40s
for i in {1..20}; do
echo "Checking if PR can be auto-merged. Try: $i"
echo "Checking if update-branch-merged exists"
git fetch
if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then
echo "Branch still exists; Continuing..."
else
echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing"
exit 0
fi
echo "Fetching checks"
cs_response=$(curl -sL \
--fail-with-body \
--connect-timeout 60 \
--max-time 120 \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/commits/${{ needs.update.outputs.update_branch_merged_commit }}/check-suites)
cs_data=$(echo $cs_response | jq '.check_suites[] | { conclusion: .conclusion, slug: .app.slug, check_runs_url: .check_runs_url }')
echo $cs_data
if [[ -z "$cs_data" ]]; then
echo "No check suite data - Assuming that there are no checks to run"
echo "perform=1" >> $GITHUB_OUTPUT
exit 0
fi
cs_failed=$(echo $cs_data | jq --arg x "$not_failed_conclusion" 'select ((.conclusion == null or (.conclusion | test($x))) | not)')
if [[ -z "$cs_failed" ]]; then
echo "No check failed so far; Checking if relevant checks are still running"
cs_relevant_still_running=$(echo $cs_data | jq --arg x "$not_relevant_app_slug" 'select (.conclusion == null and (.slug | test($x) | not))')
if [[ -z $cs_relevant_still_running ]]; then
echo "All relevant checks finished - PR can be merged"
echo "perform=1" >> $GITHUB_OUTPUT
exit 0
else
echo "Relevant checks are still running"
echo $cs_relevant_still_running
fi
else
echo "Detected failed check"
echo $cs_failed
echo "perform=0" >> $GITHUB_OUTPUT
exit 0
fi
echo "Waiting before next run..."
sleep 30s
done
echo "Timed out - Assuming executor starvation - Forcing merge"
echo "perform=1" >> $GITHUB_OUTPUT
- name: Auto-merge update_branch_merged
if: steps.auto-merge-check.outputs.perform == 1
run: |
echo "Getting base branch"
base_branch=$(git branch --show-current)
echo "Base branch is $base_branch"
echo "Fetching..."
git fetch
if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then
echo "Branch still exists; Continuing..."
else
echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing"
exit 0
fi
expected_commit="${{ needs.update.outputs.update_branch_merged_commit }}"
actual_commit=$(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }})
if [[ "$expected_commit" != "$actual_commit" ]]; then
echo "Branch ${{ env.UPDATE_BRANCH_MERGED }} contains unexpected commit $actual_commit"
echo "Expected: $expected_commit"
exit 0
fi
echo "Ensuring that current branch $base_branch is up-to-date"
git pull
echo "Merging origin/${{ env.UPDATE_BRANCH_MERGED }} into $base_branch"
git merge origin/${{ env.UPDATE_BRANCH_MERGED }} && merge_exit_code=$? || merge_exit_code=$?
if [ $merge_exit_code -ne 0 ]; then
echo "Unexpected merge failure $merge_exit_code - Requires manual resolution"
exit 0
fi
if [[ "${{ inputs.no_automatic_merge }}" == "true" ]]; then
echo "Exiting due no_automatic_merge"
exit 0
fi
echo "Pushing"
git push
echo "Cleaning up"
git branch -D ${{ env.UPDATE_BRANCH }} || true
git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true
git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true
git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true