diff --git a/bash/src/git.sh b/bash/src/git.sh index 58a9604..a879f28 100644 --- a/bash/src/git.sh +++ b/bash/src/git.sh @@ -215,490 +215,3 @@ function git_is_merged() { [ -n "$(git merge-base "$b" "$d")" ] || return 1 [ -z "$(git rev-list "$d..$b")" ] } - -################################################################################ -# git annex - -NULIB_GIT_SSH_WRAPPER= -function: git_annex_use_ssh_wrapper "" -function git_annex_use_ssh_wrapper() { - [ -n "$NULIB_GIT_SSH_WRAPPER" ] && return - NULIB_GIT_FORCE_PATH="$PATH" - NULIB_GIT_FORCE_SSH="${GIT_SSH:-ssh}" - export NULIB_GIT_FORCE_PATH NULIB_GIT_FORCE_SSH - base_delpath "$NULIBDIR/ssh-wrapper" NULIB_GIT_FORCE_PATH - base_inspath "$NULIBDIR/ssh-wrapper" PATH - NULIB_GIT_SSH_WRAPPER=1 -} - -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 -@return 1 si une erreur s'est produite" -function git_annex_initial() { - 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 --get 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 - base_array_splitl 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 - base_in_path git-annex || edie "Vous devez installer git-annex" || return - local cwd; base_push_cwd "$repodir" && - git annex init "$MYHOSTNAME" && - git annex get && - git annex sync && - base_pop_cwd || base_pop_cwd 1 || return - fi -} - -################################################################################ -# Outils de haut niveau - -function: git_commit "" -function git_commit() { - local all=auto allnew push=auto nopush args - setyesval nopush "$NULIB_GIT_OFFLINE" - [ -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 | lawk ' - 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_update "" -function git_update() { - local args autoff=1 - parse_opts + "${PRETTYOPTS[@]}" \ - -n,--no-autoff autoff= \ - @ args -- "$@" && set -- "${args[@]}" || { - eerror "$args" - return 1 - } - - if [ -z "$autoff" ]; then - git pull "$@" - return $? - fi - - local branch orig_branch restore_branch remote rbranch pbranch - local -a branches prbranches crbranches dbranches - - base_array_splitl prbranches "$(git_list_rbranches)" - git fetch -p "$@" || return - base_array_splitl crbranches "$(git_list_rbranches)" - - # vérifier s'il n'y a pas des branches distantes qui ont été supprimées - for branch in "${prbranches[@]}"; do - if ! base_array_contains crbranches "$branch"; then - base_array_add dbranches "${branch#*/}" - fi - done - if [ ${#dbranches[*]} -gt 0 ]; then - eimportant "One or more distant branches where deleted" - for branch in "${dbranches[@]}"; do - if git_have_branch "$branch"; then - if ! ask_yesno "Do you want to delete local branch $branch?" X; then - base_array_del dbranches "$branch" - fi - fi - done - fi - if [ ${#dbranches[*]} -gt 0 ]; then - base_array_splitl branches "$(git_list_branches)" - branch="$(git_get_branch)" - if base_array_contains dbranches "$branch"; then - # si la branche courante est l'une des branches à supprimer, il faut - # basculer vers develop ou master - local swto - if [ -z "$swto" ] && base_array_contains branches develop && ! base_array_contains dbranches develop; then - swto=develop - fi - if [ -z "$swto" ] && base_array_contains branches master && ! base_array_contains dbranches master; then - swto=master - fi - if ! git_check_cleancheckout; then - echo "* There are uncommitted local changes. However current branch is slated for removal. -Make your verifications then delete the local branches: - ${swto:+$(qvals git checkout "$swto") - }$(qvals git branch -D "${dbranches[@]}")" - return 1 - fi - if [ -n "$swto" ]; then - git checkout -q "$swto" - else - echo "* Current branch is slated for removal but I don't know to which branch I should switch first. -Make your choice then delete the local branches: - $(qvals git branch -D "${dbranches[@]}")" - return 1 - fi - fi - for branch in "${dbranches[@]}"; do - git branch -D "$branch" - done - fi - - # intégrer les modifications dans les branches locales - if ! git_check_cleancheckout; then - branch="$(git_get_branch)" - remote="$(git_get_branch_remote "$branch")" - rbranch="$(git_get_branch_rbranch "$branch" "$remote")" - pbranch="${rbranch#refs/remotes/}" - if git merge -q --ff-only "$rbranch"; then - echo "* There are uncommitted local changes: only CURRENT branch were updated" - fi - return 0 - fi - - orig_branch="$(git_get_branch)" - base_array_splitl branches "$(git_list_branches)" - for branch in "${branches[@]}"; do - remote="$(git_get_branch_remote "$branch")" - rbranch="$(git_get_branch_rbranch "$branch" "$remote")" - pbranch="${rbranch#refs/remotes/}" - [ -n "$remote" -a -n "$rbranch" ] || continue - if git_is_ancestor "$branch" "$rbranch"; then - if git_should_ff "$branch" "$rbranch"; then - echo "* Fast-forwarding $branch -> $pbranch" - git checkout -q "$branch" - git merge -q --ff-only "$rbranch" - restore_branch=1 - fi - else - if [ "$branch" == "$orig_branch" ]; then - echo "* Cannot fast-forward CURRENT branch $branch from $pbranch -Try to merge manually with: git merge $pbranch" - else - echo "* Cannot fast-forward local branch $branch from $pbranch -You can merge manually with: git checkout $branch; git merge $pbranch" - fi - fi - done - [ -n "$restore_branch" ] && git checkout -q "$orig_branch" - return 0 -} - -function: git_push "" -function git_push() { - local all all_branches all_tags auto force args no_annex - parse_opts + "${PRETTYOPTS[@]}" \ - -a,--all all=1 \ - -b,--branches,--all-branches all_branches=1 \ - -t,--tags,--all-tags all_tags=1 \ - --auto auto=1 \ - -f,--force force=1 \ - -n,--no-annex no_annex=1 \ - @ args -- "$@" && set -- "${args[@]}" || { - eerror "$args" - return 1 - } - - if [ -n "$all" ]; then - # On a demandé à pusher toutes les branches et tous les tags - local r - git push --all "$@"; r=$? - if [ $r -eq 0 ]; then - git push --tags "$@"; r=$? - fi - return $r - elif [ -n "$all_branches" ]; then - # On a demandé à pusher toutes les branches - git push --all "$@" - return $? - elif [ -n "$all_tags" ]; then - # On a demandé à pusher tous les tags - git push --tags "$@" - return $? - elif [ $# -gt 0 ]; then - # Sinon, si des arguments sont spécifiés, les passer à git sans - # modification - git push "$@" - return $? - elif git_have_annex; then - # Si une annexe existe dans le dépôt, demander à git-annex de faire la - # synchronisation, sauf si --no-annex est spécifié ou si on est en mode - # automatique - if [ -z "$no_annex" -a -z "$auto" ]; then - git annex sync - return $? - fi - 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 - # utiliser la configuration par défaut, qui est sous debian squeeze - # de pousser toutes les branches - git push || return - fi - elif [ -n "$force" ]; then - # utiliser la configuration par défaut, qui est sous debian squeeze de - # pousser toutes les branches - git push || return - fi - return 0 -} - -function git__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 git__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 git__filter_repos() { - lawk -v 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 -}' -} - -function: git_clone "" -function git_clone() { - 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[@]}" || edie "$args" || return - - if [ -n "$recursive" ]; then - repobase="$1" - [ -n "$repobase" ] || edie "Vous devez spécifier l'url de base des dépôts à cloner" || return - 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" ] || edie "Vous devez spécifier l'hôte e.g http://host/git/basepath" || return - 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" ] || edie "Vous devez spécifier l'hôte" || return - urlbase="$user@$host" - fi - basepath="${basepath%/}" - destbase="${2:-.}" - - git_annex_use_ssh_wrapper - prefix="${basepath:+$basepath/}" - base_array_splitl repos "$(set -o pipefail; git__gitolite_info "$mode" "$urlbase" "$prefix" | git__filter_repos "$prefix")" || edie || return - 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 - ) || edie || return - else - estepe "$(ppath2 "$destdir"): répertoire existant" - fi - elif [ -n "$no_clone" ]; then - qvals git clone "$repourl" "$destdir" - else - git__pclone "$repourl" "$destdir" "$nodevelop" || edie || return - fi - done - - else - repourl="${1%.git}" - [ -n "$repourl" ] || edie "Vous devez spécifier l'url du dépôt git" || return - - 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 - ) || edie || return - else - estepe "$(ppath2 "$destdir"): répertoire existant" - fi - elif [ -n "$no_clone" ]; then - qvals git clone "$repourl" "$destdir" - else - git__pclone "$repourl" "$destdir" "$nodevelop" || edie || return - fi - fi -} - -function: git_crone "" -function git_crone() { - repourl="${1%.git}" - [ -n "$repourl" ] || edie "Vous devez spécifier l'url du dépôt git" || return - 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" ] || edie "Vous devez spécifier l'hôte e.g http://host/git/repo" || return - 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" ] || edie "Vous devez spécifier l'hôte" || return - userhost="$user@$host" - fi - [ -n "$path" ] || edie "Vous devez spécifier le chemin du dépôt git" || return - - destdir="$2" - if [ -z "$destdir" ]; then - setx destdir=basename -- "$path" - destdir="${destdir%.git}" - fi - tmpdestdir= - if [ -d "$destdir" ]; then - [ -d "$destdir/.git" ] && edie "$(ppath2 "$destdir"): un dépôt existe déjà" || return - ac_set_tmpdir tmpdestdir - fi - - if [ "$mode" == http ]; then - setx result=curl -fs "$hostuser/create?$path" || edie || return - echo "$result" - [[ "$result" == FATAL:* ]] && edie || return - if [ -n "$tmpdestdir" ]; then - setxx destname=abspath "$destdir" // basename - git clone "$hostuser/$path" "$tmpdestdir/$destname" || edie || return - mv "$tmpdestdir/$destname/.git" "$destdir" || edie || return - ac_clean "$tmpdestdir" - else - git clone "$hostuser/$path" "$destdir" || edie || return - fi - elif [ "$mode" == ssh ]; then - git_annex_use_ssh_wrapper - ssh "$userhost" create "$path" || edie || return - if [ -n "$tmpdestdir" ]; then - setxx destname=abspath "$destdir" // basename - git clone "$userhost:$path" "$tmpdestdir/$destname" || edie || return - mv "$tmpdestdir/$destname/.git" "$destdir" || edie || return - ac_clean "$tmpdestdir" - else - git clone "$userhost:$path" "$destdir" || edie || return - fi - else - edie "$mode: mode non supporté" || return - fi - git_annex_initial "$destdir" || edie || return -} diff --git a/bin/npu b/bin/npu index 02c452b..7c227b6 100755 --- a/bin/npu +++ b/bin/npu @@ -3,11 +3,101 @@ source "$(dirname -- "$0")/../load.sh" || exit 1 require: git -basedir= +function _git_update() { + local branch + local -a prbranches crbranches dbranches + + setx -a prbranches=git_list_rbranches + git fetch -p "$@" || return + setx -a crbranches=git_list_rbranches + + # vérifier s'il y a des branches distantes qui ont été supprimées + for branch in "${prbranches[@]}"; do + if ! array_contains crbranches "$branch"; then + array_add dbranches "${branch#*/}" + fi + done + if [ ${#dbranches[*]} -gt 0 ]; then + setx -a branches=git_list_branches + setx branch=git_get_branch + + eimportant "One or more distant branches where deleted" + if git_check_cleancheckout; then + einfo "Delete the obsolete local branches with these commands:" + else + ewarn "Take care of uncommitted local changes first" + einfo "Then delete the obsolete local branches with these commands:" + fi + if array_contains dbranches "$branch"; then + # si la branche courante est l'une des branches à supprimer, il faut + # basculer vers develop ou master + local swto + if [ -z "$swto" ] && array_contains branches develop && ! array_contains dbranches develop; then + swto=develop + fi + if [ -z "$swto" ] && array_contains branches master && ! array_contains dbranches master; then + swto=master + fi + [ -n "$swto" ] && qvals git checkout "$swto" + fi + qvals git branch -D "${dbranches[@]}" + return 1 + fi + + # intégrer les modifications des branches locales + if ! git_check_cleancheckout; then + setx branch=git_get_branch + setx remote=git_get_branch_remote "$branch" + setx rbranch=git_get_branch_rbranch "$branch" "$remote" + pbranch="${rbranch#refs/remotes/}" + if git merge -q --ff-only "$rbranch"; then + enote "There are uncommitted local changes: only CURRENT branch were updated" + fi + return 0 + fi + + setx -a branches=git_list_branches + restore_branch= + for branch in "${branches[@]}"; do + setx remote=git_get_branch_remote "$branch" + setx rbranch=git_get_branch_rbranch "$branch" "$remote" + pbranch="${rbranch#refs/remotes/}" + [ -n "$remote" -a -n "$rbranch" ] || continue + if git_is_ancestor "$branch" "$rbranch"; then + if git_should_ff "$branch" "$rbranch"; then + einfo "Fast-forwarding $branch -> $pbranch" + git checkout -q "$branch" + git merge -q --ff-only "$rbranch" + restore_branch=1 + fi + else + if [ "$branch" == "$orig_branch" ]; then + echo "* Cannot fast-forward CURRENT branch $branch from $pbranch +Try to merge manually with: git merge $pbranch" + else + echo "* Cannot fast-forward local branch $branch from $pbranch +You can merge manually with: git checkout $branch; git merge $pbranch" + fi + fi + done + [ -n "$restore_branch" ] && git checkout -q "$orig_branch" + return 0 +} + +function git_update() { + local cwd r + setx cwd=ppath2 "$(pwd)" "$OrigCwd" + etitle "$cwd" + _git_update "$@"; r=$? + eend + return $r +} + +chdir= all= -remote= -autoff=1 -reset=ask +Remote= +Autoff=1 +Reset=ask args=( "\ mettre à jour les branches locales @@ -15,13 +105,43 @@ mettre à jour les branches locales si la branche courante est une branche wip, écraser les modifications locales éventuelles après un avertissement. sinon, ne mettre à jour la branche locale qu'en mode fast-forward" #"usage" - -d:,--chdir:BASEDIR basedir= "répertoire dans lequel se placer avant de lancer les opérations" + -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" -a,--all all=1 "faire l'opération sur tous les sous-répertoires de BASEDIR qui sont des dépôts git" - -o:,--remote remote= "spécifier le remote depuis lequel faire le fetch" - --autoff autoff=1 "s'il n'y a pas de modifications locales, faire un fast-forward de toutes les branches traquées. c'est l'option par défaut." - -l,--no-autoff autoff= "ne pas faire de fast-forward automatique des branches traquées. seule la branche courante est mise à jour" - --reset reset=1 "écraser les modifications locales si la branche courante est une branche wip" - -n,--no-reset reset= "ne jamais écraser les modifications locales, même si la branche courante est une branche wip" + -o:,--remote Remote= "spécifier le remote depuis lequel faire le fetch" + --autoff Autoff=1 "s'il n'y a pas de modifications locales, faire un fast-forward de toutes les branches traquées. c'est l'option par défaut." + -l,--no-autoff Autoff= "ne pas faire de fast-forward automatique des branches traquées. seule la branche courante est mise à jour" + --reset Reset=1 "écraser les modifications locales si la branche courante est une branche wip" + -n,--no-reset Reset= "ne jamais écraser les modifications locales, même si la branche courante est une branche wip" ) parse_args "$@"; set -- "${args[@]}" + +setx OrigCwd=pwd +if [ -n "$chdir" ]; then + cd "$chdir" || die +fi + +if [ -n "$all" ]; then + # liste de sous répertoires + if [ $# -gt 0 ]; then + # si on a une liste de patterns, l'utiliser + setx -a dirs=ls_dirs . "$@" + else + dirs=() + for dir in */.git; do + [ -d "$dir" ] || continue + dirs+=("${dir%/.git}") + done + fi + setx cwd=pwd + for dir in "${dirs[@]}"; do + cd "$dir" || die + git_update || die + cd "$cwd" + done +else + # répertoire courant uniquement + args=() + isatty || args+=(--porcelain) + git_update "${args[@]}" +fi