feat(repo-sync): one local tree for every repo reachable on your forges

bin/repo-sync ports the Alphalink dotfiles repo/repo-reset zsh functions to
bash + zsh on Linux and macOS, for several forges at once: GitLab, GitHub
(+GHES), Gitea/Forgejo, Bitbucket Cloud, via curl + jq (no per-forge CLI).
Tokens live in ~/.config/repos/forges.conf (0600, parsed line by line, never
tracked), written by `repo-sync add`. Cache ~/.cache/repos/list refreshed
at most once a day (mkdir lock: flock is not on macOS), same namespace/project
on two forges kept once, archived and mirrors skipped. Layout
~/repos/<namespace with / as @>/<project>.

rc files: `repo <project> [namespace]` clones on demand and cd's, with
completion (bash compgen, zsh _describe), background refresh at shell start.
install.sh: jq in the apt and brew lists, chmod repo-sync.

Verified: shellcheck + bash -n, zsh -n; stub-curl harness over the four forges
(pagination, dedup, auth headers, lock, stale cache, path/clone); live GitLab
refresh (139 projects, 3s).
This commit is contained in:
bchanot
2026-10-07 15:31:03 +02:00
parent 2573756e73
commit f42e28807b
9 changed files with 451 additions and 4 deletions
+21
View File
@@ -130,6 +130,27 @@ alias cc='dtach_claude'
# Rappeler a la demande le menu de reprise.
alias d='source ~/.local/bin/dtach-router'
# repo-sync : un arbre local (~/repos) pour tous les depots des forges declarees
# dans ~/.config/repos/forges.conf. `repo <projet> [namespace]` clone si besoin
# puis s'y place ; la liste se rafraichit en arriere-plan, au plus 1 fois par jour.
repo() {
local dir
dir="$(repo-sync path "$@")" && cd "$dir" || return
}
_repo_complete() {
local cache="${REPOS_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/repos/list}" words
[ -f "$cache" ] || return 0
if [ "$COMP_CWORD" -eq 1 ]; then
words="$(cut -f1 "$cache" | sort -u)"
else
words="$(awk -F'\t' -v p="${COMP_WORDS[1]}" '$1 == p { print $2 }' "$cache")"
fi
# shellcheck disable=SC2207
COMPREPLY=($(compgen -W "$words" -- "${COMP_WORDS[COMP_CWORD]}"))
}
complete -F _repo_complete repo
case $- in *i*) command -v repo-sync >/dev/null && (repo-sync refresh --quiet >/dev/null 2>&1 &) ;; esac
# Au demarrage d'un shell interactif, proposer de reprendre une session dtach existante
# (silencieux si aucune). Place ici plutot que dans ~/.profile car les terminaux VS Code
# Remote-SSH sont non-login et ne lisent pas ~/.profile ; ~/.bashrc, lui, est toujours lu.
+21
View File
@@ -146,6 +146,27 @@ alias cc='dtach_claude'
# Rappeler a la demande le menu de reprise.
alias d='source ~/.local/bin/dtach-router'
# repo-sync : un arbre local (~/repos) pour tous les depots des forges declarees
# dans ~/.config/repos/forges.conf. `repo <projet> [namespace]` clone si besoin
# puis s'y place ; la liste se rafraichit en arriere-plan, au plus 1 fois par jour.
repo() {
local dir
dir="$(repo-sync path "$@")" && cd "$dir" || return
}
_repo_complete() {
local cache="${REPOS_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/repos/list}" words
[ -f "$cache" ] || return 0
if [ "$COMP_CWORD" -eq 1 ]; then
words="$(cut -f1 "$cache" | sort -u)"
else
words="$(awk -F'\t' -v p="${COMP_WORDS[1]}" '$1 == p { print $2 }' "$cache")"
fi
# shellcheck disable=SC2207
COMPREPLY=($(compgen -W "$words" -- "${COMP_WORDS[COMP_CWORD]}"))
}
complete -F _repo_complete repo
case $- in *i*) command -v repo-sync >/dev/null && (repo-sync refresh --quiet >/dev/null 2>&1 &) ;; esac
# Au demarrage d'un shell interactif, proposer de reprendre une session dtach existante
# (silencieux si aucune). Place ici plutot que dans ~/.profile car les terminaux VS Code
# Remote-SSH sont non-login et ne lisent pas ~/.profile ; ~/.bashrc, lui, est toujours lu.