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:*)"
]
}
}