diff --git a/lib/uinst/conf b/lib/uinst/conf index 1aa8868..14a449d 100644 --- a/lib/uinst/conf +++ b/lib/uinst/conf @@ -31,6 +31,7 @@ for i in cg cgs; do ln -s compileAndGo "$i" done ./uproject --nutools-makelinks +./pff --nutools-makelinks ./woctl --nutools-makelinks #./dokuwiki --nutools-makelinks #./mediawiki --nutools-makelinks @@ -47,6 +48,7 @@ bcdir=lib/completion.d ./pdev --nutools-completion >"$bcdir/pdev" ./prel --nutools-completion >"$bcdir/prel" #./pfix --nutools-completion >"$bcdir/pfix" +#./pff --nutools-completion >"$bcdir/pff" # copier le fichier .nutoolsrc [ -f ~/.nutoolsrc ] || cp lib/nutoolsrc ~/.nutoolsrc diff --git a/pff b/pff new file mode 100755 index 0000000..feb811b --- /dev/null +++ b/pff @@ -0,0 +1,1020 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +SCRIPT_ALIASES=( + pfs:-s + pfa:-a + pfb:-b + pfe:-e +) + +if [ "$#" -eq 1 -a "$1" == --nutools-makelinks ]; then + # créer les liens + scriptname="$(basename -- "$0")" + for alias in "${SCRIPT_ALIASES[@]}"; do + alias="${alias%:*}" + ln -s "$scriptname" "$alias" + done + exit 0 +elif [ $# -eq 1 -a "$1" == --nutools-completion ]; then + echo ' +function __pff_profiles() { + : #TODO +} +function __pfs_completion() { + local cur + _get_comp_words_by_ref cur + COMPREPLY=($(compgen -W "$(__pff_profiles)" "$cur")) +} +complete -F __pfs_completion pfs +' + exit 0 +fi + +source "$(dirname "$0")/lib/ulib/ulib" || exit 1 +urequire DEFAULTS multiconf + +function display_help() { + uecho "$scriptname: gestion de modifications locales + +Un produit est distribué par un partenaire, et il faut maintenir certaines +modifications locales tout en continuant d'accepter des modififications de +l'upstream. Ce script aide à maintenir un tel scénario. + +En général, la distribution upstream identifie les fichiers modifiables en leur +donnant une extension particulière, par exemple 'file.origine'. On peut aussi +décider de modifier des fichiers qui n'ont pas été prévus comme tels par +l'upstream. On peut aussi avoir plusieurs ensembles de modifications, rassemblés +en profils, e.g prod ou test pour les modifications à déployer en prod ou en +test. + +Terminologie: Les fichiers pour lesquels il faut maintenir une version locale +sont appelés 'fichiers locaux', qu'ils viennent de la distribution upstream ou +non. Les autres fichiers qui proviennent de la distribution sont appelés +'fichiers upstream'. Les fichiers livrés dans la distribution upstream avec une +extension particulière pour indiquer qu'ils sont modifiables sont appelés +'fichiers origine'. + +USAGE + $scriptname [options] + +COMMANDES / OPTIONS +Les arguments du script dépendent de la commande utilisée. Les commandes +supportées sont: + -0, --init [WORKDIR [ARCHIVE]] + Initialiser un répertoire pour le suivi des distributions upstream. Le + fichier $PFFCONF contient toutes les informations paramétrables sur la + gestion du projet. + --s, --origext .EXT + Ajouter une extension à la liste des extensions origines, c'est à dire + les fichiers identifiés dans la distribution upstream comme contenus + modifiables. Par défaut, les extensions suivantes sont reconnues: + ${DEFAULT_ORIGEXTS[*]} + Cette option peut être utilisée autant de fois que nécessaire. + --k, --clear-origexts + Supprimer la liste par défaut des extensions origines. Cette option doit + être spécifiée avant l'option --origext pour construire une nouvelle + liste. La liste des extensions ne doit pas être vide. Si c'est le cas, + elle est modifiée pour contenir l'unique élément (.$ORIGEXT) + + -N, --new ARCHIVE [WORKDIR] + -M, --new-only ARCHIVE [WORKDIR] + Intégrer une nouvelle distribution upstream, sous forme d'une archive ou + d'un répertoire. Les fichiers origine et les fichiers locaux sont placés + en attente d'intégration. Les autres sont intégrés sans modification. + La variante --new appelle automatiquement --patch après l'intégration de + l'archive. + -V, --version VERSION + Spécifier la version de l'archive qui est intégrée avec l'option --new. + Normalement, cette information est calculée automatiquement à partir du + nom de l'archive. Si la source est un répertoire ou une archive sans + numéro de version, cette option est requise. + -F, --full-archive + Intégrer une distribution complète. Par défaut, les archives avec + l'extension .war sont considérées commes des livraisons complètes. Si + la source est un répertoire ou une archive sans extension, l'une des + options -F ou -P est requise. + -P, --patch-archive + Intégrer un patch. Par défaut, les archives avec l'extension .zip sont + considérées comme des patches. Si la source est un répertoire ou une + archive sans extension, l'une des options -F ou -P est requise. + Avec une distribution de type patch, on considère que les fichiers + livrés ne sont pas des fichiers origines. Il n'y a donc aucun traitement + particulier: l'archive est simplement intégrée telle quelle. + --auto-unwrap + Si l'archive ne contient qu'un seul répertoire à la racine nommé d'après + le nom de base de l'archive, c'est le contenu de ce répertoire qui est + considéré. C'est l'option par défaut. + Le test est effectué avec et sans la version. Par exemple, si l'archive + est product-x.y.zip, et qu'elle contient un unique répertoire nommé + 'product-x.y' ou 'product' alors intégrer le contenu de ce répertoire. + -E, --unwrap + Si l'archive ne contient qu'un seul répertoire à la racine, intégrer + inconditionellement le contenu de se répertoire. + --no-unwrap + Intégrer tel quel le contenu de l'archive. + + -p, --patch [WORKDIR] + Intégrer les modifications entrantes sur les fichiers nouvellement + arrivés via l'option --new + + -a, --add-global FILES... + Ajouter/Identifier un fichier comme un fichier local pour tous les + profils. + + -L, --locals [WORKDIR] + Lister les fichiers locaux + + -l, --profiles [WORKDIR] + Lister les profils valides + + -s, --switch PROFILE [WORKDIR] + Basculer le profil courant + + -b, --add-local FILES... + Ajouter/Identifier un fichier comme un fichier local et le rendre + spécifique au profil courant. + -e, --edit FILES... + Editer des fichiers, implique --add-local + + --infos [WORKDIR] + Afficher des informations sur le projet courant: profils, fichiers + locaux, profil courant, etc. C'est la commande par défaut." +} + +# Nomenclature pour le nommage des fichiers traités: +# pfile: le chemin absolu du fichier dans le projet +# rfile: le chemin relatif du fichier dans le projet +# bfile: le chemin absolu du fichier dans pff/Base/ +# cfile: le chemin absolu du fichier dans pff/Current/ +# Pfile: le chemin absolu du fichier dans pff/ANYPROFILE/ +# plink: la destination du lien pfile +# clink: la destination du lien cfile +# Plink: la destination du lien Pfile + +ORIGEXT=pff +DEFAULT_ORIGEXTS=(".$ORIGEXT" .origine .default) +PFFCONF=.pff.conf # ne pas modifier +DEFAULT_PROTECTS=(/.git/ .svn/ /pff/ "/$PFFCONF") + +PFFCONFVARS=( + "VERSION//Version actuellement installée" + -a + "PVERSIONS//Versions en attente d'intégration" + "ORIGEXTS=//Extensions origines" +) + +function find_pffdir() { + # trouver le répertoire du projet pff à partir du répertoire $2(=.) et + # mettre le chemin absolu dans la variable $1(=pffdir) + # si le répertoire n'est pas trouvé, retourner 1 + local destvar="${1:-pffdir}" pffdir + setx pffdir=abspath "${2:-.}" + while true; do + if [ -f "$pffdir/$PFFCONF" -a -d "$pffdir/pff" ]; then + local "$destvar" + upvar "$destvar" "$pffdir" + return 0 + fi + [ "$pffdir" == / -o "$pffdir" == "$HOME" ] && break + setx pffdir=dirname -- "$pffdir" + done + return 1 +} + +function ensure_pffdir() { + # trouver le répertoire du projet pff à partir du répertoire $2(=.) et + # mettre le chemin absolu dans la variable $1(=pffdir) + # si le répertoire n'est pas trouvé, arrêter le script avec un code d'erreur + local destvar="${1:-pffdir}" pffdir + if find_pffdir pffdir "$2"; then + conf_init "${PFFCONFVARS[@]}" + source "$pffdir/$PFFCONF" + local "$destvar"; upvar "$destvar" "$pffdir" + return + fi + local msg="Projet pff introuvable (utiliser --init ?)" + [ -n "$2" ] && die "$2: $msg" || die "$msg" +} + +function get_current_profile() { + # afficher le profil courant du projet pff $1, s'il est défini + local pffdir="$1" + [ -L "$pffdir/pff/.Current" ] && readlink "$pffdir/pff/.Current" +} + +function get_profiles() { + # afficher tous les profils valides du projet pff $1 + local pffdir="$1" + list_dirs "$pffdir/pff" | grep -vxF Current +} + +function get_first_profile() { + # afficher le premier profil autre que Base du projet pff $1 + get_profiles "$@" | grep -vxF Base | head -n1 +} + +function get_local_files() { + # afficher tous les fichiers locaux exprimés relativement au répertoire du + # projet pff $1 + local pffdir="$1" + find "$pffdir/pff/Base" -type f | sed "s|^$pffdir/pff/Base/||" | grep -v '__pv-.*__$' +} + +function multiups() { + # afficher un chemin vers le haut e.g ../../.. avec autant d'éléments que + # les répertoires du chemin relatif $1. + # méthode: commencer avec la valeur de départ $2 et préfixer avec autant de + # ../ que nécessaire. puis afficher le résultat. + local tmp="$1" link="$2" + setx tmp=dirname -- "$tmp" + while [ "$tmp" != . ]; do + [ -n "$link" ] && link="/$link" + link="..$link" + setx tmp=dirname -- "$tmp" + done + echo "$link" +} + +function get_rfile() { + # obtenir le chemin relatif du fichier $1 exprimé par rapport au répertoire + # du projet pff $2. Si c'est un fichier d'un répertoire de profil, + # l'exprimer comme un chemin du répertoire de projet, e.g pff/Profile/path + # devient path + # retourner 1 si le chemin est invalide (est situé en dehors de pffdir ou + # pas dans un répertoire de profil) + local rfile="$1" pffdir="$2" + setx rfile=abspath "$rfile" + [ "${rfile#$pffdir/}" != "$rfile" ] || return 1 + rfile="${rfile#$pffdir/}" + if [[ "$rfile" == pff/*/* ]]; then + rfile="${rfile#pff/*/}" + elif [[ "$rfile" == pff/* ]]; then + return 1 + fi + echo "$rfile" +} +function get_pfile() { + # obtenir le chemin du fichier $1 exprimé par rapport au répertoire du + # profil $2 dans le répertoire de projet $3 + # retourner 1 si le chemin est invalide (est situé en dehors de pffdir ou + # pas dans un répertoire de profil) + local pfile="$1" profile="$2" pffdir="$3" + setx pfile=abspath "$pfile" + [ "${pfile#$pffdir/}" != "$pfile" ] || return 1 + pfile="${pfile#$pffdir/}" + if [[ "$pfile" == pff/*/* ]]; then + pfile="${pfile#pff/*/}" + elif [[ "$pfile" == pff/* ]]; then + return 1 + fi + echo "$pffdir/pff/$profile/$pfile" +} +function get_bfile() { get_pfile "$1" Base "$2"; } +function get_cfile() { get_pfile "$1" Current "$2"; } + +function get_vlfiles() { + # afficher tous les fichiers de version + local pffdir="$1" rfile="$2" profile="${3:-Base}" + if [ -n "$rfile" ]; then + find "$pffdir/pff/$profile" \ + -type f -path "$pffdir/pff/$profile/${rfile}__pv-*__" -o \ + -type l -path "$pffdir/pff/$profile/${rfile}__pv-*__" \ + | sed "s|^$pffdir/pff/$profile/||" + else + find "$pffdir/pff/$profile" \ + -type f -name "*__pv-*__" -o \ + -type l -name "*__pv-*__" \ + | sed "s|^$pffdir/pff/$profile/||" + fi +} + +function sync_vlfiles() { + # synchroniser les fichiers de version $3..@ dans tous les répertoires de + # profil, ou seulement le répertoire de profil $2 si la valeur n'est pas + # vide. + local pffdir="$1"; shift + local profile="$1"; shift + local -a profiles + if [ -n "$profile" ]; then + profiles=("$profile") + else + array_from_lines profiles "$(get_profiles "$pffdir" | grep -vxF Base)" + fi + local vlfile rfile prefix pfile plink tmp + for vlfile in "$@"; do + rfile="${vlfile%__pv-*__}" + for profile in "${profiles[@]}"; do + prefix="$pffdir/pff/$profile" + [ -e "$prefix/$rfile" ] || continue + pfile="$prefix/$vlfile" + setx plink=multiups "$profile/$vlfile" "Base/$vlfile" + if [ -L "$pfile" ]; then + # correction éventuelle du lien existant + setx tmp=readlink "$pfile" + [ "$tmp" == "$plink" ] || ln -sfT "$plink" "$pfile" + else + ln -sf "$plink" "$pfile" || return + fi + done + done +} + +function select_profile() { + # sélectionner le profil $1 dans le projet pff $2. créer d'abord le profil + # s'il n'existe pas. + local profile="$1" pffdir="$2" + # créer le répertoire de profil si nécessaire + mkdir -p "$pffdir/pff/$profile" || return 1 + # mettre à jour les liens + local -a lfiles; local lfile src dest + setx -a lfiles=get_local_files "$pffdir" + for lfile in "${lfiles[@]}"; do + src="$pffdir/pff/Current/$lfile" + if [ -f "$pffdir/pff/$profile/$lfile" ]; then + dest="$profile/$lfile" + else + dest="Base/$lfile" + fi + setx dest=multiups "Current/$lfile" "$dest" + [ -L "$src" ] || mkdirof "$src" + ln -sfT "$dest" "$src" + done + # maj du lien "profil courant" + ln -sfT "$profile" "$pffdir/pff/.Current" +} + +function autoinit() { + # vérifications automatiques: créer les répertoires de base nécessaire au + # fonctionnement de pff dans le projet pff $1 + local pffdir="$1" profile + [ -d "$pffdir/pff/Current" ] || mkdir -p "$pffdir/pff/Current" + [ -d "$pffdir/pff/Base" ] || mkdir -p "$pffdir/pff/Base" +} + +function autoselect() { + # vérification automatiques: sélectionner le premier profil défini si aucun + # profil n'est sélectionné dans le projet pff $1 + local pffdir="$1" profile + if [ ! -L "$pffdir/pff/.Current" ]; then + setx profile=get_first_profile "$pffdir" + [ -n "$profile" ] || profile=Base + enote "Autosélection du profil $profile" + select_profile "$profile" "$pffdir" + fi +} + +function autofix() { + autoinit "$1" + autoselect "$1" +} + +################################################################################ +# Commandes + +#=========================================================== +# pff --init + +function init_cmd() { + local workdir="$1" archive="$2" + local pffdir + + if [ -z "$workdir" ]; then + estep "Initialisation d'un nouveau projet pff" + read_value "Veuillez entrer le répertoire du projet" workdir + fi + if find_pffdir pffdir "$workdir"; then + enote "$(ppath "$pffdir"): est déjà un projet pff" + if ask_yesno "Voulez-vous vérifier la configuration et la mettre à jour?" O; then + conf_init "${PFFCONFVARS[@]}" + source "$pffdir/$PFFCONF" + conf_upgrade "$workdir/$PFFCONF" + conf_update -n "$workdir/$PFFCONF" ORIGEXTS + fi + return 0 + fi + if [ ! -d "$workdir" ]; then + ewarn "$workdir: ce répertoire n'existe pas" + ask_yesno "Voulez-vous le créer?" O || return 1 + mkdir -p "$workdir" || return 1 + fi + enote "Initialisation d'un nouveau projet pff dans $workdir" + # fichier de configuration + conf_upgrade "$workdir/$PFFCONF" "${PFFCONFVARS[@]}" + if [ "${ORIGEXTS[*]}" != "${DEFAULT_ORIGEXTS[*]}" ]; then + conf_update "$workdir/$PFFCONF" ORIGEXTS + fi + # répertoire pff + mkdir -p "$workdir/pff" + [ -f "$workdir/pff/.gitignore" ] || echo >"$workdir/pff/.gitignore" "\ +/Current/ +/.Current" + autofix "$workdir" + return 0 +} + +#=========================================================== +# pff --new + +function new__prepare_archive() { + # préparer l'archive fournie en la copiant dans un répertoire temporaire + # initialiser la variable $1(=srcdir) avec le chemin du répertoire résultat + local destvar="${1:-srcdir}"; shift + local destver="${1:-version}"; shift + local archive="$1" version="$2" disttype="$3" unwrap="$4" + + local srcdir + if [ -f "$archive" ]; then + is_archive "$archive" || die "$archive: doit être un fichier archive" + [ -n "$version" ] || version="$(get_archive_version "$archive")" + archive="$(abspath "$archive")" + elif [ -d "$archive" ]; then + srcdir="$(abspath "$archive")" + archive= + else + die "$archive: fichier introuvable" + fi + + read_value "Entrez un identifiant pour cette version" version "$version" || die + + local tmpd + if [ -n "$archive" ]; then + if [ "$disttype" == auto ]; then + case "$archive" in + *.war) + enote "Auto-sélection du type distribution complète sur la base de l'extension .war" + disttype=full + ;; + *.zip) + enote "Auto-sélection du type distribution de patch sur la base de l'extension .zip" + disttype=patch + ;; + *) + die "L'extension de l'archive n'est pas reconnue. Vous devez spécifier l'une des options --full-archive ou --patch-archive" + ;; + esac + fi + + ac_set_tmpdir tmpd + estep "Extraction de $(ppath "$archive" "$cwd")" + extract_archive "$archive" "$tmpd" || die + srcdir="$tmpd" + + if [ -n "$unwrap" ]; then + local -a files + array_lsall files "$srcdir" "*" ".*" + if [ "${#files[*]}" -eq 1 ]; then + local file="${files[0]}" + if [ "$unwrap" == auto ]; then + # nom de l'archive avec la version + local banv="$(get_archive_basename "$archive")" + # nom de l'archive sans la version + local ban="${banv%$(get_archive_versionsuffix "$archive")}" + local filename="$(basename "$file")" + [ "$filename" == "$banv" -o "$filename" == "$ban" ] || unwrap= + fi + [ -n "$unwrap" -a -d "$file" ] && srcdir="$file" + fi + fi + elif [ -d "$srcdir" ]; then + if [ "$disttype" == auto ]; then + die "La source est un répertoire. Vous devez spécifier l'une des option --full-archive ou --patch-archive" + fi + + ac_set_tmpdir tmpd + estep "Création d'une copie de travail de $(ppath "$srcdir" "$cwd")" + rsync ${QUIET:+-q} -a "$srcdir/" "$tmpd" || die + srcdir="$tmpd" + fi + + local "$destvar"; upvar "$destvar" "$srcdir" + local "$destver"; upvar "$destver" "$version" +} + +function new_cmd() { + local autopatch="$1" version="$2" disttype="$3" unwrap="$4"; shift; shift; shift; shift + local archive="$1" pffdir="$2" + + ensure_pffdir pffdir "$pffdir" + + ## préparer la source: les fichiers entrants + local srcdir full + new__prepare_archive srcdir version "$archive" "$version" "$disttype" "$unwrap" + [ "$disttype" == full ] && full=1 + + ## lister les fichiers dans la source et la destination + local workdir + ac_set_tmpdir workdir + + # fichiers sources + local fnsrc0="$workdir/nsrc0" fosrc0="$workdir/osrc0" + >"$fnsrc0" + >"$fosrc0" + find "$srcdir" -type f | awkrun ORIGEXTS[@] prefix="$srcdir/" fnsrc="$fnsrc0" fosrc="$fosrc0" '{ + found = 0 + for (i = 1; i <= ORIGEXTS_count; i++) { + sub("^" prefix, "") + if ($0 ~ ORIGEXTS[i] "(/|$)") { + print >fosrc + found = 1 + break + } + } + if (!found) { + print >fnsrc + } +}' + # fichiers destination + local -a find; local p + local fndest0="$workdir/ndest0" fldest0="$workdir/ldest0" + get_local_files "$pffdir" >"$fldest0" + for p in "${PROTECTS[@]}"; do + [ ${#find[*]} -gt 0 ] && find=("${find[@]}" -o) + if [ "${p#/}" != "$p" ]; then + # les fichiers à la racine + p="${p#/}" + if [ "${p%/}" != "$p" ]; then + find=("${find[@]}" -type d -path "$pffdir/${p%/}" -prune) + else + find=("${find[@]}" -type f -path "$pffdir/$p" -o -type l -path "$pffdir/$p") + fi + else + # fichiers n'importe où dans l'arborescence + if [ "${p%/}" != "$p" ]; then + find=("${find[@]}" -type d -name "${p%/}" -prune) + else + find=("${find[@]}" -type f -name "$p" -o -type l -name "$p") + fi + fi + done + [ ${#find[*]} -gt 0 ] && find=("${find[@]}" -o) + find=(find "$pffdir" "${find[@]}" -type f -print -o -type l -print) + "${find[@]}" | sed "s|^$pffdir/||" | grep -vxf "$fldest0" >"$fndest0" + + ## Traiter les fichiers normaux + + local fnsrc="$workdir/nsrc" fndest="$workdir/ndest" + grep -vxf "$fldest0" "$fnsrc0" | csort >"$fnsrc" + csort "$fndest0" >"$fndest" + + local fcreates="$workdir/creates" fdeletes="$workdir/deletes" fcds="$workdir/cds" fupdates="$workdir/updates" + >"$fcreates" + >"$fdeletes" + diff "$fnsrc" "$fndest" | awkrun fcreates="$fcreates" fupdates="$fupdates" fdeletes="$fdeletes" '{ + c = substr($0, 1, 1) + f = substr($0, 3) + if (c == "<") print f >fcreates + else if (c == ">") print f >fdeletes +}' + cat "$fcreates" "$fdeletes" >"$fcds" + grep -vxf "$fcds" "$fnsrc" >"$fupdates" + local -a creates updates deletes r i have_creates have_updates have_deletes src dest + array_from_lines creates "$(<"$fcreates")"; [ ${#creates[*]} -gt 0 ] && have_creates=1 || have_creates= + array_from_lines updates "$(<"$fupdates")"; [ ${#updates[*]} -gt 0 ] && have_updates=1 || have_updates= + array_from_lines deletes "$(<"$fdeletes")"; [ ${#deletes[*]} -gt 0 ] && have_deletes=1 || have_deletes= + enote "Fichiers normaux: $((${#creates[*]} + ${#updates[*]})) au total, ${#creates[*]} nouveau(x)${full:+", ${#deletes[*]} à supprimer"}" + ask_any -i "Voulez-vous continuer?" +Od; r=$? + if [ $r == 2 ]; then + for i in "${updates[@]}"; do + echo " $i" + done + for i in "${creates[@]}"; do + echo "+$i" + done + if [ -n "$full" ]; then + for i in "${deletes[@]}"; do + echo "-$i" + done + fi + ask_yesno "Voulez-vous continuer?" O || return 1 + elif [ $r == 1 ]; then + return 1 + fi + + if [ -n "$have_creates" -o -n "$have_updates" -o -n "$have_deletes" ]; then + ebegin "Copie des fichiers" + r=0 + for i in "${creates[@]}"; do + mkdirof "$pffdir/$i"; r=$? + edot $r "$i"; [ $r == 0 ] || break + cp "$srcdir/$i" "$pffdir/$i"; r=$? + edot $r "$i"; [ $r == 0 ] || break + done + [ $r == 0 ] || { eend $r; return 1; } + for i in "${updates[@]}"; do + src="$srcdir/$i" + dest="$pffdir/$i" + if diff -q "$src" "$dest" >&/dev/null; then + # pas la peine de copier si les fichiers sont identiques + show_debug && edot 0 "$i: non modifié" + continue + fi + cat "$src" >"$dest"; r=$? + edot $r "$i"; [ $r == 0 ] || break + done + [ $r == 0 ] || { eend $r; return 1; } + if [ -n "$full" ]; then + # ne faire les suppression qu'en mode full + for i in "${deletes[@]}"; do + rm "$pffdir/$i"; r=$? + edot $r "$i"; [ $r == 0 ] || break + done + fi + eend $r + fi + + ## Traiter les fichiers origines + + local ftmp="$workdir/tmp" + local fosrc="$workdir/osrc" flosrc="$workdir/losrc" flsrc="$workdir/lsrc" fldest="$workdir/ldest" + csort "$fosrc0" >"$fosrc" + >"$flsrc" + >"$flosrc" + awkrun <"$fosrc0" ORIGEXTS[@] flsrc="$flsrc" flosrc="$flosrc" '{ + for (i = 1; i <= ORIGEXTS_count; i++) { + if ($0 ~ ORIGEXTS[i] "(/|$)") { + orig = $0 + local = gensub(ORIGEXTS[i] "(/|$)", "\\1", 1, $0) + print local ":" orig >flosrc + print local >flsrc + break + } + } +}' + csort "$flsrc" >"$ftmp"; cat "$ftmp" >"$flsrc" + csort "$fldest0" >"$fldest" + + local losrc lsrc osrc; local -a tmpa; declare -A losrcs + array_from_lines tmpa "$(<"$flosrc")" + for losrc in "${tmpa[@]}"; do + splitpair "$losrc" lsrc osrc + losrcs["$lsrc"]="$osrc" + done + + >"$fcreates" + >"$fdeletes" + diff "$flsrc" "$fldest" | awkrun fcreates="$fcreates" fupdates="$fupdates" fdeletes="$fdeletes" '{ + c = substr($0, 1, 1) + f = substr($0, 3) + if (c == "<") print f >fcreates + else if (c == ">") print f >fdeletes +}' + cat "$fcreates" "$fdeletes" >"$fcds" + grep -vxf "$fcds" "$flsrc" >"$fupdates" + array_from_lines creates "$(<"$fcreates")"; [ ${#creates[*]} -gt 0 ] && have_creates=1 || have_creates= + array_from_lines updates "$(<"$fupdates")"; [ ${#updates[*]} -gt 0 ] && have_updates=1 || have_updates= + enote "Fichiers origines: $((${#creates[*]} + ${#updates[*]})) au total, ${#creates[*]} nouveau(x)" + ask_any -i "Voulez-vous continuer?" +Od; r=$? + if [ $r == 2 ]; then + for i in "${updates[@]}"; do + echo " $i" + done + for i in "${creates[@]}"; do + echo "+$i" + done + ask_yesno "Voulez-vous continuer?" O || return 1 + elif [ $r == 1 ]; then + return 1 + fi + + local vlfile; local -a vlfiles + if [ -n "$have_creates" -o -n "$have_updates" ]; then + ebegin "Copie des fichiers" + r=0 + for i in "${creates[@]}"; do + src="$srcdir/${losrcs[$i]}" + dest="$pffdir/$i" + mkdirof "$dest"; r=$? + edot $r "$i"; [ $r == 0 ] || break + cp "$src" "$dest"; r=$? + edot $r "$i"; [ $r == 0 ] || break + add_global__link "$dest" "$pffdir" + edot $r "$i"; [ $r == 0 ] || break + done + [ $r == 0 ] || { eend $r; return 1; } + for i in "${updates[@]}"; do + src="$srcdir/${losrcs[$i]}" + vlfile="${i}__pv-${version}__" + dest="$pffdir/pff/Base/$vlfile" + mkdirof "$dest"; r=$? + edot $r "$i"; [ $r == 0 ] || break + cp "$src" "$dest"; r=$? + edot $r "$i"; [ $r == 0 ] || break + array_add vlfiles "$vlfile" + done + [ $r == 0 ] || { eend $r; return 1; } + eend $r + fi + + sync_vlfiles "$pffdir" "" "${vlfiles[@]}" + array_addu PVERSIONS "$version" + conf_update "$pffdir/$PFFCONF" PVERSIONS + + ## Fin du traitement + ac_clean "$srcdir" "$workdir" + + if [ -n "$autopatch" ]; then + # lancer la commande patch pour intégrer les modifications + patch_cmd "$pffdir" + fi +} + +#=========================================================== +# pff --patch + +function patch_cmd() { + local pffdir="$1" + + ensure_pffdir pffdir "$pffdir" +} + +#=========================================================== +# pff --add-global + +function add_global__link() { + local pfile="$1" pffdir="$2" + local rfile bfile cfile plink clink tmp + + setx rfile=get_rfile "$pfile" "$pffdir" || return + [ -L "$pffdir/$rfile" -o -e "$pffdir/$rfile" ] || touch "$pffdir/$rfile" + + setx bfile=get_bfile "$pfile" "$pffdir" + setx cfile=get_cfile "$pfile" "$pffdir" + setx plink=multiups "$rfile" "pff/Current/$rfile" + setx clink=multiups "Current/$rfile" "Base/$rfile" + if [ -e "$bfile" -a -L "$pfile" -a -L "$cfile" ]; then + : # ok pour les liens + else + # Création des liens pour $rfile + mkdirof "$bfile" || return + mv "$pfile" "$bfile" || return + [ -L "$pfile" ] || ln -sf "$plink" "$pfile" || return + mkdirof "$cfile" || return + [ -L "$cfile" ] || ln -sf "$clink" "$cfile" || return + fi + # correction éventuelle du lien existant + setx tmp=readlink "$pfile" + [ "$tmp" == "$plink" ] || ln -sfT "$plink" "$pfile" + return 0 +} + +function add_global_cmd() { + local pffdir file pfile + + ensure_pffdir pffdir + for file in "$@"; do + if [ -d "$file" ]; then + ewarn "$file: est un répertoire. argument ignoré" + continue + elif [ -L "$file" -o -e "$file" ]; then + : + else + ewarn "$file: fichier introuvable" + ask_yesno "Voulez-vous le créer?" O || continue + fi + setx pfile=abspath "$file" + setx rfile=get_rfile "$pfile" || { + eerror "$file: chemin invalide. argument ignoré" + continue + } + add_global__link "$pfile" "$pffdir" || return + done +} + +#=========================================================== +# pff --locals + +function list_locals_cmd() { + local pffdir="$1" + + ensure_pffdir pffdir "$pffdir" + get_local_files "$pffdir" +} + +#=========================================================== +# pff --profiles + +function list_profiles_cmd() { + local pffdir="$1" + + ensure_pffdir pffdir "$pffdir" + get_profiles "$pffdir" +} + +#=========================================================== +# pff --switch + +function switch_cmd() { + local profile="$1" pffdir="$2" + local -a profiles + + [ -n "$profile" ] || profile=Base + + ensure_pffdir pffdir "$pffdir" + autoinit "$pffdir" + setx -a profiles=get_profiles "$pffdir" + if ! array_contains profiles "$profile"; then + ewarn "$profile: ce profil n'existe pas" + ask_yesno "Voulez-vous le créer?" O || return 1 + fi + enote "Sélection du profil $profile" + select_profile "$profile" "$pffdir" +} + +#=========================================================== +# pff --add-local + +function add_local__link() { + local pfile="$1" profile="$2" pffdir="$3" + local rfile bfile pfile Pfile plink Plink + # vérifier validité du chemin + setx rfile=get_rfile "$pfile" "$pffdir" || return + [ -L "$pffdir/$rfile" -o -e "$pffdir/$rfile" ] || touch "$pffdir/$rfile" + # vérifier la présence du lien global + setx bfile=get_bfile "$pfile" "$pffdir" + if [ ! -e "$bfile" ]; then + add_global__link "$pfile" "$pffdir" || return + fi + # vérifier la présence du lien local + setx Pfile=get_pfile "$pfile" "$profile" "$pffdir" + [ -e "$Pfile" ] && return 0 + # créer le fichier local + mkdirof "$Pfile" + cp "$bfile" "$Pfile" + # mettre à jour le profil courant + setx cfile=get_cfile "$pfile" "$pffdir" + setx clink=multiups "Current/$rfile" "$profile/$rfile" + ln -sf "$clink" "$cfile" + # intégrer les fichiers de version + local -a vlfiles + array_from_lines vlfiles "$(get_vlfiles "$pffdir" "$rfile")" + sync_vlfiles "$pffdir" "$profile" "${vlfiles[@]}" + return 0 +} + +function add_local_cmd() { + local pffdir file rfile pfile + local profile r + + ensure_pffdir pffdir + setx profile=get_current_profile "$pffdir" + r=0 + for file in "$@"; do + if [ -d "$file" ]; then + ewarn "$file: est un répertoire. argument ignoré" + continue + elif [ -L "$file" -o -e "$file" ]; then + : + else + ewarn "$file: fichier introuvable" + ask_yesno "Voulez-vous le créer?" O || continue + fi + setx pfile=abspath "$file" + setx rfile=get_rfile "$pfile" || { + eerror "$file: chemin invalide. argument ignoré" + r=1 + continue + } + add_local__link "$pfile" "$profile" "$pffdir" || { + ewarn "$file: erreur lors de la création du lien local" + r=1 + continue + } + done + return $r +} + +#=========================================================== +# pff --edit + +function edit_cmd() { + local pffdir file rfile pfile Pfile + local profile r + local -a edits + + ensure_pffdir pffdir + setx profile=get_current_profile "$pffdir" + enote "Edition des fichiers dans le profil $profile" + + r=0 + for file in "$@"; do + if [ -d "$file" ]; then + ewarn "$file: est un répertoire. argument ignoré" + continue + fi + setx pfile=abspath "$file" + setx rfile=get_rfile "$pfile" || { + eerror "$file: chemin invalide. argument ignoré" + r=1 + continue + } + add_local__link "$pfile" "$profile" "$pffdir" || { + ewarn "$file: erreur lors de la création du lien local" + r=1 + continue + } + setx Pfile=get_pfile "$pfile" "$profile" "$pffdir" + array_add edits "$Pfile" + done + + "${EDITOR:-vi}" "${edits[@]}" || r=$? + return $r +} + +#=========================================================== +# pff --infos + +function infos_cmd() { + local pffdir="$1" + local -a profiles vlfiles + local rfile Pfile flag + + ensure_pffdir pffdir "$pffdir" + autofix "$pffdir" + setx -a lfiles=get_local_files "$pffdir" + setx profile=get_current_profile "$pffdir" + setx -a profiles=get_profiles "$pffdir" + + if [ ${#lfiles[*]} -gt 0 ]; then + [ ${#lfiles[*]} -gt 1 ] && estep "${#lfiles[*]} fichiers locaux:" || estep "1 fichier local:" + for rfile in "${lfiles[@]}"; do + setx -a vlfiles=get_vlfiles "$pffdir" "$rfile" "$profile" + setx Pfile=get_pfile "$pffdir/$rfile" "$profile" "$pffdir" + if [ ${#vlfiles[*]} -gt 0 ]; then + flag="${COULEUR_ROUGE}P${COULEUR_NORMALE} " + elif [ -f "$Pfile" ]; then + flag="${COULEUR_BLEUE}*${COULEUR_NORMALE} " + else + flag=" " + fi + uecho " $flag$rfile" + done + else + estep "Pas de fichiers locaux définis" + fi + estep "Version courante: ${COULEUR_BLEUE}$VERSION${COULEUR_NORMALE}" + estep "Versions en attente: ${COULEUR_ROUGE}${PVERSIONS[*]}${COULEUR_NORMALE}" + [ -n "$profile" ] && estep "Profil courant: ${COULEUR_BLEUE}$profile${COULEUR_NORMALE}" || estep "${COULEUR_JAUNE}Pas de profil courant${COULEUR_NORMALE}" + [ ${#profiles[*]} -gt 0 ] && estep "Profils valides: ${profiles[*]}" || estep "Pas de profils définis" +} + +################################################################################ +# Programme principal + +for script_alias in "${SCRIPT_ALIASES[@]}"; do + splitpair "$script_alias" src option + if [ "$scriptname" == "$src" ]; then + eval "set -- $option \"\$@\"" + break + fi +done + +QUIET=1 # masquer les messages de git et rsync? +VERYQUIET=1 # masquer les messages de git commit? + +action=infos +autopatch=1 +version= +disttype=auto +ORIGEXTS=("${DEFAULT_ORIGEXTS[@]}") +PROTECTS=("${DEFAULT_PROTECTS[@]}") +unwrap=auto +args=( + --help '$exit_with display_help' + -0,--init action=init + --s:,--origext: '$add@ ORIGEXTS' + --k,--clear-origexts '$ORIGEXTS=()' + -N,--new action=new + -M,--new-only '$action=new; autopatch=' + -V:,--version: version= + --auto-archive disttype=auto + -F,--full-archive disttype=full + -P,--patch-archive disttype=patch + --auto-unwrap unwrap=auto + --no-unwrap unwrap= + -E,--unwrap unwrap=1 + -p,--patch action=patch + -a,--add-global action=add-global + -L,--locals action=list-locals + -l,--profiles action=list-profiles + -s,--switch action=switch + -b,--add-local action=add-local + -e,--edit action=edit + --infos action=infos +) +parse_args "$@"; set -- "${args[@]}" + +array_fix_paths ORIGEXTS + +case "$action" in +init) init_cmd "$@";; +new) new_cmd "$autopatch" "$version" "$disttype" "$unwrap" "$@";; +patch) patch_cmd "$@";; +add-global) add_global_cmd "$@";; +list-locals) list_locals_cmd "$@";; +list-profiles) list_profiles_cmd "$@";; +switch) switch_cmd "$@";; +add-local) add_local_cmd "$@";; +edit) edit_cmd "$@";; +infos) infos_cmd "$@";; +esac