#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

function display_help() {
    uecho "$scriptname: Outil pour gérer des projets

USAGE
    $scriptname cmd [args]

COMMANDS
    getvcs [dir]
        Afficher le type de VCS pour dir.
    getroot [dir]
        Si dir est un répertoire versionné, retourner le répertoire racine du
        projet versionné.
    getrepos [dir]
        Si dir est un répertoire versionné, retourner l'url du repository du
        projet versionné.
    geturl [dir]
        Si dir est un répertoire versionné, retourner son url dans le
        repository.
    fold [dir]
    unfold [dir]
        Utiliser uinc pour défaire (resp. refaire) toutes les inclusions des
        fichiers de dir. Cela nécessite qu'un fichier .udir soit configuré à la
        racine du projet avec uinc=true
    vcs [args]
        Appeler le gestionnaire de gestion approprié avec les arguments donnés.
    add files...
        Ajouter les fichiers files dans le gestionnaire de version.
    remove files...
        Supprimer les fichiers versionnés files.
    copy from to
        Copier le fichier versionné from vers le fichier to.
    move from to
        Renommer le fichier versionné from vers le fichier to.
    mkdir dir
        Créer un nouveau répertoire versionné.
    commit message [files...]
        Enregistrer les modifications (par défaut sur tous les fichiers
        modifiés) avec le commentaire message.
    status
        Afficher l'état des fichiers versionnés et non versionnés.
    update [-x]
        Mettre à jour la copie locale avec la copie sur le serveur.
        -x  Ne pas mettre à jour les références externes (uniquement pour svn)
        -c, --clean-before
            Avant de faire git pull, supprimer toutes les branches locales qui
            sont à jour par rapport à l'origine. La copie de travail doit être
            propre. Basculer sur la branche develop avant de supprimer les
            branches. S'il n'y a pas de branche develop, c'est un NOP.
        -n, --no-autoff
            Ne pas faire de fast-forward automatique pour toutes les branches
            traquées. Par défaut, s'il n'y a pas de modifications locales,
            essayer de fast-fowarder toutes les branches locales traquées.
    diff [options]
        Afficher les différences.
        -l  Afficher les différences non commitées (par défaut)
        -c  Afficher les différences en passe d'être commitées (si appliquable)
        -r REV
            Afficher les différences depuis la révision REV.
        -R  Afficher les modifications effectuées depuis la dernière release.

    clone git@host:path/to/repo [destdir]
    clone http://host/gituser/path/to/repo [destdir]
        Cloner un dépôt distant. Basculer sur la branche develop si elle existe.
        Initialiser git annex si le dépôt contient des fichiers annexés.
        Récupérer aussi ces fichiers avec 'git annex get'
        -n, --no-clone
            Ne pas cloner, afficher simplement ce qui serait fait
        -u, --update
            Si le dépôt a déjà été cloné, le mettre à jour avec git pull
        -m, --master
            Ne pas chercher à basculer sur la branche develop, même si elle
            existe.
        -r, --recursive
            Cloner récursivement tous les dépôt à partir du chemin spécifié
            depuis un serveur gitolite. La signature est alors sensiblement
            différente:
                clone -r git@host:basepath [destdir]
                clone -r http://host/gituser/basepath [destdir]
            L'arborescence en dessous de basepath est recréée à partir de
            destdir, e.g le dépôt git@host:basepath/to/repo est cloné dans
            destdir/to/repo

    crone git@host:path/to/repo [destdir]
    crone http://host/gituser/path/to/repo [destdir]
        Créer un dépôt sur un serveur gitolite, puis le cloner. La commande
        'create' doit avoir été activée sur ce serveur.

    develop
    release
    hotfix
        Démarrer le travail sur une branche respectivement de développement, de
        release, ou de correction de bugs. Lancer chaque commande avec --help
        pour les détails. Nécessite git.

    archive
        Créer une archive du projet courant. Nécessite git.

    annex [args]
        Lancer git annex avec les arguments spécifiés.
    xadd
    xunlock
    xdrop
    xwhereis
    xwebapp
        Chacune de ces commandes est un raccourci vers la commande
        correspondante de git annex, sans le préfixe 'x'
    xsync
        Sur un dépot où git-annex est activé, lancer 'git annex sync' si on est
        en mode indirect ou 'git annex sync --content' si on est en mode direct.
        Sur un dépôt où git-annex n'est pas activé, faire l'équivalent des
        commandes 'git add -A && git commit && git pull && git push'
    xcopy
    xmove
    xget
        Comme ci-dessus, mais si la commande s'exécute sans erreur, lancer
        aussi 'git annex sync'
    xinitial
        Sur un dépôt fraichement cloné, initialiser le dépôt avec 'annex init'
        s'il contient des fichiers annexés. Récupérer aussi ces fichiers avec
        'annex get'
    xconfig-export [dir]
        Installer des hooks pour qu'un dépôt puisse être utilisé pour servir des
        fichiers, par exemple avec un serveur web. Plus précisément, un hook
        post-receive est créé avec la commande 'git annex merge', et un hook
        post-update est créé avec la commande 'git update-server-info'

    printml [-t TYPE]
        Afficher le modeline pour un fichier du type spécifié
    addml [-t TYPE] file
        Ajouter un modele pour le fichier spécifié, s'il n'en a pas déjà un.
        Si nécessaire, forcer le type du fichier au lieu de l'autodétecter
    new [options] file [template options]
        Créer un nouveau fichier à partir d'un modèle.
        Avant le nom du fichier, les options suivantes sont valides:
        -t TEMPLATE
            Spécifier le modèle de fichier à utiliser. Par défaut, le modèle
            à utiliser est déduit de l'extension ou du nom du fichier.
        -e  Editer le fichier après l'avoir créé.
        Après le nom du fichier, toutes les options sont spécifiques au modèle
        utilisé pour créer le nouveau fichier. Utiliser l'option --help pour
        avoir une description des options disponibles."
}

SCRIPT_ALIASES=(
    pv:vcs
    pa:add prm:remove pcp:copy pmv:move pmd:mkdir
    pci:commit pu:update pp:push pdiff:diff
    pclone:clone
    pcrone:crone
    pxx:annex
    pxa:xadd pxu:xunlock pxc:xcopy pxd:xdrop pxm:xmove
    pxg:xget pxs:xsync pxw:xwhereis pxwa:xwebapp
    pxinitial:xinitial
    pnew:new
    pgr:grep
    paddml:addml
)
CMD_ALIASES=(
    getrepo:getrepos repo:getrepos repos:getrepos
    url:geturl
    a:add
    rm:remove del:remove delete:remove
    cp:copy
    mv:move ren:move rename:move
    md:mkdir
    ci:commit
    s:status st:status
    u:update upd:update
    p:push
    version:pver ver:pver
    develop:pdev dev:pdev release:prel rel:prel hotfix:pfix fix:pfix
    archive:pz arch:pz
    xx:annex
    xa:xadd
    xu:xunlock
    xc:xcopy
    xd:xdrop
    xm:xmove
    xg:xget
    xs:xsync
    xw:xwhereis
    xwa:xwebapp
    xce:xconfig-export
    gr:grep
)
DEFAULT_CMD=status
PY_CMDS=(new)
VCS_CMDS=(getvcs getroot getrepos geturl vcs add remove copy move mkdir commit status update push diff tag)
SH_CMDS=(pver pdev prel pfix pz)
GITANNEX_CMDS=(annex xadd xunlock xcopy xdrop xmove xget xsync xwhereis xwebapp xinitial)
ML_CMDS=(printml addml)

if [ "$#" -eq 1 -a "$1" == --nutools-makelinks ]; then
    # créer les liens
    scriptname="$(basename "$0")"
    for alias in p "${SCRIPT_ALIASES[@]}"; do
        alias="${alias%:*}"
        ln -s "$scriptname" "$alias"
    done
    exit 0
fi

source "$(dirname "$0")/lib/ulib/ulib" || exit 1
urequire DEFAULTS modeline vcs

# Traduire le nom du script
for script_alias in "${SCRIPT_ALIASES[@]}"; do
    splitpair "$script_alias" src dest
    if [ "$scriptname" == "$src" ]; then
        eval "set -- $dest \"\$@\""
        break
    fi
done

# Parser les options de uproject
parse_opts + "${PRETTYOPTS[@]}" \
    --help '$exit_with display_help' \
    @ args -- "$@" && set -- "${args[@]}" || die "$args"

# Traduire la commande
[ -n "$*" ] || set -- "$DEFAULT_CMD"
CMD=
found_cmd=
while [ -z "$found_cmd" ]; do
    CMD="$1"; shift; found_cmd=1
    [ -n "$CMD" ] || break

    for cmd_alias in "${CMD_ALIASES[@]}"; do
        splitpair "$cmd_alias" src dest
        if [ "$CMD" == "$src" ]; then
            eval "set -- $dest \"\$@\""
            found_cmd=
            break
        fi
    done
done

################################################################################
# Traiter les commandes

if [ "$CMD" == "grep" ]; then
    ## grep
    if [ $# -eq 1 -a "$1" == "--help" ]; then
        uecho "uproject grep: Lancer une recherche récursive en ignorant les répertoire de controle de version

USAGE
    uproject grep [grep options]"
        exit 0
    fi
    EXCLUDES=(--exclude-dir .svn --exclude-dir CVS --exclude-dir .git --exclude "*.pyc")
    exec grep -r "${EXCLUDES[@]}" "$@"

elif array_contains SH_CMDS "$CMD"; then
    exec "$scriptdir/$CMD" "$@"

elif array_contains ML_CMDS "$CMD"; then
    "$CMD" "$@"

elif array_contains VCS_CMDS "$CMD"; then
    "vcs_$CMD" "$@"

elif array_contains GITANNEX_CMDS "$CMD"; then
    function xsync() {
        if ! git_have_annex; then
            setyesval offline "$UTOOLS_VCS_OFFLINE"
            if git_commit -Al "Maj des fichiers"; then
                [ -n "$offline" ] && return 0
                git_have_remote || return 0
                git pull && git_push
            fi
        elif is_yes "$(git config --get annex.direct)"; then
            git annex add &&
            git annex sync &&
            git annex sync --content &&
            git annex sync
        else
            git annex sync
        fi
    }
    git_annex_use_ssh_wrapper
    case "$CMD" in
    annex) git annex "$@";;
    xsync) xsync;;
    xcopy|xmove|xget) git annex "${CMD#x}" "$@" && git annex sync;;
    xinitial) git_annex_initial "$@";;
    *) git annex "${CMD#x}" "$@";;
    esac

