nutools/ulib/vcs

697 lines
18 KiB
Bash

##@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...>"
}
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...>"
}
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>"
}
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>"
}
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 <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 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 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 push=auto nopush args
normyesval nopush "$UTOOLS_NOPUSH"
[ -n "$nopush" ] && push=
parse_opts + "${PRETTYOPTS[@]}" \
-a,--all all=1 \
-c,--cached all= \
-p,--push push=1 \
-l,--local push= \
@ 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
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 dummy args
parse_opts + "${PRETTYOPTS[@]}" \
-x dummy \
@ 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 \
-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_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 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")"
}