113 lines
5.0 KiB
Bash
Executable File
113 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# git-hotfix <remote> <branche> -m "message"
|
|
#
|
|
# Sans changer de branche à la main. Tu es sur la branche de dev avec un
|
|
# correctif en cours (non commité). Le script :
|
|
# 1. met tes modifs de côté (stash),
|
|
# 2. bascule sur la branche d'env et y applique le correctif,
|
|
# 3. le committe (message obligatoire),
|
|
# 4. le rapatrie (merge) dans la branche de dev,
|
|
# 5. push en dernière étape (atomique) puis te ramène sur la branche de dev.
|
|
#
|
|
# Sécurité : toutes les mutations locales sont faites AVANT tout push.
|
|
# En cas d'échec local, l'arbre est restauré à l'identique (tes modifs
|
|
# reviennent en l'état sur la branche de dev) et rien n'est poussé.
|
|
#
|
|
# Config git-native :
|
|
# git config deploy.source <branche-dev> (défaut : master)
|
|
# git config deploy.push true|false (défaut : false — push désactivé)
|
|
set -euo pipefail
|
|
|
|
DIR=$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)
|
|
# shellcheck source=_deploy_lib.sh
|
|
. "$DIR/_deploy_lib.sh"
|
|
|
|
usage() { echo 'Usage : git hotfix <remote> <branche> -m "message" (depuis la branche de dev)'; }
|
|
|
|
SOURCE=$(deploy_source)
|
|
PUSH=$(deploy_push)
|
|
|
|
MSG=""; MSG_SET=0; POS=()
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-m|--message) MSG="${2:-}"; MSG_SET=1; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
-*) usage; die "Option inconnue : $1" ;;
|
|
*) POS+=("$1"); shift ;;
|
|
esac
|
|
done
|
|
REMOTE="${POS[0]:-}"; BRANCH="${POS[1]:-}"
|
|
{ [ -n "$REMOTE" ] && [ -n "$BRANCH" ]; } || { usage; exit 1; }
|
|
|
|
# ---- Validations pré-vol (aucune mutation) ---------------------------------
|
|
git remote get-url "$REMOTE" >/dev/null 2>&1 || die "Remote inconnu : $REMOTE"
|
|
git show-ref --verify --quiet "refs/heads/$BRANCH" || die "Branche locale inconnue : $BRANCH"
|
|
[ "$BRANCH" != "$SOURCE" ] || die "'$SOURCE' est la branche de dev, pas une cible de hotfix."
|
|
current=$(git branch --show-current)
|
|
[ "$current" = "$SOURCE" ] || die "Lance 'git hotfix' depuis la branche de dev '$SOURCE' (git switch $SOURCE)."
|
|
assert_clean_git_state
|
|
[ -n "$(git status --porcelain)" ] || die "Aucune modification à hotfixer sur '$SOURCE'."
|
|
{ [ "$MSG_SET" = 1 ] && [ -n "$MSG" ]; } || die 'Message requis : -m "…".'
|
|
|
|
# ---- Confirmation ----------------------------------------------------------
|
|
if [ "$PUSH" = true ]; then push_line="OUI vers '$REMOTE' ('$BRANCH')"; else push_line="NON (désactivé)"; fi
|
|
confirm " • stash des modifs de '$SOURCE'
|
|
• application + commit sur '$BRANCH' : \"$MSG\"
|
|
• rapatriement (merge) dans '$SOURCE'
|
|
• push : $push_line"
|
|
|
|
# ---- Sauvegarde de l'état pour rollback ------------------------------------
|
|
SAVED_SOURCE=$(git rev-parse --verify "refs/heads/$SOURCE")
|
|
SAVED_BRANCH=$(git rev-parse --verify "refs/heads/$BRANCH")
|
|
STASH_MADE=0
|
|
|
|
rollback() {
|
|
set +e
|
|
echo "↩ Restauration de l'état initial…" >&2
|
|
local gd; gd=$(git rev-parse --git-dir)
|
|
[ -e "$gd/MERGE_HEAD" ] && git merge --abort
|
|
git reset --hard -q
|
|
git update-ref "refs/heads/$SOURCE" "$SAVED_SOURCE"
|
|
git update-ref "refs/heads/$BRANCH" "$SAVED_BRANCH"
|
|
git switch -q "$SOURCE" 2>/dev/null || git switch -q --force "$SOURCE"
|
|
git reset --hard -q "$SAVED_SOURCE"
|
|
if [ "$STASH_MADE" = 1 ]; then
|
|
# le stash est conservé tant qu'on ne l'a pas 'drop' : on le remet en place
|
|
git stash pop -q 2>/dev/null \
|
|
|| echo "⚠ Tes modifs sont sauvegardées dans le stash (git stash list)." >&2
|
|
fi
|
|
echo "✓ État initial restauré. Aucun push effectué." >&2
|
|
}
|
|
fail() { echo "✗ $1" >&2; rollback; exit 1; }
|
|
|
|
# ---- Mutations locales (aucun réseau) --------------------------------------
|
|
git stash push --include-untracked -q -m "git-hotfix: $MSG" || fail "Échec du stash."
|
|
STASH_MADE=1
|
|
git switch -q "$BRANCH" || fail "Impossible de basculer sur '$BRANCH'."
|
|
# 'apply' (et non 'pop') : le stash reste dispo pour un rollback propre en cas de conflit.
|
|
git stash apply -q || fail "Conflit en appliquant le correctif sur '$BRANCH'."
|
|
git add -A
|
|
git commit -q -m "$MSG" || fail "Échec du commit sur '$BRANCH'."
|
|
git switch -q "$SOURCE" || fail "Impossible de revenir sur '$SOURCE'."
|
|
git merge --no-edit -q "$BRANCH" || fail "Conflit en rapatriant dans '$SOURCE'."
|
|
|
|
# Local OK : le correctif est commité sur les deux branches → on lâche le stash.
|
|
git stash drop -q 2>/dev/null || true
|
|
STASH_MADE=0
|
|
echo "✓ Local OK : hotfix sur '$BRANCH' + rapatrié dans '$SOURCE'."
|
|
|
|
# ---- Push : dernière étape, atomique, jamais forcé -------------------------
|
|
if [ "$PUSH" = true ]; then
|
|
echo "→ Vérification du push (dry-run)…"
|
|
git push --dry-run "$REMOTE" "$BRANCH" \
|
|
|| { echo "✗ Push rejeté au dry-run (non fast-forward ?). RIEN poussé. Local conservé." >&2; exit 1; }
|
|
echo "→ Push '$BRANCH' → '$REMOTE'…"
|
|
git push "$REMOTE" "$BRANCH" \
|
|
|| { echo "✗ Échec du push. RIEN poussé. Local conservé." >&2; \
|
|
echo " Relance : git push $REMOTE $BRANCH" >&2; exit 1; }
|
|
echo "✓ Poussé sur '$REMOTE'."
|
|
else
|
|
echo "→ (push désactivé) aurait fait : git push $REMOTE $BRANCH"
|
|
fi
|
|
echo "✓ Terminé. Tu es sur '$SOURCE'."
|