Created
May 7, 2026 16:59
-
-
Save ikouchiha47/385392b709245c9f3f01bc5ae0be7894 to your computer and use it in GitHub Desktop.
Migrate repos and groups from github to gitlab
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -uo pipefail | |
| VISIBILITY="${VISIBILITY:-private}" | |
| WORKDIR="$HOME/dev/migrate/_mirror" | |
| LOG="$HOME/dev/migrate/migrate.log" | |
| SSH_HOST="${SSH_HOST_KEY:-gitlab.com}" | |
| DRY_RUN=0 | |
| TEST=0 | |
| for arg in "$@"; do | |
| case "$arg" in | |
| --dry-run) DRY_RUN=1 ;; | |
| --test) TEST=1 ;; | |
| -h|--help) | |
| cat <<EOF | |
| Usage: $0 [--dry-run] [--test] | |
| --dry-run List repos that would migrate, no changes made. | |
| --test Migrate only the single smallest non-fork/non-archived repo | |
| from each source (ikouchiha47 user, go-batteries group). | |
| (no flag) Full migration. | |
| Env: | |
| VISIBILITY=private|public (default: private) | |
| EOF | |
| exit 0 ;; | |
| esac | |
| done | |
| mkdir -p "$WORKDIR" | |
| [ "$DRY_RUN" -eq 1 ] || : > "$LOG" | |
| list_repos() { | |
| local owner="$1" | |
| gh repo list "$owner" --limit 500 --json name,isFork,isArchived,diskUsage \ | |
| -q '.[] | select(.isFork|not) | select(.isArchived|not) | select(.diskUsage > 0) | select(.name | startswith(".") | not) | "\(.diskUsage)\t\(.name)"' \ | |
| | sort -n | |
| } | |
| migrate_repo() { | |
| local gh_owner="$1" gl_namespace="$2" name="$3" | |
| local gl_path="$gl_namespace/$name" | |
| if [ "$DRY_RUN" -eq 1 ]; then | |
| echo "DRY $gh_owner/$name -> $gl_path" | |
| return 0 | |
| fi | |
| local enc | |
| enc=$(printf '%s' "$gl_path" | jq -sRr @uri) | |
| if glab api "projects/$enc" >/dev/null 2>&1; then | |
| echo "SKIP exists: $gl_path" | tee -a "$LOG" | |
| return 0 | |
| fi | |
| if [ "$gl_namespace" = "ikouchiha47" ]; then | |
| local priv=true | |
| [ "$VISIBILITY" = public ] && priv=false | |
| glab repo create "$name" --private="$priv" >/dev/null 2>>"$LOG" \ | |
| || { echo "FAIL create $gl_path" | tee -a "$LOG"; return 1; } | |
| else | |
| local nsid | |
| nsid=$(glab api "groups/$gl_namespace" | jq .id) | |
| glab api -X POST projects \ | |
| -f name="$name" -f path="$name" \ | |
| -f namespace_id="$nsid" \ | |
| -f visibility="$VISIBILITY" >/dev/null 2>>"$LOG" \ | |
| || { echo "FAIL create $gl_path" | tee -a "$LOG"; return 1; } | |
| fi | |
| cd "$WORKDIR" | |
| rm -rf "$name.git" | |
| git clone --mirror "git@github.com:$gh_owner/$name.git" "$name.git" >>"$LOG" 2>&1 \ | |
| || { echo "FAIL clone $gh_owner/$name" | tee -a "$LOG"; return 1; } | |
| cd "$name.git" | |
| if [ -z "$(git for-each-ref)" ]; then | |
| cd "$WORKDIR"; rm -rf "$name.git" | |
| echo "OK $gh_owner/$name -> $gl_path (empty, project created)" | tee -a "$LOG" | |
| return 0 | |
| fi | |
| git remote add gitlab "git@$SSH_HOST:$gl_path.git" | |
| git push --mirror gitlab >>"$LOG" 2>&1 \ | |
| || { echo "FAIL push $gl_path" | tee -a "$LOG"; cd "$WORKDIR"; rm -rf "$name.git"; return 1; } | |
| cd "$WORKDIR" | |
| rm -rf "$name.git" | |
| echo "OK $gh_owner/$name -> $gl_path" | tee -a "$LOG" | |
| } | |
| run_source() { | |
| local gh_owner="$1" gl_namespace="$2" | |
| if [ "$TEST" -eq 1 ]; then | |
| list_repos "$gh_owner" | head -1 | cut -f2 \ | |
| | while read -r r; do migrate_repo "$gh_owner" "$gl_namespace" "$r"; done | |
| else | |
| list_repos "$gh_owner" | cut -f2 \ | |
| | while read -r r; do migrate_repo "$gh_owner" "$gl_namespace" "$r"; done | |
| fi | |
| } | |
| echo "Done. Log: $LOG" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment