fuzzyworld-tools / commit
Add fw repoint: move working copies' origin from GitLab to DreamHost; v1.0.5
README.md | 1 + bin/fw | 3 +++ lib/repoint.sh | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b8f3d50..4558830 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ On the laptop, `bin/fw` runs `deploy` locally and forwards repo commands to Drea | Command | What it does | |---|---| | `fw deploy [--ref B] [--here] [--dry-run]` | build and publish this repo's app (above) | +| `fw repoint [DIR] [--https] [--dry-run]` | for each repo in DIR whose `origin` is on gitlab.com: rename it `gitlab`, add `origin` → `dh:git/<repo>.git` (or read-only `https://code.fuzzyworld.net/<repo>.git` with `--https`, public repos only), fetch, and retarget branch tracking. Changes only remotes; checks that uncommitted work, HEAD and stashes are unchanged | | `fw list` | every repo, its visibility and last commit | | `fw new <name> [--public] [--description TEXT]` | create `~/git/<name>.git` on DreamHost; **private unless `--public`** | | `fw public <name>` | publish the source on code.fuzzyworld.net right away | diff --git a/bin/fw b/bin/fw index 6ec609d..3815490 100755 --- a/bin/fw +++ b/bin/fw @@ -1,6 +1,7 @@ #!/bin/bash # fw (laptop side). # fw deploy [--ref <branch>] [--here] [--dry-run] build and publish this repo's app (see lib/deploy.mjs) +# fw repoint [DIR] [--https] [--dry-run] move working copies' origin from GitLab to DreamHost # fw list | new <name> [--public] [--description TEXT] | public <name> | private <name> # fw describe <name> TEXT | publish <name> | publish-all (run on DreamHost over ssh) set -euo pipefail @@ -11,6 +12,8 @@ HERE=$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd) case "${1:-help}" in deploy) shift; exec node "$HERE/lib/deploy.mjs" "$@" ;; + repoint) + shift; exec bash "$HERE/lib/repoint.sh" "$@" ;; list|new|public|private|describe|publish|publish-all|help|-h|--help) # printf %q keeps arguments (e.g. a description with spaces) intact through ssh. exec ssh "$HOST" "~/bin/fw $(printf '%q ' "$@")" ;; diff --git a/lib/repoint.sh b/lib/repoint.sh new file mode 100755 index 0000000..687462f --- /dev/null +++ b/lib/repoint.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# fw repoint: move working copies from GitLab to DreamHost. Changes only remotes and tracking. +# +# fw repoint [DIR] [--https] [--dry-run] +# +# For every git repo directly inside DIR (default: current directory) whose `origin` is +# gitlab.com/PavelSafronov/<repo> (or the old gitlab.com/fuzzy-rp/<repo>): +# origin -> renamed `gitlab`; new origin -> dh:git/<repo>.git (needs the `dh` ssh alias) +# with --https: new origin -> https://code.fuzzyworld.net/<repo>.git (read-only; public repos only) +# then fetches, and points each branch that tracked a GitLab branch at the same branch on origin. +# Uncommitted work, HEAD and stashes are fingerprinted before and after; a difference is reported. +set -u +DIR=.; HTTPS=false; DRY=false +for a in "$@"; do + case "$a" in + --https) HTTPS=true ;; + --dry-run) DRY=true ;; + -*) echo "fw repoint: unknown option $a" >&2; exit 2 ;; + *) DIR=$a ;; + esac +done + +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; } + +ok=0; skipped=0; failed=0 +for d in "$DIR"/*/; do + d=${d%/} + [ -d "$d/.git" ] || continue + url=$(git -C "$d" remote get-url origin 2>/dev/null) || continue + repo=$(printf '%s' "$url" | sed -nE 's#^.*gitlab\.com[:/](PavelSafronov|fuzzy-rp)/([^/]+)/?$#\2#p' | sed 's/\.git$//') + [ -n "$repo" ] || continue + name=$(basename "$d") + if $HTTPS; then + new="https://code.fuzzyworld.net/$repo.git" + if ! git ls-remote --exit-code "$new" HEAD >/dev/null 2>&1; then + echo "skip $name: $repo is not public on code.fuzzyworld.net"; skipped=$((skipped+1)); continue + fi + else + new="dh:git/$repo.git" + fi + if git -C "$d" remote get-url gitlab >/dev/null 2>&1; then + echo "skip $name: already has a remote named gitlab"; skipped=$((skipped+1)); continue + fi + if $DRY; then echo "would $name: origin $url -> $new"; ok=$((ok+1)); continue; fi + + before=$(fp "$d") + git -C "$d" remote rename origin gitlab + git -C "$d" remote add origin "$new" + if ! git -C "$d" fetch --quiet origin; then + echo "FAIL $name: fetch from $new failed (remotes changed; run 'git -C $d fetch origin' to retry)"; failed=$((failed+1)); continue + fi + moved="" + for b in $(git -C "$d" for-each-ref --format='%(refname:short)' refs/heads); do + [ "$(git -C "$d" config --get "branch.$b.remote")" = gitlab ] || continue + m=$(git -C "$d" config --get "branch.$b.merge" | sed 's#^refs/heads/##') + git -C "$d" rev-parse -q --verify "refs/remotes/origin/$m" >/dev/null && + git -C "$d" branch --quiet --set-upstream-to="origin/$m" "$b" && moved="$moved $b" + done + after=$(fp "$d") + if [ "$before" = "$after" ]; then + echo "ok $name -> $new (tracking:${moved:- none})"; ok=$((ok+1)) + else + echo "WARN $name: working copy fingerprint changed; check it"; failed=$((failed+1)) + fi +done +echo "repointed $ok, skipped $skipped, problems $failed$($DRY && echo ' (dry run: nothing changed)')" diff --git a/package.json b/package.json index 6c29f66..4749b64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fuzzyworld-tools", - "version": "1.0.4", + "version": "1.0.5", "description": "Tooling for code.fuzzyworld.net and apps.fuzzyworld.net: fw command, deploy, vite plugin.", "type": "module", "bin": {