git-sync: ein Aufruf statt mehrerer Nachfragen (all-Modus + allow-Regel)
This commit is contained in:
+70
-64
@@ -1,78 +1,84 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# git-sync — add/commit/pull --rebase/push a single repo.
|
# git-sync — add/commit/pull --rebase/push.
|
||||||
# Safe: rebases onto remote, never merges, never force-pushes; conflicts are
|
# git-sync alle Repos aus permissions.additionalDirectories
|
||||||
# reported (rebase aborted), not auto-resolved.
|
# git-sync <dir> [message] ein einzelnes Repo
|
||||||
# Usage: git-sync <dir> [commit-message]
|
# Safe: rebaset auf den Remote, kein Merge, kein force-push; Konflikte werden
|
||||||
# Invoked by the /git-sync slash command, once per configured project repo.
|
# gemeldet (Rebase abgebrochen), nicht automatisch aufgelöst.
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
dir=${1:?usage: git-sync <dir> [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
|
ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; } # green
|
||||||
note() { printf '\033[1;36m· %s\033[0m\n' "$*"; } # cyan
|
note() { printf '\033[1;36m· %s\033[0m\n' "$*"; } # cyan
|
||||||
warn() { printf '\033[1;33m⚠ %s\033[0m\n' "$*"; } # yellow
|
warn() { printf '\033[1;33m⚠ %s\033[0m\n' "$*"; } # yellow
|
||||||
|
|
||||||
g() { git -C "$dir" "$@"; }
|
sync_one() {
|
||||||
label=$(basename "$dir")
|
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
|
if ! git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
warn "$label: kein Git-Repo — übersprungen."
|
warn "$label: kein Git-Repo — übersprungen."; return 0
|
||||||
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
|
|
||||||
fi
|
fi
|
||||||
else
|
branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD)
|
||||||
note "$label: keine Änderungen."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- pull --rebase, then push (no force, no merge) -------------------------
|
# add/commit
|
||||||
if ! g remote get-url origin >/dev/null 2>&1; then
|
if [ -n "$(git -C "$dir" status --porcelain)" ]; then
|
||||||
note "$label: kein origin-Remote — push übersprungen."
|
git -C "$dir" add -A
|
||||||
exit 0
|
[ -z "$msg" ] && msg="git-sync: $(date '+%Y-%m-%d %H:%M')"
|
||||||
fi
|
if git -C "$dir" commit --quiet -m "$msg"; then
|
||||||
|
note "$label: committed — $msg"
|
||||||
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."
|
|
||||||
else
|
else
|
||||||
g rebase --abort >/dev/null 2>&1 || true
|
warn "$label: commit fehlgeschlagen."; return 0
|
||||||
warn "$label: Pull/Rebase-Konflikt — bitte manuell lösen. Push übersprungen."
|
fi
|
||||||
exit 0
|
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
|
||||||
fi
|
fi
|
||||||
ahead=$(g rev-list --count '@{u}..HEAD' 2>/dev/null || echo 0)
|
}
|
||||||
if [[ "$ahead" -eq 0 ]]; then
|
|
||||||
note "$label: nichts zu pushen."
|
# --- dispatch --------------------------------------------------------------
|
||||||
elif g push --quiet 2>/dev/null; then
|
if [ $# -ge 1 ]; then
|
||||||
ok "$label: $ahead Commit(s) gepusht ($branch)."
|
sync_one "$1" "${2:-}"
|
||||||
else
|
exit 0
|
||||||
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
|
|
||||||
fi
|
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"
|
||||||
|
|||||||
@@ -2,23 +2,12 @@
|
|||||||
description: Commit + Push aller persistent konfigurierten Projekt-Repos
|
description: Commit + Push aller persistent konfigurierten Projekt-Repos
|
||||||
---
|
---
|
||||||
|
|
||||||
Du committest und pushst die persistent hinterlegten Projekt-Verzeichnisse
|
Führe genau dieses eine Kommando aus, sonst nichts:
|
||||||
(`permissions.additionalDirectories` aus den Settings) — nicht das CWD.
|
|
||||||
|
|
||||||
1. Verzeichnisse sammeln (Projekt-, lokale, User-Settings, dedupliziert):
|
```bash
|
||||||
```bash
|
~/claude-projects/global/bin/git-sync
|
||||||
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 <dir> status` und
|
|
||||||
`git -C <dir> diff --staged --stat` plus `git -C <dir> 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" "<dir>" "<message>"
|
|
||||||
```
|
|
||||||
4. Ergebnisse pro Repo zusammenfassen: committed / gepusht / nichts zu tun / abgelehnt.
|
|
||||||
|
|
||||||
Verändere nichts außerhalb dieser Verzeichnisse. Kein force, kein merge — das Skript
|
Das Skript liest selbst die `permissions.additionalDirectories` aus den Settings und
|
||||||
ist darauf ausgelegt und meldet Konflikte nur, statt sie aufzulösen.
|
synct jedes Repo (add, commit mit Timestamp-Message, pull --rebase, push). Kein force,
|
||||||
|
kein Merge. Gib anschließend die Ausgabe des Skripts wieder.
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
"Bash(git diff:*)",
|
"Bash(git diff:*)",
|
||||||
"Bash(git -C:*)",
|
"Bash(git -C:*)",
|
||||||
"Bash(git mv:*)",
|
"Bash(git mv:*)",
|
||||||
"Bash(git check-ignore:*)"
|
"Bash(git check-ignore:*)",
|
||||||
|
"Bash(~/claude-projects/global/bin/git-sync:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user