Adds script to recursively check Git submodules and switch from detached HEAD to the appropriate branch.
Qt6 Migration CI / migrate (push) Waiting to run
Qt6 Migration CI / migrate (push) Waiting to run
This commit is contained in:
2 files changed
+347
No files matched your search
@@ -0,0 +1,63 @@
|
||||
# fix-submodule-branches-prod.sh
|
||||
|
||||
Bash-Script zum rekursiven Prüfen von Git-Submodulen und Umschalten von `detached HEAD` auf den passenden Branch.
|
||||
|
||||
## Verhalten
|
||||
|
||||
Für jedes Submodul gilt:
|
||||
|
||||
1. Wenn in `.gitmodules` ein `branch` konfiguriert ist, wird **dieser** verwendet.
|
||||
2. Wenn **kein** `branch` konfiguriert ist, wird der **Default-Branch von `origin`** verwendet.
|
||||
3. Standardmäßig wird **nur bei `detached HEAD`** umgeschaltet.
|
||||
4. Optional kann mit `--force-switch` auch von einem anderen Branch auf den Ziel-Branch gewechselt werden.
|
||||
5. Submodule mit **uncommitted changes** werden aus Sicherheitsgründen **übersprungen**.
|
||||
6. Verschachtelte Submodule werden rekursiv verarbeitet.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Bash
|
||||
- Git
|
||||
|
||||
## Nutzung
|
||||
|
||||
```bash
|
||||
chmod +x fix-submodule-branches-prod.sh
|
||||
./fix-submodule-branches-prod.sh [optionen] [root-dir]
|
||||
```
|
||||
|
||||
## Optionen
|
||||
|
||||
- `-n`, `--dry-run`
|
||||
Zeigt nur an, was ausgeführt würde.
|
||||
|
||||
- `--force-switch`
|
||||
Wechselt auch dann auf den Ziel-Branch, wenn das Submodul bereits auf einem anderen Branch steht.
|
||||
|
||||
- `--init-missing`
|
||||
Initialisiert fehlende/nicht initialisierte Submodule automatisch.
|
||||
|
||||
- `-h`, `--help`
|
||||
Zeigt die Hilfe an.
|
||||
|
||||
## Beispiele
|
||||
|
||||
```bash
|
||||
./fix-submodule-branches-prod.sh --dry-run /pfad/zum/repo-root
|
||||
./fix-submodule-branches-prod.sh --init-missing /pfad/zum/repo-root
|
||||
./fix-submodule-branches-prod.sh --init-missing --force-switch /pfad/zum/repo-root
|
||||
```
|
||||
|
||||
## Ausgabe
|
||||
|
||||
Am Ende wird eine Zusammenfassung ausgegeben, z. B.:
|
||||
|
||||
- Anzahl geprüfter Repositories mit `.gitmodules`
|
||||
- Anzahl gefundener Submodule
|
||||
- Anzahl gefixter `detached HEAD`/Branch-Wechsel
|
||||
- Anzahl bereits korrekter Branches
|
||||
- Anzahl übersprungener dirty Submodule
|
||||
- Anzahl übersprungener Fälle/Fehler
|
||||
|
||||
## Sicherheitsverhalten
|
||||
|
||||
Das Script überspringt Submodule mit lokalen Änderungen (`git status --porcelain` nicht leer), damit keine uncommitted Änderungen durch Branch-Wechsel gefährdet werden.
|
||||
Executable
+284
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DRY_RUN=0
|
||||
FORCE_SWITCH=0
|
||||
INIT_MISSING=0
|
||||
ROOT_DIR="."
|
||||
|
||||
ERRORS=0
|
||||
COUNT_REPOS=0
|
||||
COUNT_SUBMODULES=0
|
||||
COUNT_FIXED=0
|
||||
COUNT_SKIPPED=0
|
||||
COUNT_ALREADY_OK=0
|
||||
COUNT_OTHER_BRANCH=0
|
||||
COUNT_INITIALIZED=0
|
||||
COUNT_DIRTY=0
|
||||
|
||||
log() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf 'WARN: %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
err() {
|
||||
printf 'ERROR: %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
on_error() {
|
||||
local exit_code=$?
|
||||
err "Abbruch in Zeile $1 mit Exit-Code $exit_code"
|
||||
exit "$exit_code"
|
||||
}
|
||||
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
run_cmd() {
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
printf '[dry-run] '
|
||||
printf '%q ' "$@"
|
||||
printf '\n'
|
||||
return 0
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
is_dirty_repo() {
|
||||
local repo_dir="$1"
|
||||
[[ -n "$(git -C "$repo_dir" status --porcelain 2>/dev/null || true)" ]]
|
||||
}
|
||||
|
||||
get_default_branch() {
|
||||
local repo_dir="$1"
|
||||
local head_ref default_branch
|
||||
|
||||
head_ref="$(git -C "$repo_dir" symbolic-ref -q refs/remotes/origin/HEAD 2>/dev/null || true)"
|
||||
if [[ -n "$head_ref" ]]; then
|
||||
default_branch="${head_ref#refs/remotes/origin/}"
|
||||
printf '%s\n' "$default_branch"
|
||||
return 0
|
||||
fi
|
||||
|
||||
git -C "$repo_dir" fetch origin >/dev/null 2>&1 || true
|
||||
|
||||
head_ref="$(git -C "$repo_dir" symbolic-ref -q refs/remotes/origin/HEAD 2>/dev/null || true)"
|
||||
if [[ -n "$head_ref" ]]; then
|
||||
default_branch="${head_ref#refs/remotes/origin/}"
|
||||
printf '%s\n' "$default_branch"
|
||||
return 0
|
||||
fi
|
||||
|
||||
default_branch="$(git -C "$repo_dir" remote show origin 2>/dev/null | sed -n 's/.*HEAD branch: //p' | head -n1 || true)"
|
||||
if [[ -n "$default_branch" ]]; then
|
||||
printf '%s\n' "$default_branch"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
init_submodule_if_needed() {
|
||||
local repo_dir="$1"
|
||||
local sub_path="$2"
|
||||
local submodule_abs="$repo_dir/$sub_path"
|
||||
|
||||
if [[ -d "$submodule_abs/.git" || -f "$submodule_abs/.git" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$INIT_MISSING" -ne 1 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
log " Initialisiere fehlendes Submodule: $sub_path"
|
||||
if run_cmd git -C "$repo_dir" submodule update --init -- "$sub_path"; then
|
||||
COUNT_INITIALIZED=$((COUNT_INITIALIZED + 1))
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
checkout_target_branch() {
|
||||
local submodule_abs="$1"
|
||||
local resolved_branch="$2"
|
||||
|
||||
run_cmd git -C "$submodule_abs" fetch origin "$resolved_branch" >/dev/null 2>&1 || true
|
||||
|
||||
if git -C "$submodule_abs" show-ref --verify --quiet "refs/heads/$resolved_branch"; then
|
||||
log " Checke lokalen Branch '$resolved_branch' aus"
|
||||
run_cmd git -C "$submodule_abs" checkout "$resolved_branch"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if git -C "$submodule_abs" show-ref --verify --quiet "refs/remotes/origin/$resolved_branch"; then
|
||||
log " Erzeuge Tracking-Branch '$resolved_branch' von origin/$resolved_branch"
|
||||
run_cmd git -C "$submodule_abs" checkout -b "$resolved_branch" --track "origin/$resolved_branch"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
process_repo() {
|
||||
local repo_dir="$1"
|
||||
local gitmodules_file="$repo_dir/.gitmodules"
|
||||
|
||||
[[ -f "$gitmodules_file" ]] || return 0
|
||||
|
||||
COUNT_REPOS=$((COUNT_REPOS + 1))
|
||||
log "Prüfe Repository: $repo_dir"
|
||||
|
||||
local entries=()
|
||||
while IFS= read -r line; do
|
||||
entries+=("$line")
|
||||
done < <(git -C "$repo_dir" config -f "$gitmodules_file" --get-regexp '^submodule\..*\.path$' 2>/dev/null || true)
|
||||
|
||||
local entry key sub_path sub_name branch submodule_abs current_branch resolved_branch
|
||||
for entry in "${entries[@]}"; do
|
||||
COUNT_SUBMODULES=$((COUNT_SUBMODULES + 1))
|
||||
|
||||
key="${entry%% *}"
|
||||
sub_path="${entry#* }"
|
||||
|
||||
sub_name="${key#submodule.}"
|
||||
sub_name="${sub_name%.path}"
|
||||
submodule_abs="$repo_dir/$sub_path"
|
||||
|
||||
if [[ ! -e "$submodule_abs" ]]; then
|
||||
if [[ "$INIT_MISSING" -eq 1 ]]; then
|
||||
log " Submodule '$sub_name' ($sub_path): Pfad fehlt, versuche Initialisierung"
|
||||
else
|
||||
err "Submodule '$sub_name' ($sub_path): Pfad existiert nicht"
|
||||
ERRORS=1
|
||||
COUNT_SKIPPED=$((COUNT_SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! init_submodule_if_needed "$repo_dir" "$sub_path"; then
|
||||
err "Submodule '$sub_name' ($sub_path): nicht initialisiert"
|
||||
ERRORS=1
|
||||
COUNT_SKIPPED=$((COUNT_SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
if is_dirty_repo "$submodule_abs"; then
|
||||
warn "Submodule '$sub_name' ($sub_path): uncommitted changes vorhanden, überspringe"
|
||||
COUNT_DIRTY=$((COUNT_DIRTY + 1))
|
||||
COUNT_SKIPPED=$((COUNT_SKIPPED + 1))
|
||||
process_repo "$submodule_abs"
|
||||
continue
|
||||
fi
|
||||
|
||||
branch="$(git -C "$repo_dir" config -f "$gitmodules_file" --get "submodule.${sub_name}.branch" 2>/dev/null || true)"
|
||||
|
||||
if [[ -n "$branch" ]]; then
|
||||
resolved_branch="$branch"
|
||||
log " Submodule '$sub_name' ($sub_path): branch aus .gitmodules = $resolved_branch"
|
||||
else
|
||||
if resolved_branch="$(get_default_branch "$submodule_abs")"; then
|
||||
log " Submodule '$sub_name' ($sub_path): kein branch in .gitmodules, nehme Default-Branch = $resolved_branch"
|
||||
else
|
||||
err "Submodule '$sub_name' ($sub_path): weder branch in .gitmodules noch Default-Branch von origin ermittelbar"
|
||||
ERRORS=1
|
||||
COUNT_SKIPPED=$((COUNT_SKIPPED + 1))
|
||||
process_repo "$submodule_abs"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
current_branch="$(git -C "$submodule_abs" symbolic-ref --short -q HEAD 2>/dev/null || true)"
|
||||
|
||||
if [[ -n "$current_branch" ]]; then
|
||||
if [[ "$current_branch" == "$resolved_branch" ]]; then
|
||||
log " Bereits auf richtigem Branch: $current_branch"
|
||||
COUNT_ALREADY_OK=$((COUNT_ALREADY_OK + 1))
|
||||
else
|
||||
log " Auf anderem Branch: $current_branch"
|
||||
if [[ "$FORCE_SWITCH" -eq 1 ]]; then
|
||||
log " --force-switch aktiv, wechsle auf '$resolved_branch'"
|
||||
if checkout_target_branch "$submodule_abs" "$resolved_branch"; then
|
||||
COUNT_FIXED=$((COUNT_FIXED + 1))
|
||||
else
|
||||
err "Submodule '$sub_name' ($sub_path): Branch '$resolved_branch' existiert weder lokal noch als origin/$resolved_branch"
|
||||
ERRORS=1
|
||||
COUNT_SKIPPED=$((COUNT_SKIPPED + 1))
|
||||
fi
|
||||
else
|
||||
log " Keine Änderung, da nur detached HEADs umgestellt werden"
|
||||
COUNT_OTHER_BRANCH=$((COUNT_OTHER_BRANCH + 1))
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log " Detached HEAD erkannt"
|
||||
if checkout_target_branch "$submodule_abs" "$resolved_branch"; then
|
||||
COUNT_FIXED=$((COUNT_FIXED + 1))
|
||||
else
|
||||
err "Submodule '$sub_name' ($sub_path): Branch '$resolved_branch' existiert weder lokal noch als origin/$resolved_branch"
|
||||
ERRORS=1
|
||||
COUNT_SKIPPED=$((COUNT_SKIPPED + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
process_repo "$submodule_abs"
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
fix-submodule-branches-prod.sh [options] [root-dir]
|
||||
|
||||
Optionen:
|
||||
-n, --dry-run Nur anzeigen, was getan würde
|
||||
--force-switch Auch von falschem Branch auf Ziel-Branch wechseln
|
||||
--init-missing Nicht initialisierte Submodule automatisch initen
|
||||
-h, --help Hilfe anzeigen
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-n|--dry-run)
|
||||
DRY_RUN=1
|
||||
;;
|
||||
--force-switch)
|
||||
FORCE_SWITCH=1
|
||||
;;
|
||||
--init-missing)
|
||||
INIT_MISSING=1
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
ROOT_DIR="$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
process_repo "$ROOT_DIR"
|
||||
|
||||
log ""
|
||||
log "Zusammenfassung:"
|
||||
log " Repositories mit .gitmodules geprüft: $COUNT_REPOS"
|
||||
log " Submodule insgesamt: $COUNT_SUBMODULES"
|
||||
log " Detached/falsch Branch gefixt: $COUNT_FIXED"
|
||||
log " Bereits korrekt auf Branch: $COUNT_ALREADY_OK"
|
||||
log " Auf anderem Branch belassen: $COUNT_OTHER_BRANCH"
|
||||
log " Initialisierte Submodule: $COUNT_INITIALIZED"
|
||||
log " Dirty Submodule übersprungen: $COUNT_DIRTY"
|
||||
log " Übersprungen/Fehler: $COUNT_SKIPPED"
|
||||
|
||||
if [[ "$ERRORS" -ne 0 ]]; then
|
||||
log "Status: Fertig mit Fehlern"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Status: Fertig ohne Fehler"
|
||||
Reference in new issue
Block a user