##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## Fonctions pour gérer les outils de vcs (subversion, git)
##@cooked nocomments
##@require base
uprovide vcs
urequire base

################################################################################
# Général 

function __vcs_get_type() {
    # Afficher le type de vcs du répertoire $1. L'arborescence n'est pas
    # parcourue. Si l'information n'est pas immédiatement disponible, cette
    # fonction échoue.
    # retourner 1 si le type n'a pas été trouvé
    [ -d "$1/.git" ] && { echo "git"; return; }
    [ -d "$1/.svn" ] && { echo "svn"; return; }
    [ -d "$1/CVS" ] && { echo "cvs"; return; }
    return 1
}

function __vcs_find_type() {
    # Chercher le type de vcs du répertoire $1. L'arborescence jusqu'au
    # répertoire $HOME est parcourue si nécessaire. $1 doit être un répertoire
    # absolu.
    # Retourner 1 si le type n'a pas été trouvé
    local root="$1"
    # soit on a l'information immédiatement...
    __vcs_get_type "$root" && return
    # soit il faut la chercher en parcourant l'arborescence...
    while [ -n "$root" -a "$root" != "/" -a "$root" != "." -a "$root" != "$HOME" ]; do
        __vcs_get_type "$root" && return
        root="$(dirname "$root")"
    done
    return 1
}

function __vcs_upward_until() {
    local root="$1"
    local type="$2"
    while [ -n "$root" -a "$root" != "/" -a "$root" != "." -a "$(__vcs_get_type "$root/..")" == "$type" ]; do
        root="$(dirname "$root")"
    done
    echo "$root"
}

function __vcs_find_root() {
    # A partir du répertoire $1, chercher le répertoire de base du checkout
    # local. $1 doit être un répertoire absolu.
    # retourner 1 si le type n'a pas été trouvé
    local root="$1"
    local type="$(__vcs_get_type "$root")"
    if [ "$type" == "git" ]; then
        # si le type est git, nous sommes forcément déjà à la racine
        echo "$root"
        return
    elif [ "$type" == "svn" -o "$type" == "cvs" ]; then
        # trouver le répertoire le plus haut
        __vcs_upward_until "$root" "$type"
        return
    else
        # sinon, il faut chercher dans les répertoire au dessus jusqu'à trouver
        # un repository. On arrête la recherche à $HOME
        local found
        while [ -n "$root" -a "$root" != "/" -a "$root" != "." -a "$root" != "$HOME" ]; do
            type="$(__vcs_get_type "$root")" && { found=1; break; }
            root="$(dirname "$root")"
        done
        if [ -n "$found" ]; then
            if [ "$type" == "git" ]; then
                echo "$root"
                return
            elif [ "$type" == "svn" -o "$type" == "cvs" ]; then
                __vcs_upward_until "$root" "$type"
                return
            fi
        fi
    fi
    return 1
}

function _vcs_get_dir() {
    abspath "${1:-.}"
}
function _vcs_get_type() {
    __vcs_find_type "$1" || {
        eerror "$(ppath "$1"): Ce répertoire n'est pas versionné"
        return 1
    }
}

function _vcs_unsupported() {
    eerror "${1:+$1: }Opération ou option non supportée"
    return 1
}
function _vcs_invalid_copy() {
    eerror "Vous ne pouvez copier/déplacer qu'un fichier sur un fichier"
    return 1
}
function _vcs_check_nbargs() {
    [ "$1" -ge "$2" ] || {
        eerror "Cette commande nécessite au moins $2 argument(s)"
        return 1
    }
    if [ -n "$3" ]; then
        [ "$1" -le "$3" ] || {
            eerror "Cette commande supporte au plus $3 argument(s)"
            return 1
        }
    fi
    return 0
}

function _vcs_showhelp() {
    local action func
    action="$1"; shift
    func="vcs_${action}_help"
    [ $# == 1 -a "$1" == "--help" ] && { "$func" "$action"; return; }
    return 1
}

function _vcs_dispatch() {
    local dir action type func
    dir="$(_vcs_get_dir "$1")"; shift
    action="$1"; shift
    _vcs_showhelp "$action" "$@" && return
    type="$(_vcs_get_type "$dir")" || return
    func="${type}_${action}"
    "$func" "$@"
}

########################################
function vcs_getvcs_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Afficher le type de vcs pour le répertoire spécifié

USAGE
    uproject $1 [dir]"
}
function vcs_getvcs() {
    _vcs_showhelp getvcs "$@" && return
    __vcs_find_type "$(_vcs_get_dir "$1")"
}
########################################
function vcs_getroot_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Afficher le répertoire racine du projet versionné correspondant au répertoire spécifié

