#!/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/ulib" || exit 1
urequire DEFAULTS 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 par manière particulière.
    -e, --edit
        Editer le fichier CHANGES.txt autogénéré par -u -w
        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. Puis basculer sur la branche master.
        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
    -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."
}

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' \
    @ 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

git_ensure_gitvcs

setx branch=git_get_branch

if [ "$action" == update ]; then
    setx oldver=pver -g ""
    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" "${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
    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[@]}"
        release="release-$minorv"
        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-}"
        ;;
    major)
        setx newver=pver -s "$oldver" -ux "${pver_opts[@]}"
        release="release-$newver"
        ;;
    minor)
        setx newver=pver -s "$oldver" -uz "${pver_opts[@]}"
        release="release-$newver"
        ;;
    patchlevel)
        setx newver=pver -s "$oldver" -up "${pver_opts[@]}"
        release="release-$newver"
        ;;
    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
    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

        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
            is_release_branch "$release" && newver="${release#release-}"
        fi

        if [ "$r" -eq 0 -a -n "$write" ]; then
            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"
            setxp modifs=git log --oneline "$mergebase..$release" // grep -v "Intégration de la branche release-"
            [ -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.txt ]; then
                echo >>"$tmpchanges"
                cat CHANGES.txt >>"$tmpchanges"
            fi
            cat "$tmpchanges" >CHANGES.txt
            ac_clean "$tmpchanges"

            git add -A
            git commit -m "$commitmsg"
        fi
    fi
    if [ "$r" -eq 0 ]; then
        # éventuellement fast-forwarder automatiquement
        git_fast_forward "$release" "" "$origin"
    fi

    if [ "$r" -eq 0 -a -n "$merge" ]; then
        # mettre à jour la branche sur laquelle on se trouve
        setx branch=git_get_branch

        set -- "$release"
        action=merge
    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

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
        is_release_branch "$release" && newver="${release#release-}"
    fi

    if [ -n "$newver" ]; then
        estepn "Création du tag $newver"
        git tag --force "$newver" || die
        if git_have_remote "$origin"; then
            if [ -z "$UTOOLS_VCS_OFFLINE" ]; then
                git push "$origin" tag "$newver"
            else
                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
    fi

    # mettre à jour la branche sur laquelle on se trouve
    git checkout master || die
    setx branch=git_get_branch

    action=delete
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
fi

if [ "$action" == summary ]; then
    setx mergebase=git merge-base master develop
    git log --oneline "$mergebase..develop" | grep -v "Intégration de la branche release-"

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