##@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] note: les commandes suivantes permettent de lancer des opérations avancées: $(array_to_lines __VCS_GIT_ADVANCED "" " ")" } function vcs_vcs() { _vcs_showhelp vcs "$@" && return local i f t for i in "${__VCS_GIT_ADVANCED_MAP[@]}"; do splitpair "$i" f t if [ "$1" == "$f" ]; then shift set -- "$t" "$@" break fi done if array_contains __VCS_GIT_ADVANCED "$1"; then "$@" return $? fi 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 " } 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 " } 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 " } 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 " } 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" } 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 [files...] Si files n'est pas spécifié, prendre tous les fichiers modifiés actuellement OPTIONS -a Enregistrer les modifications sur les fichiers modifiés uniquement. -A Enregistrer les modifictions sur les nouveaux fichiers et les fichiers modifiés. -c Enregistrer uniquement les modifications de l'index. (si applicable) -p Pousser les modifications sur le serveur après le commit (par défaut) -l Garder le commit local, i.e. les modifications ne sont pas poussé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_push_help() { local OENC="$UTF8" uecho "uproject $1: Pousser les modifications locales sur le serveur USAGE uproject $1" } function vcs_push() { _vcs_dispatch "" push "$@" } ######################################## 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 REVISION Afficher les différences depuis la révision spécifiée -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 " } 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 allnew push=auto nopush args normyesval nopush "$UTOOLS_NOPUSH" [ -n "$nopush" ] && push= parse_opts + "${PRETTYOPTS[@]}" \ -a,--all all=1 \ -A,--all-new allnew=1 \ -c,--cached all= \ -p,--push push=1 \ -l,--local push= \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 1 } if [ -n "$allnew" ]; then git add -A all= fi 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 if ! "${cmd[@]}" "$@"; then [ "$push" == auto ] && return 1 fi if [ "$push" == auto ]; then git_push --auto || return elif [ -n "$push" ]; then git_push --force || return fi return 0 } function git_status() { git status "$@" } function git_update() { local args parse_opts + "${PRETTYOPTS[@]}" \ -x '$_vcs_unsupported -x' \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 1 } git pull "$@" } function git_push() { local all auto force args parse_opts + "${PRETTYOPTS[@]}" \ -a,--all all=1 \ --auto auto=1 \ -f,--force force=1 \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 1 } if [ $# -gt 0 ]; then # si des arguments sont spécifiés, les passer à git sans modification git push "$@" return $? elif [ -n "$all" ]; then # On a demandé à pusher toutes les branches git push --all "$@" return $? fi # sinon on push vers origin. vérifier la présence du remote [ -n "$(git config --get remote.origin.url)" ] || { if [ -n "$auto" ]; then # en mode automatique, ignorer l'absence de remote return 0 else eerror "Aucun remote origin n'est défini" return 1 fi } # puis calculer la branche à pusher local branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" local origin="$(git config --get "branch.$branch.remote")" if [ -n "$branch" -a "$origin" == origin ]; then if [ -n "$auto" ]; then # en mode automatique, ne pousser que la branche courante git push origin "$branch" || return else # pousser toutes les branches git push || return fi elif [ -n "$force" ]; then git push || return fi return 0 } function git_diff() { local dummy cached args parse_opts + "${PRETTYOPTS[@]}" \ -l dummy \ -c cached \ -r: '$_vcs_unsupported -r' \ -R '$_vcs_unsupported -R' \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 1 } git diff ${cached:+--cached} "$@" } function git_tag() { _vcs_unsupported tag #XXX } # Fonctions avancées de git __VCS_GIT_ADVANCED_MAP=( cg:git_check_gitvcs eg:git_ensure_gitvcs lbs:git_list_branches rbs:git_list_rbranches hlb:git_have_branch hrb:git_have_rbranch gb:git_get_branch ib:git_is_branch hr:git_have_remote tb:git_track_branch cc:git_check_cleancheckout ec:git_ensure_cleancheckout ) __VCS_GIT_ADVANCED=( git_check_gitvcs git_ensure_gitvcs git_list_branches git_list_rbranches git_have_branch git_have_rbranch git_get_branch git_is_branch git_have_remote git_track_branch git_check_cleancheckout git_ensure_cleancheckout ) function git_check_gitvcs() { [ "$(_vcs_get_type "$(_vcs_get_dir)")" == git ] } function git_ensure_gitvcs() { git_check_gitvcs || die "Ce n'est pas un dépôt git" } function git_list_branches() { git for-each-ref refs/heads/ --format='%(refname:short)' | csort } function git_list_rbranches() { git for-each-ref "refs/remotes/${1:-origin}/" --format='%(refname:short)' | csort } function git_have_branch() { git_list_branches | grep -qF "$1" } function git_have_rbranch() { git_list_rbranches "${2:-origin}" | grep -qF "$1" } function git_get_branch() { git rev-parse --abbrev-ref HEAD 2>/dev/null } function git_is_branch() { [ "$(git_get_branch)" == "${1:-master}" ] } function git_have_remote() { [ -n "$(git config --get remote.${1:-origin}.url)" ] } function git_track_branch() { local branch="$1" origin="${2:-origin}" [ -n "$branch" ] || return git_have_remote "$origin" || return [ "$(git config --get branch.$branch.remote)" == "$origin" ] && return git branch -t --set-upstream "$branch" "$origin/$branch" } function git_ensure_branch() { # retourner 0 si la branche a été créée, 1 si elle existait déjà, 2 en cas d'erreur local branch="$1" source="${2:-master}" origin="${3:-origin}" [ -n "$branch" ] || return 2 git_have_branch "$branch" && return 1 if git_have_rbranch "$branch" "$origin"; then # une branche du même nom existe dans l'origine. faire une copie de cette branche git branch -t "$branch" "$origin/$branch" || return 2 else # créer une nouvelle branche du nom spécifié git_have_branch "$source" || return 2 git branch "$branch" "$source" || return 2 if git_have_remote "$origin"; then git push "$origin" "$branch" && git_track_branch "$branch" "$origin" fi fi return 0 } function git_check_cleancheckout() { # vérifier qu'il n'y a pas de modification locales dans le dépôt # correspondant au répertoire courant. [ -z "$(git status --porcelain 2>/dev/null)" ] } function git_ensure_cleancheckout() { git_check_cleancheckout || die "Vous avez des modifications locales. Enregistrez ces modifications avant de continuer" } # fonctions pour git annex function git_annex_initial() { # sur le dépôt $1 fraichement cloné, vérifier s'il faut faire git annex # init. Si oui, l'initialiser avec le nom d'hôte, et récupérer tous les # fichiers annexés # retourner 1 si une erreur s'est produite local repodir="${1:-.}" [ -d "$repodir" ] || return 1 repodir="$(abspath "$repodir")" local GIT_DIR GIT_WORK_TREE [ "$(cd "$repodir"; git rev-parse --is-bare-repository)" == false ] || return 0 [ -n "$(GIT_DIR="$repodir/.git" git config annex.uuid)" ] && return 0 # ici, on sait que git annex n'a pas encore été configuré # vérifier s'il existe des fichiers annexés local -a links array_from_lines links "$(find "$repodir" -type l)" local link hasannex= for link in "${links[@]}"; do link="$(readlink "$link")" if [ "${link#.git/annex/}" != "$link" ]; then hasannex=1 break elif [[ "$link" == */.git/annex/* ]]; then hasannex=1 break fi done if [ -n "$hasannex" ]; then progexists git-annex || { eerror "Vous devez installer git-annex" return 1 } (cd "$repodir" git annex init "$MYHOSTNAME" && git annex get && git annex sync ) || return 1 fi } ################################################################################ # 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 dummy args parse_opts + "${PRETTYOPTS[@]}" \ -a,--all dummy \ -A,--all-new '$_vcs_unsupported -A' \ -c,--cached '$_vcs_unsupported -c' \ -p,--push dummy \ -l,--local '$_vcs_unsupported -l' \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 1 } 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_push() { local args parse_opts + "${PRETTYOPTS[@]}" \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 1 } } 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 dummy args parse_opts + "${PRETTYOPTS[@]}" \ -a,--all dummy \ -A,--all-new '$_vcs_unsupported -A' \ -c,--cached '$_vcs_unsupported -c' \ -p,--push dummy \ -l,--local '$_vcs_unsupported -l' \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 1 } 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_push() { local args parse_opts + "${PRETTYOPTS[@]}" \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 1 } } 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")" }