-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmirror-to-github
More file actions
136 lines (124 loc) · 6.82 KB
/
Copy pathmirror-to-github
File metadata and controls
136 lines (124 loc) · 6.82 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
#!/usr/bin/env bash
# --------------------------------------------------------------------------- #
# Mirror the SMS++ project from GitLab to the SMSpp-Project org on GitHub. #
# #
# Discovers every project in the GitLab `smspp` group (not just the ones #
# in the umbrella) and, for each, idempotently: #
# 1. creates the matching public GitHub repo if it does not exist; #
# 2. keeps the GitHub description in sync with the GitLab one; #
# 3. configures a native GitLab push mirror to it (Settings -> #
# Repository -> Mirroring repositories) through the GitLab API. #
# Re-running it is safe: existing repos and mirrors are detected and #
# skipped, so a new GitLab project only requires running this again. #
# #
# Skipped: archived projects and `pysmspp` (itself a mirror of the #
# GitHub-hosted pySMSpp). #
# #
# Prerequisites: #
# - gh (GitHub CLI) authenticated: gh auth login -h github.com #
# - GITLAB_TOKEN : a GitLab Personal Access Token with the `api` scope #
# and Maintainer/Owner role on the projects, generated at: #
# https://gitlab.com/-/user_settings/personal_access_tokens #
# - GITHUB_MIRROR_PAT : a GitHub Personal Access Token (classic) with #
# the `repo` and `workflow` scopes; the latter lets the mirror #
# push the .github/workflows files, generated at: #
# https://github.com/settings/tokens #
# #
# Usage: #
# GITLAB_TOKEN=glpat-xxx GITHUB_MIRROR_PAT=ghp_xxx bash mirror-to-github #
# DRY_RUN=1 ... bash mirror-to-github # preview #
# ROTATE=1 ... bash mirror-to-github # rotate existing mirrors' token #
# #
# Donato Meoli #
# Dipartimento di Informatica #
# Universita' di Pisa #
# --------------------------------------------------------------------------- #
set -uo pipefail
: "${GITLAB_TOKEN:?set GITLAB_TOKEN (GitLab PAT, scope api)}"
: "${GITHUB_MIRROR_PAT:?set GITHUB_MIRROR_PAT (GitHub PAT, scopes repo + workflow)}"
gh auth token >/dev/null 2>&1 || { echo "run 'gh auth login -h github.com' first" >&2; exit 1; }
API="https://gitlab.com/api/v4"
GROUP="smspp"
GH_ORG="SMSpp-Project"
# Emit, per non-archived group project, a TSV line:
# <gitlab/path>\t<github-repo-name>\t<gitlab-description>
# GitHub repo names use the GitLab project name (CamelCase) for modules, with a
# few hand-mapped special cases; `pysmspp` is skipped (it is itself a mirror).
list_projects() {
curl -sf -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$API/groups/$GROUP/projects?per_page=100&include_subgroups=true&archived=false&visibility=public" \
| jq -r '
{ smspp: "SMSpp", "smspp-project": "smspp-project",
tests: "tests", tools: "tools", "vcpkg-registry": "vcpkg-registry" } as $special
| .[]
| select(.path != "pysmspp")
| [ .path_with_namespace,
($special[.path] // .name),
((.description // "") | gsub("[\t|\n]"; " ") | gsub("(^ +| +$)"; "")) ]
| @tsv'
}
while IFS=$'\t' read -r gl gh desc; do
[ -n "$gl" ] || continue
enc="${gl/\//%2F}"
echo "== $gl -> $GH_ORG/$gh"
# desired GitHub description = GitLab description + a mirror note
if [ -n "$desc" ]; then
want="$desc | mirror of https://gitlab.com/$gl"
else
want="Mirror of https://gitlab.com/$gl"
fi
# 1) GitHub repo: create if missing, keep its description in sync with GitLab
if gh repo view "$GH_ORG/$gh" >/dev/null 2>&1; then
have=$(gh repo view "$GH_ORG/$gh" --json description --jq '.description // ""')
if [ "$have" = "$want" ]; then
echo " repo: exists"
elif [ "${DRY_RUN:-0}" = "1" ]; then
echo " repo: would update description"
else
gh repo edit "$GH_ORG/$gh" --description "$want" >/dev/null \
&& echo " repo: description synced" || echo " repo: description sync FAILED"
fi
elif [ "${DRY_RUN:-0}" = "1" ]; then
echo " repo: would create"
else
gh repo create "$GH_ORG/$gh" --public --description "$want" >/dev/null \
&& echo " repo: created" || { echo " repo: FAILED"; continue; }
fi
# 2) GitLab push mirror to that GitHub repo. With ROTATE=1 an existing
# mirror is deleted and recreated, so its embedded GitHub token is
# refreshed (needed e.g. to grant the `workflow` scope).
mid=$(curl -sf -H "PRIVATE-TOKEN: $GITLAB_TOKEN" "$API/projects/$enc/remote_mirrors" \
| jq -r --arg u "github.com/${GH_ORG}/${gh}.git" \
'first(.[] | select(.url | contains($u)) | .id) // empty')
if [ -n "$mid" ] && [ "${ROTATE:-0}" != "1" ]; then
echo " mirror: exists"
elif [ "${DRY_RUN:-0}" = "1" ]; then
[ -n "$mid" ] && echo " mirror: would rotate" || echo " mirror: would create"
else
if [ -n "$mid" ]; then
curl -sf -X DELETE -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$API/projects/$enc/remote_mirrors/$mid" >/dev/null \
|| echo " mirror: delete FAILED"
fi
curl -sf -X POST -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data-urlencode "url=https://x-access-token:${GITHUB_MIRROR_PAT}@github.com/${GH_ORG}/${gh}.git" \
--data "enabled=true" \
--data "only_protected_branches=false" \
--data "keep_divergent_refs=false" \
"$API/projects/$enc/remote_mirrors" >/dev/null \
&& echo " mirror: $([ -n "$mid" ] && echo rotated || echo created)" \
|| echo " mirror: FAILED (Maintainer role required?)"
fi
# 3) trigger an immediate sync: a (re)created push mirror stays idle until
# the next push, so without this it would not propagate existing commits
if [ "${DRY_RUN:-0}" != "1" ]; then
sid=$(curl -sf -H "PRIVATE-TOKEN: $GITLAB_TOKEN" "$API/projects/$enc/remote_mirrors" \
| jq -r --arg u "github.com/${GH_ORG}/${gh}.git" \
'first(.[] | select(.url | contains($u)) | .id) // empty')
[ -n "$sid" ] && curl -sf -o /dev/null -X POST -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$API/projects/$enc/remote_mirrors/$sid/sync" \
&& echo " mirror: sync triggered"
fi
done < <(list_projects)
echo
echo "Done."