From 25b2b89bd8af5e93d377ce5f0448c2b148d46a7e Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 12 Jan 2026 14:13:47 -0500 Subject: [PATCH] ci: add DDMAL workflow for syncing with RISM changes - This workflow runs every Monday at 3am montreal time. - An issue will be created if rebase or push fails - Previous related issues will be closed if the latest run passes. refs: Neon/issues/1347 --- .github/workflows/ddmal-sync-upstream.yml | 231 ++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 .github/workflows/ddmal-sync-upstream.yml diff --git a/.github/workflows/ddmal-sync-upstream.yml b/.github/workflows/ddmal-sync-upstream.yml new file mode 100644 index 0000000000..b9bcac01f7 --- /dev/null +++ b/.github/workflows/ddmal-sync-upstream.yml @@ -0,0 +1,231 @@ +name: Sync with RISM Upstream + +on: + # Run automatically every Monday at 07:00 UTC (3am in Montreal) + schedule: + - cron: "0 7 * * 1" + + # Allow manual triggering + workflow_dispatch: + inputs: + force_rebase: + description: "Force rebase even if no new commits detected" + required: false + default: false + type: boolean + +jobs: + sync-upstream: + runs-on: ubuntu-latest + # Only run this workflow on the DDMAL/verovio repository + if: github.repository == 'DDMAL/verovio' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # Fetch full history for proper rebasing + fetch-depth: 0 + # Use a personal access token with repo permissions + token: ${{ secrets.GITHUB_TOKEN }} + ref: develop + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Add upstream remote + run: | + # Check if upstream remote already exists + if git remote get-url upstream 2>/dev/null; then + echo "Upstream remote already exists" + git remote set-url upstream https://github.com/rism-digital/verovio.git + else + echo "Adding upstream remote" + git remote add upstream https://github.com/rism-digital/verovio.git + fi + + - name: Fetch upstream changes + run: | + echo "Fetching upstream changes..." + git fetch upstream develop + + - name: Check for new commits + id: check_commits + run: | + # Get the latest commit hash from upstream + UPSTREAM_COMMIT=$(git rev-parse upstream/develop) + + # Get the latest commit hash from our develop branch + LOCAL_COMMIT=$(git rev-parse develop) + + echo "Upstream commit: $UPSTREAM_COMMIT" + echo "Local commit: $LOCAL_COMMIT" + + # Check if there are new commits to sync + if [ "$UPSTREAM_COMMIT" != "$LOCAL_COMMIT" ]; then + echo "new_commits=true" >> $GITHUB_OUTPUT + echo "New commits detected in upstream" + + # Count the number of commits behind + COMMITS_BEHIND=$(git rev-list --count develop..upstream/develop) + echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT + echo "Local branch is $COMMITS_BEHIND commits behind upstream" + else + echo "new_commits=false" >> $GITHUB_OUTPUT + echo "No new commits in upstream" + echo "commits_behind=0" >> $GITHUB_OUTPUT + fi + + - name: Check for local uncommitted changes + # It's a good practice to check if the runner is in a dirty state before running a rebase + if: steps.check_commits.outputs.new_commits == 'true' || github.event.inputs.force_rebase == 'true' + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "Error: Working directory has uncommitted changes" + git status + exit 1 + fi + + - name: Rebase onto upstream + if: steps.check_commits.outputs.new_commits == 'true' || github.event.inputs.force_rebase == 'true' + run: | + echo "Starting rebase onto upstream/develop..." + + # Attempt to rebase + if git rebase upstream/develop; then + echo "✅ Rebase successful!" + else + echo "❌ Rebase failed due to conflicts" + echo "Aborting rebase..." + git rebase --abort + + # Set failure type + echo "SYNC_FAILED=rebase" >> $GITHUB_ENV + exit 1 + fi + + - name: Push rebased changes + if: (steps.check_commits.outputs.new_commits == 'true' || github.event.inputs.force_rebase == 'true') && env.SYNC_FAILED == '' + # force-with-lease is used to avoid overwriting changes that have been pushed and not yet merged to upstream + run: | + echo "Pushing rebased changes to origin/develop..." + if ! git push --force-with-lease origin develop; then + echo "❌ Push failed" + echo "SYNC_FAILED=push" >> $GITHUB_ENV + exit 1 + fi + + - name: Create issue on sync failure + if: env.SYNC_FAILED != '' + uses: actions/github-script@v7 + with: + script: | + const failureType = process.env.SYNC_FAILED || 'unknown'; + + // Determine failure-specific details + let title, failureReason, manualSteps; + + if (failureType === 'rebase') { + title = `🚨 Verovio Upstream Sync Failed - Rebase Conflicts`; + failureReason = 'merge conflicts during rebase'; + manualSteps = `1. Go to the DDMAL/verovio repository + 2. Fetch the latest upstream changes: \`git fetch upstream develop\` + 3. Rebase manually: \`git rebase upstream/develop\` + 4. Resolve any conflicts + 5. Push the rebased branch: \`git push --force-with-lease origin develop\``; + } else if (failureType === 'push') { + title = `🚨 Verovio Upstream Sync Failed - Push Error`; + failureReason = 'push failure (likely due to permission issues)'; + manualSteps = `1. Check if the GitHub token has \`workflow\` scope permissions + 2. Verify the workflow file hasn't been modified in a way that requires approval + 3. If using GITHUB_TOKEN, consider switching to a PAT or GitHub App with workflow permissions + 4. Try running the workflow again manually + 5. If the issue persists, check the workflow run logs for details`; + } else { + title = `🚨 Verovio Upstream Sync Failed`; + failureReason = 'an unknown error'; + manualSteps = `1. Check the workflow run logs for details + 2. Verify repository permissions and token scopes + 3. Try running the workflow manually`; + } + + const body = `## Automatic upstream sync failed in DDMAL/verovio + + The scheduled rebase onto \`rism-digital/verovio:develop\` failed due to ${failureReason}. + + **Details:** + - Repository: ${{ github.repository }} + - Failure type: ${failureType} + - Upstream commits to sync: ${{ steps.check_commits.outputs.commits_behind }} + - Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + **Manual action required:** + ${manualSteps} + + This issue will be automatically closed when the next successful sync occurs.`; + + // Check if an issue already exists in DDMAL/Neon + const existingIssues = await github.rest.issues.listForRepo({ + owner: 'DDMAL', + repo: 'Neon', + labels: ['verovio-upstream-sync-failure'], + state: 'open' + }); + + if (existingIssues.data.length === 0) { + await github.rest.issues.create({ + owner: 'DDMAL', + repo: 'Neon', + title: title, + body: body, + labels: ['verovio-upstream-sync-failure', 'automation'] + }); + } + + - name: Close existing sync failure issues + if: (steps.check_commits.outputs.new_commits == 'true' || github.event.inputs.force_rebase == 'true') && env.SYNC_FAILED == '' + uses: actions/github-script@v7 + with: + script: | + // Close any existing upstream sync failure issues in DDMAL/Neon + const existingIssues = await github.rest.issues.listForRepo({ + owner: 'DDMAL', + repo: 'Neon', + labels: ['verovio-upstream-sync-failure'], + state: 'open' + }); + + for (const issue of existingIssues.data) { + await github.rest.issues.createComment({ + owner: 'DDMAL', + repo: 'Neon', + issue_number: issue.number, + body: '✅ Verovio upstream sync completed successfully. Closing this issue.' + }); + + await github.rest.issues.update({ + owner: 'DDMAL', + repo: 'Neon', + issue_number: issue.number, + state: 'closed' + }); + } + + - name: Summary + run: | + if [ "${{ steps.check_commits.outputs.new_commits }}" = "true" ] || [ "${{ github.event.inputs.force_rebase }}" = "true" ]; then + if [ "$SYNC_FAILED" = "rebase" ]; then + echo "❌ Sync failed due to rebase conflicts" + echo "📝 An issue has been created for manual intervention" + elif [ "$SYNC_FAILED" = "push" ]; then + echo "❌ Sync failed due to push error" + echo "📝 An issue has been created for manual intervention" + else + echo "✅ Successfully synced ${{ steps.check_commits.outputs.commits_behind }} commits from upstream" + echo "🚀 Changes pushed to develop branch" + fi + else + echo "â„šī¸ No new commits to sync" + fi