Repository navigation
fix(sync): add workflow run self-link to PR body (review feedback) #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync main to edge (reusable) | ||
| # Reusable workflow that reads the caller repo's `.gitupstream` config and | ||
| # syncs the configured upstream branch (typically `main`) into one or more | ||
| # target "edge" branches inside the SAME repo, using the | ||
| # aormsby/Fork-Sync-With-Upstream-action marketplace action. | ||
| # | ||
| # Schema of .gitupstream (YAML, repo root, default branch): | ||
| # version: 1 | ||
| # sync: | ||
| # - upstream: main | ||
| # targets: | ||
| # - alex/edge | ||
| # - jack/edge | ||
| # | ||
| # Multiple sync entries are supported; each entry's `targets` are expanded | ||
| # into a job matrix. | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| config_path: | ||
| description: 'Path to the .gitupstream config file (relative to repo root)' | ||
| required: false | ||
| type: string | ||
| default: '.gitupstream' | ||
| test_mode: | ||
| description: 'Run the sync action in test mode (no push)' | ||
| required: false | ||
| type: boolean | ||
| default: false | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| jobs: | ||
| plan: | ||
| name: Plan sync targets | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| matrix: ${{ steps.plan.outputs.matrix }} | ||
| has_targets: ${{ steps.plan.outputs.has_targets }} | ||
| steps: | ||
| - name: Checkout caller repo | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Install yq | ||
| env: | ||
| YQ_VERSION: v4.53.2 | ||
| run: | | ||
| set -euo pipefail | ||
| sudo curl -fsSL -o /usr/local/bin/yq \ | ||
| "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" | ||
| sudo chmod +x /usr/local/bin/yq | ||
| yq --version | ||
| - name: Build matrix from .gitupstream | ||
| id: plan | ||
| env: | ||
| CONFIG_PATH: ${{ inputs.config_path }} | ||
| run: | | ||
| set -euo pipefail | ||
| if [ ! -f "$CONFIG_PATH" ]; then | ||
| echo "No $CONFIG_PATH file found; nothing to sync." | ||
| echo 'matrix={"include":[]}' >> "$GITHUB_OUTPUT" | ||
| echo 'has_targets=false' >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| # Flatten sync[*] entries into one row per (upstream, target). | ||
| # Each row -> {upstream, target} | ||
| MATRIX_JSON=$( | ||
| yq -o=json -I=0 ' | ||
| {"include": | ||
| [.sync[] | ||
| | . as $s | ||
| | .targets[] | ||
| | {"upstream": $s.upstream, "target": .} | ||
| ] | ||
| } | ||
| ' "$CONFIG_PATH" | ||
| ) | ||
| echo "Computed matrix: $MATRIX_JSON" | ||
| echo "matrix=$MATRIX_JSON" >> "$GITHUB_OUTPUT" | ||
| COUNT=$(echo "$MATRIX_JSON" | yq -p=json '.include | length') | ||
| if [ "$COUNT" -gt 0 ]; then | ||
| echo 'has_targets=true' >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo 'has_targets=false' >> "$GITHUB_OUTPUT" | ||
| fi | ||
| sync: | ||
| name: Sync ${{ matrix.upstream }} -> ${{ matrix.target }} | ||
| needs: plan | ||
| if: needs.plan.outputs.has_targets == 'true' | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: ${{ fromJSON(needs.plan.outputs.matrix) }} | ||
| steps: | ||
| # The sync action expects the target branch to exist on the remote. | ||
| # If it does not (first run before any edge work), skip gracefully so | ||
| # the workflow stays green for repos that have not bootstrapped edge yet. | ||
| - name: Check target branch exists | ||
| id: check | ||
| env: | ||
| TARGET: ${{ matrix.target }} | ||
| REPO: ${{ github.repository }} | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| set -euo pipefail | ||
| if gh api "repos/$REPO/branches/$TARGET" --silent 2>/dev/null; then | ||
| echo "exists=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "Target branch '$TARGET' does not exist yet; skipping." | ||
| echo "exists=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| - name: Checkout target branch | ||
| if: steps.check.outputs.exists == 'true' | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| ref: ${{ matrix.target }} | ||
| persist-credentials: false | ||
| fetch-depth: 0 | ||
| # The aormsby action fast-forwards upstream -> target. It fails when the | ||
| # branches have diverged or when branch protection rejects the push. | ||
| # continue-on-error lets us fall back to opening a PR in that case instead | ||
| # of failing the whole job. | ||
| - name: Sync upstream changes | ||
| if: steps.check.outputs.exists == 'true' | ||
| id: sync | ||
| continue-on-error: true | ||
| uses: aormsby/Fork-Sync-With-Upstream-action@83d78e8c4bf524e79b091cf257deba4dee9e60df # v3.4.3 | ||
| with: | ||
| target_sync_branch: ${{ matrix.target }} | ||
| target_repo_token: ${{ secrets.GITHUB_TOKEN }} | ||
| upstream_sync_branch: ${{ matrix.upstream }} | ||
| upstream_sync_repo: ${{ github.repository }} | ||
| test_mode: ${{ inputs.test_mode }} | ||
| # When the direct push could not happen (diverged history or branch | ||
| # protection), open a PR upstream -> target instead. Idempotent: skips if | ||
| # a PR is already open for the same head/base, and tolerates "no diff". | ||
| - name: Open PR if direct sync could not push | ||
| if: steps.check.outputs.exists == 'true' && steps.sync.outcome == 'failure' | ||
| env: | ||
| UPSTREAM: ${{ matrix.upstream }} | ||
| TARGET: ${{ matrix.target }} | ||
| REPO: ${{ github.repository }} | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| set -euo pipefail | ||
| existing=$(gh pr list --repo "$REPO" --base "$TARGET" --head "$UPSTREAM" \ | ||
| --state open --json number -q '.[0].number' || true) | ||
| if [ -n "$existing" ]; then | ||
| echo "PR #$existing already open for $UPSTREAM -> $TARGET; nothing to do." | ||
| exit 0 | ||
| fi | ||
| BODY="Automated sync could not fast-forward \`$TARGET\` from \`$UPSTREAM\` (diverged history or branch protection). Merging via PR instead. | ||
| Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | ||
| gh pr create --repo "$REPO" --base "$TARGET" --head "$UPSTREAM" \ | ||
| --title "sync: $UPSTREAM -> $TARGET" \ | ||
| --body "$BODY" \ | ||
| || echo "No PR created (possibly no diff between $UPSTREAM and $TARGET)." | ||
| - name: Report | ||
| if: steps.check.outputs.exists == 'true' | ||
| run: | | ||
| if [ "${{ steps.sync.outcome }}" = "failure" ]; then | ||
| echo "Direct sync could not push ${{ matrix.upstream }} -> ${{ matrix.target }}; opened/ensured a PR instead." | ||
| elif [ "${{ steps.sync.outputs.has_new_commits }}" = "true" ]; then | ||
| echo "Synced new commits from ${{ matrix.upstream }} into ${{ matrix.target }}." | ||
| else | ||
| echo "${{ matrix.target }} already up-to-date with ${{ matrix.upstream }}." | ||
| fi | ||