USAGE
    uproject $1 [dir]"
}
function vcs_getroot() {
    _vcs_showhelp getvcs "$@" && return
    __vcs_find_root "$(_vcs_get_dir "$1")"
}
########################################
function vcs_getrepos_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Afficher l'url du repository du projet versionné correspondant au répertoire spécifié

USAGE
    uproject $1 [dir]"
}
function vcs_getrepos() {
    _vcs_dispatch "$1" getrepos "$@"
}
########################################
function vcs_geturl_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Afficher l'url dans le repository du répertoire versionné spécifié

USAGE
    uproject $1 [dir]"
}
function vcs_geturl() {
    _vcs_dispatch "$1" geturl "$@"
}
########################################
function vcs_vcs_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Appeler le gestionnaire de version approprié avec les arguments donnés

USAGE
    uproject $1 [args]"
}
function vcs_vcs() {
    _vcs_showhelp vcs "$@" && return

    local type
    type="$(_vcs_get_type "$(_vcs_get_dir)")" || return
    "$type" "$@"
}
########################################
function vcs_add_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Ajouter les fichiers spécifiés dans le gestionnaire de version

USAGE
    uproject $1 <files...>

OPTIONS"
}
function vcs_add() {
    # le répertoire de référence est le répertoire du premier fichier ajouté
    _vcs_dispatch "$(dirname "$1")" add "$@"
}
########################################
function vcs_remove_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Suppprimer les fichiers versionnés spécifiés du gestionaire de version

USAGE
    uproject $1 <files...>

OPTIONS"
}
function vcs_remove() {
    # le répertoire de référence est le répertoire du premier fichier supprimé
    _vcs_dispatch "$(dirname "$1")" remove "$@"
}
########################################
function vcs_copy_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Copier les fichiers versionnés spécifiés

USAGE
    uproject $1 <sources...> <dest>

OPTIONS"
}
function vcs_copy() {
    # le répertoire de référence est le répertoire de destination
    local last=$#
    _vcs_dispatch "$(dirname "${!last}")" copy "$@"
}
########################################
function vcs_move_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Déplacer les fichiers versionnés spécifiés

USAGE
    uproject $1 <sources...> <dest>

OPTIONS"
}
function vcs_move() {
    # le répertoire de référence est le répertoire de destination
    local last=$#
    _vcs_dispatch "$(dirname "${!last}")" move "$@"
}
########################################
function vcs_mkdir_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Créer un nouveau répertoire versionné

USAGE
    uproject $1

OPTIONS"
}
function vcs_mkdir() {
    # le répertoire de référence est le répertoire du premier répertoire créé
    _vcs_dispatch "$(dirname "$1")" mkdir "$@"
}
########################################
function vcs_commit_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Enregistrer les modifications sur les fichiers modifiés avec le message spécifiés

USAGE
    uproject $1 <message> [files...]

Si files n'est pas spécifié, prendre tous les fichiers modifiés actuellement

OPTIONS
    -a  Enregistrer les modifications sur tous les fichiers modifiés.
    -c  Enregistrer uniquement les modifications de l'index. (si applicable)
    -l  Garder le commit local, i.e. les modifications ne sont pas pushées sur
        le serveur. (si applicable)"
}
function vcs_commit() {
    _vcs_dispatch "" commit "$@"
}
########################################
function vcs_status_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Afficher l'état des fichiers versionnés et non versionnés

USAGE
    uproject $1"
}
function vcs_status() {
    _vcs_dispatch "" status "$@"
}
########################################
function vcs_update_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Mettre à jour le checkout local avec les modifications présentes sur le serveur

USAGE
    uproject $1

