#!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 if [ $# -eq 1 -a "$1" == --nutools-completion ]; then echo ' function __prel_completion() { local cur _get_comp_words_by_ref cur COMPREPLY=($(compgen -W "$(__prel_branches)" "$cur")) } complete -F __prel_completion prel ' exit 0 fi source "$(dirname -- "$0")/lib/ulib/auto" || exit 1 urequire ptools # XXX Ajouter une option pour fusionner les modifications d'une branche de # pré-release dans develop function display_help() { uecho "$scriptname: basculer sur une branche de release USAGE $scriptname -u [SOURCE] $scriptname -c [RELEASE [SOURCE]] $scriptname -m|-l|-d [RELEASE] - Vérifier s'il n'y a pas de modifications locales. Sinon, proposer de faire un commit ou un stash. - Avec l'option -c, s'il existe une branche de release, proposer de basculer vers elle ou sur la branche master. Sinon, basculer sur la branche master. - Avec l'option -u, proposer ou fixer une branche de release à créer. Si elle existe déjà, basculer vers elle. Sinon, la créer en la basant sur SOURCE, qui vaut par défaut develop OPTIONS -C, --projdir PROJDIR Spécifier le répertoire de base du projet qui est dans git. Par défaut, on travaille dans le répertoire courant et on laisse git trouver le répertoire de base du projet. Avec cette option, le répertoire courant est modifié avant de lancer les commandes git. -O, --origin ORIGIN Spécifier le nom de l'origine. Par défaut, utiliser 'origin' -o, --offline En cas de création d'une branche, ne pas pousser vers l'origine; ne pas tenter le cas échéant de traquer la branche dans l'origine; ne pas supprimer une branche dans l'origine. Cette option est automatiquement activée si la variable UTOOLS_VCS_OFFLINE est définie. --online Annuler l'effet de la variable UTOOLS_VCS_OFFLINE: forcer le mode online -c, --checkout Basculer vers une branche de release existante. C'est l'option par défaut. Si aucune branche de release n'existe, basculer vers master -u, --update Préparer une nouvelle release. Utiliser une des options -x, -z ou -p pour spécifier le type de release à préparer. Si la branche qui serait créée pour le type de release existe déjà, basculer vers cette branche. S'il faut la créer, la baser sur la branche SOURCE, qui vaut par défaut develop --menu -x, --major -z, --minor -p, --patchlevel Utilisé avec l'option -u, soit afficher un menu pour choisir la version de la nouvelle release (par défaut), soit préparer respectivement une release majeure, mineure, ou pour correction de bug. -v-OPT Avec l'option -u, spécifier une option de pver permettant de choisir la version de la nouvelle release. Les options supportées sont -v, -l, -a, -b, -r et -R. Par exemple, si la version actuelle sur la branche master est 0.2.3, les options '-uz -v-lbeta' permettent de préparer la release 0.3.0-beta En principe, cette option n'a pas à être utilisée, puisque dans une branche de release, on peut faire vivre les versions de pré-release jusqu'à la release finale. Ainsi, la branche de release est nommée d'après la version finale, mais le projet peut recevoir une version de pré-release incrémentale. -w, --write Si une nouvelle branche est créée avec -u, mettre à jour le fichier VERSION.txt avec pver. C'est l'option par défaut. -n, --no-write Si une nouvelle branche est créée avec -u, NE PAS mettre à jour le fichier VERSION.txt avec pver. Utiliser cette option si la mise à jour du numéro de version doit être faite d'une manière particulière. -e, --edit Editer le fichier CHANGES.md autogénéré par les options -u -w ou un fichier CHANGES.txt existant. Cette option est surtout utile si -m est utilisé avec -u, pour donner la possibilité de corriger la liste des modifications avant leur enregistrement définitif. -m, --merge Si la branche actuelle est une branche de release, ou s'il existe une branche de release, la merger dans master, puis dans develop, puis la supprimer. A l'issu de cette opération, rester sur la branche develop. S'il n'existe pas de branche de release, proposer de fusionner les modifications de la branche develop dans la branche master, sans préparer de branche de release au préalable. --delete Supprimer une branche de release, à condition qu'elle aie déjà été entièrement fusionnée dans la branch master --force-delete Supprimer une branche de release, même si elle n'a pas encore été fusionnée dans la branche master -s, --summary Afficher la liste des différences entre la branche develop et la branche master, comme elle serait générée par les options -u -w pour le fichier CHANGES.txt (la syntaxe pour CHANGES.md est légèrement différente) -l, --log Afficher les modifications actuellement effectuée dans la branche de release par rapport à develop. -d, --diff Afficher les modifications actuellement effectuée dans la branche de release par rapport à develop, sous forme de diff. OPTIONS AVANCEES --uc, --upgrade-changes Convertir un fichier CHANGES.txt en CHANGES.md" } function show_summary() { git log --oneline --graph "$@" | grep -vF '|\' | grep -vF '|/' | sed 's/\* //; s/^ /+ /' | grep -v "Intégration de la branche release-" | grep -v "Branche develop en version .*-SNAPSHOT" } function format_md() { awk ' $0 == "" || $0 ~ /^#/ { print; next } $1 == "+" { $1 = "*" $2 = "`" $2 "`" print; next } $1 == "|" { $1 = " *" $2 = "`" $2 "`" print; next } { $1 = "* `" $1 "`" print; next } ' } function __get_newver_from_release() { local relver filever is_release_branch "$release" && relver="${release#release-}" setx filever=pver --sw "" --allow-empty if [ -n "$relver" -a "$filever" != "$relver" ]; then newver="$filever" ewarn "La version de la branche de release est différente de la version dans le fichier" enote "La version effectivement sélectionnée est $newver" elif [ -n "$filever" ]; then newver="$filever" elif [ -n "$relver" ]; then newver="$relver" fi } projdir= origin=origin action=auto checkout= update= incversion= pver_opts=() write=1 edit_changes= merge= force_delete= log= diff= parse_opts "${PRETTYOPTS[@]}" \ --help '$exit_with display_help' \ -C:,--projdir: projdir= \ -O:,--origin: origin= \ -o,--offline UTOOLS_VCS_OFFLINE=1 \ --online UTOOLS_VCS_OFFLINE= \ -c,--checkout checkout=1 \ -u,--update '$update=1; [ -z "$incversion" ] && incversion=auto' \ --menu '$update=1; incversion=menu' \ -x,--major '$update=1; incversion=major' \ -z,--minor '$update=1; incversion=minor' \ -p,--patchlevel '$update=1; incversion=patchlevel' \ -v: '$update=1; add@ pver_opts' \ -w,--write write=1 \ -n,--no-write write= \ -e,--edit edit_changes=1 \ -m,--merge merge=1 \ --delete action=delete \ --force-delete '$action=delete; force_delete=1' \ -s,--summary action=summary \ -l,--log '$action=diff; log=1' \ -d,--diff '$action=diff; diff=1' \ --upgrade-changes,--uc action=upgrade-changes \ @ args -- "$@" && set -- "${args[@]}" || die "$args" if [ "$action" == auto ]; then if [ -n "$update" ]; then action=update elif [ -n "$merge" ]; then action=merge elif [ -n "$checkout" ]; then action=checkout else # checkout par défaut action=checkout fi fi if [ -n "$projdir" ]; then cd "$projdir" || die fi if [ "$action" == upgrade-changes ]; then [ -f CHANGES.txt ] || die "CHANGES.txt: fichier introuvable" format_md CHANGES.md || die rm CHANGES.txt enote "Conversion CHANGES.txt --> CHANGES.md effectuée" exit 0 fi git_ensure_gitvcs if [ -f .prel-noauto -a -n "$update" -a -n "$merge" ]; then die "Vous ne pouvez pas faire de release automatique sur ce dépôt. Vous devez utiliser -u et -m séparement." fi setx vertype=pver --sw "" --show-source [[ "$vertype" == pom* ]] && maven_update=1 || maven_update= push_branches=() push_tags=() push_deferred= [ -n "$UTOOLS_VCS_OFFLINE" ] && push_deferred= || push_deferred=1 [ -n "$update" -a -n "$merge" ] && UTOOLS_VCS_OFFLINE=1 setx branch=git_get_branch update_opt= if [ "$action" == update ]; then git_ensure_cleancheckout if [ -n "$maven_update" ]; then setx oldver=pver --gw develop: enote "La version de la branche develop est $oldver" else setx oldver=pver --gw master: fi newver= if [ "$incversion" == auto ]; then if [ ${#pver_opts[*]} -gt 0 ]; then # des options ont été spécifiées, les honorer setx newver=pver -s "$oldver" ${maven_update:+--maven-update -R} "${pver_opts[@]}" release="release-$newver" else # sinon, prendre une décision en fonction des branches de release # qui existent déjà setx -a branches=list_release_branches "$origin" if [ ${#branches[*]} -eq 0 ]; then # en l'absence de branche de release, proposer d'en créer une incversion=menu elif [ ${#branches[*]} -eq 1 ]; then # s'il n'y en a qu'une, la prendre release="${branches[0]}" else # sinon, donner le choix dans un menu array_add branches master default_branch="$branch" array_contains branches "$default_branch" || default_branch="${branches[0]}" simple_menu release branches -d "$default_branch" \ -t "Basculer vers une branche de release" \ -m "Veuillez choisir la branche vers laquelle basculer" fi fi fi pver_opts=(${maven_update:+--maven-update -R} "${pver_opts[@]}") case "$incversion" in menu) setx majorv=pver -s "$oldver" -ux "${pver_opts[@]}" setx minorv=pver -s "$oldver" -uz "${pver_opts[@]}" setx patchlevelv=pver -s "$oldver" -up "${pver_opts[@]}" if [ -z "$maven_update" ]; then release="release-$minorv" else release="release-$patchlevelv" fi branches=("release-$majorv" "release-$minorv" "release-$patchlevelv" master) simple_menu release branches \ -t "Basculer vers une nouvelle branche de release" \ -m "Veuillez choisir la branche à créer" [ "$release" != master ] && newver="${release#release-}" if [ "$release" == "release-$majorv" ]; then update_opt=-x elif [ "$release" == "release-$minorv" ]; then update_opt=-z elif [ "$release" == "release-$patchlevelv" ]; then update_opt=-p fi ;; major) setx newver=pver -s "$oldver" -ux "${pver_opts[@]}" release="release-$newver" update_opt=-x ;; minor) setx newver=pver -s "$oldver" -uz "${pver_opts[@]}" release="release-$newver" update_opt=-z ;; patchlevel) setx newver=pver -s "$oldver" -up "${pver_opts[@]}" release="release-$newver" update_opt=-p ;; esac set -- "$release" "$1" action=checkout fi if [ "$action" == checkout ]; then release="$1" source="${2:-develop}" if [ -z "$release" ]; then setx -a branches=list_release_branches "$origin" if [ ${#branches[*]} -eq 0 ]; then # en l'absence de branche de release, basculer sur master release=master elif [ ${#branches[*]} -eq 1 ]; then # s'il n'y en a qu'une, la prendre release="${branches[0]}" else # sinon, donner le choix dans un menu array_add branches master default_branch="$branch" array_contains branches "$default_branch" || default_branch="${branches[0]}" simple_menu release branches -d "$default_branch" \ -t "Basculer vers une branche de release" \ -m "Veuillez choisir la branche vers laquelle basculer" fi fi # On est peut-être déjà sur la bonne branche if git_is_branch "$release"; then if [ -z "$UTOOLS_VCS_OFFLINE" ]; then git_track_branch "$release" "$origin" fi git_fast_forward "$release" "" "$origin" exit 0 fi # Créer/basculer vers une release branch git_ensure_cleancheckout is_any_branch "$release" master release || die "$release: ce n'est pas une branche de release" r=0 newbranch= if git_have_branch "$release"; then git checkout "$release"; r=$? elif git_have_rbranch "$release"; then git checkout "$release"; r=$? else estepn "\ Vous allez créer la nouvelle branche de release ${COULEUR_VERTE}$release${COULEUR_NORMALE} à partir de la branche source ${COULEUR_BLEUE}$source${COULEUR_NORMALE}" ask_yesno "Voulez-vous continuer?" O || die newbranch=1 git_ensure_branch "$release" "$source" "$origin" [ $? -eq 2 ] && die "Impossible de créer la branche $release. Veuillez vérifier que la branche $source existe" git checkout "$release"; r=$? if [ -z "$newver" ]; then # le cas échéant, tenter de calculer la version en fonction de la release __get_newver_from_release fi if [ "$r" -eq 0 -a -n "$write" ]; then set cwd=pwd setx workdir=git rev-parse --show-toplevel [ -d "$workdir" ] && cd "$workdir" commitmsg="Init changelog" if [ -n "$newver" ]; then pver -uv "$newver" && commitmsg="Init changelog & version $newver" fi version="$newver" [ -n "$version" ] || version="$release" changelog="## Version $version du $(date +%d/%m/%Y-%H:%M)" setx mergebase=git merge-base master "$release" tmpcmd=(show_summary "$mergebase..$release") if [ -f CHANGES.txt ]; then changes=CHANGES.txt else changes=CHANGES.md array_add tmpcmd // format_md fi setxp modifs "${tmpcmd[@]}" [ -n "$modifs" ] && changelog="$changelog $modifs" ac_set_tmpfile tmpchanges echo "$changelog" >>"$tmpchanges" if [ -n "$edit_changes" ]; then estep "Lancement d'un éditeur pour vérifier la liste des modifications" "${EDITOR:-vi}" "$tmpchanges" fi if [ -f "$changes" ]; then echo >>"$tmpchanges" cat "$changes" >>"$tmpchanges" fi cat "$tmpchanges" >"$changes" ac_clean "$tmpchanges" git add -A git commit -m "$commitmsg" cd "$cwd" fi fi if [ "$r" -eq 0 ]; then # éventuellement fast-forwarder automatiquement git_fast_forward "$release" "" "$origin" fi set -- "$release" if [ "$r" -eq 0 -a -n "$merge" ]; then # mettre à jour la branche sur laquelle on se trouve setx branch=git_get_branch action=merge elif [ "$r" -eq 0 -a -n "$newbranch" ]; then action=push array_addu push_branches "$release" else exit "$r" fi fi setx -a branches=list_release_branches setb have_release_branches=[ ${#branches[*]} -gt 0 ] release="$1" if [ -n "$release" ]; then if [ -n "$have_release_branches" ]; then is_release_branch "$release" || die "$release: ce n'est pas une branche de release" elif ! is_develop_branch "$release"; then die "Aucune branche de release n'existe, vous devez fusionner à partir de develop" fi git_have_branch "$release" || die "$release: branche introuvable" elif is_release_branch "$branch"; then release="$branch" fi final_branch= # branche sur laquelle se placer après avoir fusionné la branche de release if [ "$action" == merge ]; then confirm= if [ -z "$release" ]; then if [ ${#branches[*]} -eq 0 ]; then ewarn "Aucune branche de release n'a été préparée." ewarn "La branche develop sera fusionnée directement dans master." release=develop confirm=-y elif [ ${#branches[*]} -eq 1 ]; then release="${branches[0]}" estepn "Autosélection de $release" else default_release="$branch" array_contains branches "$default_release" || default_release="${branches[0]}" simple_menu release branches -d "$default_release" \ -t "Choix de la branche de release" \ -m "Veuillez choisir la branche" fi fi estepn "Intégration ${COULEUR_VERTE}$release${COULEUR_NORMALE} --> ${COULEUR_BLEUE}master${COULEUR_NORMALE}" ask_yesno $confirm "Voulez-vous continuer?" O || die git checkout master || die git merge "$release" -m "Intégration de la branche $release" --no-ff || die if [ -z "$newver" ]; then # le cas échéant, tenter de calculer la version en fonction de la release __get_newver_from_release fi if [ -n "$newver" ]; then estepn "Création du tag $newver" array_addu push_tags "$newver" git tag --force "$newver" || die if git_have_remote "$origin"; then if [ -z "$UTOOLS_VCS_OFFLINE" ]; then git push "$origin" tag "$newver" elif [ -z "$push_deferred" ]; then eimportant "Le tag $newver n'a pas été poussé vers l'origine. Il faudra le faire manuellement avec la commande suivante: $(qvals git push "$origin" tag "$newver") ou celle-ci pour pour pousser TOUS les tags: $(qvals git push "$origin" --tags)" fi fi fi if [ "$release" != develop ]; then estepn "Intégration ${COULEUR_VERTE}$release${COULEUR_NORMALE} --> ${COULEUR_BLEUE}develop${COULEUR_NORMALE}" git checkout develop git merge "$release" -m "Intégration de la branche $release" --no-ff || die if [ -n "$maven_update" ]; then [ -n "$update_opt" ] || update_opt=-p pver -u $update_opt -S git add -A git commit -m "Branche develop en version $(pver --show)" || die fi fi # mettre à jour la branche sur laquelle on se trouve git checkout master || die setx branch=git_get_branch action=delete final_branch=develop fi if [ "$action" == delete -a "$release" != develop ]; then if [ -z "$force_delete" ]; then # vérifier que la branche a été fusionnée git_is_merged "$release" master || die "Refus de supprimer la branche $release: elle n'a pas été fusionnée dans master" git_is_merged "$release" develop || die "Refus de supprimer la branche $release: elle n'a pas été fusionnée dans develop" fi if [ "$branch" == "$release" ]; then # si on est sur la branche en question, en sortir pour pouvoir la # supprimer git checkout master || die fi estepi "Suppression de la branche locale" git branch -D "$release" if git_have_remote "$origin"; then if [ -z "$UTOOLS_VCS_OFFLINE" ]; then estepi "Suppression de la branche distante" git push "$origin" ":$release" elif git_have_rbranch "$release" "$origin"; then eimportant "\ La branche $origin/$release n'a plus lieu d'être, mais la configuration actuelle interdit de la supprimer. Veuillez le faire manuellement avec la commande suivante: $(qvals git push "$origin" ":$release")" fi fi action=push array_addu push_branches master array_addu push_branches develop fi if [ "$action" == push -a -n "$push_deferred" ]; then if git_have_remote "$origin"; then estepi "Mise à jour de l'origine" for tag in "${push_tags[@]}"; do git push "$origin" tag "$tag" done for branch in "${push_branches[@]}"; do setx rbranch=git_get_branch_rbranch "$branch" "$origin" rbranch="${rbranch#refs/remotes/$origin/}" git push "$origin" "$branch:$rbranch" done fi fi if [ "$action" == summary ]; then setx mergebase=git merge-base master develop show_summary "$mergebase..develop" elif [ "$action" == diff ]; then if [ -n "$log" ]; then if [ -n "$release" ]; then git log ${diff:+-p} master.."$release" else git log ${diff:+-p} master..develop fi elif [ -n "$diff" ]; then if [ -n "$release" ]; then git diff master.."$release" else git diff master..develop fi fi fi # en fin de traitement, revenir le cas échéant sur $final_branch if [ -n "$final_branch" ]; then git checkout "$final_branch" || die fi