284 lines
7.7 KiB
Bash
Executable File
284 lines
7.7 KiB
Bash
Executable File
#!/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" |