Sync upstream nightly #6
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 upstream nightly | |
| # Match upstream's nightly cadence (every 3h) so personal always | |
| # includes the same commits that ship as pingdotgg nightlies, plus CU/History. | |
| on: | |
| schedule: | |
| - cron: "15 */3 * * *" | |
| workflow_dispatch: | |
| # Also wake when we manually announce a sync is needed | |
| repository_dispatch: | |
| types: [sync-upstream] | |
| concurrency: | |
| group: sync-upstream-nightly | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| issues: write | |
| jobs: | |
| sync: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| changed: ${{ steps.merge.outputs.changed }} | |
| upstream_sha: ${{ steps.merge.outputs.upstream_sha }} | |
| nightly_tag: ${{ steps.merge.outputs.nightly_tag }} | |
| steps: | |
| - name: Checkout personal branch | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: personal | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - name: Fetch upstream main + latest nightly tag tip | |
| id: merge | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| git remote add upstream https://github.com/pingdotgg/t3code.git | |
| git fetch upstream main | |
| git fetch origin personal main | |
| # Prefer the commit of the newest upstream nightly release when present; | |
| # otherwise fall back to upstream/main (nightlies are cut from main). | |
| NIGHTLY_TAG=$(gh api repos/pingdotgg/t3code/releases --jq '[.[] | select(.prerelease==true and (.tag_name|test("nightly")))] | sort_by(.published_at) | reverse | .[0].tag_name // empty') | |
| if [[ -n "$NIGHTLY_TAG" ]]; then | |
| git fetch upstream "refs/tags/${NIGHTLY_TAG}:refs/tags/${NIGHTLY_TAG}" | |
| TARGET=$(git rev-parse "${NIGHTLY_TAG}^{commit}") | |
| echo "Using upstream nightly tag ${NIGHTLY_TAG} @ ${TARGET}" | |
| else | |
| TARGET=$(git rev-parse upstream/main) | |
| NIGHTLY_TAG="" | |
| echo "No nightly tag found; using upstream/main @ ${TARGET}" | |
| fi | |
| # Always also ensure we are not behind upstream/main tip | |
| MAIN=$(git rev-parse upstream/main) | |
| if [[ "$(git merge-base --is-ancestor "$MAIN" "$TARGET" && echo yes || echo no)" == "no" ]]; then | |
| # main moved past the last nightly tag — take main | |
| TARGET="$MAIN" | |
| echo "upstream/main is ahead of nightly tag tip; using main" | |
| fi | |
| echo "upstream_sha=$TARGET" >> "$GITHUB_OUTPUT" | |
| echo "nightly_tag=$NIGHTLY_TAG" >> "$GITHUB_OUTPUT" | |
| if git merge-base --is-ancestor "$TARGET" HEAD; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| echo "personal already contains $TARGET" | |
| exit 0 | |
| fi | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| MSG="chore: sync upstream $(git rev-parse --short "$TARGET")" | |
| if [[ -n "$NIGHTLY_TAG" ]]; then | |
| MSG="$MSG ($NIGHTLY_TAG)" | |
| fi | |
| if git merge --no-ff "$TARGET" -m "$MSG"; then | |
| echo "merge_ok=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "merge_ok=false" >> "$GITHUB_OUTPUT" | |
| git merge --abort || true | |
| exit 1 | |
| fi | |
| - name: Push personal and update main | |
| if: steps.merge.outputs.changed == 'true' | |
| run: | | |
| git push origin HEAD:personal | |
| git push origin HEAD:main | |
| - name: Summarize | |
| run: | | |
| { | |
| echo "## Upstream sync" | |
| echo "- changed: \`${{ steps.merge.outputs.changed }}\`" | |
| echo "- upstream: \`${{ steps.merge.outputs.upstream_sha }}\`" | |
| echo "- nightly tag: \`${{ steps.merge.outputs.nightly_tag }}\`" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Open issue on merge conflict | |
| if: failure() && steps.merge.outputs.merge_ok == 'false' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TITLE="Nightly upstream sync failed — conflict on $(date -u +%Y-%m-%d)" | |
| BODY=$(cat <<'MD' | |
| Automatic merge of `pingdotgg/t3code` into `personal` failed due to conflicts. | |
| Resolve locally: | |
| ```bash | |
| cd ~/dev/t3code | |
| git fetch origin && git fetch upstream | |
| git checkout personal | |
| git merge upstream/main | |
| # resolve, then: | |
| git push origin personal | |
| git push origin personal:main | |
| ``` | |
| Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| MD | |
| ) | |
| EXISTING=$(gh issue list --state open --search "Nightly upstream sync failed" --json number --jq '.[0].number // empty') | |
| if [ -n "$EXISTING" ]; then | |
| gh issue comment "$EXISTING" --body "Sync failed again. Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| else | |
| gh issue create --title "$TITLE" --body "$BODY" | |
| fi | |
| # Signal that apps should be rebuilt. Machines poll this release marker. | |
| announce: | |
| needs: sync | |
| if: needs.sync.outputs.changed == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Publish personal-sync marker tag | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| SHA="${{ needs.sync.outputs.upstream_sha }}" | |
| SHORT=$(echo "$SHA" | cut -c1-7) | |
| DATE=$(date -u +%Y%m%d.%H%M) | |
| TAG="personal-sync.${DATE}.${SHORT}" | |
| # lightweight tag on personal tip (fetched via API after push) | |
| gh api repos/${{ github.repository }}/git/refs \ | |
| -f ref="refs/tags/${TAG}" \ | |
| -f sha="$(gh api repos/${{ github.repository }}/git/ref/heads/personal --jq .object.sha)" | |
| echo "Tagged ${TAG}" | |
| echo "tag=${TAG}" >> "$GITHUB_OUTPUT" |