OPTIONS
    -x  Ne pas mettre à jour les références externes"
}
function vcs_update() {
    _vcs_dispatch "" update "$@"
}
########################################
function vcs_diff_help() {
    local OENC="$UTF8"
    uecho "uproject $1: Afficher les différences

USAGE
    uproject $1

OPTIONS
    -l  (par défaut) Afficher les différences non commitées
    -c  Si cela a du sens, afficher les différences en passe d'être commitées
    -R  Afficher les modifications effectuées depuis la dernière release
Pour le moment, l'option -R n'est pas implémentée"
}
function vcs_diff() {
    _vcs_dispatch "" diff "$@"
}
########################################
function vcs_tag_help() {
    local OENC="$UTF8"
    uecho "uproject $1: donner un nom symbolique à la révision courante

USAGE
    uproject $1 <name>"
}
function vcs_tag() {
    _vcs_dispatch "" tag "$@"
}
########################################
# XXX Ajouter vcs_revert, vcs_resolve, uproject_grep

################################################################################
# Git

function git_getrepos() {
    git config --get remote.origin.url
}
function git_geturl() {
    git config --get remote.origin.url
}
function git_add() {
    git add "$@"
}
function git_remove() {
    git rm "$@"
}
function git_copy() {
    git cp "$@"
}
function git_move() {
    git mv "$@"
}
function git_mkdir() {
    git mkdir "$@"
}
function git_commit() {
    local all=auto nopush args
    normyesval nopush "$NOPUSH"
    parse_opts + "${PRETTYOPTS[@]}" \
        -a all=1 \
        -c all= \
        -l nopush=1 \
        @ args -- "$@" && set -- "${args[@]}" || {
        eerror "$args"
        return 1
    }

    local message="$1"; shift
    local -a cmd
    cmd=(git commit)
    [ -n "$message" ] && cmd=("${cmd[@]}" -m "$message")
    if [ "$all" == "auto" ]; then
        # Si des fichiers sont spécifiés, prendre ceux-là.
        if [ -z "$*" ]; then
            # Sinon, s'il y a des fichiers dans l'index, commiter uniquement ces
            # fichiers
            # Sinon, committer tous les fichiers modifiés
            # le code suivant retourne vrai si l'index contient au moins fichier
            git status --porcelain 2>/dev/null | awk '
                BEGIN { ec = 1 }
                substr($0, 1, 1) ~ /[^ ?]/ { ec = 0; exit }
                END { exit ec }' ||
            cmd=("${cmd[@]}" -a)
        fi
    else
        [ -n "$all" ] && cmd=("${cmd[@]}" -a)
    fi        
    "${cmd[@]}" "$@"
    if [ -z "$nopush" ]; then
        [ -n "$(git config --get remote.origin.url)" ] &&
        git push origin master
    fi
}
function git_status() {
    git status "$@"
}
function git_update() {
    local dummy args
    parse_opts + "${PRETTYOPTS[@]}" \
        -x dummy \
        @ args -- "$@" && set -- "${args[@]}" || {
        eerror "$args"
        return 1
    }

    git pull "$@"
}
function git_diff() {
    local dummy cached args
    parse_opts + "${PRETTYOPTS[@]}" \
        -l dummy \
        -r: '$_vcs_unsupported -r' \
        -c cached \
        -R '$_vcs_unsupported -R' \
        @ args -- "$@" && set -- "${args[@]}" || {
        eerror "$args"
        return 1
    }

    git diff ${cached:+--cached} "$@"
}
function git_tag() {
    _vcs_unsupported tag #XXX
}

################################################################################
# Subversion

