#!/bin/sh # ghq get + resolve the on-disk clone path; prints the repo directory. # Shell wrappers (fish/bash/zsh `ghg` functions) cd into the printed path — # a child process can't change the caller's cwd. # # Then sets up a prad.codes mirror when the repo has none (best effort; the # clone never fails over it): # github.com// you admin → pull mirror into a prad.codes org # (picked with gum, guessed from the language; esc skips), every 8h # prad.codes// that isn't itself a mirror → push mirror to # github.com// (created if missing), on every push and every 8h repo=$1 if [ -z "$repo" ]; then echo "usage: ghg-clone " >&2 exit 1 fi # ghq natively understands https://, ssh://, git@host:…, and host/user/repo. # Keep its output on stderr so stdout stays clean for the wrapper's cd. ghq get "$repo" >&2 || exit # Reduce the input to ghq's on-disk shape: // — # strip scheme (https://, ssh://, git://), user@ (git@github.com:…), # turn scp host:path into host/path, drop trailing .git. rel=$(printf '%s\n' "$repo" \ | sed -E 's#^[[:alnum:]]+://##; s#^[^@/]+@##; s#:#/#; s#\.git$##') # bare "user/repo" with no host → default to github.com case "$rel" in *.*/*) ;; *) rel=github.com/$rel ;; esac dir=$(ghq root)/$rel if [ ! -d "$dir" ]; then echo "ghg: expected clone at $dir but it does not exist" >&2 exit 1 fi command -v zoxide >/dev/null 2>&1 && zoxide add "$dir" # ── Mirrors ─────────────────────────────────────────────────────────────── # Everything below talks on stderr; stdout carries only the final path. have() { command -v "$1" >/dev/null 2>&1; } # prad.codes orgs are per language; guess one from GitHub's primary language. lang_org() { case $1 in Go) echo go ;; Python | "Jupyter Notebook") echo py ;; Rust) echo rs ;; Dart) echo dart ;; TeX) echo tex ;; JavaScript | TypeScript | Astro | Svelte | Vue | HTML | CSS) echo js ;; Markdown | MDX) echo md ;; *) echo env ;; # Lua, Shell, no code at all (dotfiles, configs) esac } pull_mirror() { # owner name info=$(gh repo view "$1/$2" --json viewerPermission,isPrivate,primaryLanguage \ -q '[.viewerPermission, .isPrivate, .primaryLanguage.name // ""] | @tsv' 2>/dev/null) || return 0 perm=$(printf '%s\n' "$info" | cut -f1) private=$(printf '%s\n' "$info" | cut -f2) lang=$(printf '%s\n' "$info" | cut -f3) [ "$perm" = ADMIN ] || return 0 # someone else's repo: just a clone # tea api exits 0 even on errors, hence jq -e on every response. tea api "/api/v1/repos/search?q=$2&limit=50" | jq -e --arg u "github.com/$1/$2" \ 'any(.data[]; (.original_url // "") | ascii_downcase | sub("(\\.git)?/?$"; "") | endswith($u | ascii_downcase))' \ >/dev/null && return 0 org=$(tea api '/api/v1/orgs?limit=50' | jq -r '.[].username' | gum choose --header "Pull-mirror github.com/$1/$2 into which prad.codes org? (esc skips)" \ --selected "$(lang_org "$lang")") || return 0 [ -n "$org" ] || return 0 echo "→ pull mirror: github.com/$1/$2 → prad.codes/$org/$2 (every 8h)" >&2 priv= [ "$private" = true ] && priv=--private # shellcheck disable=SC2086 # $priv is one optional flag tea repos migrate --name "$2" --owner "$org" --clone-url "https://github.com/$1/$2.git" \ --service git --mirror --mirror-interval 8h --auth-token "$(gh auth token)" $priv >&2 || echo "ghg: pull mirror failed" >&2 } push_mirror() { # org name repo=$(tea api "/api/v1/repos/$1/$2") printf '%s' "$repo" | jq -e '.id and (.mirror | not)' >/dev/null || return 0 tea api "/api/v1/repos/$1/$2/push_mirrors" | jq -e 'length == 0' >/dev/null || return 0 me=$(gh api user -q .login 2>/dev/null) || return 0 if gh repo view "$me/$2" >/dev/null 2>&1; then gum confirm "Push-mirror prad.codes/$1/$2 to the existing github.com/$me/$2? Mirror pushes overwrite it." || return 0 else gum confirm "Push-mirror prad.codes/$1/$2 to a new github.com/$me/$2?" || return 0 vis=--public [ "$(printf '%s' "$repo" | jq -r .private)" = true ] && vis=--private gh repo create "$me/$2" "$vis" --description "Mirror of https://prad.codes/$1/$2" >&2 || return 0 fi if jq -n --arg url "https://github.com/$me/$2.git" --arg user "$me" --arg pass "$(gh auth token)" \ '{remote_address: $url, remote_username: $user, remote_password: $pass, interval: "8h0m0s", sync_on_commit: true}' | tea api -d @- "/api/v1/repos/$1/$2/push_mirrors" | jq -e .remote_name >/dev/null; then tea api -X POST "/api/v1/repos/$1/$2/push_mirrors-sync" >/dev/null # first push now echo "→ push mirror: prad.codes/$1/$2 → github.com/$me/$2 (on push + every 8h)" >&2 else echo "ghg: push mirror failed" >&2 fi } if have gh && have tea && have jq && have gum; then owner=${rel#*/} name=${owner#*/} owner=${owner%%/*} case $rel in github.com/*/*) pull_mirror "$owner" "$name" ;; prad.codes/*/*) push_mirror "$owner" "$name" ;; esac fi printf '%s\n' "$dir"