gitploy: outillage de déploiement git (deploy + hotfix transactionnels)

This commit is contained in:
2026-09-07 11:27:03 +02:00
commit 73b8910c73
6 changed files with 437 additions and 0 deletions
Executable
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# git-deploy <remote> <branche> -m "message"
#
# Depuis la branche de dev : commit (message obligatoire s'il y a des modifs)
# → merge dans la branche d'env → push (dernière étape, atomique) → retour dev.
#
# Sécurité : toutes les mutations locales sont faites AVANT tout push.
# En cas d'échec local, l'arbre est restauré à l'identique 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 "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=_deploy_lib.sh
. "$DIR/_deploy_lib.sh"
usage() { echo 'Usage : git deploy <remote> <branche> -m "message"'; }
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 branche de déploiement."
current=$(git branch --show-current)
[ "$current" = "$SOURCE" ] || die "Place-toi sur '$SOURCE' pour déployer (git switch $SOURCE)."
assert_clean_git_state
dirty=0; [ -n "$(git status --porcelain)" ] && dirty=1
if [ "$dirty" = 1 ]; then
{ [ "$MSG_SET" = 1 ] && [ -n "$MSG" ]; } || die 'Des modifs sont présentes : message requis (-m "…").'
fi
# ---- Confirmation ----------------------------------------------------------
if [ "$PUSH" = true ]; then push_line="OUI vers '$REMOTE' ('$BRANCH' + '$SOURCE', atomique)"; else push_line="NON (désactivé)"; fi
[ "$dirty" = 1 ] && commit_line="commit sur '$SOURCE' : \"$MSG\"" || commit_line="aucun commit (arbre propre)"
confirm " • $commit_line
• merge '$SOURCE' → '$BRANCH'
• 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")
COMMITTED=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/$BRANCH" "$SAVED_BRANCH"
git switch -q "$SOURCE" 2>/dev/null || git switch -q --force "$SOURCE"
if [ "$COMMITTED" = 1 ]; then
# défait le commit mais garde les fichiers modifiés (état d'origine)
git reset --mixed -q "$SAVED_SOURCE"
else
git reset --hard -q "$SAVED_SOURCE"
fi
echo "✓ État initial restauré. Aucun push effectué." >&2
}
fail() { echo "✗ $1" >&2; rollback; exit 1; }
# ---- Mutations locales (aucun réseau) --------------------------------------
if [ "$dirty" = 1 ]; then
git add -A
git commit -q -m "$MSG" || fail "Échec du commit sur '$SOURCE'."
COMMITTED=1
echo "✓ Commit sur '$SOURCE' : $MSG"
fi
git switch -q "$BRANCH" || fail "Impossible de basculer sur '$BRANCH'."
git merge --no-edit -q "$SOURCE" || fail "Conflit de merge sur '$BRANCH'."
git switch -q "$SOURCE" || fail "Impossible de revenir sur '$SOURCE'."
echo "✓ Local OK : '$BRANCH' à jour depuis '$SOURCE'."
# ---- Push : dernière étape, atomique, jamais forcé -------------------------
if [ "$PUSH" = true ]; then
echo "→ Vérification du push (dry-run)…"
git push --atomic --dry-run "$REMOTE" "$BRANCH" "$SOURCE" \
|| { echo "✗ Push rejeté au dry-run (non fast-forward ?). RIEN poussé. Local conservé." >&2; exit 1; }
echo "→ Push atomique '$BRANCH' + '$SOURCE' → '$REMOTE'…"
git push --atomic "$REMOTE" "$BRANCH" "$SOURCE" \
|| { echo "✗ Échec du push. RIEN poussé (atomique). Local conservé." >&2; \
echo " Relance : git push --atomic $REMOTE $BRANCH $SOURCE" >&2; exit 1; }
echo "✓ Poussé sur '$REMOTE'."
else
echo "→ (push désactivé) aurait fait : git push --atomic $REMOTE $BRANCH $SOURCE"
fi
echo "✓ Terminé. Tu es sur '$SOURCE'."