function svn_getrepos() {
    LANG=C svn info "${@:-.}" | awk '/^Repository Root: / { print substr($0, 18) }'
}
function svn_geturl() {
    LANG=C svn info "${@:-.}" | awk '/^URL: / { print substr($0, 6) }'
}
function svn_add() {
    svn add "$@"
}
function svn_remove() {
    svn delete "$@"
}
function svn_copy() {
    svn copy --parents "$@"
}
function svn_move() {
    svn move --parents "$@"
}
function svn_mkdir() {
    svn mkdir --parents "$@"
}
function svn_commit() {
    local message="$1"; shift
    local -a cmd
    cmd=(svn commit)
    [ -n "$message" ] && cmd=("${cmd[@]}" -m "$message")
    cmd=("${cmd[@]}" "${@:-.}")
    "${cmd[@]}"
}
function svn_status() {
    svn status "$@"
}
function svn_update() {
    local ignore_externals args
    parse_opts + "${PRETTYOPTS[@]}" \
        -x ignore_externals \
        @ args -- "$@" && set -- "${args[@]}" || {
        eerror "$args"
        return 1
    }

    svn update ${ignore_externals:+--ignore-externals} "$@"
}
function svn_diff() {
    local dummy revision args tmpfile0 tmpfile
    parse_opts + "${PRETTYOPTS[@]}" \
        -l dummy \
        -r: revision \
        -c '$_vcs_unsupported -c' \
        -R '$_vcs_unsupported -R' \
        @ args -- "$@" && set -- "${args[@]}" || {
        eerror "$args"
        return 1
    }

    ac_set_tmpfile tmpfile0
    ac_set_tmpfile tmpfile
    LANG=C svn status 2>/dev/null | awk '/^$/ { next } /^(M|X|Performing status on external item at)/ { next } { print }' >"$tmpfile0"
    if [ -s "$tmpfile0" ]; then
        echo "Fichiers non modifiés dans un état spécial:" >>"$tmpfile"
        cat "$tmpfile0" >>"$tmpfile"
        echo "===================================================================" >>"$tmpfile"
    fi
    ac_clean "$tmpfile0"

    svn diff -x -u ${revision:+-r "$revision"} "$@" >>"$tmpfile"

    if [ -s "$tmpfile" ]; then
        "${PAGER:-less}" "$tmpfile"
        ac_clean "$tmpfile"
        return 0
    else
        einfo "Aucune différence détectée"
        ac_clean "$tmpfile"
        return 1
    fi
}
function svn_tag() {
    _vcs_unsupported tag #XXX
}

################################################################################
# CVS

function cvs_getrepos() {
    _vcs_unsupported getrepos
}
function cvs_geturl() {
    _vcs_unsupported geturl
}
function cvs_add() {
    cvs add "$@"
}
function cvs_remove() {
    rm "$@"
    cvs remove "$@"
}

function cvs_copy() {
    _vcs_check_nbargs $# 2 2 || return
    local src="$1" dest="$2"
    if [ -d "$dest" ]; then
        # copie d'un fichier vers un répertoire
        cp "$src" "$dest" || return
        cvs add "$dest/$(basename "$src")"
    elif [ -f "$src" ]; then
        # copie d'un fichier vers un fichier
        cp "$src" "$dest" || return
        cvs add "$dest"
    else
        _vcs_invalid_copy
        return 1
    fi
}
function cvs_move() {
    _vcs_check_nbargs $# 2 2 || return
    local src="$1" dest="$2"
    if [ -d "$dest" ]; then
        # copie d'un fichier vers un répertoire
        mv "$src" "$dest" || return
        cvs remove "$src"
        cvs add "$dest/$(basename "$src")"
    elif [ -f "$src" ]; then
        # copie d'un fichier vers un fichier
        mv "$src" "$dest" || return
        cvs remove "$src"
        cvs add "$dest"
    else
        _vcs_invalid_copy
        return 1
    fi
}
function cvs_mkdir() {
    mkdir -p "$@"
    cvs add "$@"
}
function cvs_commit() {
    local message="$1"; shift
    local -a cmd
    cmd=(cvs commit)
    [ -n "$message" ] && cmd=("${cmd[@]}" -m "$message")
    cmd=("${cmd[@]}" "${@:-.}")
    "${cmd[@]}"
}
function cvs_status() {
    cvs update "$@"
}
function cvs_update() {
    local dummy args
    parse_opts + "${PRETTYOPTS[@]}" \
        -x dummy \
        @ args -- "$@" && set -- "${args[@]}" || {
        eerror "$args"
        return 1
    }

    cvs update "$@"
}
function cvs_diff() {
    local dummy revision args tmpfile
    parse_opts + "${PRETTYOPTS[@]}" \
        -l dummy \
        -r: revision \
        -c '$_vcs_unsupported -c' \
        -R '$_vcs_unsupported -R' \
        @ args -- "$@" && set -- "${args[@]}" || {
        eerror "$args"
        return 1
    }
    ac_set_tmpfile tmpfile
    cvs diff -u ${revision:+-r "$revision"} "$@" >"$tmpfile"

    if [ -s "$tmpfile" ]; then
        "${PAGER:-less}" "$tmpfile"
        ac_clean "$tmpfile"
        return 0
    else
        ac_clean "$tmpfile"
        einfo "Aucune différence détectée"
        return 1
    fi
}
function cvs_tag() {
    local cwd rootdir
    cwd="$(pwd)"
    rootdir="$(__vcs_find_root "$cwd")"
    cvs tag "$1" "$(relpath "$rootdir" "$cwd")"
}