1390 lines
36 KiB
Bash
Executable File
1390 lines
36 KiB
Bash
Executable File
#!/bin/bash
|
||
set -e
|
||
|
||
# =====================================
|
||
# StackPulse Dev-Skript – Hauptmenü & Aktionen
|
||
# =====================================
|
||
|
||
show_menu() {
|
||
cat <<'MENU'
|
||
Bitte wähle eine Aktion:
|
||
|
||
1) Neues Feature anlegen
|
||
2) Merge & Commit
|
||
3) Dev-Umgebungen
|
||
4) Hotfix
|
||
5) Branch-Verwaltung
|
||
6) Branch wechseln
|
||
0) Beenden
|
||
MENU
|
||
}
|
||
|
||
pause_for_menu() {
|
||
echo ""
|
||
read -rp "Zurück zum Hauptmenü mit Enter... " _
|
||
}
|
||
|
||
merge_commit_menu() {
|
||
local selection
|
||
while true; do
|
||
echo ""
|
||
cat <<'MENU'
|
||
Merge & Commit – Aktionen:
|
||
|
||
1) Feature-Branch in dev mergen
|
||
2) dev in master mergen
|
||
3) Änderungen committen & pushen
|
||
4) Docker-Release bauen & pushen
|
||
5) Git-Stashes verwalten
|
||
6) README in Feature-Branches aktualisieren
|
||
0) Zurück
|
||
MENU
|
||
read -rp "Auswahl: " selection
|
||
echo ""
|
||
case $selection in
|
||
1)
|
||
merge_feature_into_dev
|
||
pause_for_menu
|
||
;;
|
||
2)
|
||
merge_dev_into_master
|
||
pause_for_menu
|
||
;;
|
||
3)
|
||
push_changes
|
||
pause_for_menu
|
||
;;
|
||
4)
|
||
docker_release
|
||
pause_for_menu
|
||
;;
|
||
5)
|
||
manage_stash
|
||
pause_for_menu
|
||
;;
|
||
6)
|
||
sync_readme_to_features
|
||
pause_for_menu
|
||
;;
|
||
0)
|
||
break
|
||
;;
|
||
*)
|
||
echo "❌ Ungültige Auswahl."
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
dev_environments_menu() {
|
||
local selection
|
||
while true; do
|
||
echo ""
|
||
cat <<'MENU'
|
||
Dev-Umgebungen:
|
||
|
||
1) Dev-Umgebung starten
|
||
2) Docker Compose (lokal) starten
|
||
0) Zurück
|
||
MENU
|
||
read -rp "Auswahl: " selection
|
||
echo ""
|
||
case $selection in
|
||
1)
|
||
start_dev_environment
|
||
pause_for_menu
|
||
;;
|
||
2)
|
||
start_docker_compose
|
||
pause_for_menu
|
||
;;
|
||
0)
|
||
break
|
||
;;
|
||
*)
|
||
echo "❌ Ungültige Auswahl."
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
hotfix_menu() {
|
||
local selection
|
||
while true; do
|
||
echo ""
|
||
cat <<'MENU'
|
||
Hotfix:
|
||
|
||
1) Neuen Hotfix erstellen
|
||
2) Hotfix auf master anwenden (Cherry-Pick)
|
||
3) Hotfix auf dev anwenden (Cherry-Pick)
|
||
4) Hotfix auf Feature-Branches anwenden (Cherry-Pick)
|
||
0) Zurück
|
||
MENU
|
||
read -rp "Auswahl: " selection
|
||
echo ""
|
||
case $selection in
|
||
1)
|
||
create_hotfix_branch
|
||
pause_for_menu
|
||
;;
|
||
2)
|
||
apply_hotfix_to_master
|
||
pause_for_menu
|
||
;;
|
||
3)
|
||
apply_hotfix_to_dev
|
||
pause_for_menu
|
||
;;
|
||
4)
|
||
apply_hotfix_to_features
|
||
pause_for_menu
|
||
;;
|
||
0)
|
||
break
|
||
;;
|
||
*)
|
||
echo "❌ Ungültige Auswahl."
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
create_hotfix_branch() {
|
||
local -a TAGS
|
||
local selection selected_tag hotfix_name
|
||
local version_without_prefix patch_part branch_version branch_name
|
||
local -a ver_parts
|
||
|
||
if ! git diff-index --quiet HEAD --; then
|
||
echo "⚠️ Es gibt noch uncommittete Änderungen. Bitte committen oder stashen, bevor ein Hotfix-Branch erstellt wird."
|
||
return 1
|
||
fi
|
||
|
||
git fetch --tags >/dev/null 2>&1 || true
|
||
mapfile -t TAGS < <(git tag --sort=-version:refname) || true
|
||
|
||
if [[ ${#TAGS[@]} -eq 0 ]]; then
|
||
echo "Keine Tags vorhanden."
|
||
return 1
|
||
fi
|
||
|
||
while true; do
|
||
echo "Verfügbare Tags:"
|
||
local i=1 tag_name
|
||
for tag_name in "${TAGS[@]}"; do
|
||
echo " $i) $tag_name"
|
||
((i++))
|
||
done
|
||
echo " 0) Zurück"
|
||
|
||
read -rp "Auswahl: " selection
|
||
echo ""
|
||
|
||
if [[ "$selection" == "0" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if [[ "$selection" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= ${#TAGS[@]} )); then
|
||
selected_tag="${TAGS[selection-1]}"
|
||
break
|
||
else
|
||
echo "Ungültige Auswahl. Bitte erneut versuchen."
|
||
fi
|
||
done
|
||
|
||
while true; do
|
||
read -rp "Hotfix-Namen eingeben (nur Buchstaben/Zahlen/._- | 0 zum Abbrechen): " hotfix_name
|
||
if [[ "$hotfix_name" == "0" ]]; then
|
||
echo "Abgebrochen."
|
||
return 0
|
||
fi
|
||
if [[ -z "$hotfix_name" ]]; then
|
||
echo "Eingabe darf nicht leer sein."
|
||
continue
|
||
fi
|
||
if [[ "$hotfix_name" =~ [^a-zA-Z0-9._-] ]]; then
|
||
echo "Ungültiger Name. Erlaubt sind Buchstaben, Zahlen, Punkt, Unterstrich oder Bindestrich."
|
||
continue
|
||
fi
|
||
break
|
||
done
|
||
|
||
if [[ $selected_tag != v* ]]; then
|
||
echo "Tag '$selected_tag' entspricht nicht dem erwarteten Format (vX.Y oder vX.Y.Z)."
|
||
return 1
|
||
fi
|
||
|
||
version_without_prefix="${selected_tag#v}"
|
||
IFS='.' read -r -a ver_parts <<< "$version_without_prefix"
|
||
if (( ${#ver_parts[@]} < 2 )); then
|
||
echo "Tag '$selected_tag' besitzt nicht genügend Versionsbestandteile."
|
||
return 1
|
||
fi
|
||
if ! [[ ${ver_parts[0]} =~ ^[0-9]+$ && ${ver_parts[1]} =~ ^[0-9]+$ ]]; then
|
||
echo "Tag '$selected_tag' enthält keine numerische Haupt-/Nebenversion."
|
||
return 1
|
||
fi
|
||
|
||
patch_part=${ver_parts[2]:-0}
|
||
if ! [[ $patch_part =~ ^[0-9]+$ ]]; then
|
||
echo "Tag '$selected_tag' enthält eine ungültige Patch-Version."
|
||
return 1
|
||
fi
|
||
|
||
patch_part=$((patch_part + 1))
|
||
ver_parts[2]=$patch_part
|
||
|
||
local branch_prefix="v${ver_parts[0]}${ver_parts[1]}"
|
||
branch_version="${branch_prefix}.$patch_part"
|
||
branch_name="hotfix/${branch_version}-hotfix_${hotfix_name}"
|
||
|
||
if git show-ref --verify --quiet "refs/heads/$branch_name"; then
|
||
echo "❌ Fehler: Der Branch '$branch_name' existiert lokal bereits."
|
||
return 1
|
||
fi
|
||
if git ls-remote --heads origin "$branch_name" | grep -q "$branch_name"; then
|
||
echo "❌ Fehler: Der Branch '$branch_name' existiert bereits auf Remote."
|
||
return 1
|
||
fi
|
||
|
||
git checkout -b "$branch_name" "$selected_tag"
|
||
git push -u origin "$branch_name"
|
||
|
||
echo "✅ Hotfix-Branch '$branch_name' wurde von Tag '$selected_tag' erstellt und gepusht."
|
||
echo " Basisversion: $branch_version"
|
||
}
|
||
|
||
select_hotfix_branch() {
|
||
local __resultvar=$1
|
||
local selection
|
||
local -a HOTFIX_BRANCHES
|
||
local branch
|
||
local i
|
||
|
||
if [[ -z "$__resultvar" ]]; then
|
||
echo "Interner Fehler: Kein Ausgabe-Parameter übergeben."
|
||
return 1
|
||
fi
|
||
|
||
git fetch origin --prune >/dev/null 2>&1 || true
|
||
mapfile -t HOTFIX_BRANCHES < <(git branch -r --format='%(refname:lstrip=3)' | grep '^hotfix/') || true
|
||
|
||
if [[ ${#HOTFIX_BRANCHES[@]} -eq 0 ]]; then
|
||
echo "Keine Hotfix-Branches gefunden."
|
||
return 1
|
||
fi
|
||
|
||
while true; do
|
||
echo "Verfügbare Hotfix-Branches:"
|
||
i=1
|
||
for branch in "${HOTFIX_BRANCHES[@]}"; do
|
||
echo " $i) $branch"
|
||
((i++))
|
||
done
|
||
echo " 0) Zurück"
|
||
|
||
read -rp "Auswahl: " selection
|
||
echo ""
|
||
|
||
if [[ "$selection" == "0" ]]; then
|
||
echo "Abgebrochen."
|
||
return 1
|
||
fi
|
||
|
||
if [[ "$selection" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= ${#HOTFIX_BRANCHES[@]} )); then
|
||
printf -v "$__resultvar" '%s' "${HOTFIX_BRANCHES[selection-1]}"
|
||
return 0
|
||
else
|
||
echo "Ungültige Auswahl. Bitte erneut versuchen."
|
||
fi
|
||
done
|
||
}
|
||
|
||
apply_hotfix_branch_to_target() {
|
||
local HOTFIX_BRANCH="$1"
|
||
local TARGET_BRANCH="$2"
|
||
local CURRENT_BRANCH REMOTE_HOTFIX base_commit
|
||
local -a commit_list
|
||
local applied_count skipped_count commit commit_desc
|
||
local patch_tmp err_tmp status_output
|
||
|
||
if [[ -z "$HOTFIX_BRANCH" || -z "$TARGET_BRANCH" ]]; then
|
||
echo "Interner Fehler: Hotfix- oder Ziel-Branch fehlt."
|
||
return 1
|
||
fi
|
||
|
||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||
|
||
if ! git diff-index --quiet HEAD --; then
|
||
echo "⚠️ Es gibt uncommittete Änderungen auf $CURRENT_BRANCH. Bitte bereinigen, bevor Hotfixes angewendet werden."
|
||
return 1
|
||
fi
|
||
|
||
git fetch origin "$HOTFIX_BRANCH" >/dev/null 2>&1 || true
|
||
|
||
if git show-ref --verify --quiet "refs/heads/$TARGET_BRANCH"; then
|
||
git checkout "$TARGET_BRANCH"
|
||
git pull origin "$TARGET_BRANCH"
|
||
else
|
||
if ! git ls-remote --heads origin "$TARGET_BRANCH" >/dev/null 2>&1; then
|
||
echo "❌ Ziel-Branch '$TARGET_BRANCH' existiert nicht auf origin."
|
||
return 1
|
||
fi
|
||
git checkout -b "$TARGET_BRANCH" "origin/$TARGET_BRANCH"
|
||
fi
|
||
|
||
if ! git diff-index --quiet HEAD --; then
|
||
echo "⚠️ Bitte stelle sicher, dass $TARGET_BRANCH vor dem Anwenden sauber ist."
|
||
return 1
|
||
fi
|
||
|
||
REMOTE_HOTFIX="origin/$HOTFIX_BRANCH"
|
||
base_commit=$(git merge-base "$TARGET_BRANCH" "$REMOTE_HOTFIX") || true
|
||
|
||
if [[ -z "$base_commit" ]]; then
|
||
echo "❌ Konnte gemeinsamen Stand zwischen $TARGET_BRANCH und $REMOTE_HOTFIX nicht ermitteln."
|
||
return 1
|
||
fi
|
||
|
||
mapfile -t commit_list < <(git rev-list --reverse "$base_commit".."$REMOTE_HOTFIX") || true
|
||
|
||
if [[ ${#commit_list[@]} -eq 0 ]]; then
|
||
echo "ℹ️ $TARGET_BRANCH enthält bereits alle Hotfix-Commits."
|
||
if [[ "$CURRENT_BRANCH" != "$TARGET_BRANCH" ]]; then
|
||
git checkout "$CURRENT_BRANCH"
|
||
fi
|
||
return 0
|
||
fi
|
||
|
||
echo "Übernehme ${#commit_list[@]} Hotfix-Commit(s) nach $TARGET_BRANCH (ohne Commit) ..."
|
||
|
||
applied_count=0
|
||
skipped_count=0
|
||
|
||
for commit in "${commit_list[@]}"; do
|
||
commit_desc=$(git show -s --format='%h %s' "$commit")
|
||
echo "➡️ Übertrage $commit_desc"
|
||
|
||
patch_tmp=$(mktemp)
|
||
err_tmp=$(mktemp)
|
||
|
||
if ! git format-patch -1 --stdout "$commit" > "$patch_tmp"; then
|
||
echo "❌ Konnte Patch für $commit_desc nicht erstellen."
|
||
rm -f "$patch_tmp" "$err_tmp"
|
||
return 1
|
||
fi
|
||
|
||
if git apply --check --index --3way "$patch_tmp" >/dev/null 2>"$err_tmp"; then
|
||
if git apply --index --3way "$patch_tmp" >/dev/null 2>>"$err_tmp"; then
|
||
((applied_count++))
|
||
else
|
||
echo "❌ Fehler beim Anwenden von $commit_desc."
|
||
cat "$err_tmp"
|
||
rm -f "$patch_tmp" "$err_tmp"
|
||
return 1
|
||
fi
|
||
else
|
||
if grep -qi 'already applied' "$err_tmp"; then
|
||
echo "ℹ️ $commit_desc ist bereits in $TARGET_BRANCH enthalten – übersprungen."
|
||
((skipped_count++))
|
||
else
|
||
echo "❌ Konflikte beim Anwenden von $commit_desc."
|
||
cat "$err_tmp"
|
||
rm -f "$patch_tmp" "$err_tmp"
|
||
echo " Bitte Konflikte manuell lösen; die Änderungen verbleiben auf $TARGET_BRANCH."
|
||
return 1
|
||
fi
|
||
fi
|
||
|
||
rm -f "$patch_tmp" "$err_tmp"
|
||
done
|
||
|
||
if (( applied_count > 0 )); then
|
||
echo "✅ Hotfix-Änderungen wurden nach $TARGET_BRANCH übertragen. Es wurde kein Commit erstellt."
|
||
echo " Bitte Änderungen prüfen, bei Bedarf anpassen und manuell committen/pushen."
|
||
else
|
||
echo "ℹ️ Keine neuen Hotfix-Änderungen für $TARGET_BRANCH erforderlich. ($skipped_count übersprungen)"
|
||
fi
|
||
|
||
status_output=$(git status --porcelain)
|
||
if [[ "$CURRENT_BRANCH" != "$TARGET_BRANCH" ]]; then
|
||
if [[ -z "$status_output" ]]; then
|
||
git checkout "$CURRENT_BRANCH"
|
||
else
|
||
echo "ℹ️ Du befindest dich weiterhin auf $TARGET_BRANCH, um die Änderungen zu prüfen."
|
||
echo " Kehre nach Abschluss manuell zu $CURRENT_BRANCH zurück."
|
||
fi
|
||
fi
|
||
}
|
||
apply_hotfix_to_master() {
|
||
local HOTFIX_BRANCH
|
||
if ! select_hotfix_branch HOTFIX_BRANCH; then
|
||
return 0
|
||
fi
|
||
apply_hotfix_branch_to_target "$HOTFIX_BRANCH" "master"
|
||
}
|
||
|
||
apply_hotfix_to_dev() {
|
||
local HOTFIX_BRANCH
|
||
if ! select_hotfix_branch HOTFIX_BRANCH; then
|
||
return 0
|
||
fi
|
||
apply_hotfix_branch_to_target "$HOTFIX_BRANCH" "dev"
|
||
}
|
||
|
||
apply_hotfix_to_features() {
|
||
local HOTFIX_BRANCH
|
||
local -a FEATURE_BRANCHES selection target_branches
|
||
local branch choice i
|
||
|
||
if ! select_hotfix_branch HOTFIX_BRANCH; then
|
||
return 0
|
||
fi
|
||
|
||
mapfile -t FEATURE_BRANCHES < <(git branch -r --format='%(refname:lstrip=3)' | grep '^feature/') || true
|
||
|
||
if [[ ${#FEATURE_BRANCHES[@]} -eq 0 ]]; then
|
||
echo "Keine Feature-Branches gefunden."
|
||
return 0
|
||
fi
|
||
|
||
echo "Verfügbare Feature-Branches:"
|
||
i=1
|
||
for branch in "${FEATURE_BRANCHES[@]}"; do
|
||
echo " $i) $branch"
|
||
((i++))
|
||
done
|
||
echo " 0) Abbrechen"
|
||
|
||
read -rp "Bitte Branch-Auswahl (z.B. 1 3 4 oder 0): " -a selection
|
||
echo ""
|
||
|
||
if [[ ${selection[0]} == "0" ]]; then
|
||
echo "Abgebrochen."
|
||
return 0
|
||
fi
|
||
|
||
for choice in "${selection[@]}"; do
|
||
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#FEATURE_BRANCHES[@]} )); then
|
||
target_branches+=("${FEATURE_BRANCHES[choice-1]}")
|
||
else
|
||
echo "⚠️ Ungültige Auswahl ignoriert: $choice"
|
||
fi
|
||
done
|
||
|
||
if [[ ${#target_branches[@]} -eq 0 ]]; then
|
||
echo "Keine gültigen Feature-Branches ausgewählt."
|
||
return 0
|
||
fi
|
||
|
||
for branch in "${target_branches[@]}"; do
|
||
echo ""
|
||
echo "➡️ Übernehme Hotfix auf $branch"
|
||
if ! apply_hotfix_branch_to_target "$HOTFIX_BRANCH" "$branch"; then
|
||
echo "❌ Abbruch nach Fehler in $branch."
|
||
return 1
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
echo "✅ Hotfix wurde auf alle ausgewählten Feature-Branches angewendet."
|
||
}
|
||
|
||
create_new_feature() {
|
||
local dev_branch="dev"
|
||
local feature_name feature_branch base_commit
|
||
|
||
read -rp "Bitte den Namen des neuen Features eingeben: " feature_name
|
||
if [[ -z "$feature_name" ]]; then
|
||
echo "❌ Fehler: Kein Feature-Name angegeben."
|
||
return 1
|
||
fi
|
||
|
||
if [[ "$feature_name" =~ [^a-zA-Z0-9._-] ]]; then
|
||
echo "❌ Fehler: Ungültiger Branch-Name. Erlaubt sind Buchstaben, Zahlen, Punkt, Unterstrich oder Bindestrich."
|
||
return 1
|
||
fi
|
||
|
||
feature_branch="feature/$feature_name"
|
||
|
||
if ! git diff-index --quiet HEAD --; then
|
||
echo "⚠️ Es gibt noch uncommittete Änderungen. Bitte committen oder stashen, bevor ein neuer Branch erstellt wird."
|
||
return 1
|
||
fi
|
||
|
||
if git show-ref --verify --quiet "refs/heads/$feature_branch"; then
|
||
echo "❌ Fehler: Der Branch '$feature_branch' existiert lokal bereits."
|
||
return 1
|
||
fi
|
||
|
||
if git ls-remote --heads origin "$feature_branch" | grep -q "$feature_branch"; then
|
||
echo "❌ Fehler: Der Branch '$feature_branch' existiert bereits auf Remote."
|
||
return 1
|
||
fi
|
||
|
||
git checkout "$dev_branch"
|
||
git pull origin "$dev_branch"
|
||
|
||
git checkout -b "$feature_branch" "$dev_branch"
|
||
git push -u origin "$feature_branch"
|
||
|
||
base_commit=$(git rev-parse --short HEAD)
|
||
|
||
echo "✅ Neuer Feature-Branch '$feature_branch' wurde erstellt und auf Remote gepusht."
|
||
echo " Basis: $dev_branch@$base_commit"
|
||
}
|
||
|
||
merge_feature_into_dev() {
|
||
local DEV_BRANCH="dev"
|
||
local FEATURE_BRANCH branch_name
|
||
local choice i
|
||
local -a BRANCH_ARRAY
|
||
|
||
mapfile -t BRANCH_ARRAY < <(git branch -r --format='%(refname:lstrip=3)' | grep '^feature/') || true
|
||
|
||
if [[ ${#BRANCH_ARRAY[@]} -eq 0 ]]; then
|
||
echo "Keine Feature-Branches vorhanden."
|
||
return 0
|
||
fi
|
||
|
||
while true; do
|
||
echo "Verfügbare Feature-Branches:"
|
||
i=1
|
||
for branch_name in "${BRANCH_ARRAY[@]}"; do
|
||
echo " $i) $branch_name"
|
||
((i++))
|
||
done
|
||
echo " 0) Zurück"
|
||
|
||
read -rp "Auswahl: " choice
|
||
echo ""
|
||
|
||
if [[ "$choice" == "0" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#BRANCH_ARRAY[@]} )); then
|
||
FEATURE_BRANCH="${BRANCH_ARRAY[choice-1]}"
|
||
break
|
||
else
|
||
echo "Ungültige Auswahl. Bitte erneut versuchen."
|
||
fi
|
||
done
|
||
|
||
echo "Ausgewählter Feature-Branch: $FEATURE_BRANCH"
|
||
|
||
git checkout "$DEV_BRANCH"
|
||
git pull origin "$DEV_BRANCH"
|
||
|
||
if ! git show-ref --verify --quiet "refs/heads/$FEATURE_BRANCH"; then
|
||
git checkout -b "$FEATURE_BRANCH" "origin/$FEATURE_BRANCH"
|
||
else
|
||
git checkout "$FEATURE_BRANCH"
|
||
git pull origin "$FEATURE_BRANCH"
|
||
fi
|
||
|
||
git checkout "$DEV_BRANCH"
|
||
git merge --no-ff "$FEATURE_BRANCH" -m "Merge $FEATURE_BRANCH into $DEV_BRANCH"
|
||
|
||
git push origin "$DEV_BRANCH"
|
||
|
||
echo "Feature $FEATURE_BRANCH wurde erfolgreich in $DEV_BRANCH gemerged."
|
||
}
|
||
|
||
|
||
sync_readme_to_features() {
|
||
local BASE_BRANCH="dev"
|
||
local MASTER_BRANCH="master"
|
||
local -a FEATURE_BRANCHES TARGET_BRANCHES selection
|
||
local -a ALL_BRANCHES
|
||
local choice branch i
|
||
local readme_path="README.md"
|
||
|
||
if [[ ! -f "$readme_path" ]]; then
|
||
echo "❌ README.md nicht gefunden."
|
||
return 1
|
||
fi
|
||
|
||
mapfile -t FEATURE_BRANCHES < <(git branch -r --format='%(refname:lstrip=3)' | grep '^feature/') || true
|
||
ALL_BRANCHES=("${FEATURE_BRANCHES[@]}" "master")
|
||
|
||
if [[ ${#ALL_BRANCHES[@]} -eq 0 ]]; then
|
||
echo "Keine geeigneten Branches gefunden."
|
||
return 0
|
||
fi
|
||
|
||
echo "Verfügbare Branches:"
|
||
i=1
|
||
for branch in "${ALL_BRANCHES[@]}"; do
|
||
echo " $i) $branch"
|
||
((i++))
|
||
done
|
||
echo " 0) Abbrechen"
|
||
|
||
read -rp "Bitte Branch-Auswahl (z.B. 1 3 4 oder 0): " -a selection
|
||
echo ""
|
||
|
||
if [[ ${selection[0]} == "0" ]]; then
|
||
echo "Abgebrochen."
|
||
return 0
|
||
fi
|
||
|
||
for choice in "${selection[@]}"; do
|
||
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#ALL_BRANCHES[@]} )); then
|
||
TARGET_BRANCHES+=("${ALL_BRANCHES[choice-1]}")
|
||
else
|
||
echo "⚠️ Ungültige Auswahl ignoriert: $choice"
|
||
fi
|
||
done
|
||
|
||
if [[ ${#TARGET_BRANCHES[@]} -eq 0 ]]; then
|
||
echo "Keine gültigen Branches ausgewählt."
|
||
return 0
|
||
fi
|
||
|
||
local CURRENT_BRANCH
|
||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||
|
||
git checkout "$BASE_BRANCH"
|
||
git pull origin "$BASE_BRANCH"
|
||
|
||
for branch in "${TARGET_BRANCHES[@]}"; do
|
||
echo "\n➡️ Synchronisiere README in $branch"
|
||
|
||
if ! git show-ref --verify --quiet "refs/heads/$branch"; then
|
||
git checkout -b "$branch" "origin/$branch"
|
||
else
|
||
git checkout "$branch"
|
||
git pull origin "$branch"
|
||
fi
|
||
|
||
git checkout "$BASE_BRANCH" -- "$readme_path"
|
||
git add "$readme_path"
|
||
git commit -m "Sync README from $BASE_BRANCH" || echo "Keine README-Änderungen in $branch"
|
||
git push origin "$branch"
|
||
|
||
git checkout "$BASE_BRANCH"
|
||
done
|
||
|
||
git checkout "$CURRENT_BRANCH"
|
||
echo "\n✅ README wurde in den ausgewählten Feature-Branches aktualisiert."
|
||
}
|
||
|
||
|
||
merge_dev_into_master() {
|
||
local DEV_BRANCH="dev"
|
||
local MASTER_BRANCH="master"
|
||
|
||
# Auf master wechseln und aktuell holen
|
||
git checkout "$MASTER_BRANCH"
|
||
git pull origin "$MASTER_BRANCH"
|
||
|
||
# Dev aktuell holen
|
||
if ! git show-ref --verify --quiet "refs/heads/$DEV_BRANCH"; then
|
||
git checkout -b "$DEV_BRANCH" "origin/$DEV_BRANCH"
|
||
else
|
||
git checkout "$DEV_BRANCH"
|
||
git pull origin "$DEV_BRANCH"
|
||
fi
|
||
|
||
# Merge Dev in Master
|
||
git checkout "$MASTER_BRANCH"
|
||
git merge --no-ff "$DEV_BRANCH" -m "Merge $DEV_BRANCH into $MASTER_BRANCH"
|
||
|
||
# Push Master auf Remote
|
||
git push origin "$MASTER_BRANCH"
|
||
|
||
echo "Branch $DEV_BRANCH wurde in $MASTER_BRANCH gemerged."
|
||
}
|
||
|
||
|
||
push_changes() {
|
||
set -e
|
||
|
||
# Aktuellen Branch herausfinden
|
||
local BRANCH
|
||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||
|
||
# Branch bestätigen
|
||
read -rp "Aktueller Branch: $BRANCH. Ist das korrekt? (y/n): " CONFIRM
|
||
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
|
||
echo "Abgebrochen."
|
||
return 1
|
||
fi
|
||
|
||
# Commit-Nachricht
|
||
local COMMIT_MSG
|
||
read -rp "Bitte Commit-Nachricht eingeben (default: 'Update'): " COMMIT_MSG
|
||
COMMIT_MSG=${COMMIT_MSG:-Update}
|
||
|
||
# Änderungen stagen und committen
|
||
echo "Änderungen werden gestaged..."
|
||
git add .
|
||
echo "Commit wird erstellt..."
|
||
git commit -m "$COMMIT_MSG" || echo "Nichts zu committen"
|
||
|
||
local VERSION_TAG=""
|
||
# Prüfen, ob Branch master ist
|
||
if [[ "$BRANCH" == "master" ]]; then
|
||
# Versionsnummer abfragen (0 bedeutet: kein Tag)
|
||
while true; do
|
||
read -rp "Bitte Versionsnummer für Master-Release Tag eingeben (oder 0 für keinen Tag): " VERSION_TAG
|
||
if [[ -n "$VERSION_TAG" ]]; then
|
||
break
|
||
else
|
||
echo "Eingabe darf nicht leer sein. Bitte eingeben."
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Push Branch
|
||
echo "Push nach origin/$BRANCH..."
|
||
git push -f origin "$BRANCH"
|
||
|
||
# Tag setzen und pushen, nur bei master und wenn nicht 0
|
||
if [[ "$BRANCH" == "master" && "$VERSION_TAG" != "0" ]]; then
|
||
git tag -a "$VERSION_TAG" -m "Release $VERSION_TAG"
|
||
git push origin -f "$VERSION_TAG"
|
||
echo "Tag $VERSION_TAG gesetzt und gepusht."
|
||
else
|
||
if [[ "$BRANCH" == "master" && "$VERSION_TAG" == "0" ]]; then
|
||
echo "Kein Tag gesetzt (manuelle Auswahl: 0)."
|
||
fi
|
||
fi
|
||
|
||
echo "Push abgeschlossen."
|
||
}
|
||
|
||
|
||
|
||
docker_release() {
|
||
local ghcr_username="mboehmlaender"
|
||
local repo_name="stackpulse"
|
||
local branch version_tag
|
||
|
||
|
||
|
||
|
||
while true; do
|
||
read -rp "Bitte Versionsnummer für das Docker-Image eingeben (z.B. v0.1): " version_tag
|
||
if [[ -n "$version_tag" ]]; then
|
||
break
|
||
fi
|
||
echo "Versionsnummer darf nicht leer sein."
|
||
done
|
||
|
||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||
|
||
if [[ "$branch" != "master" ]]; then
|
||
echo "Fehler: Du musst auf 'master' sein, um ein Release zu machen."
|
||
return 1
|
||
fi
|
||
|
||
if [[ -z "$CR_PAT" ]]; then
|
||
echo "CR_PAT (GitHub Token) nicht gesetzt! Bitte export CR_PAT=<token>"
|
||
return 1
|
||
fi
|
||
|
||
echo "$CR_PAT" | docker login ghcr.io -u "$ghcr_username" --password-stdin
|
||
docker build -t "ghcr.io/$ghcr_username/$repo_name:$version_tag" .
|
||
docker tag "ghcr.io/$ghcr_username/$repo_name:$version_tag" "ghcr.io/$ghcr_username/$repo_name:latest"
|
||
|
||
docker push "ghcr.io/$ghcr_username/$repo_name:$version_tag"
|
||
docker push "ghcr.io/$ghcr_username/$repo_name:latest"
|
||
|
||
echo "Docker-Release $version_tag erfolgreich gebaut und zu GHCR gepusht!"
|
||
}
|
||
|
||
|
||
|
||
sync_frontend_build() {
|
||
local source_dir="$1"
|
||
local target_dir="$2"
|
||
|
||
if [[ -z "$source_dir" || -z "$target_dir" ]]; then
|
||
echo "sync_frontend_build benötigt Quell- und Zielverzeichnis."
|
||
return 1
|
||
fi
|
||
|
||
if [[ ! -d "$source_dir" ]]; then
|
||
echo "⚠️ Build-Verzeichnis '$source_dir' wurde nicht gefunden."
|
||
return 1
|
||
fi
|
||
|
||
mkdir -p "$target_dir"
|
||
|
||
if command -v rsync >/dev/null 2>&1; then
|
||
rsync -a --delete "$source_dir"/ "$target_dir"/
|
||
else
|
||
find "$target_dir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||
cp -R "$source_dir"/. "$target_dir"/
|
||
fi
|
||
|
||
echo "✅ Frontend-Build nach '$target_dir' synchronisiert."
|
||
}
|
||
|
||
normalize_semver() {
|
||
local version="$1"
|
||
echo "${version#v}" | sed -E 's/[^0-9.].*$//'
|
||
}
|
||
|
||
version_gte() {
|
||
local current required
|
||
local c_major c_minor c_patch r_major r_minor r_patch
|
||
|
||
current=$(normalize_semver "$1")
|
||
required=$(normalize_semver "$2")
|
||
|
||
IFS='.' read -r c_major c_minor c_patch <<< "$current"
|
||
IFS='.' read -r r_major r_minor r_patch <<< "$required"
|
||
|
||
c_major=${c_major:-0}
|
||
c_minor=${c_minor:-0}
|
||
c_patch=${c_patch:-0}
|
||
r_major=${r_major:-0}
|
||
r_minor=${r_minor:-0}
|
||
r_patch=${r_patch:-0}
|
||
|
||
if (( c_major > r_major )); then return 0; fi
|
||
if (( c_major < r_major )); then return 1; fi
|
||
if (( c_minor > r_minor )); then return 0; fi
|
||
if (( c_minor < r_minor )); then return 1; fi
|
||
if (( c_patch >= r_patch )); then return 0; fi
|
||
return 1
|
||
}
|
||
|
||
load_nvm_if_available() {
|
||
if command -v nvm >/dev/null 2>&1; then
|
||
return 0
|
||
fi
|
||
|
||
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
|
||
if [[ -s "$NVM_DIR/nvm.sh" ]]; then
|
||
# shellcheck source=/dev/null
|
||
. "$NVM_DIR/nvm.sh" >/dev/null 2>&1
|
||
fi
|
||
|
||
command -v nvm >/dev/null 2>&1
|
||
}
|
||
|
||
try_use_project_node() {
|
||
local project_root="$1"
|
||
local required_version="$2"
|
||
local current_version
|
||
|
||
current_version=$(node -v 2>/dev/null || true)
|
||
if [[ -n "$current_version" ]] && version_gte "$current_version" "$required_version"; then
|
||
return 0
|
||
fi
|
||
|
||
if ! load_nvm_if_available; then
|
||
return 1
|
||
fi
|
||
|
||
pushd "$project_root" >/dev/null
|
||
if [[ -f ".nvmrc" ]]; then
|
||
nvm use --silent >/dev/null 2>&1 || true
|
||
fi
|
||
popd >/dev/null
|
||
|
||
current_version=$(node -v 2>/dev/null || true)
|
||
if [[ -n "$current_version" ]] && version_gte "$current_version" "$required_version"; then
|
||
echo "ℹ️ Projekt-Node aktiviert: ${current_version}"
|
||
return 0
|
||
fi
|
||
|
||
return 1
|
||
}
|
||
|
||
ensure_minimum_node_version() {
|
||
local project_root="$1"
|
||
local min_version="20.19.0"
|
||
local current_version
|
||
|
||
if ! command -v node >/dev/null 2>&1; then
|
||
echo "❌ Node.js wurde nicht gefunden. Bitte Node.js >= ${min_version} installieren."
|
||
return 1
|
||
fi
|
||
|
||
try_use_project_node "$project_root" "$min_version" || true
|
||
|
||
current_version=$(node -v 2>/dev/null || true)
|
||
if [[ -z "$current_version" ]]; then
|
||
echo "❌ Konnte die Node.js-Version nicht ermitteln."
|
||
return 1
|
||
fi
|
||
|
||
if ! version_gte "$current_version" "$min_version"; then
|
||
echo "❌ Node.js ${current_version} erkannt. Für StackPulse wird Node.js >= ${min_version} benötigt."
|
||
echo " Projektlokal (ohne andere Projekte zu ändern):"
|
||
echo " nvm install ${min_version} && nvm use"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
ensure_backend_sqlite_binding() {
|
||
if [[ ! -d node_modules/better-sqlite3 ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if node -e "require('better-sqlite3')" >/dev/null 2>&1; then
|
||
return 0
|
||
fi
|
||
|
||
echo "⚠️ better-sqlite3 ist nicht mit der aktuellen Node-Version kompatibel. Versuche Rebuild ..."
|
||
npm rebuild better-sqlite3
|
||
|
||
if ! node -e "require('better-sqlite3')" >/dev/null 2>&1; then
|
||
echo "❌ better-sqlite3 konnte weiterhin nicht geladen werden."
|
||
echo " Bitte im Ordner 'backend' ausführen: rm -rf node_modules package-lock.json && npm install"
|
||
return 1
|
||
fi
|
||
|
||
echo "✅ better-sqlite3 wurde erfolgreich für $(node -v) vorbereitet."
|
||
}
|
||
|
||
start_dev_environment() {
|
||
local back_pid front_pid
|
||
local script_dir project_root
|
||
|
||
echo "🚀 Starte StackPulse Dev-Umgebung..."
|
||
script_dir="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
project_root="$(cd -- "$script_dir/.." && pwd)"
|
||
ensure_minimum_node_version "$project_root" || return 1
|
||
|
||
pushd "$project_root/backend" >/dev/null
|
||
read -rp "➡️ Backend: npm install ausführen? (y/N) " backend_install_choice
|
||
if [[ "$backend_install_choice" =~ ^([YyJj]|[Yy]es|[Jj]a)$ ]]; then
|
||
npm install
|
||
else
|
||
echo "ℹ️ Backend npm install übersprungen."
|
||
fi
|
||
ensure_backend_sqlite_binding || return 1
|
||
npm run migrate
|
||
npm start &
|
||
back_pid=$!
|
||
popd >/dev/null
|
||
|
||
pushd "$project_root/frontend" >/dev/null
|
||
read -rp "➡️ Frontend: npm install ausführen? (y/N) " frontend_install_choice
|
||
if [[ "$frontend_install_choice" =~ ^([YyJj]|[Yy]es|[Jj]a)$ ]]; then
|
||
npm install
|
||
else
|
||
echo "ℹ️ Frontend npm install übersprungen."
|
||
fi
|
||
npm run build
|
||
sync_frontend_build "dist" "../backend/public"
|
||
npm run dev -- --host &
|
||
front_pid=$!
|
||
popd >/dev/null
|
||
|
||
echo ""
|
||
echo "✅ StackPulse läuft lokal:"
|
||
echo "Frontend (Vite Dev): http://localhost:5173"
|
||
echo "Backend API: http://localhost:4001"
|
||
echo "Die gebaute Oberfläche liegt unter backend/public"
|
||
echo "Beenden mit STRG+C"
|
||
|
||
wait "$back_pid" "$front_pid"
|
||
}
|
||
|
||
start_docker_compose() {
|
||
echo "🔄 Starte lokale Docker-Umgebung..."
|
||
docker compose -f docker-compose.dev.yml up --build --force-recreate
|
||
}
|
||
|
||
manage_stash() {
|
||
local current_branch action user_input stash_name choice selected_stash
|
||
local -A stash_map
|
||
local -a stash_list
|
||
local i line stash_ref stash_msg
|
||
|
||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||
|
||
echo "Aktueller Branch: $current_branch"
|
||
echo "Was möchtest du tun?"
|
||
echo "1) Neuen Stash anlegen"
|
||
echo "2) Vorhandenen Stash laden (apply)"
|
||
echo "3) Vorhandenen Stash löschen"
|
||
echo "4) Stash anwenden und löschen (pop)"
|
||
echo "0) Zurück"
|
||
read -rp "Auswahl: " action
|
||
|
||
case $action in
|
||
0)
|
||
return 0
|
||
;;
|
||
1)
|
||
read -rp "Gib einen Namen für den Stash ein: " user_input
|
||
stash_name="$current_branch - $user_input"
|
||
|
||
if git stash list | grep -q "$stash_name"; then
|
||
while IFS= read -r line; do
|
||
stash_ref=$(echo "$line" | awk -F: '{print $1}')
|
||
echo "Lösche vorhandenen Stash: $stash_ref"
|
||
git stash drop "$stash_ref"
|
||
done < <(git stash list | grep "$stash_name")
|
||
fi
|
||
|
||
git stash push -u -m "$stash_name"
|
||
echo "Stash '$stash_name' wurde angelegt."
|
||
;;
|
||
|
||
2|3|4)
|
||
local action_text
|
||
case $action in
|
||
2) action_text="laden" ;;
|
||
3) action_text="löschen" ;;
|
||
4) action_text="anwenden & löschen" ;;
|
||
esac
|
||
|
||
echo "Liste aller Stashes für Branch '$current_branch':"
|
||
mapfile -t stash_list < <(git stash list | grep "$current_branch") || true
|
||
|
||
if [[ ${#stash_list[@]} -eq 0 ]]; then
|
||
echo "Keine Stashes für diesen Branch vorhanden."
|
||
return 0
|
||
fi
|
||
|
||
i=1
|
||
for line in "${stash_list[@]}"; do
|
||
stash_ref=$(echo "$line" | awk -F: '{print $1}')
|
||
stash_msg=$(echo "$line" | cut -d':' -f3- | sed 's/^ //')
|
||
echo " $i) $stash_ref -> $stash_msg"
|
||
stash_map[$i]=$stash_ref
|
||
((i++))
|
||
done
|
||
echo " 0) Zurück"
|
||
|
||
read -rp "Wähle einen Stash zum ${action_text} (Nummer): " choice
|
||
if [[ "$choice" == "0" ]]; then
|
||
return 0
|
||
fi
|
||
if [[ -z "${stash_map[$choice]}" ]]; then
|
||
echo "Ungültige Auswahl!"
|
||
return 1
|
||
fi
|
||
|
||
selected_stash=${stash_map[$choice]}
|
||
|
||
case $action in
|
||
2)
|
||
echo "Wende Stash an: $selected_stash"
|
||
git stash apply "$selected_stash"
|
||
;;
|
||
3)
|
||
echo "Lösche Stash: $selected_stash"
|
||
git stash drop "$selected_stash"
|
||
;;
|
||
4)
|
||
echo "Wende Stash an und lösche ihn: $selected_stash"
|
||
git stash pop "$selected_stash"
|
||
;;
|
||
esac
|
||
;;
|
||
|
||
*)
|
||
echo "Ungültige Auswahl!"
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
|
||
branch_management_menu() {
|
||
local selection
|
||
while true; do
|
||
echo ""
|
||
cat <<'MENU'
|
||
Branch-Verwaltung:
|
||
|
||
1) Branch löschen (lokal/remote)
|
||
2) Branch-Listen aktualisieren
|
||
0) Zurück
|
||
MENU
|
||
read -rp "Auswahl: " selection
|
||
echo ""
|
||
case $selection in
|
||
1)
|
||
delete_branch
|
||
pause_for_menu
|
||
;;
|
||
2)
|
||
update_branch_lists
|
||
pause_for_menu
|
||
;;
|
||
0)
|
||
break
|
||
;;
|
||
*)
|
||
echo "❌ Ungültige Auswahl."
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
delete_branch() {
|
||
local -a local_branches remote_branches all_branches
|
||
local -A has_local has_remote branch_map
|
||
local current_branch branch label selection selected_branch delete_choice
|
||
local i=1
|
||
|
||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||
|
||
mapfile -t local_branches < <(git branch --format='%(refname:short)')
|
||
mapfile -t remote_branches < <(git branch -r | grep -v 'HEAD' | sed 's|^[[:space:]]*origin/||')
|
||
mapfile -t all_branches < <(printf "%s\n" "${local_branches[@]}" "${remote_branches[@]}" | sed '/^$/d' | sort -u)
|
||
|
||
for branch in "${local_branches[@]}"; do
|
||
[[ -n "$branch" ]] && has_local["$branch"]=1
|
||
done
|
||
for branch in "${remote_branches[@]}"; do
|
||
[[ -n "$branch" ]] && has_remote["$branch"]=1
|
||
done
|
||
|
||
if [[ ${#all_branches[@]} -eq 0 ]]; then
|
||
echo "Keine Branches zum Löschen gefunden."
|
||
return 1
|
||
fi
|
||
|
||
echo "Verfügbare Branches zum Löschen:"
|
||
for branch in "${all_branches[@]}"; do
|
||
label=""
|
||
if [[ -n "${has_local[$branch]}" && -n "${has_remote[$branch]}" ]]; then
|
||
label="(lokal & remote)"
|
||
elif [[ -n "${has_local[$branch]}" ]]; then
|
||
label="(nur lokal)"
|
||
else
|
||
label="(nur remote)"
|
||
fi
|
||
|
||
printf " %d) %s %s\n" "$i" "$branch" "$label"
|
||
branch_map[$i]=$branch
|
||
((i++))
|
||
done
|
||
echo " 0) Zurück"
|
||
|
||
read -rp "Wähle einen Branch (Nummer): " selection
|
||
if [[ "$selection" == "0" ]]; then
|
||
echo "Abgebrochen."
|
||
return 0
|
||
fi
|
||
if [[ -z "${branch_map[$selection]}" ]]; then
|
||
echo "Ungültige Auswahl."
|
||
return 1
|
||
fi
|
||
|
||
selected_branch=${branch_map[$selection]}
|
||
|
||
if [[ "$selected_branch" == "$current_branch" && -n "${has_local[$selected_branch]}" ]]; then
|
||
echo "Der aktuell ausgecheckte Branch '$selected_branch' kann nicht gelöscht werden."
|
||
return 1
|
||
fi
|
||
|
||
if [[ -n "${has_local[$selected_branch]}" && -n "${has_remote[$selected_branch]}" ]]; then
|
||
echo ""
|
||
echo "Branch '$selected_branch' existiert lokal und remote."
|
||
echo " 1) Nur lokal löschen"
|
||
echo " 2) Nur remote löschen"
|
||
echo " 3) Lokal und remote löschen"
|
||
read -rp "Auswahl: " delete_choice
|
||
echo ""
|
||
case $delete_choice in
|
||
1)
|
||
delete_choice="local"
|
||
;;
|
||
2)
|
||
delete_choice="remote"
|
||
;;
|
||
3)
|
||
delete_choice="both"
|
||
;;
|
||
*)
|
||
echo "Ungültige Auswahl."
|
||
return 1
|
||
;;
|
||
esac
|
||
elif [[ -n "${has_local[$selected_branch]}" ]]; then
|
||
delete_choice="local"
|
||
else
|
||
delete_choice="remote"
|
||
fi
|
||
|
||
if [[ "$delete_choice" == "local" || "$delete_choice" == "both" ]]; then
|
||
read -rp "Lokalen Branch '$selected_branch' wirklich löschen? (j/N): " confirmation
|
||
if [[ "$confirmation" =~ ^[JjYy]$ ]]; then
|
||
if git branch -D "$selected_branch"; then
|
||
echo "Lokaler Branch '$selected_branch' wurde gelöscht."
|
||
else
|
||
echo "Fehler beim Löschen des lokalen Branches '$selected_branch'."
|
||
return 1
|
||
fi
|
||
else
|
||
echo "Löschen des lokalen Branches abgebrochen."
|
||
[[ "$delete_choice" == "local" ]] && return 0
|
||
fi
|
||
fi
|
||
|
||
if [[ "$delete_choice" == "remote" || "$delete_choice" == "both" ]]; then
|
||
read -rp "Remote-Branch '$selected_branch' auf origin wirklich löschen? (j/N): " confirmation
|
||
if [[ "$confirmation" =~ ^[JjYy]$ ]]; then
|
||
if git push origin --delete "$selected_branch"; then
|
||
echo "Remote-Branch 'origin/$selected_branch' wurde gelöscht."
|
||
else
|
||
echo "Fehler beim Löschen des Remote-Branches '$selected_branch'."
|
||
return 1
|
||
fi
|
||
else
|
||
echo "Löschen des Remote-Branches abgebrochen."
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
update_branch_lists() {
|
||
echo "Aktualisiere Branch-Listen (git fetch origin --prune)..."
|
||
if git fetch origin --prune; then
|
||
echo "Branch-Listen wurden aktualisiert."
|
||
else
|
||
echo "Fehler beim Aktualisieren der Branch-Listen."
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
switch_branch() {
|
||
local -a unversioned_files=("devscripts/*")
|
||
local file
|
||
local local_branches remote_branches all_branches master_branch dev_branch feature_branches hotfix_branches other_branches
|
||
local -a sorted_branches
|
||
local i=1 choice selected_branch
|
||
declare -A branch_map
|
||
|
||
for file in "${unversioned_files[@]}"; do
|
||
if [[ -f "$file" ]]; then
|
||
mkdir -p /tmp/git_safe_backup
|
||
cp "$file" "/tmp/git_safe_backup/$(basename "$file")"
|
||
fi
|
||
done
|
||
local_branches=$(git branch | sed 's/* //' | sed 's/^[[:space:]]*//')
|
||
remote_branches=$(git branch -r | grep -v 'HEAD' | sed 's|^[[:space:]]*origin/||' | sed 's/^[[:space:]]*//')
|
||
all_branches=$(printf "%s\n%s\n" "$local_branches" "$remote_branches" | sort -u)
|
||
|
||
master_branch=$(echo "$all_branches" | grep -x 'master' || true)
|
||
dev_branch=$(echo "$all_branches" | grep -x 'dev' || true)
|
||
feature_branches=$(echo "$all_branches" | grep '^feature/' | sort || true)
|
||
hotfix_branches=$(echo "$all_branches" | grep '^hotfix/' | sort || true)
|
||
other_branches=$(echo "$all_branches" | grep -Ev '^(master|dev|feature/|hotfix/)' | sort || true)
|
||
|
||
[[ -n "$master_branch" ]] && sorted_branches+=("$master_branch")
|
||
[[ -n "$dev_branch" ]] && sorted_branches+=("$dev_branch")
|
||
if [[ -n "$feature_branches" ]]; then
|
||
while IFS= read -r line; do
|
||
[[ -n "$line" ]] && sorted_branches+=("$line")
|
||
done <<< "$feature_branches"
|
||
fi
|
||
if [[ -n "$hotfix_branches" ]]; then
|
||
while IFS= read -r line; do
|
||
[[ -n "$line" ]] && sorted_branches+=("$line")
|
||
done <<< "$hotfix_branches"
|
||
fi
|
||
if [[ -n "$other_branches" ]]; then
|
||
while IFS= read -r line; do
|
||
[[ -n "$line" ]] && sorted_branches+=("$line")
|
||
done <<< "$other_branches"
|
||
fi
|
||
|
||
if [[ ${#sorted_branches[@]} -eq 0 ]]; then
|
||
echo "Keine Branches gefunden."
|
||
return 1
|
||
fi
|
||
|
||
echo "Verfügbare Branches:"
|
||
for line in "${sorted_branches[@]}"; do
|
||
echo " $i) $line"
|
||
branch_map[$i]=$line
|
||
((i++))
|
||
done
|
||
echo " 0) Zurück"
|
||
|
||
read -rp "Wähle einen Branch (Nummer): " choice
|
||
if [[ "$choice" == "0" ]]; then
|
||
rm -rf /tmp/git_safe_backup
|
||
echo "Abgebrochen."
|
||
return 0
|
||
fi
|
||
if [[ -z "${branch_map[$choice]}" ]]; then
|
||
echo "Ungültige Auswahl!"
|
||
rm -rf /tmp/git_safe_backup
|
||
return 1
|
||
fi
|
||
|
||
selected_branch=${branch_map[$choice]}
|
||
echo "Wechsle zu Branch: $selected_branch"
|
||
|
||
git fetch origin
|
||
|
||
if git show-ref --verify --quiet "refs/heads/$selected_branch"; then
|
||
git checkout "$selected_branch"
|
||
else
|
||
git checkout -b "$selected_branch" "origin/$selected_branch"
|
||
fi
|
||
|
||
git reset --hard "origin/$selected_branch"
|
||
git clean -fd
|
||
|
||
for file in "${unversioned_files[@]}"; do
|
||
if [[ -f "/tmp/git_safe_backup/$(basename "$file")" ]]; then
|
||
mkdir -p "$(dirname "$file")"
|
||
mv "/tmp/git_safe_backup/$(basename "$file")" "$file"
|
||
fi
|
||
done
|
||
rm -rf /tmp/git_safe_backup
|
||
|
||
echo "Branch '$selected_branch' ist nun aktiv. Arbeitsverzeichnis entspricht exakt dem Remote-Stand."
|
||
echo "Gesicherte Dateien wurden wiederhergestellt."
|
||
}
|
||
|
||
main() {
|
||
local selection
|
||
while true; do
|
||
echo ""
|
||
show_menu
|
||
read -rp "Auswahl: " selection
|
||
echo ""
|
||
case $selection in
|
||
1)
|
||
create_new_feature
|
||
pause_for_menu
|
||
;;
|
||
2)
|
||
merge_commit_menu
|
||
;;
|
||
3)
|
||
dev_environments_menu
|
||
;;
|
||
4)
|
||
hotfix_menu
|
||
;;
|
||
5)
|
||
branch_management_menu
|
||
;;
|
||
6)
|
||
switch_branch
|
||
pause_for_menu
|
||
;;
|
||
0)
|
||
echo "Auf Wiedersehen!"
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo "❌ Ungültige Auswahl."
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
main "$@"
|