diff --git a/dot_config/fish/config.fish b/dot_config/fish/config.fish index 4331c78..1552191 100644 --- a/dot_config/fish/config.fish +++ b/dot_config/fish/config.fish @@ -34,3 +34,6 @@ fish_add_path $HOME/.grok/bin if status is-interactive atuin init fish | source end + +# Generated for envman. Do not edit. +test -s ~/.config/envman/load.fish; and source ~/.config/envman/load.fish diff --git a/dot_config/fish/functions/gh-sonr-org-migrate.fish b/dot_config/fish/functions/gh-sonr-org-migrate.fish new file mode 100644 index 0000000..e960b7a --- /dev/null +++ b/dot_config/fish/functions/gh-sonr-org-migrate.fish @@ -0,0 +1,151 @@ +#!/usr/bin/env fish +# sonr-consolidate.fish +# Migrates all repos from orgs matching "sonr" into sonr-io, +# prompting via gum for renames on collision, then deletes emptied orgs. +# +# Requires: gh (authenticated as org owner on all orgs), gum, jq + +set -g TARGET_ORG sonr-io + +# ── Preflight ──────────────────────────────────────────────────────────────── +for bin in gh gum jq + if not command -q $bin + echo (set_color red)"missing dependency: $bin"(set_color normal) + exit 1 + end +end + +if not gh auth status &>/dev/null + echo (set_color red)"gh is not authenticated"(set_color normal) + exit 1 +end + +# ── Discover source orgs ───────────────────────────────────────────────────── +# All orgs the user belongs to whose login contains "sonr", excluding target. +set -l source_orgs (gh api user/orgs --paginate --jq '.[].login' \ + | string match -r '.*sonr.*' \ + | string match -v $TARGET_ORG) + +if test (count $source_orgs) -eq 0 + echo "No source orgs found. Nothing to do." + exit 0 +end + +echo (set_color cyan)"Source orgs:"(set_color normal) +printf ' %s\n' $source_orgs +echo (set_color cyan)"Target org:"(set_color normal)" $TARGET_ORG" +echo + +# Let the user confirm / trim the list interactively +set -l selected_orgs (printf '%s\n' $source_orgs | gum choose --no-limit \ + --header "Select orgs to migrate into $TARGET_ORG (space to toggle, enter to confirm)") + +if test (count $selected_orgs) -eq 0 + echo "Nothing selected. Exiting." + exit 0 +end + +# ── Helpers ────────────────────────────────────────────────────────────────── +function repo_exists_in_target + gh api "repos/$TARGET_ORG/$argv[1]" --silent &>/dev/null +end + +function transfer_repo + # $1 = source org, $2 = repo name, $3 = (optional) new name + set -l src $argv[1] + set -l repo $argv[2] + set -l args -f new_owner=$TARGET_ORG + if set -q argv[3]; and test -n "$argv[3]" + set -a args -f new_name=$argv[3] + end + gh api -X POST "repos/$src/$repo/transfer" $args --silent +end + +# ── Migration ──────────────────────────────────────────────────────────────── +set -g failed_transfers + +for org in $selected_orgs + echo + echo (set_color --bold cyan)"── $org ──"(set_color normal) + + set -l repos (gh api "orgs/$org/repos" --paginate --jq '.[].name') + + if test (count $repos) -eq 0 + echo " (no repos)" + continue + end + + for repo in $repos + set -l new_name "" + + if repo_exists_in_target $repo + echo (set_color yellow)" ⚠ $repo already exists in $TARGET_ORG"(set_color normal) + while true + set new_name (gum input \ + --header "New name for $org/$repo (exists in $TARGET_ORG)" \ + --placeholder "$org-$repo" \ + --value "$org-$repo") + if test -z "$new_name" + echo " skipped $org/$repo" + set new_name __SKIP__ + break + end + if repo_exists_in_target $new_name + echo (set_color yellow)" $new_name also exists — pick another"(set_color normal) + else + break + end + end + test "$new_name" = __SKIP__; and continue + end + + set -l label "$org/$repo → $TARGET_ORG/"(test -n "$new_name"; and echo $new_name; or echo $repo) + if transfer_repo $org $repo $new_name + echo (set_color green)" ✓ $label"(set_color normal) + else + echo (set_color red)" ✗ failed: $label"(set_color normal) + set -a failed_transfers "$org/$repo" + end + end +end + +if test (count $failed_transfers) -gt 0 + echo + echo (set_color red)"Failed transfers (orgs containing these will NOT be deleted):"(set_color normal) + printf ' %s\n' $failed_transfers +end + +# ── Wait for transfers to settle ───────────────────────────────────────────── +# GitHub repo transfers are asynchronous; give them time to drain. +echo +gum spin --title "Waiting 30s for transfers to settle..." -- sleep 30 + +# ── Delete emptied orgs ────────────────────────────────────────────────────── +echo +for org in $selected_orgs + # Skip orgs with failed transfers + if string match -q "$org/*" -- $failed_transfers + echo (set_color yellow)"skipping delete: $org (had failures)"(set_color normal) + continue + end + + set -l remaining (gh api "orgs/$org/repos" --paginate --jq '. | length' | jq -s 'add // 0') + + if test "$remaining" -gt 0 + echo (set_color yellow)"skipping delete: $org still has $remaining repo(s) (transfer pending?)"(set_color normal) + continue + end + + if gum confirm "Delete empty org $org?" + if gh api -X DELETE "orgs/$org" --silent + echo (set_color green)"✓ deleted $org"(set_color normal) + else + echo (set_color red)"✗ could not delete $org (API delete requires org owner; may need manual deletion in Settings)"(set_color normal) + end + else + echo "kept $org" + end +end + +echo +echo (set_color --bold green)"Done."(set_color normal)