Skip to content

Sync upstream nightly #6

Sync upstream nightly

Sync upstream nightly #6

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"