diff --git a/lib/ulib/.ulibver b/lib/ulib/.ulibver
index 4ba0555..2584705 100644
--- a/lib/ulib/.ulibver
+++ b/lib/ulib/.ulibver
@@ -1 +1 @@
-002001000
+002004000
diff --git a/lib/ulib/auto b/lib/ulib/auto
index 8b11aea..a48cf0f 100644
--- a/lib/ulib/auto
+++ b/lib/ulib/auto
@@ -22,7 +22,8 @@ else
 fi
 ULIBDIR="$(cd "$ULIBDIR" 2>/dev/null; pwd)"
 
-function __ulibver_parse() { # copie verbatim de la fonction parseversion() dans ulib
+function __ulibver_parse() {
+# copie verbatim de la fonction parseversion() du script ulib dans nutools
     if [ -n "$2" ]; then
         local version="${1:-${version:-000000000}}"
         local major minor patch pversion
@@ -49,7 +50,7 @@ function __ulibver_check() {
     [ "$sminor" -gt "$minor" ] && return 0
     [ "$sminor" -lt "$minor" ] && return 1
     # puis tester patchlevel
-    [ "$spatch" -ge "$patch" ]
+    [ "$spatch" -gt "$patch" ]
 }
 
 if [ -f /etc/ulib ] && __ulibver_check; then
diff --git a/lib/ulib/base b/lib/ulib/base
index 8bdf20f..b312d60 100644
--- a/lib/ulib/base
+++ b/lib/ulib/base
@@ -139,37 +139,43 @@ function _setv() {
     eval "$__s_var=\"$(_qval "$*")\""
 }
 function setx() {
-# initialiser la variable $1 avec le résultat de la commande "$2..@"
-# note: en principe, la syntaxe est 'setx var cmd args...'. cependant, la
-# syntaxe 'setx var=cmd args...' est supportée aussi
-    local __s_var="$1"; shift
-    if [[ "$__s_var" == *=* ]]; then
-        set -- "${__s_var#*=}" "$@"
-        __s_var="${__s_var%%=*}"
+# syntaxe 1: setx var cmd
+#   initialiser la variable $1 avec le résultat de la commande "$2..@"
+#   note: en principe, la syntaxe est 'setx var cmd args...'. cependant, la
+#   syntaxe 'setx var=cmd args...' est supportée aussi
+# syntaxe 2: setx -a array cmd
+#   initialiser le tableau $1 avec le résultat de la commande "$2..@", chaque
+#   ligne du résultat étant un élément du tableau
+#   note: en principe, la syntaxe est 'setx -a array cmd args...'. cependant, la
+#   syntaxe 'setx -a array=cmd args...' est supportée aussi
+    if [ "$1" == -a ]; then
+        shift
+        local __s_array="$1"; shift
+        if [[ "$__s_array" == *=* ]]; then
+            set -- "${__s_array#*=}" "$@"
+            __s_array="${__s_array%%=*}"
+        fi
+        eval "$__s_array=($("$@" | qlines))"
+    else
+        local __s_var="$1"; shift
+        if [[ "$__s_var" == *=* ]]; then
+            set -- "${__s_var#*=}" "$@"
+            __s_var="${__s_var%%=*}"
+        fi
+        eval "$__s_var="'"$("$@")"'
     fi
-    eval "$__s_var="'"$("$@")"'
 }
-function _setx() {
-# Comme la fonction setx() mais ne supporte que la syntaxe '_setx var cmd args...'
-# Cette fonction est légèrement plus rapide que setx()
+function _setvx() {
+# Comme la fonction setx() mais ne supporte que l'initialisation d'une variable
+# scalaire avec la syntaxe '_setvx var cmd args...' pour gagner (un peu) en
+# rapidité d'exécution.
     local __s_var="$1"; shift
     eval "$__s_var="'"$("$@")"'
 }
-function seta() {
-# initialiser le tableau $1 avec le résultat de la commande "$2..@", chaque
-# ligne du résultat étant un élément du tableau
-# note: en principe, la syntaxe est 'seta var cmd args...'. cependant, la
-# syntaxe 'seta var=cmd args...' est supportée aussi
-    local __s_array="$1"; shift
-    if [[ "$__s_array" == *=* ]]; then
-        set -- "${__s_array#*=}" "$@"
-        __s_array="${__s_array%%=*}"
-    fi
-    eval "$__s_array=($("$@" | qlines))"
-}
-function _seta() {
-# Comme la fonction seta() mais ne supporte que la syntaxe '_seta var cmd args...'
-# Cette fonction est légèrement plus rapide que seta()
+function _setax() {
+# Comme la fonction setx() mais ne supporte que l'initialisation d'un tableau
+# avec la syntaxe '_setax array cmd args...' pour gagner (un peu) en rapidité
+# d'exécution.
     local __s_array="$1"; shift
     eval "$__s_array=($("$@" | qlines))"
 }
@@ -200,7 +206,7 @@ function evalx() {
             __e_cmd=("${__e_cmd[@]}" "$__e_arg")
         done
 
-        if [ -n "$first" ]; then
+        if [ -n "$__e_first" ]; then
             __e_val="$("${__e_cmd[@]}")" || __e_r=$?
         else
             __e_val="$("${__e_cmd[@]}" "$__e_val")" || __e_r=$?
@@ -212,12 +218,15 @@ function evalx() {
 }
 function setx2() {
 # équivalent à setx $1 evalx $2..@
+    local -a __s_args
+    if [ "$1" == -a ]; then __s_args=(-a); shift; fi
     local __s_var="$1"; shift
     if [[ "$__s_var" == *=* ]]; then
         set -- "${__s_var#*=}" "$@"
         __s_var="${__s_var%%=*}"
     fi
-    setx "$__s_var" evalx "$@"
+    __s_args=("${__s_args[@]}" "$__s_var")
+    setx "${__s_args[@]}" evalx "$@"
 }
 function evalp() {
 # Implémenter une syntaxe alternative permettant d'enchainer des traitements sur
@@ -1230,6 +1239,18 @@ function relpath() {
         echo "${rp%//}"
     fi
 }
+function relpathx() {
+# Comme relpath, mais pour un chemin vers un exécutable qu'il faut lancer:
+# s'assurer qu'il y a une spécification de chemin, e.g. ./script
+    local p="$(relpath "$@")"
+    if [ -z "$p" ]; then
+        echo .
+    elif [ "${p#../}" != "$p" -o "${p#./}" != "$p" ]; then
+        echo "$p"
+    else
+        echo "./$p"
+    fi
+}
 function withinpath() {
 # Tester si le chemin absolu $2 se trouve dans le chemin absolu "$1" (appelée
 # barrière). Soit un chemin P, on considère que P est dans P. Si ce comportement
diff --git a/lib/ulib/ldif b/lib/ulib/ldif
index 20e2976..bb88e21 100644
--- a/lib/ulib/ldif
+++ b/lib/ulib/ldif
@@ -272,6 +272,18 @@ function tl_deleteentry() {
 '
 }
 
+function tl_touchentry() {
+    awk '
+/^dn:/ {
+    dn = $0
+    print dn
+    print "changetype: modify"
+    print ""
+    next
+}
+'
+}
+
 function tl_keepattr() {
     local match_attr="$1"
     awk "$match_attr"'
@@ -1036,7 +1048,9 @@ d, moddelval
 D, moddelattr
     Supprimer l'attribut
 delentry
-    Supprimer l'objet"
+    Supprimer l'objet
+touchentry
+    Forcer la réplication de l'objet en simulant une modification"
 
 function get_transform_cmd() {
     # Créer une liste de commandes bash à évaluer en fonction des arguments: une
@@ -1151,6 +1165,7 @@ function get_transform_cmd() {
         d|md|moddel|moddelval) cmdparts=(tl_modifyattr delete);;
         D|moddelattr) cmdparts=(tl_deleteattr);;
         delentry|moddelentry) cmdparts=(tl_deleteentry);;
+        touch|touchentry|modtouchentry) cmdparts=(tl_touchentry);;
         litteral) cmdpars=("$@");;
         *)
             eerror "$cmd: commande inconnue. elle sera ignorée"
diff --git a/lib/ulib/support/install-pubkeys.sh b/lib/ulib/support/install-pubkeys.sh
index 4b86391..eae216e 100755
--- a/lib/ulib/support/install-pubkeys.sh
+++ b/lib/ulib/support/install-pubkeys.sh
@@ -52,7 +52,7 @@ function recho_() {
         echo -n "$@"
     fi
 }
-function qval() {
+function _qval() {
     local s="$*"
     s="${s//\\/\\\\}"
     s="${s//\"/\\\"}"
@@ -60,6 +60,18 @@ function qval() {
     s="${s//\`/\\\`}"
     recho_ "$s"
 }
+function qval() {
+    echo -n \"
+    _qval "$@"
+    echo \"
+}
+function qvalr() {
+    if [ -n "$*" ]; then
+        echo -n \"
+        _qval "$@"
+        echo n \"
+    fi
+}
 function should_quote() {
     [ -z "$1" ] && return 0
     local s="${*//[a-zA-Z0-9]/}"
@@ -78,7 +90,7 @@ function qvals() {
         [ -z "$first" ] && echo -n " "
         if should_quote "$arg"; then
             echo -n \"
-            qval "$arg"
+            _qval "$arg"
             echo -n \"
         else
             recho_ "$arg"
@@ -91,21 +103,134 @@ function qlines() {
 }
 function setv() {
     local __s_var="$1"; shift
-    eval "$__s_var=\"$(qval "$*")\""
+    if [[ "$__s_var" == *=* ]]; then
+        set -- "${__s_var#*=}" "$@"
+        __s_var="${__s_var%%=*}"
+    fi
+    eval "$__s_var=\"$(_qval "$*")\""
+}
+function _setv() {
+    local __s_var="$1"; shift
+    eval "$__s_var=\"$(_qval "$*")\""
 }
 function setx() {
     local __s_var="$1"; shift
-    eval "$__s_var=\"\$(\"\$@\")\""
+    if [[ "$__s_var" == *=* ]]; then
+        set -- "${__s_var#*=}" "$@"
+        __s_var="${__s_var%%=*}"
+    fi
+    eval "$__s_var="'"$("$@")"'
+}
+function _setx() {
+    local __s_var="$1"; shift
+    eval "$__s_var="'"$("$@")"'
 }
 function seta() {
     local __s_array="$1"; shift
+    if [[ "$__s_array" == *=* ]]; then
+        set -- "${__s_array#*=}" "$@"
+        __s_array="${__s_array%%=*}"
+    fi
     eval "$__s_array=($("$@" | qlines))"
 }
-function e2of() {
-    "$@" 2>&1
+function _seta() {
+    local __s_array="$1"; shift
+    eval "$__s_array=($("$@" | qlines))"
 }
-function nef() {
-    "$@" | sed '/^$/d'
+function evalx() {
+    local __e_val __e_arg __e_r=0
+    local -a __e_cmd
+
+    local __e_first=1
+    while [ $# -gt 0 ]; do
+        __e_cmd=()
+        while [ $# -gt 0 ]; do
+            __e_arg="$1"; shift
+            [ "$__e_arg" == // ] && break
+            if [ "${__e_arg%//}" != "$__e_arg" ]; then
+                local __e_tmp="${__e_arg%//}"
+                if [ -z "${__e_tmp//\\/}" ]; then
+                    __e_arg="${__e_arg#\\}"
+                    __e_cmd=("${__e_cmd[@]}" "$__e_arg")
+                    continue
+                fi
+            fi
+            __e_cmd=("${__e_cmd[@]}" "$__e_arg")
+        done
+
+        if [ -n "$first" ]; then
+            __e_val="$("${__e_cmd[@]}")" || __e_r=$?
+        else
+            __e_val="$("${__e_cmd[@]}" "$__e_val")" || __e_r=$?
+        fi
+        __e_first=
+    done
+    [ -n "$__e_val" ] && echo "$__e_val"
+    return $__e_r
+}
+function setx2() {
+    local __s_var="$1"; shift
+    if [[ "$__s_var" == *=* ]]; then
+        set -- "${__s_var#*=}" "$@"
+        __s_var="${__s_var%%=*}"
+    fi
+    setx "$__s_var" evalx "$@"
+}
+function evalp() {
+    local __e_arg __e_cmd
+
+    while [ $# -gt 0 ]; do
+        __e_arg="$1"; shift
+        if [ "$__e_arg" == // ]; then
+            __e_cmd="$__e_cmd |"
+            continue
+        elif [ "${__e_arg%//}" != "$__e_arg" ]; then
+            local __e_tmp="${__e_arg%//}"
+            if [ -z "${__e_tmp//\\/}" ]; then
+                __e_arg="${__e_arg#\\}"
+            fi
+        fi
+        __e_cmd="${__e_cmd:+$__e_cmd }\"$(_qval "$__e_arg")\""
+    done
+    eval "$__e_cmd"
+}
+function testx() {
+    local __t_op="$1"; shift
+    local __t_val="$(evalx "$@")"
+    [ $__t_op "$__t_val" ]
+}
+function test2x() {
+    local __t_val1="$1"; shift
+    local __t_op="$1"; shift
+    local __t_val2="$(evalx "$@")"
+    [ "$__t_val1" $__t_op "$__t_val2" ]
+}
+function testrx() {
+    local __t_val1="$1"; shift
+    local __t_op="$1"; shift
+    local __t_val2="$(evalx "$@")"
+    eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]'
+}
+function testp() {
+    local __t_op="$1"; shift
+    local __t_val="$(evalp "$@")"
+    [ $__t_op "$__t_val" ]
+}
+function test2p() {
+    local __t_val1="$1"; shift
+    local __t_op="$1"; shift
+    local __t_val2="$(evalp "$@")"
+    [ "$__t_val1" $__t_op "$__t_val2" ]
+}
+function testrp() {
+    local __t_val1="$1"; shift
+    local __t_op="$1"; shift
+    local __t_val2="$(evalp "$@")"
+    eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]'
+}
+
+function err2out() {
+    "$@" 2>&1
 }
 
 function tolower() {
@@ -420,10 +545,12 @@ function array_new() {
     eval "$1=()"
 }
 function array_add() {
-    eval "$1=(\"\${$1[@]}\" \"$(quote_arg "$2")\")"
+    local __aa_a="$1"; shift
+    eval "$__aa_a=(\"\${$__aa_a[@]}\" \"\$@\")"
 }
 function array_ins() {
-    eval "$1=(\"$(quote_arg "$2")\" \"\${$1[@]}\")"
+    local __aa_a="$1"; shift
+    eval "$__aa_a=(\"\$@\" \"\${$__aa_a[@]}\")"
 }
 function array_del() {
     local __ad_v
@@ -1254,6 +1381,9 @@ function array_lsdirs() {
 function array_lsfiles() {
     __array_ls files "$@"
 }
+function filter_empty() {
+    sed '/^$/d'
+}
 function filter_vcspath() {
     sed '
 /^.git$/d
@@ -2444,7 +2574,7 @@ function pidfile_check() {
 }
 function page_maybe() {
     if isatty; then
-        less -XF
+        less -XF "$@"
     else
         cat
     fi
@@ -3602,17 +3732,25 @@ if check_sysinfos -s macosx; then
     function tolower() { tr A-Z a-z <<<"$*"; }
     function toupper() { tr a-z A-Z <<<"$*"; }
     function is_yes() {
+        case "$1" in
+        o|oui|y|yes|v|vrai|t|true|on) return 0;;
+        O|OUI|Y|YES|V|VRAI|T|TRUE|ON) return 0;;
+        esac
+        isnum "$1" && [ "$1" -ne 0 ] && return 0
         case "$(tolower "$1")" in
         o|oui|y|yes|v|vrai|t|true|on) return 0;;
         esac
-        isnum "$1" && [ "$1" -ne 0 ] && return 0
         return 1
     }
     function is_no() {
+        case "$1" in
+        n|non|no|f|faux|false|off) return 0;;
+        N|NON|NO|F|FAUX|FALSE|OFF) return 0;;
+        esac
+        isnum "$1" && [ "$1" -eq 0 ] && return 0
         case "$(tolower "$1")" in
         n|non|no|f|faux|false|off) return 0;;
         esac
-        isnum "$1" && [ "$1" -eq 0 ] && return 0
         return 1
     }
 
diff --git a/lib/ulib/ulib b/lib/ulib/ulib
index 2791aeb..4e5b577 100644
--- a/lib/ulib/ulib
+++ b/lib/ulib/ulib
@@ -106,3 +106,89 @@ function ulibsync() {
     local __CPNOVCS_RSYNC_ARGS=(-q --delete)
     [ "$destdir/ulib" != "$ULIBDIR" ] && cpdirnovcs "$ULIBDIR" "$destdir/ulib"
 }
+
+function __ulibver_parse_version() {
+# copie verbatim de la fonction parseversion() du script ulib dans nutools
+    if [ -n "$2" ]; then
+        local version="${1:-${version:-000000000}}"
+        local major minor patch pversion
+    else
+        version="${1:-${version:-000000000}}"
+    fi
+    while [ ${#version} -lt 9 ]; do version="0$version"; done
+    major="${version:0:3}"; while [ ${#major} -gt 1 -a "${major#0}" != "$major" ]; do major="${major#0}"; done
+    minor="${version:3:3}"; while [ ${#minor} -gt 1 -a "${minor#0}" != "$minor" ]; do minor="${minor#0}"; done
+    patch="${version:6:3}"; while [ ${#patch} -gt 1 -a "${patch#0}" != "$patch" ]; do patch="${patch#0}"; done
+    pversion="$major.$minor.$patch"
+    [ -n "$2" ] && eval "${2}version=\$version; ${2}major=\$major; ${2}minor=\$minor; ${2}patch=\$patch; ${2}pversion=\$pversion"
+}
+function __ulibver_format_version() {
+# copie verbatim de la fonction formatversion() du script ulib dans nutools
+    local major="${1:-${major:-0}}" minor="${2:-${minor:-0}}" patch="${3:-${patch:-0}}"
+    while [ ${#major} -lt 3 ]; do major="0$major"; done
+    while [ ${#minor} -lt 3 ]; do minor="0$minor"; done
+    while [ ${#patch} -lt 3 ]; do patch="0$patch"; done
+    echo "$major$minor$patch"
+}
+function __ulibver_parse_pversion() {
+# copie verbatim de la fonction parsepversion() du script ulib dans nutools
+    local v M m p
+    if [[ "$1" == *.* ]]; then
+        local v="$1"; shift
+        local M=0 m=0 p=0
+        if [[ "$v" == *.* ]]; then
+            p="${v##*.}"; v="${v%.*}"
+            if [[ "$v" == *.* ]]; then
+                m="${v##*.}"; v="${v%.*}"
+                if [[ "$v" == *.* ]]; then M="${v##*.}"; v="${v%.*}"
+                else M="$v"
+                fi
+            else m="$v"
+            fi
+        else p="$v"
+        fi
+        __ulibver_parse_version "$(__ulibver_format_version "$M" "$m" "$p")" "$@"
+    else
+        __ulibver_parse_version "$@"
+    fi
+}
+function ulibver() {
+    # Vérifier que la version actuelle de ulib est au moins à la version $1
+    # (inclue) et éventuellement au plus à la version $2 (exclue)
+    [ -f "$ULIBDIR/.ulibver" ] || return 1
+    local version=000000000 major minor patch pversion
+    __ulibver_parse_version "$(<"$ULIBDIR/.ulibver")"
+    if [ -n "$1" ]; then
+        local minversion=000000000 minmajor minminor minpatch minpversion
+        __ulibver_parse_pversion "$1" min
+        if [ "$major" -lt "$minmajor" ]; then
+            return 1
+        elif [ "$major" -eq "$minmajor" ]; then
+            if [ "$minor" -lt "$minminor" ]; then
+                return 1
+            elif [ "$minor" -eq "$minminor" ]; then
+                [ "$patch" -lt "$minpatch" ] && return 1
+            fi
+        fi
+    fi
+    if [ -n "$2" ]; then
+        local maxversion=000000000 maxmajor maxmaxor maxpatch maxpversion
+        __ulibver_parse_pversion "$2" max
+        if [ "$major" -gt "$maxmajor" ]; then
+            return 1
+        elif [ "$major" -eq "$maxmajor" ]; then
+            if [ "$minor" -gt "$maxminor" ]; then
+                return 1
+            elif [ "$minor" -eq "$maxminor" ]; then
+                [ "$patch" -ge "$maxpatch" ] && return 1
+            fi
+        fi
+    fi
+    return 0
+}
+
+function ulibver_require() {
+    ulibver "$@" && return 0
+    eerror "Ce script nécessite ulib version${1:+" >= $1"}${2:+" < $2"}"
+    return 1
+}
diff --git a/lib/ulib/ulibsh b/lib/ulib/ulibsh
index 299f5d0..50b342c 100644
--- a/lib/ulib/ulibsh
+++ b/lib/ulib/ulibsh
@@ -96,5 +96,86 @@ function ulibsync() {
     local __CPNOVCS_RSYNC_ARGS=(-q --delete)
     [ "$destdir/ulib" != "$ULIBDIR" ] && cpdirnovcs "$ULIBDIR" "$destdir/ulib"
 }
+
+function __ulibver_parse_version() {
+    if [ -n "$2" ]; then
+        local version="${1:-${version:-000000000}}"
+        local major minor patch pversion
+    else
+        version="${1:-${version:-000000000}}"
+    fi
+    while [ ${#version} -lt 9 ]; do version="0$version"; done
+    major="${version:0:3}"; while [ ${#major} -gt 1 -a "${major#0}" != "$major" ]; do major="${major#0}"; done
+    minor="${version:3:3}"; while [ ${#minor} -gt 1 -a "${minor#0}" != "$minor" ]; do minor="${minor#0}"; done
+    patch="${version:6:3}"; while [ ${#patch} -gt 1 -a "${patch#0}" != "$patch" ]; do patch="${patch#0}"; done
+    pversion="$major.$minor.$patch"
+    [ -n "$2" ] && eval "${2}version=\$version; ${2}major=\$major; ${2}minor=\$minor; ${2}patch=\$patch; ${2}pversion=\$pversion"
+}
+function __ulibver_format_version() {
+    local major="${1:-${major:-0}}" minor="${2:-${minor:-0}}" patch="${3:-${patch:-0}}"
+    while [ ${#major} -lt 3 ]; do major="0$major"; done
+    while [ ${#minor} -lt 3 ]; do minor="0$minor"; done
+    while [ ${#patch} -lt 3 ]; do patch="0$patch"; done
+    echo "$major$minor$patch"
+}
+function __ulibver_parse_pversion() {
+    local v M m p
+    if [[ "$1" == *.* ]]; then
+        local v="$1"; shift
+        local M=0 m=0 p=0
+        if [[ "$v" == *.* ]]; then
+            p="${v##*.}"; v="${v%.*}"
+            if [[ "$v" == *.* ]]; then
+                m="${v##*.}"; v="${v%.*}"
+                if [[ "$v" == *.* ]]; then M="${v##*.}"; v="${v%.*}"
+                else M="$v"
+                fi
+            else m="$v"
+            fi
+        else p="$v"
+        fi
+        __ulibver_parse_version "$(__ulibver_format_version "$M" "$m" "$p")" "$@"
+    else
+        __ulibver_parse_version "$@"
+    fi
+}
+function ulibver() {
+    [ -f "$ULIBDIR/.ulibver" ] || return 1
+    local version=000000000 major minor patch pversion
+    __ulibver_parse_version "$(<"$ULIBDIR/.ulibver")"
+    if [ -n "$1" ]; then
+        local minversion=000000000 minmajor minminor minpatch minpversion
+        __ulibver_parse_pversion "$1" min
+        if [ "$major" -lt "$minmajor" ]; then
+            return 1
+        elif [ "$major" -eq "$minmajor" ]; then
+            if [ "$minor" -lt "$minminor" ]; then
+                return 1
+            elif [ "$minor" -eq "$minminor" ]; then
+                [ "$patch" -lt "$minpatch" ] && return 1
+            fi
+        fi
+    fi
+    if [ -n "$2" ]; then
+        local maxversion=000000000 maxmajor maxmaxor maxpatch maxpversion
+        __ulibver_parse_pversion "$2" max
+        if [ "$major" -gt "$maxmajor" ]; then
+            return 1
+        elif [ "$major" -eq "$maxmajor" ]; then
+            if [ "$minor" -gt "$maxminor" ]; then
+                return 1
+            elif [ "$minor" -eq "$maxminor" ]; then
+                [ "$patch" -ge "$maxpatch" ] && return 1
+            fi
+        fi
+    fi
+    return 0
+}
+
+function ulibver_require() {
+    ulibver "$@" && return 0
+    eerror "Ce script nécessite ulib version${1:+" >= $1"}${2:+" < $2"}"
+    return 1
+}
 ##@inc]ulib
 uprovide ulibsh
diff --git a/lib/ulib/vcs b/lib/ulib/vcs
index c1664f5..b6d1938 100644
--- a/lib/ulib/vcs
+++ b/lib/ulib/vcs
@@ -487,6 +487,38 @@ function git_tag() {
     _vcs_unsupported tag #XXX
 }
 
+# Fonctions avancées de 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_get_branch() {
+    git rev-parse --abbrev-ref HEAD 2>/dev/null
+}
+function git_is_branch() {
+    [ "$(get_branch)" == "${1:-master}" ]
+}
+function git_has_remote() {
+    [ -n "$(git config --get remote.${1:-origin}.url)" ]
+}
+function git_track_branch() {
+    local branch="$1" origin="${2:-origin}"
+    [ -n "$branch" ] || return
+    git_has_remote "$origin" || return
+    [ "$(git config --get branch.$branch.remote)" == "$origin" ] && return
+    git branch -t --set-upstream "$branch" "$origin/$branch"
+}
+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() {
+    check_cleancheckout || die "Vous avez des modifications locales. Enregistrez ces modifications avant de continuer"
+}
+
 ################################################################################
 # Subversion
 
diff --git a/ucrontab b/ucrontab
index cd656cd..1c5b04b 100755
--- a/ucrontab
+++ b/ucrontab
@@ -140,7 +140,7 @@ function recho_() {
         echo -n "$@"
     fi
 }
-function qval() {
+function _qval() {
     local s="$*"
     s="${s//\\/\\\\}"
     s="${s//\"/\\\"}"
@@ -148,6 +148,18 @@ function qval() {
     s="${s//\`/\\\`}"
     recho_ "$s"
 }
+function qval() {
+    echo -n \"
+    _qval "$@"
+    echo \"
+}
+function qvalr() {
+    if [ -n "$*" ]; then
+        echo -n \"
+        _qval "$@"
+        echo n \"
+    fi
+}
 function should_quote() {
     [ -z "$1" ] && return 0
     local s="${*//[a-zA-Z0-9]/}"
@@ -166,7 +178,7 @@ function qvals() {
         [ -z "$first" ] && echo -n " "
         if should_quote "$arg"; then
             echo -n \"
-            qval "$arg"
+            _qval "$arg"
             echo -n \"
         else
             recho_ "$arg"
@@ -179,21 +191,134 @@ function qlines() {
 }
 function setv() {
     local __s_var="$1"; shift
-    eval "$__s_var=\"$(qval "$*")\""
+    if [[ "$__s_var" == *=* ]]; then
+        set -- "${__s_var#*=}" "$@"
+        __s_var="${__s_var%%=*}"
+    fi
+    eval "$__s_var=\"$(_qval "$*")\""
+}
+function _setv() {
+    local __s_var="$1"; shift
+    eval "$__s_var=\"$(_qval "$*")\""
 }
 function setx() {
     local __s_var="$1"; shift
-    eval "$__s_var=\"\$(\"\$@\")\""
+    if [[ "$__s_var" == *=* ]]; then
+        set -- "${__s_var#*=}" "$@"
+        __s_var="${__s_var%%=*}"
+    fi
+    eval "$__s_var="'"$("$@")"'
+}
+function _setx() {
+    local __s_var="$1"; shift
+    eval "$__s_var="'"$("$@")"'
 }
 function seta() {
     local __s_array="$1"; shift
+    if [[ "$__s_array" == *=* ]]; then
+        set -- "${__s_array#*=}" "$@"
+        __s_array="${__s_array%%=*}"
+    fi
     eval "$__s_array=($("$@" | qlines))"
 }
-function e2of() {
-    "$@" 2>&1
+function _seta() {
+    local __s_array="$1"; shift
+    eval "$__s_array=($("$@" | qlines))"
 }
-function nef() {
-    "$@" | sed '/^$/d'
+function evalx() {
+    local __e_val __e_arg __e_r=0
+    local -a __e_cmd
+
+    local __e_first=1
+    while [ $# -gt 0 ]; do
+        __e_cmd=()
+        while [ $# -gt 0 ]; do
+            __e_arg="$1"; shift
+            [ "$__e_arg" == // ] && break
+            if [ "${__e_arg%//}" != "$__e_arg" ]; then
+                local __e_tmp="${__e_arg%//}"
+                if [ -z "${__e_tmp//\\/}" ]; then
+                    __e_arg="${__e_arg#\\}"
+                    __e_cmd=("${__e_cmd[@]}" "$__e_arg")
+                    continue
+                fi
+            fi
+            __e_cmd=("${__e_cmd[@]}" "$__e_arg")
+        done
+
+        if [ -n "$first" ]; then
+            __e_val="$("${__e_cmd[@]}")" || __e_r=$?
+        else
+            __e_val="$("${__e_cmd[@]}" "$__e_val")" || __e_r=$?
+        fi
+        __e_first=
+    done
+    [ -n "$__e_val" ] && echo "$__e_val"
+    return $__e_r
+}
+function setx2() {
+    local __s_var="$1"; shift
+    if [[ "$__s_var" == *=* ]]; then
+        set -- "${__s_var#*=}" "$@"
+        __s_var="${__s_var%%=*}"
+    fi
+    setx "$__s_var" evalx "$@"
+}
+function evalp() {
+    local __e_arg __e_cmd
+
+    while [ $# -gt 0 ]; do
+        __e_arg="$1"; shift
+        if [ "$__e_arg" == // ]; then
+            __e_cmd="$__e_cmd |"
+            continue
+        elif [ "${__e_arg%//}" != "$__e_arg" ]; then
+            local __e_tmp="${__e_arg%//}"
+            if [ -z "${__e_tmp//\\/}" ]; then
+                __e_arg="${__e_arg#\\}"
+            fi
+        fi
+        __e_cmd="${__e_cmd:+$__e_cmd }\"$(_qval "$__e_arg")\""
+    done
+    eval "$__e_cmd"
+}
+function testx() {
+    local __t_op="$1"; shift
+    local __t_val="$(evalx "$@")"
+    [ $__t_op "$__t_val" ]
+}
+function test2x() {
+    local __t_val1="$1"; shift
+    local __t_op="$1"; shift
+    local __t_val2="$(evalx "$@")"
+    [ "$__t_val1" $__t_op "$__t_val2" ]
+}
+function testrx() {
+    local __t_val1="$1"; shift
+    local __t_op="$1"; shift
+    local __t_val2="$(evalx "$@")"
+    eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]'
+}
+function testp() {
+    local __t_op="$1"; shift
+    local __t_val="$(evalp "$@")"
+    [ $__t_op "$__t_val" ]
+}
+function test2p() {
+    local __t_val1="$1"; shift
+    local __t_op="$1"; shift
+    local __t_val2="$(evalp "$@")"
+    [ "$__t_val1" $__t_op "$__t_val2" ]
+}
+function testrp() {
+    local __t_val1="$1"; shift
+    local __t_op="$1"; shift
+    local __t_val2="$(evalp "$@")"
+    eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]'
+}
+
+function err2out() {
+    "$@" 2>&1
 }
 
 function tolower() {
@@ -508,10 +633,12 @@ function array_new() {
     eval "$1=()"
 }
 function array_add() {
-    eval "$1=(\"\${$1[@]}\" \"$(quote_arg "$2")\")"
+    local __aa_a="$1"; shift
+    eval "$__aa_a=(\"\${$__aa_a[@]}\" \"\$@\")"
 }
 function array_ins() {
-    eval "$1=(\"$(quote_arg "$2")\" \"\${$1[@]}\")"
+    local __aa_a="$1"; shift
+    eval "$__aa_a=(\"\$@\" \"\${$__aa_a[@]}\")"
 }
 function array_del() {
     local __ad_v
@@ -1342,6 +1469,9 @@ function array_lsdirs() {
 function array_lsfiles() {
     __array_ls files "$@"
 }
+function filter_empty() {
+    sed '/^$/d'
+}
 function filter_vcspath() {
     sed '
 /^.git$/d
@@ -2532,7 +2662,7 @@ function pidfile_check() {
 }
 function page_maybe() {
     if isatty; then
-        less -XF
+        less -XF "$@"
     else
         cat
     fi
@@ -3890,17 +4020,25 @@ if check_sysinfos -s macosx; then
     function tolower() { tr A-Z a-z <<<"$*"; }
     function toupper() { tr a-z A-Z <<<"$*"; }
     function is_yes() {
+        case "$1" in
+        o|oui|y|yes|v|vrai|t|true|on) return 0;;
+        O|OUI|Y|YES|V|VRAI|T|TRUE|ON) return 0;;
+        esac
+        isnum "$1" && [ "$1" -ne 0 ] && return 0
         case "$(tolower "$1")" in
         o|oui|y|yes|v|vrai|t|true|on) return 0;;
         esac
-        isnum "$1" && [ "$1" -ne 0 ] && return 0
         return 1
     }
     function is_no() {
+        case "$1" in
+        n|non|no|f|faux|false|off) return 0;;
+        N|NON|NO|F|FAUX|FALSE|OFF) return 0;;
+        esac
+        isnum "$1" && [ "$1" -eq 0 ] && return 0
         case "$(tolower "$1")" in
         n|non|no|f|faux|false|off) return 0;;
         esac
-        isnum "$1" && [ "$1" -eq 0 ] && return 0
         return 1
     }