Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 10 additions & 125 deletions .github/workflows/sync-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ concurrency:

permissions:
contents: write
pull-requests: write
issues: write

env:
SYNC_BRANCH: automation/upstream-main
UPSTREAM_REPO: Wei-Shaw/sub2api
UPSTREAM_REMOTE: upstream
CONFLICT_LABEL: upstream-sync-conflict
Expand All @@ -29,17 +27,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
token: ${{ secrets.PAT_TOKEN }}

- name: Ensure labels exist
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
run: |
gh label create automation --color 5319e7 --description "Automation managed" --force || true
gh label create upstream-sync --color 0e8a16 --description "Upstream synchronization" --force || true
gh label create "$CONFLICT_LABEL" --color b60205 --description "Upstream sync needs manual conflict resolution" --force || true

- name: Configure Git
run: |
git config user.name "github-actions[bot]"
Expand All @@ -48,40 +39,34 @@ jobs:
- name: Add upstream remote
run: |
git remote add "$UPSTREAM_REMOTE" "https://github.com/$UPSTREAM_REPO.git" 2>/dev/null || true
git fetch origin main --tags --force
git fetch "$UPSTREAM_REMOTE" main --tags --force

- name: Prepare sync branch
- name: Sync and push
id: sync
shell: bash
run: |
set -euo pipefail

BASE_SHA="$(git rev-parse origin/main)"
UPSTREAM_SHA="$(git rev-parse "${UPSTREAM_REMOTE}/main")"
UPSTREAM_SHORT="${UPSTREAM_SHA:0:7}"
MERGE_BASE="$(git merge-base origin/main "${UPSTREAM_REMOTE}/main")"
LATEST_TAG="$(git describe --tags --abbrev=0 "${UPSTREAM_REMOTE}/main" 2>/dev/null || true)"

{
echo "base_sha=$BASE_SHA"
echo "upstream_sha=$UPSTREAM_SHA"
echo "upstream_short=$UPSTREAM_SHORT"
echo "merge_base=$MERGE_BASE"
echo "latest_tag=${LATEST_TAG:-}"
} >> "$GITHUB_OUTPUT"

if git merge-base --is-ancestor "$UPSTREAM_SHA" "$BASE_SHA"; then
if git merge-base --is-ancestor "$UPSTREAM_SHA" HEAD; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "merge_conflict=false" >> "$GITHUB_OUTPUT"
echo "No changes to sync"
exit 0
fi

echo "changed=true" >> "$GITHUB_OUTPUT"
git checkout -B "$SYNC_BRANCH" "$BASE_SHA"

set +e
git merge "$UPSTREAM_SHA" --no-edit
git merge "$UPSTREAM_SHA" --no-edit -m "sync: upstream/main @ $UPSTREAM_SHORT [skip ci]"
merge_status=$?
set -e

Expand All @@ -93,59 +78,10 @@ jobs:
fi

echo "merge_conflict=false" >> "$GITHUB_OUTPUT"

- name: Auto-resolve known conflicts
if: steps.sync.outputs.merge_conflict == 'true'
id: auto_resolve
shell: bash
run: |
set -euo pipefail

CONFLICT_FILES="$(git diff --name-only --diff-filter=U)"
ALL_RESOLVED=true

echo "Attempting to auto-resolve conflicts..."

