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