-
Notifications
You must be signed in to change notification settings - Fork 1
193 lines (172 loc) · 7.77 KB
/
upstream-sync.yaml
File metadata and controls
193 lines (172 loc) · 7.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# Template for any upstream repository - update the upstream URL as needed
#
# Key Features:
# - Creates/updates ONE evergreen PR (bot/upstream-sync -> main)
# - Syncs daily or on demand; force-resets sync branch to upstream/main
# - Respects branch protections (no direct pushes to main)
# - Protects hotfix/* branches (we never touch them)
# - Includes error handling and status reporting
name: Mirror Sync with Upstream
on:
schedule:
- cron: '0 6 * * *' # Daily at 6 AM UTC
workflow_dispatch: # Allow manual trigger
permissions:
contents: write
pull-requests: write
concurrency:
group: upstream-sync
cancel-in-progress: true
jobs:
sync:
runs-on: ubuntu-latest
env:
SYNC_BRANCH: bot/upstream-sync
BASE_BRANCH: main
UPSTREAM_REPO: kubernetes-sigs/cluster-api-provider-openstack
steps:
- name: Checkout mirror repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- 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 & fetch
run: |
git remote add upstream https://github.com/${UPSTREAM_REPO}.git || true
if ! git fetch upstream ${BASE_BRANCH}; then
echo "::error::Failed to fetch from upstream repository"
exit 1
fi
echo "Fetched upstream/${BASE_BRANCH}"
- name: Determine if main is already in sync
id: diff
run: |
set -euo pipefail
git fetch origin ${BASE_BRANCH} --quiet
echo "Checking differences excluding .github/workflows/**"
if git diff --quiet upstream/${BASE_BRANCH} origin/${BASE_BRANCH} -- . ':(exclude).github/workflows/**'; then
echo "in_sync=true" >> $GITHUB_OUTPUT
else
echo "in_sync=false" >> $GITHUB_OUTPUT
fi
- name: Update sync branch to upstream/main
if: steps.diff.outputs.in_sync == 'false'
run: |
set -euo pipefail
git fetch origin --quiet || true
# Create or switch to the evergreen sync branch
if git show-ref --verify --quiet refs/remotes/origin/${SYNC_BRANCH}; then
# Sync branch exists on remote - check if we have it locally
if git show-ref --verify --quiet refs/heads/${SYNC_BRANCH}; then
git switch ${SYNC_BRANCH}
else
git switch -c ${SYNC_BRANCH} --track origin/${SYNC_BRANCH}
fi
else
# Sync branch doesn't exist - create from main
git switch -c ${SYNC_BRANCH} origin/${BASE_BRANCH}
fi
# Rebase local state of sync branch onto our main to start clean
git reset --hard origin/${BASE_BRANCH}
# Compute and apply upstream changes excluding workflow files
if git diff --quiet origin/${BASE_BRANCH} upstream/${BASE_BRANCH} -- . ':(exclude).github/workflows/**'; then
echo "No non-workflow changes from upstream; nothing to apply."
else
git diff --binary origin/${BASE_BRANCH} upstream/${BASE_BRANCH} -- . ':(exclude).github/workflows/**' > /tmp/upstream-no-workflows.patch
git apply -3 /tmp/upstream-no-workflows.patch || {
echo "::error::Failed to apply upstream patch excluding workflows";
exit 1;
}
git add -A
git commit -m "Sync with upstream/${BASE_BRANCH} (excluding .github/workflows)" || true
fi
# Push the updated sync branch
git push -f origin ${SYNC_BRANCH}
- name: Check if sync branch differs from main
if: steps.diff.outputs.in_sync == 'false'
id: branch_diff
run: |
# Fetch latest state to ensure we have current refs
git fetch origin ${BASE_BRANCH} ${SYNC_BRANCH} --quiet
# Decide if PR is needed based on content differences excluding workflow files
if git diff --quiet origin/${BASE_BRANCH} origin/${SYNC_BRANCH} -- . ':(exclude).github/workflows/**'; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "All upstream changes already present in main (excluding workflows); no PR needed"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "PR is needed; content changes detected outside .github/workflows/**"
echo "Changed files (excluding workflows):"
git diff --name-only origin/${BASE_BRANCH} origin/${SYNC_BRANCH} -- . ':(exclude).github/workflows/**' | head -50 || true
fi
- name: Create or update PR to main
if: steps.diff.outputs.in_sync == 'false' && steps.branch_diff.outputs.has_changes == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const {owner, repo} = context.repo;
const headRef = `${owner}:${process.env.SYNC_BRANCH}`;
const base = process.env.BASE_BRANCH;
// Find existing open PR for this branch
const prs = await github.rest.pulls.list({
owner, repo, state: 'open', head: headRef, base
});
let pr;
if (prs.data.length) {
pr = prs.data[0];
core.info(`Found existing PR #${pr.number}`);
} else {
pr = (await github.rest.pulls.create({
owner, repo,
title: 'Sync with upstream/main',
head: process.env.SYNC_BRANCH,
base,
body: 'Automated sync from upstream. This PR is continuously updated.'
})).data;
core.info(`Created PR #${pr.number}`);
}
// Try to enable auto-merge (requires repo setting + permissions)
try {
await github.graphql(
`mutation($id:ID!){
enablePullRequestAutoMerge(input:{pullRequestId:$id, mergeMethod:MERGE}) { clientMutationId }
}`,
{ id: pr.node_id }
);
core.info('Auto-merge enabled.');
} catch (e) {
core.warning('Auto-merge not enabled (this is OK): ' + e.message);
}
- name: Clean up stale upstream tracking refs
run: |
git remote prune upstream
echo "Pruned stale upstream refs"
- name: Report sync status
run: |
echo ""
echo "=== Mirror Sync Report ==="
if [ "${{ steps.diff.outputs.in_sync }}" = "true" ]; then
echo "Already in sync with upstream/main (excluding .github/workflows). No PR updates needed."
elif [ "${{ steps.branch_diff.outputs.has_changes || 'false' }}" = "false" ]; then
echo "Branches were out of sync but have identical content outside workflows. Sync branch updated."
else
echo "Opened/updated PR from ${SYNC_BRANCH} -> ${BASE_BRANCH}."
echo "Content changes detected between branches (excluding .github/workflows)"
fi
echo ""
echo "Latest commits on upstream/main:"
git log upstream/${BASE_BRANCH} --oneline -5 || true
echo ""
echo "Hotfix branches (preserved, untouched by this workflow):"
git ls-remote --heads origin 'hotfix/*' | awk '{print $2}' || echo " No hotfix/* branches found"
echo "=========================="
- name: Notify on failure
if: failure()
run: |
echo "::error::🚨 Mirror sync failed! Manual intervention required."
echo "::error::Repository: ${{ github.repository }}"
echo "::error::Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"