elif [ "$CMD" == clone ]; then
    git_annex_use_ssh_wrapper
    function pclone() {
        estep "$1 --> $(ppath "$2")"
        mkdirof "$2" || return 1
        git clone "$1" "$2" || return 1
        if [ -z "$3" ]; then
            (
                cd "$2"
                if git_have_rbranch develop; then
                    git checkout develop || exit 1
                fi
            ) || return 1
        fi
        git_annex_initial "$2" || return 1
    }
    function gitolite_info() {
        local mode="$1" urlbase="$2" pattern="$3"
        case "$mode" in
        http) curl -fs "$urlbase/info${pattern:+"?$pattern"}";;
        ssh) ssh -q "$urlbase" info ${pattern:+"$pattern"} 2>/dev/null;;
        esac
    }
    function filter_repos() {
        awkrun prefix="$1" '
NR <= 2 { next }
{
  # filtrer les projets qui ne sont pas encore créés
  if (substr($0, 5, 2) == " C") next
  repo = substr($0, 6)
  # filtrer les projets de type wildcard
  if (repo ~ /[\[\]\*]/) next
  # enlever le prefixe
  if (prefix != "" && substr(repo, 1, length(prefix)) != prefix) next
  print repo
}
'
    }

    no_clone=
    update=
    nodevelop=
    recursive=
    parse_opts "${PRETTYOPTS[@]}" \
        -n,--no-clone no_clone=1 \
        -u,--update update=1 \
        -m,--master nodevelop=1 \
        -r,--recursive recursive=1 \
        @ args -- "$@" && set -- "${args[@]}" || die "$args"

    if [ -n "$recursive" ]; then
        repobase="$1"
        [ -n "$repobase" ] || die "Vous devez spécifier l'url de base des dépôts à cloner"
        if [ "${repobase#http://}" != "$repobase" -o "${repobase#https://}" != "$repobase" ]; then
            # accès par http
            mode=http
            splitfsep "$repobase" :// scheme hostuserpath
            splitfsep "$hostuserpath" / host userpath
            splitfsep "$userpath" / user basepath
            [ -n "$host" -a -n "$user" ] || die "Vous devez spécifier l'hôte e.g http://host/git/basepath"
            urlbase="$scheme://$host/$user"
        else
            # accès par ssh
            mode=ssh
            splitfsep "$repobase" : userhost basepath
            splituserhost "$userhost" user host
            [ -n "$user" ] || user=git
            [ -n "$host" ] || die "Vous devez spécifier l'hôte"
            urlbase="$user@$host"
        fi
        basepath="${basepath%/}"
        destbase="${2:-.}"

        git_annex_use_ssh_wrapper
        prefix="${basepath:+$basepath/}"
        array_from_lines repos "$(set -o pipefail; gitolite_info "$mode" "$urlbase" "$prefix" | filter_repos "$prefix")" || die
        for repo in "${repos[@]}"; do
            case "$mode" in
            http) repourl="$urlbase/$repo";;
            ssh) repourl="$urlbase:$repo";;
            esac
            setx destdir=abspath "$destbase/${repo#$prefix}"
            if [ -d "$destdir" ]; then
                if [ -n "$update" ]; then
                    (
                        ${no_clone:+qvals} cd "$destdir"
                        ${no_clone:+qvals} git pull
                    ) || die
                else
                    estepe "$(ppath2 "$destdir"): répertoire existant"
                fi
            elif [ -n "$no_clone" ]; then
                qvals git clone "$repourl" "$destdir"
            else
                pclone "$repourl" "$destdir" "$nodevelop" || die
            fi
        done

    else
        repourl="${1%.git}"
        [ -n "$repourl" ] || die "Vous devez spécifier l'url du dépôt git"

        destdir="$2"
        if [ -z "$destdir" ]; then
            splitfsep "$repourl" : userhost path
            setx destdir=basename -- "$path"
            destdir="${destdir%.git}"
        fi
        setx destdir=abspath "$destdir"

        git_annex_use_ssh_wrapper
        if [ -d "$destdir" ]; then
            if [ -n "$update" ]; then
                (
                    ${no_clone:+qvals} cd "$destdir"
                    ${no_clone:+qvals} git pull
                ) || die
            else
                estepe "$(ppath2 "$destdir"): répertoire existant"
            fi
        elif [ -n "$no_clone" ]; then
            qvals git clone "$repourl" "$destdir"
        else
            pclone "$repourl" "$destdir" "$nodevelop" || die
        fi
    fi

