Skip to content

Commit 6eba0de

Browse files
committed
Poll for upstream releases daily and sync them into master
Checks metabase/metabase for new releases each day at 06:00 UTC. Clean merges are pushed to master automatically. Conflicts result in a branch and PR opened for manual resolution. Requires a SYNC_TOKEN secret: a fine-grained PAT with Contents and Workflows read/write permissions. GITHUB_TOKEN cannot push branches containing workflow file changes.
1 parent 2d95f0b commit 6eba0de

2 files changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
name: Sync Upstream Metabase Release
2+
3+
# Monitors metabase/metabase for new releases daily and syncs them into master.
4+
# - Clean merge: pushed to master automatically.
5+
# - Merge conflicts: branch pushed + PR opened for manual resolution.
6+
#
7+
# Requires a secret named SYNC_TOKEN: a classic PAT with repo + workflow scopes.
8+
# GitHub Apps tokens (GITHUB_TOKEN) cannot push branches containing workflow files.
9+
10+
on:
11+
schedule:
12+
- cron: "0 6 * * *" # daily at 06:00 UTC
13+
workflow_dispatch: # allow manual trigger
14+
15+
jobs:
16+
sync:
17+
name: Check and sync upstream release
18+
runs-on: ubuntu-latest
19+
permissions:
20+
contents: write
21+
pull-requests: write
22+
issues: write
23+
24+
steps:
25+
- name: Checkout fork
26+
uses: actions/checkout@v4
27+
with:
28+
ref: master
29+
fetch-depth: 0
30+
token: ${{ secrets.SYNC_TOKEN }}
31+
32+
- name: Configure git identity
33+
run: |
34+
git config user.name "github-actions[bot]"
35+
git config user.email "github-actions[bot]@users.noreply.github.com"
36+
37+
- name: Fetch latest upstream release tag
38+
id: upstream
39+
env:
40+
GH_TOKEN: ${{ secrets.SYNC_TOKEN }}
41+
run: |
42+
TAG=$(gh api repos/metabase/metabase/releases/latest --jq '.tag_name')
43+
if [[ -z "$TAG" || "$TAG" == "null" ]]; then
44+
echo "::error::Could not determine latest upstream release tag"
45+
exit 1
46+
fi
47+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
48+
echo "Latest upstream release: $TAG"
49+
50+
- name: Add upstream remote and fetch tag
51+
run: |
52+
git remote add upstream https://github.com/metabase/metabase.git
53+
git fetch upstream tag "${{ steps.upstream.outputs.tag }}" --no-tags
54+
55+
- name: Check sync status
56+
id: status
57+
run: |
58+
TAG="${{ steps.upstream.outputs.tag }}"
59+
BRANCH="sync/upstream-$TAG"
60+
61+
if git merge-base --is-ancestor "$TAG" HEAD 2>/dev/null; then
62+
echo "result=already-synced" >> "$GITHUB_OUTPUT"
63+
echo "$TAG is already in master history — nothing to do."
64+
elif git ls-remote --exit-code origin "refs/heads/$BRANCH" > /dev/null 2>&1; then
65+
echo "result=pr-open" >> "$GITHUB_OUTPUT"
66+
echo "Sync branch $BRANCH already exists — PR is likely open."
67+
else
68+
echo "result=needs-sync" >> "$GITHUB_OUTPUT"
69+
echo "$TAG not yet in master — proceeding with sync."
70+
fi
71+
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
72+
73+
- name: Attempt merge
74+
if: steps.status.outputs.result == 'needs-sync'
75+
id: merge
76+
run: |
77+
TAG="${{ steps.upstream.outputs.tag }}"
78+
BRANCH="${{ steps.status.outputs.branch }}"
79+
80+
git checkout -b "$BRANCH"
81+
82+
if git merge "$TAG" -m "chore: sync upstream $TAG" --no-edit; then
83+
echo "outcome=clean" >> "$GITHUB_OUTPUT"
84+
else
85+
# Capture conflicted file list before staging
86+
{
87+
echo "conflicts<<EOF"
88+
git diff --name-only --diff-filter=U
89+
echo "EOF"
90+
} >> "$GITHUB_OUTPUT"
91+
92+
# Commit the conflicted state so it is visible on the PR branch
93+
git add -A
94+
git commit -m "chore: sync upstream $TAG (conflicts — resolve before merging)"
95+
echo "outcome=conflicts" >> "$GITHUB_OUTPUT"
96+
fi
97+
98+
- name: Push to master (clean merge)
99+
if: steps.status.outputs.result == 'needs-sync' && steps.merge.outputs.outcome == 'clean'
100+
run: |
101+
TAG="${{ steps.upstream.outputs.tag }}"
102+
BRANCH="${{ steps.status.outputs.branch }}"
103+
104+
git checkout master
105+
git merge "$BRANCH" --ff-only
106+
git push origin master
107+
echo "::notice::Synced $TAG to master automatically."
108+
109+
- name: Open conflict-resolution PR
110+
if: steps.status.outputs.result == 'needs-sync' && steps.merge.outputs.outcome == 'conflicts'
111+
env:
112+
GH_TOKEN: ${{ secrets.SYNC_TOKEN }}
113+
CONFLICTS: ${{ steps.merge.outputs.conflicts }}
114+
run: |
115+
TAG="${{ steps.upstream.outputs.tag }}"
116+
BRANCH="${{ steps.status.outputs.branch }}"
117+
118+
git push origin "$BRANCH"
119+
120+
BODY_FILE=$(mktemp)
121+
echo "## Upstream Sync: $TAG" >> "$BODY_FILE"
122+
echo "" >> "$BODY_FILE"
123+
echo "Merge conflicts were detected when syncing the latest upstream release. Manual resolution is required before this PR can be merged into \`master\`." >> "$BODY_FILE"
124+
echo "" >> "$BODY_FILE"
125+
echo "### Files with conflicts" >> "$BODY_FILE"
126+
echo '```' >> "$BODY_FILE"
127+
echo "$CONFLICTS" >> "$BODY_FILE"
128+
echo '```' >> "$BODY_FILE"
129+
echo "" >> "$BODY_FILE"
130+
echo "### How to resolve" >> "$BODY_FILE"
131+
echo '```bash' >> "$BODY_FILE"
132+
echo "git fetch origin" >> "$BODY_FILE"
133+
echo "git checkout $BRANCH" >> "$BODY_FILE"
134+
echo "# Resolve the <<<<<<< markers in each conflicted file, then:" >> "$BODY_FILE"
135+
echo "git add -A" >> "$BODY_FILE"
136+
echo "git commit --amend --no-edit" >> "$BODY_FILE"
137+
echo "git push --force-with-lease origin $BRANCH" >> "$BODY_FILE"
138+
echo '```' >> "$BODY_FILE"
139+
echo "" >> "$BODY_FILE"
140+
echo "> **Tip:** The most likely files to conflict are the Teal branding files:" >> "$BODY_FILE"
141+
echo "> \`frontend/src/metabase/ui/colors/constants/themes/light.ts\`" >> "$BODY_FILE"
142+
echo "> \`frontend/src/metabase/ui/colors/constants/themes/dark.ts\`" >> "$BODY_FILE"
143+
echo "> \`src/metabase/appearance/settings.clj\`" >> "$BODY_FILE"
144+
145+
gh pr create \
146+
--title "chore: sync upstream $TAG (merge conflicts)" \
147+
--body-file "$BODY_FILE" \
148+
--base master \
149+
--head "$BRANCH"
150+
151+
- name: Summary
152+
if: always()
153+
run: |
154+
TAG="${{ steps.upstream.outputs.tag }}"
155+
RESULT="${{ steps.status.outputs.result }}"
156+
OUTCOME="${{ steps.merge.outputs.outcome }}"
157+
158+
if [[ "$RESULT" == "already-synced" ]]; then
159+
echo "### Already up to date" >> "$GITHUB_STEP_SUMMARY"
160+
echo "$TAG is already in master — no action taken." >> "$GITHUB_STEP_SUMMARY"
161+
elif [[ "$RESULT" == "pr-open" ]]; then
162+
echo "### PR already open" >> "$GITHUB_STEP_SUMMARY"
163+
echo "A sync branch for $TAG already exists. Check open PRs." >> "$GITHUB_STEP_SUMMARY"
164+
elif [[ "$OUTCOME" == "clean" ]]; then
165+
echo "### Synced automatically" >> "$GITHUB_STEP_SUMMARY"
166+
echo "$TAG was merged cleanly into master." >> "$GITHUB_STEP_SUMMARY"
167+
elif [[ "$OUTCOME" == "conflicts" ]]; then
168+
echo "### Conflicts — PR opened" >> "$GITHUB_STEP_SUMMARY"
169+
echo "$TAG has merge conflicts. A PR has been opened for manual resolution." >> "$GITHUB_STEP_SUMMARY"
170+
fi

README-TEAL.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,19 @@ Metabase upstream ships ~100 GitHub Actions workflows. On a fork, all of them ar
5151
**Initial setup:** after pushing this repository for the first time, run the workflow manually via:
5252
> Actions → Enforce Workflow Allowlist → Run workflow
5353
54+
### Upstream Sync — `teal-sync-upstream-release.yml`
55+
56+
Polls [metabase/metabase](https://github.com/metabase/metabase) for new releases daily and merges them into `master`.
57+
58+
| Outcome | Result |
59+
|---------|--------|
60+
| Clean merge | Pushed to `master` automatically |
61+
| Merge conflicts | Branch `sync/upstream-vX.Y.Z` pushed, PR opened for manual resolution |
62+
63+
**Trigger:** Daily at 06:00 UTC, and available as a manual trigger.
64+
65+
**Requires:** A repository secret named `SYNC_TOKEN` — a fine-grained PAT scoped to this repo with Contents and Workflows read/write permissions.
66+
67+
**Conflict resolution:** The three branding files above are the most likely to conflict. In each case, keep the Teal values — the upstream values for those specific keys will always be wrong for this fork.
68+
5469

0 commit comments

Comments
 (0)