diff --git a/global/bin/git-sync b/global/bin/git-sync index f53c051..2ca90d0 100755 --- a/global/bin/git-sync +++ b/global/bin/git-sync @@ -1,78 +1,84 @@ #!/usr/bin/env bash -# git-sync — add/commit/pull --rebase/push a single repo. -# Safe: rebases onto remote, never merges, never force-pushes; conflicts are -# reported (rebase aborted), not auto-resolved. -# Usage: git-sync [commit-message] -# Invoked by the /git-sync slash command, once per configured project repo. +# git-sync — add/commit/pull --rebase/push. +# git-sync alle Repos aus permissions.additionalDirectories +# git-sync [message] ein einzelnes Repo +# Safe: rebaset auf den Remote, kein Merge, kein force-push; Konflikte werden +# gemeldet (Rebase abgebrochen), nicht automatisch aufgelöst. set -uo pipefail -dir=${1:?usage: git-sync [message]} -msg=${2:-} - -# expand leading ~ (not expanded inside variables) so $HOME-based paths work -case "$dir" in - "~") dir="$HOME" ;; - "~/"*) dir="$HOME/${dir#"~/"}" ;; -esac - ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; } # green note() { printf '\033[1;36m· %s\033[0m\n' "$*"; } # cyan warn() { printf '\033[1;33m⚠ %s\033[0m\n' "$*"; } # yellow -g() { git -C "$dir" "$@"; } -label=$(basename "$dir") +sync_one() { + local dir=$1 msg=${2:-} label branch ahead behind + case "$dir" in "~") dir="$HOME" ;; "~/"*) dir="$HOME/${dir#"~/"}" ;; esac + label=$(basename "$dir") -if ! g rev-parse --is-inside-work-tree >/dev/null 2>&1; then - warn "$label: kein Git-Repo — übersprungen." - exit 0 -fi -branch=$(g rev-parse --abbrev-ref HEAD) - -# --- commit working-tree changes ------------------------------------------- -if [[ -n "$(g status --porcelain)" ]]; then - g add -A - [[ -z "$msg" ]] && msg="git-sync: $(date '+%Y-%m-%d %H:%M')" - if g commit --quiet -m "$msg"; then - note "$label: committed — $msg" - else - warn "$label: commit fehlgeschlagen." - exit 0 + if ! git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + warn "$label: kein Git-Repo — übersprungen."; return 0 fi -else - note "$label: keine Änderungen." -fi + branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD) -# --- pull --rebase, then push (no force, no merge) ------------------------- -if ! g remote get-url origin >/dev/null 2>&1; then - note "$label: kein origin-Remote — push übersprungen." - exit 0 -fi - -if g rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then - # integrate remote first: rebase keeps history linear, no merge commits - g fetch --quiet 2>/dev/null || true - behind=$(g rev-list --count 'HEAD..@{u}' 2>/dev/null || echo 0) - if [[ "$behind" -gt 0 ]]; then - if g pull --rebase --quiet 2>/dev/null; then - note "$label: $behind Commit(s) per Rebase integriert." + # add/commit + if [ -n "$(git -C "$dir" status --porcelain)" ]; then + git -C "$dir" add -A + [ -z "$msg" ] && msg="git-sync: $(date '+%Y-%m-%d %H:%M')" + if git -C "$dir" commit --quiet -m "$msg"; then + note "$label: committed — $msg" else - g rebase --abort >/dev/null 2>&1 || true - warn "$label: Pull/Rebase-Konflikt — bitte manuell lösen. Push übersprungen." - exit 0 + warn "$label: commit fehlgeschlagen."; return 0 + fi + else + note "$label: keine Änderungen." + fi + + # pull --rebase, dann push + if ! git -C "$dir" remote get-url origin >/dev/null 2>&1; then + note "$label: kein origin-Remote — push übersprungen."; return 0 + fi + + if git -C "$dir" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then + git -C "$dir" fetch --quiet 2>/dev/null || true + behind=$(git -C "$dir" rev-list --count 'HEAD..@{u}' 2>/dev/null || echo 0) + if [ "$behind" -gt 0 ]; then + if git -C "$dir" pull --rebase --quiet 2>/dev/null; then + note "$label: $behind Commit(s) per Rebase integriert." + else + git -C "$dir" rebase --abort >/dev/null 2>&1 || true + warn "$label: Pull/Rebase-Konflikt — bitte manuell lösen. Push übersprungen."; return 0 + fi + fi + ahead=$(git -C "$dir" rev-list --count '@{u}..HEAD' 2>/dev/null || echo 0) + if [ "$ahead" -eq 0 ]; then + note "$label: nichts zu pushen." + elif git -C "$dir" push --quiet 2>/dev/null; then + ok "$label: $ahead Commit(s) gepusht ($branch)." + else + warn "$label: push abgelehnt — Remote voraus. Manuell prüfen." + fi + else + if git -C "$dir" push --quiet -u origin "$branch" 2>/dev/null; then + ok "$label: Upstream gesetzt und gepusht ($branch)." + else + warn "$label: kein Upstream, push fehlgeschlagen — einmal manuell pushen." fi fi - ahead=$(g rev-list --count '@{u}..HEAD' 2>/dev/null || echo 0) - if [[ "$ahead" -eq 0 ]]; then - note "$label: nichts zu pushen." - elif g push --quiet 2>/dev/null; then - ok "$label: $ahead Commit(s) gepusht ($branch)." - else - warn "$label: push abgelehnt — Remote voraus. Manuell prüfen." - fi -else - if g push --quiet -u origin "$branch" 2>/dev/null; then - ok "$label: Upstream gesetzt und gepusht ($branch)." - else - warn "$label: kein Upstream, push fehlgeschlagen — einmal manuell pushen." - fi +} + +# --- dispatch -------------------------------------------------------------- +if [ $# -ge 1 ]; then + sync_one "$1" "${2:-}" + exit 0 fi + +# ohne Argument: alle additionalDirectories aus den Settings (cwd + User) +dirs=$(for f in .claude/settings.json .claude/settings.local.json "$HOME/.claude/settings.json"; do + [ -f "$f" ] && jq -r '.permissions.additionalDirectories[]?' "$f" 2>/dev/null + done | sort -u) +if [ -z "$dirs" ]; then + note "keine additionalDirectories gefunden (cwd: $PWD)"; exit 0 +fi +while IFS= read -r d; do + [ -n "$d" ] && sync_one "$d" +done <<< "$dirs" diff --git a/global/commands/git-sync.md b/global/commands/git-sync.md index 7ba65ad..9f22c32 100644 --- a/global/commands/git-sync.md +++ b/global/commands/git-sync.md @@ -2,23 +2,12 @@ description: Commit + Push aller persistent konfigurierten Projekt-Repos --- -Du committest und pushst die persistent hinterlegten Projekt-Verzeichnisse -(`permissions.additionalDirectories` aus den Settings) — nicht das CWD. +Führe genau dieses eine Kommando aus, sonst nichts: -1. Verzeichnisse sammeln (Projekt-, lokale, User-Settings, dedupliziert): - ```bash - for f in .claude/settings.json .claude/settings.local.json "$HOME/.claude/settings.json"; do - [ -f "$f" ] && jq -r '.permissions.additionalDirectories[]?' "$f" 2>/dev/null - done | sort -u | sed "s|^~/|$HOME/|" - ``` -2. Für jedes Verzeichnis, das ein Git-Repo ist: aus `git -C status` und - `git -C diff --staged --stat` plus `git -C diff --stat` eine knappe, - sachliche einzeilige Commit-Message auf Deutsch erzeugen (beschreibt, was geändert wurde). -3. Pro Verzeichnis aufrufen: - ```bash - "$HOME/claude-projects/global/bin/git-sync" "" "" - ``` -4. Ergebnisse pro Repo zusammenfassen: committed / gepusht / nichts zu tun / abgelehnt. +```bash +~/claude-projects/global/bin/git-sync +``` -Verändere nichts außerhalb dieser Verzeichnisse. Kein force, kein merge — das Skript -ist darauf ausgelegt und meldet Konflikte nur, statt sie aufzulösen. +Das Skript liest selbst die `permissions.additionalDirectories` aus den Settings und +synct jedes Repo (add, commit mit Timestamp-Message, pull --rebase, push). Kein force, +kein Merge. Gib anschließend die Ausgabe des Skripts wieder. diff --git a/global/settings.json b/global/settings.json index 3ae6802..82cc0b5 100644 --- a/global/settings.json +++ b/global/settings.json @@ -15,7 +15,8 @@ "Bash(git diff:*)", "Bash(git -C:*)", "Bash(git mv:*)", - "Bash(git check-ignore:*)" + "Bash(git check-ignore:*)", + "Bash(~/claude-projects/global/bin/git-sync:*)" ] } }