#!/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.

    -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.
    -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
update=
merge=
checkout=
incversion=
pver_opts=()
write=1
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= \
    -m,--merge merge=1 \
    -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
    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
            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 release branch" \
                    -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 -g "$oldver" -up "${pver_opts[@]}"
        release="release-$minorv"
        branches=("release-$majorv" "release-$minorv" "release-$patchlevelv" master)
        simple_menu release branches \
            -t "Basculer vers une nouvelle release branch" \
            -m "Veuillez choisir la branche à créer"
        ;;
    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 -g "$oldver" -up "${pver_opts[@]}"
        release="release-$newver"
        ;;
    esac

    if [ -z "$newver" ]; then
        # le cas échéant, tenter de calculer la version en fonction de la
        # release
        [[ "$release" == release-* ]] && newver="${release#release-}"
    fi

    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

        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 release branch" \
                -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
        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 release branch"
    r=0
    if git_have_branch "$release"; then
        git checkout "$release"; r=$?
    else
        estepn "\
Vous allez créer la nouvelle release branch ${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 [ "$r" -eq 0 -a -n "$newver" -a -n "$write" ]; then
            if pver -uv "$newver"; then
                git add -A
                git commit -m "Initialiser la version $newver"
            fi
        fi
    fi

    if [ "$r" -eq 0 -a -n "$merge" ]; then
        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 release branch"
    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 invalide"
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 release branch 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 release branch" \
                -m "Veuillez choisir la branche"
        fi
    fi

    estepn "\
Intégration de la branche ${COULEUR_VERTE}$release${COULEUR_NORMALE}
dans la branche de destination ${COULEUR_BLEUE}master${COULEUR_NORMALE}"
    ask_yesno $confirm "Voulez-vous continuer?" O || die

    git checkout master
    git merge "$release" -m "Intégration de la branche $release" --no-ff || die

    if [ "$release" != develop ]; then
        estepn "\
Intégration de la branche ${COULEUR_VERTE}$release${COULEUR_NORMALE}
dans la branche de destination ${COULEUR_BLEUE}develop${COULEUR_NORMALE}"
        git checkout develop
        git merge "$release" -m "Intégration de la branche $release" --no-ff || die

        git checkout master

        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"
            else
                eimportant "\
La branche $release n'a plus lieu d'être, mais la configuration actuelle interdit de la supprimer dans le dépôt distant.
Veuillez le faire manuellement avec la commande suivante:
    $(qvals git push "$origin" ":$release")"
            fi
        fi
    fi

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