Skip to content

Merge upstream actions/runner commits into sirredbeard/runner (keep one commit ahead) #83

Merge upstream actions/runner commits into sirredbeard/runner (keep one commit ahead)

Merge upstream actions/runner commits into sirredbeard/runner (keep one commit ahead) #83

name: Merge upstream actions/runner commits into sirredbeard/runner (keep one commit ahead)
on:
workflow_dispatch:
schedule:
# Run daily at 2 AM UTC to check for upstream updates
- cron: '0 2 * * *'
permissions:
contents: write # required to push commits
issues: write # required to create an issue when automatic merge fails
concurrency:
group: merge-upstream-${{ github.repository }}
cancel-in-progress: false
env:
UPSTREAM_REPO: actions/runner
UPSTREAM_BRANCH: main
WORK_BRANCH: main
COMMIT_GREP: Add Windows Containers Support
jobs:
merge:
runs-on: ubuntu-latest
steps:
- name: Checkout repository (full history)
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: true
- name: Configure git user
run: |
git config user.name "sirredbeard"
git config user.email "hbarnes@herodevs.com"
- name: Add upstream remote and fetch
run: |
git remote add upstream https://github.com/${{ env.UPSTREAM_REPO }}.git || true
git fetch upstream ${{ env.UPSTREAM_BRANCH }} --tags || true
- name: Check for new upstream release tag
id: check_upstream
run: |
# Get the latest upstream release tag
latest_upstream_tag=$(git tag -l --sort=-version:refname "v*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | head -n 1 || true)
# Get our current version - releaseVersion may contain the upstream placeholder text
# if it was never set after a rebase; fall back to src/runnerversion in that case
current_version=$(cat releaseVersion)
if ! echo "$current_version" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+$"; then
echo "releaseVersion contains placeholder, falling back to src/runnerversion"
current_version=$(cat src/runnerversion)
fi
current_tag="v${current_version}"
echo "Current version: $current_tag"
echo "Latest upstream tag: $latest_upstream_tag"
if [ -z "$latest_upstream_tag" ]; then
echo "Could not find any upstream release tags matching pattern v[0-9]+.[0-9]+.[0-9]+"
echo "needs_update=false" >> $GITHUB_OUTPUT
elif [ "$current_tag" = "$latest_upstream_tag" ]; then
echo "Already up to date with upstream release $latest_upstream_tag"
echo "needs_update=false" >> $GITHUB_OUTPUT
else
echo "New upstream release found: $latest_upstream_tag"
echo "needs_update=true" >> $GITHUB_OUTPUT
echo "new_tag=$latest_upstream_tag" >> $GITHUB_OUTPUT
fi
- name: Save current HEAD (original fork state)
id: save
run: echo "old_head=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- name: Find the 'Add Windows Containers Support' commit in history
if: steps.check_upstream.outputs.needs_update == 'true'
id: find
run: |
# Find commit hash by commit message (first match)
commit=$(git log --pretty=format:%H --grep="${{ env.COMMIT_GREP }}" -n 1 || true)
if [ -z "$commit" ]; then
echo "No commit found matching '${{ env.COMMIT_GREP }}' in local history."
# Fail early so we can create an issue in the next step
echo "commit_found=false" >> $GITHUB_OUTPUT
else
echo "commit=$commit" >> $GITHUB_OUTPUT
echo "commit_found=true" >> $GITHUB_OUTPUT
fi
- name: Abort if special commit not found (create issue)
if: steps.check_upstream.outputs.needs_update == 'true' && steps.find.outputs.commit_found == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: "Automated merge aborted: patch commit not found",
body: `The workflow could not find a commit with message matching "${{ env.COMMIT_GREP }}" in the repository history. Please verify the commit message and rerun the workflow manually.`
})
# stop the job after issue is created
- name: Reset to upstream/main and cherry-pick patch
if: steps.check_upstream.outputs.needs_update == 'true' && steps.find.outputs.commit_found == 'true'
id: merge
shell: bash
run: |
set -uo pipefail
commit="${{ steps.find.outputs.commit }}"
old_head="${{ steps.save.outputs.old_head }}"
new_tag="${{ steps.check_upstream.outputs.new_tag }}"
# Files where conflicts are expected and can be auto-resolved
# These are always overwritten by our fork and should never block a merge
AUTO_RESOLVE_OURS=(README.md images/Dockerfile AGENTS.md .github/workflows/release.yml .github/workflows/build.yml .github/workflows/docker-publish.yml releaseNote.md)
# Ensure we're on the working branch
git checkout ${{ env.WORK_BRANCH }} || exit 1
# Reset to upstream/main (NOT the release tag) so we stay exactly one commit
# above upstream/main. The releaseVersion is updated via --amend below.
git fetch upstream ${{ env.UPSTREAM_BRANCH }} --tags --force || true
git reset --hard upstream/${{ env.UPSTREAM_BRANCH }} || exit 1
# Try to cherry-pick the special commit onto the new base
if git cherry-pick "$commit" 2>/dev/null; then
echo "Cherry-pick applied cleanly"
cherry_pick_ok=true
else
echo "Cherry-pick had conflicts, attempting auto-resolution..."
# Get list of conflicted files
conflicted_files=$(git diff --name-only --diff-filter=U)
echo "Conflicted files:"
echo "$conflicted_files"
# Separate auto-resolvable conflicts from real conflicts
has_real_conflicts=false
for f in $conflicted_files; do
is_auto=false
# Check if this file is in our auto-resolve list
for auto_file in "${AUTO_RESOLVE_OURS[@]}"; do
if [ "$f" = "$auto_file" ]; then
is_auto=true
break
fi
done
if [ "$f" = "releaseVersion" ]; then
# releaseVersion is always overwritten after cherry-pick via --amend; take upstream's for now
echo "Auto-resolving releaseVersion (will be squash-amended after cherry-pick)"
git checkout --ours "$f"
git add "$f"
elif [ "$is_auto" = "true" ]; then
# For fork-owned files, always take our cherry-picked version
echo "Auto-resolving $f (taking ours from patch)"
git checkout --theirs "$f"
git add "$f"
else
echo "REAL CONFLICT in $f — cannot auto-resolve"
has_real_conflicts=true
fi
done
if [ "$has_real_conflicts" = "true" ]; then
echo "Unresolvable conflicts remain, aborting cherry-pick"
git cherry-pick --abort 2>/dev/null || true
short=${old_head:0:7}
manual_branch="manual-merge-${short}"
git branch "$manual_branch" "$old_head" 2>/dev/null || true
git push origin "$manual_branch" 2>/dev/null || true
echo "result=failed" >> $GITHUB_OUTPUT
echo "manual_branch=$manual_branch" >> $GITHUB_OUTPUT
cherry_pick_ok=false
else
echo "All conflicts auto-resolved, continuing cherry-pick"
git cherry-pick --continue --no-edit
cherry_pick_ok=true
fi
fi
if [ "$cherry_pick_ok" = "true" ]; then
echo "result=success" >> $GITHUB_OUTPUT
# Update releaseVersion to match the new upstream tag (strip 'v' prefix).
# This MUST be squashed into the single "Add Windows Containers Support" commit
# via --amend so the repo stays exactly one commit ahead of upstream.
# Never use a plain `git commit` here — two commits above upstream is wrong.
upstream_version="${new_tag#v}"
echo "$upstream_version" > releaseVersion
git add releaseVersion
git commit --amend --no-edit
# Push the updated branch (force-with-lease to be safer)
# This will trigger release.yml via the releaseVersion push trigger
git push origin ${{ env.WORK_BRANCH }} --force-with-lease
fi
- name: Create issue on failure with manual-merge branch info
if: steps.check_upstream.outputs.needs_update == 'true' && steps.merge.outputs.result == 'failed'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const branch = '${{ steps.merge.outputs.manual_branch }}';
const commit = '${{ steps.find.outputs.commit }}';
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Automated merge failed — manual resolution required (${branch})`,
body: `The workflow attempted to rebase the fork onto upstream release tag and cherry-pick the patch commit \`${commit}\` ("${{ env.COMMIT_GREP }}"). The cherry-pick failed (likely due to merge conflicts). A branch \`${branch}\` pointing at the original fork state has been pushed for manual resolution.\n\nPlease check the branch, resolve conflicts, and merge as appropriate.`
})
- name: Success notification
if: steps.check_upstream.outputs.needs_update == 'true' && steps.merge.outputs.result == 'success'
run: |
echo "Upstream release ${{ steps.check_upstream.outputs.new_tag }} merged and patch commit reapplied successfully."
echo "Release workflow will now be triggered by the releaseVersion update."
- name: Already up to date
if: steps.check_upstream.outputs.needs_update == 'false'
run: echo "Fork is already up to date with latest upstream release. No action needed."