for file in $CONFLICT_FILES; do
echo "Processing: $file"
case "$file" in
backend/cmd/server/VERSION)
git checkout "$UPSTREAM_REMOTE/main" -- "$file"
git add "$file"
echo "✓ Resolved: $file (adopted upstream)"
;;
frontend/src/components/account/__tests__/*.spec.ts)
git checkout "$UPSTREAM_REMOTE/main" -- "$file"
git add "$file"
echo "✓ Resolved: $file (adopted upstream)"
;;
.github/workflows/release.yml)
if python3 .github/scripts/merge-release-yml.py; then
git add "$file"
echo "✓ Resolved: $file (smart merge)"
else
ALL_RESOLVED=false
echo "✗ Failed: $file (needs manual resolution)"
fi
;;
*)
ALL_RESOLVED=false
echo "✗ Unknown conflict: $file"
;;
esac
done

if [ "$ALL_RESOLVED" = "true" ]; then
git commit -m "chore: auto-resolve upstream sync conflicts [skip ci]"
echo "resolved=true" >> "$GITHUB_OUTPUT"
echo "All conflicts auto-resolved!"
else
echo "resolved=false" >> "$GITHUB_OUTPUT"
echo "Some conflicts require manual resolution"
fi
git push origin main

- name: Close stale conflict issue
if: steps.sync.outputs.changed == 'false' || steps.sync.outputs.merge_conflict == 'false' || steps.auto_resolve.outputs.resolved == 'true'
if: steps.sync.outputs.changed == 'false' || steps.sync.outputs.merge_conflict == 'false'
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
shell: bash
Expand All @@ -157,19 +93,18 @@ jobs:
fi

- name: Create or update conflict issue
if: steps.sync.outputs.merge_conflict == 'true' && steps.auto_resolve.outputs.resolved != 'true'
if: steps.sync.outputs.merge_conflict == 'true'
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
shell: bash
run: |
set -euo pipefail
BODY_FILE="$(mktemp)"
{
echo "Upstream sync hit a merge conflict and stopped before updating \`$SYNC_BRANCH\`."
echo "Upstream sync hit a merge conflict."
echo
echo "- Upstream repository: \`$UPSTREAM_REPO\`"
echo "- Upstream commit: \`${{ steps.sync.outputs.upstream_sha }}\`"
echo "- Current main commit: \`${{ steps.sync.outputs.base_sha }}\`"
echo "- Workflow run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo
echo "Conflict files:"
Expand All @@ -187,57 +122,7 @@ jobs:
fi

- name: Stop on merge conflict
if: steps.sync.outputs.merge_conflict == 'true' && steps.auto_resolve.outputs.resolved != 'true'
if: steps.sync.outputs.merge_conflict == 'true'
run: |
echo "Upstream sync conflict recorded in issue tracker."
exit 1

- name: Push sync branch
if: steps.sync.outputs.changed == 'true' && (steps.sync.outputs.merge_conflict == 'false' || steps.auto_resolve.outputs.resolved == 'true')
run: git push --force-with-lease origin "$SYNC_BRANCH"

- name: Create or update sync PR
if: steps.sync.outputs.changed == 'true' && (steps.sync.outputs.merge_conflict == 'false' || steps.auto_resolve.outputs.resolved == 'true')
id: pr
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
shell: bash
run: |
set -euo pipefail

TITLE="sync: upstream/main @ ${{ steps.sync.outputs.upstream_short }}"
BODY_FILE="$(mktemp)"
{
echo "This automation PR syncs \`$UPSTREAM_REPO\` into \`main\`."
echo
echo "- Upstream commit: \`${{ steps.sync.outputs.upstream_sha }}\`"
if [ -n "${{ steps.sync.outputs.latest_tag }}" ]; then
echo "- Latest upstream tag: \`${{ steps.sync.outputs.latest_tag }}\`"
fi
echo "- Compare: https://github.com/$UPSTREAM_REPO/compare/${{ steps.sync.outputs.merge_base }}...${{ steps.sync.outputs.upstream_sha }}"
echo "- Workflow run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo
echo "This PR is expected to auto-merge after required checks pass."
} > "$BODY_FILE"

PR_NUMBER="$(gh pr list --head "$SYNC_BRANCH" --base main --state open --json number --jq '.[0].number' || true)"
if [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != "null" ]; then
gh pr edit "$PR_NUMBER" --title "$TITLE" --body-file "$BODY_FILE"
else
gh pr create \
--base main \
--head "$SYNC_BRANCH" \
--title "$TITLE" \
--body-file "$BODY_FILE"
PR_NUMBER="$(gh pr list --head "$SYNC_BRANCH" --base main --state open --json number --jq '.[0].number')"
fi

echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"

- name: Enable auto-merge
if: steps.pr.outputs.number != ''
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
run: |
# Preserve upstream ancestry on main; squash merges would break ancestor-based sync detection.
gh pr merge "${{ steps.pr.outputs.number }}" --auto --merge