fuzzyworld-tools / files / lib / repoint.sh
repoint.sh
| 1 | #!/bin/bash |
| 2 | # fw repoint: move working copies from GitLab to DreamHost. Changes only remotes and tracking. |
| 3 | # |
| 4 | # fw repoint [DIR] [--https] [--dry-run] |
| 5 | # |
| 6 | # For every git repo directly inside DIR (default: current directory) whose `origin` is |
| 7 | # gitlab.com/PavelSafronov/<repo> (or the old gitlab.com/fuzzy-rp/<repo>): |
| 8 | # origin -> renamed `gitlab`; new origin -> dh:git/<repo>.git (needs the `dh` ssh alias) |
| 9 | # with --https: new origin -> https://code.fuzzyworld.net/<repo>.git (read-only; public repos only) |
| 10 | # then fetches, and points each branch that tracked a GitLab branch at the same branch on origin. |
| 11 | # Uncommitted work, HEAD and stashes are fingerprinted before and after; a difference is reported. |
| 12 | set -u |
| 13 | DIR=.; HTTPS=false; DRY=false |
| 14 | for a in "$@"; do |
| 15 | case "$a" in |
| 16 | --https) HTTPS=true ;; |
| 17 | --dry-run) DRY=true ;; |
| 18 | -*) echo "fw repoint: unknown option $a" >&2; exit 2 ;; |
| 19 | *) DIR=$a ;; |
| 20 | esac |
| 21 | done |
| 22 | |
| 23 | fp() { { git -C "$1" status --porcelain=v1 -uall; git -C "$1" rev-parse HEAD; git -C "$1" symbolic-ref -q HEAD; git -C "$1" stash list; } 2>/dev/null | shasum | cut -c1-12; } |
| 24 | |
| 25 | ok=0; skipped=0; failed=0 |
| 26 | for d in "$DIR"/*/; do |
| 27 | d=${d%/} |
| 28 | [ -d "$d/.git" ] || continue |
| 29 | url=$(git -C "$d" remote get-url origin 2>/dev/null) || continue |
| 30 | repo=$(printf '%s' "$url" | sed -nE 's#^.*gitlab\.com[:/](PavelSafronov|fuzzy-rp)/([^/]+)/?$#\2#p' | sed 's/\.git$//') |
| 31 | [ -n "$repo" ] || continue |
| 32 | name=$(basename "$d") |
| 33 | if $HTTPS; then |
| 34 | new="https://code.fuzzyworld.net/$repo.git" |
| 35 | if ! git ls-remote --exit-code "$new" HEAD >/dev/null 2>&1; then |
| 36 | echo "skip $name: $repo is not public on code.fuzzyworld.net"; skipped=$((skipped+1)); continue |
| 37 | fi |
| 38 | else |
| 39 | new="dh:git/$repo.git" |
| 40 | fi |
| 41 | if git -C "$d" remote get-url gitlab >/dev/null 2>&1; then |
| 42 | echo "skip $name: already has a remote named gitlab"; skipped=$((skipped+1)); continue |
| 43 | fi |
| 44 | if $DRY; then echo "would $name: origin $url -> $new"; ok=$((ok+1)); continue; fi |
| 45 | |
| 46 | before=$(fp "$d") |
| 47 | git -C "$d" remote rename origin gitlab |
| 48 | git -C "$d" remote add origin "$new" |
| 49 | if ! git -C "$d" fetch --quiet origin; then |
| 50 | echo "FAIL $name: fetch from $new failed (remotes changed; run 'git -C $d fetch origin' to retry)"; failed=$((failed+1)); continue |
| 51 | fi |
| 52 | moved="" |
| 53 | for b in $(git -C "$d" for-each-ref --format='%(refname:short)' refs/heads); do |
| 54 | [ "$(git -C "$d" config --get "branch.$b.remote")" = gitlab ] || continue |
| 55 | m=$(git -C "$d" config --get "branch.$b.merge" | sed 's#^refs/heads/##') |
| 56 | git -C "$d" rev-parse -q --verify "refs/remotes/origin/$m" >/dev/null && |
| 57 | git -C "$d" branch --quiet --set-upstream-to="origin/$m" "$b" && moved="$moved $b" |
| 58 | done |
| 59 | after=$(fp "$d") |
| 60 | if [ "$before" = "$after" ]; then |
| 61 | echo "ok $name -> $new (tracking:${moved:- none})"; ok=$((ok+1)) |
| 62 | else |
| 63 | echo "WARN $name: working copy fingerprint changed; check it"; failed=$((failed+1)) |
| 64 | fi |
| 65 | done |
| 66 | echo "repointed $ok, skipped $skipped, problems $failed$($DRY && echo ' (dry run: nothing changed)')" |