Repository navigation
113 lines (105 loc) · 4.78 KB
/
Copy pathsync-upstream.yml
File metadata and controls
113 lines (105 loc) · 4.78 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
# Sync gix-main branch with upstream/main.
#
# gix-main is a pristine mirror of GitoxideLabs/gitoxide's main branch.
# It MUST stay fast-forward-only. If a fast-forward fails, something
# has gone wrong and we want to know about it immediately rather than
# papering over it with a merge commit.
#
# Branch protection for gix-main should forbid direct pushes from
# anyone but this workflow.
name: sync-gix-main
on:
schedule:
# Every 6 hours. Upstream moves at a few commits per day, this is enough
# cadence to have gix-main be fresh without burning Actions minutes.
- cron: "17 */6 * * *"
workflow_dispatch: {}
permissions:
contents: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout gix-main
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: gix-main
fetch-depth: 0
# Don't leave the token in .git/config (CI hardening: the
# check-no-persist-credentials gate forbids persisted creds).
# Pushes below re-supply the token explicitly.
persist-credentials: false
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure git identity
run: |
git config user.name "gix-main sync bot"
git config user.email "ci@ethosengine.invalid"
- name: Add upstream remote
run: |
git remote add upstream https://github.com/GitoxideLabs/gitoxide.git
git fetch upstream main --tags
- name: Fast-forward gix-main to upstream/main
id: ff
run: |
set -eu
before="$(git rev-parse HEAD)"
target="$(git rev-parse upstream/main)"
if [ "$before" = "$target" ]; then
echo "Already up-to-date at $before."
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Fast-forward only. If this fails, gix-main has diverged from
# upstream/main — that should never happen and is worth a loud failure.
git merge --ff-only upstream/main
after="$(git rev-parse HEAD)"
echo "Advanced gix-main: $before -> $after"
echo "changed=true" >> "$GITHUB_OUTPUT"
# Pushes use GIX_SYNC_TOKEN, not GITHUB_TOKEN: upstream edits its own
# .github/workflows/*, and GitHub refuses any push that creates or
# updates a workflow file unless the token carries the `workflow`
# scope — which GITHUB_TOKEN can never be granted (no `workflows:`
# key exists under `permissions:`). Needs a fine-grained PAT (or App
# token) on this repo with Contents: write + Workflows: write.
- name: Push updated gix-main
if: steps.ff.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GIX_SYNC_TOKEN }}
run: |
if [ -z "${GH_TOKEN}" ]; then
echo "::error::GIX_SYNC_TOKEN secret is not set. Upstream commits touch .github/workflows/*, which GITHUB_TOKEN cannot push. Add a token with Contents: write + Workflows: write."
exit 1
fi
git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:gix-main
# Tags are nice-to-have but not critical for our sync purposes, so this
# step is best-effort. Push only the tags the fork lacks, in batches:
# GitHub push protection secret-scans every pushed ref, and one push of
# the full upstream tag set (~3.5k refs) times out ("Timed out
# validating Secrets detected" -> GH013 repository rule violations).
# Runs even when gix-main did not move, so a tag backlog still drains.
- name: Push mirrored tags (best effort, batched)
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GIX_SYNC_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -u
remote="https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git"
git ls-remote --tags --refs "$remote" | sed 's|.*refs/tags/||' | sort > remote-tags.txt
git tag -l | sort > local-tags.txt
comm -23 local-tags.txt remote-tags.txt > missing-tags.txt
total=$(wc -l < missing-tags.txt)
echo "Tags missing on the fork: $total"
failed=0
while mapfile -t -n 100 batch && [ "${#batch[@]}" -gt 0 ]; do
refs=()
for t in "${batch[@]}"; do refs+=("refs/tags/$t:refs/tags/$t"); done
if ! git push --no-verify "$remote" "${refs[@]}" > push-batch.log 2>&1; then
failed=$((failed + ${#batch[@]}))
grep -E "remote: +- |error:" push-batch.log | sort -u | head -5
fi
done < missing-tags.txt
if [ "$failed" -gt 0 ]; then
echo "::warning::$failed of $total missing tags did not push; they will be retried on the next run."
fi
exit 0