elif [ "$CMD" == crone ]; then
    repourl="${1%.git}"
    [ -n "$repourl" ] || die "Vous devez spécifier l'url du dépôt git"
    if [ "${repourl#http://}" != "$repourl" -o "${repourl#https://}" != "$repourl" ]; then
        # accès par http
        mode=http
        splitfsep "$repourl" :// scheme hostuserpath
        splitfsep "$hostuserpath" / host userpath
        splitfsep "$userpath" / user path
        [ -n "$host" -a -n "$user" ] || die "Vous devez spécifier l'hôte e.g http://host/git/repo"
        hostuser="$scheme://$host/$user"
    else
        # accès par ssh
        mode=ssh
        splitfsep "$repourl" : userhost path
        splituserhost "$userhost" user host
        [ -n "$user" ] || user=git
        [ -n "$host" ] || die "Vous devez spécifier l'hôte"
        userhost="$user@$host"
    fi
    [ -n "$path" ] || die "Vous devez spécifier le chemin du dépôt git"

    destdir="$2"
    if [ -z "$destdir" ]; then
        setx destdir=basename -- "$path"
        destdir="${destdir%.git}"
    fi
    tmpdestdir=
    if [ -d "$destdir" ]; then
        [ -d "$destdir/.git" ] && die "$(ppath2 "$destdir"): un dépôt existe déjà"
        ac_set_tmpdir tmpdestdir
    fi

    if [ "$mode" == http ]; then
        setx result=curl -fs "$hostuser/create?$path" || die
        echo "$result"
        [[ "$result" == FATAL:* ]] && die
        if [ -n "$tmpdestdir" ]; then
            setxx destname=abspath "$destdir" // basename
            git clone "$hostuser/$path" "$tmpdestdir/$destname" || die
            mv "$tmpdestdir/$destname/.git" "$destdir" || die
            ac_clean "$tmpdestdir"
        else
            git clone "$hostuser/$path" "$destdir" || die
        fi
    elif [ "$mode" == ssh ]; then
        git_annex_use_ssh_wrapper
        ssh "$userhost" create "$path" || die
        if [ -n "$tmpdestdir" ]; then
            setxx destname=abspath "$destdir" // basename
            git clone "$userhost:$path" "$tmpdestdir/$destname" || die
            mv "$tmpdestdir/$destname/.git" "$destdir" || die
            ac_clean "$tmpdestdir"
        else
            git clone "$userhost:$path" "$destdir" || die
        fi
    else
        die "bug: mode non prévu"
    fi
    if ask_yesno "Voulez-vous créer des fichiers .gitignore et .gitattributes initiaux?" O; then
        echo >"$destdir/.gitignore"
        echo >"$destdir/.gitattributes" "\
*.zip -delta
*.gz -delta
*.bz2 -delta
*.whl -delta
*.exe -delta"
    fi

elif [ "$CMD" == xconfig-export ]; then
    unset GIT_DIR; unset GIT_WORK_TREE
    dir="${1:-.}"
    [ -d "$dir" ] || die "$dir: répertoire introuvable"
    setx dir=abspath "$dir"
    setx repodir=ppath "$dir"
    cd "$dir"

    git rev-parse 2>/dev/null || die "$repodir: n'est pas un dépôt git"
    [ -n "$(git config --get annex.uuid)" ] || die "$repodir: n'est pas un dépôt git-annex"
    cd "$(__vcs_find_root "$dir")"
    [ -d .git ] || die "$repodir: est un dépôt nu"

    prhook=.git/hooks/post-receive
    prscript='if [ -n "$GIT_DIR" ]; then cd "$GIT_DIR"; cd ..; unset GIT_DIR; fi
git annex merge'
    puhook=.git/hooks/post-update
    puscript='git update-server-info'
    if [ -f "$prhook" ]; then
        ewarn "Le fichier $prhook existe déjà dans $repodir
Vérifiez qu'il contient les commandes suivantes:
--------8<--------
$prscript
--------8<--------"
    else
        estep "post-receive"
        echo "#!/bin/bash
$prscript" >"$prhook"
        chmod +x "$prhook"
    fi
    if [ -f "$puhook" ]; then
        ewarn "Le fichier $puhook existe déjà dans $repodir
Vérifiez qu'il contient les commandes suivantes:
--------8<--------
$puscript
--------8<--------"
    else
        estep "post-update"
        echo "#!/bin/bash
$puscript" >"$puhook"
        chmod +x "$puhook"
    fi

elif array_contains PY_CMDS "$CMD"; then
    exec "$scriptdir/lib/pywrapper" uproject.py "$CMD" "$@"

else
    die "$CMD: commande inconnue"
fi