#!/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/auto" || exit 1 urequire 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 function cxone_init() { repourl="$1" [ -n "$repourl" ] || return rname= rtype=gitolite rprefix= REPO_PREFIXES=() REPO_TYPES=() set_defaults repoctl # Traduire les aliases éventuels local asrcdest asrc adest for asrcdest in "${REPO_PREFIXES[@]}"; do splitfsep "$asrcdest" = asrc adest if [ "${repourl#$asrc}" != "$repourl" ]; then newurl="$adest${repourl#$asrc}" if [ "$newurl" != "$repourl" ]; then enote "$repourl --> $newurl" repourl="$newurl" break fi fi done local rnametypeprefix tmp found for rnametypeprefix in "${REPO_TYPES[@]}"; do splitfsep "$rnametypeprefix" : rname tmp splitfsep "$tmp" : rtype rprefix if [ "${repourl#$rprefix}" != "$repourl" ]; then found=1 break fi done if [ -z "$found" ]; then rname= rtype=gitolite rprefix= fi } function curlto() { local url="$1"; shift local payload="$1"; shift local outfile="$1"; shift local tmpfile if [ -z "$outfile" ]; then ac_set_tmpfile tmpfile outfile="$tmpfile" fi local -a args local r http_code args=(-s -w '%{http_code}' -o "$outfile") [ -n "$payload" ] && args+=(-d "$payload") args+=("$@" "$url") setx http_code=curl "${args[@]}" case "$http_code" in 2*) r=0;; 4*) r=1;; 5*) r=3;; *) r=11;; esac if [ -n "$tmpfile" ]; then cat "$tmpfile" ac_clean "$tmpfile" fi upvar http_code "$http_code" return "$r" } 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 "modifs.mineures sans commentaires"; 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" cxone_init "$@" [ -n "$repourl" ] && set -- "$repourl" "${@:2}" if [ -n "$recursive" ]; then repobase="$1" [ -n "$repobase" ] || die "Vous devez spécifier l'url de base des dépôts à cloner" [ "$rtype" == gitolite ] || die "Le clonage récursif n'est supporté que pour les dépôts de type gitolite" 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" [ -n "$repourl" ] || die "Vous devez spécifier l'url du dépôt git" [ "$rtype" == gogs -o "$rtype" == gitea ] && repourl="${repourl%.git}.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 no_clone= parse_opts "${PRETTYOPTS[@]}" \ -n,--no-clone no_clone=1 \ @ args -- "$@" && set -- "${args[@]}" || die "$args" cxone_init "$@" [ -n "$repourl" ] || die "Vous devez spécifier l'url du dépôt git" if [ "$rtype" == "gitolite" ]; then if [ "${repourl#http://}" != "$repourl" -o "${repourl#https://}" != "$repourl" ]; then # accès par http mode=gitolite_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=gitolite_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 elif [ "$rtype" == gogs -o "$rtype" == gitea ]; then mode=gogs_http gogs_url="${rname}_GOGS_URL"; gogs_url="${!gogs_url}" gogs_user="${rname}_GOGS_USER"; gogs_user="${!gogs_user}" gogs_key="${rname}_GOGS_KEY"; gogs_key="${!gogs_key}" userpath="${repourl#$rprefix}" splitfsep "$userpath" / user path else die "$rtype: type de dépôt non supporté" 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" -a -z "$no_clone" ]; then [ -d "$destdir/.git" ] && die "$(ppath2 "$destdir"): un dépôt existe déjà" ac_set_tmpdir tmpdestdir fi if [ "$mode" == gitolite_http ]; then setx result=curl -fs "$hostuser/create?$path" || die echo "$result" [[ "$result" == FATAL:* ]] && die [ -n "$no_clone" ] && exit 0 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" == gitolite_ssh ]; then git_annex_use_ssh_wrapper ssh "$userhost" create "$path" || die [ -n "$no_clone" ] && exit 0 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 elif [ "$mode" == gogs_http ]; then payload='{"private":true,"name":"'"$path"'"}' if [ "$gogs_user" != "$user" ]; then url="$gogs_url/api/v1/org/$user/repos" else url="$gogs_url/api/v1/user/repos" fi setx result=curlto "$url" "$payload" "" \ -H 'Content-Type: application/json' \ -H "Authorization: token $gogs_key" || \ die "Une erreur s'est produite lors de la tentative de création du dépôt url: $url payload: $payload result: $result" echo "$result" [ -n "$no_clone" ] && exit 0 if [ -n "$tmpdestdir" ]; then setxx destname=abspath "$destdir" // basename git clone "${repourl%.git}.git" "$tmpdestdir/$destname" || die mv "$tmpdestdir/$destname/.git" "$destdir" || die ac_clean "$tmpdestdir" else git clone "${repourl%.git}.git" "$destdir" || die fi else die "bug: mode non prévu" fi if [ -f "$destdir/.gitignore" ]; then : elif ask_yesno "Voulez-vous créer le fichier initial .gitignore?" O; then echo >"$destdir/.gitignore" "\ .~lock*# .*.swp" fi if [ -f "$destdir/.gitattributes" ]; then : elif ask_yesno "Voulez-vous créer le fichier initial .gitattributes?" N; then echo >"$destdir/.gitattributes" "\ *.zip -delta *.jar -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