#!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 SCRIPT_ALIASES=( pf0:-0 pfn:-N pfg:-g pfb:-b pfs:-s pfa:-a pfe:-e pfd:-d ) ORIGEXT=pff DEFAULT_ORIGEXTS=(".$ORIGEXT" .origine .default) PFFCONF=.pff.conf # ne pas modifier DEFAULT_PROTECTS=(/.git/ .svn/ /pff/ "/$PFFCONF") NUTOOLS_SH_ARCHIVE_SUPPORT=1 PFFCONFVARS=( "VERSION//Version actuellement installée" -a "PVERSIONS//Versions en attente d'intégration" "PROFILES//Profils définis" -s "DISTTYPE=auto//Type de distribution upstream: full ou patch" -a "ORIGEXTS=//Extensions origines" "AUTO_CMDS//Commandes à lancer lors des vérifications automatiques" "NEW_CMDS//Commandes à lancer avant l'action new" "PROTECTS=//Fichiers locaux à protéger lors de l'intégration au format rsync e.g /dir/, /file, etc." "FILTERS//Filtres appliqués aux fichiers lors de l'intégration, de la forme 'filespec:filter'. Cf la doc pour le détail du format" "NOMERGES=//Fichiers qu'il ne faut pas chercher à fusionner. Cf la doc pour le détail du format" -s "NOUPSTREAM=//Indiquer qu'il n'y a pas de fichiers upstream. pff est uniquement utilisé pour gérer des profils de fichiers" ) 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() { local cwd="$(pwd)" local pffdir="$cwd" while true; do if [ -f "$pffdir/'"$PFFCONF"'" -a -d "$pffdir/pff" ]; then cd "$pffdir/pff" /bin/ls -1d * | while read f; do [ -d "$f" -a "$f" != Current ] || continue f="$1$f" if [[ "${f:0:2}" == -[eEn] ]]; then echo -n - echo "${f:1}" else echo "$f" fi done cd "$cwd" break fi [ "$pffdir" == / -o "$pffdir" == "$HOME" ] && break pffdir="$(dirname -- "$pffdir")" done } function __pfe_profiles() { echo ALL __pff_profiles "$@" | grep -vxF Base } function __pfs_completion() { local cur _get_comp_words_by_ref cur COMPREPLY=($(compgen -W "$(__pff_profiles)" "$cur")) } complete -F __pfs_completion -o bashdefault -o default pfs function __pfe_completion() { local cur prev opt comp _get_comp_words_by_ref cur prev if [[ "$prev" == -*p ]]; then COMPREPLY=($(compgen -W "$(__pfe_profiles)" -- "$cur")) elif [ "$prev" == --profile ]; then COMPREPLY=($(compgen -W "$(__pfe_profiles)" -- "$cur")) elif [[ "$cur" == -*p* ]]; then comp="${cur#-*p}"; opt="${cur:0:$((${#cur}-${#comp}))}" COMPREPLY=($(compgen -W "$(__pfe_profiles "$opt")" -- "$cur")) fi } complete -F __pfe_completion -o bashdefault -o default pfe function __pff_completion() { local cur prev opt comp _get_comp_words_by_ref cur prev if [ "${COMP_WORDS[1]}" == -e -o "${COMP_WORDS[1]}" == --edit ]; then # ne compléter -p que si on est en mode --edit if [[ "$prev" == -*p ]]; then COMPREPLY=($(compgen -W "$(__pfe_profiles)" -- "$cur")) elif [ "$prev" == --profile ]; then COMPREPLY=($(compgen -W "$(__pfe_profiles)" -- "$cur")) elif [[ "$cur" == -*p* ]]; then comp="${cur#-*p}"; opt="${cur:0:$((${#cur}-${#comp}))}" COMPREPLY=($(compgen -W "$(__pfe_profiles "$opt")" -- "$cur")) fi elif [[ "$prev" == -*s ]]; then COMPREPLY=($(compgen -W "$(__pff_profiles)" -- "$cur")) elif [ "$prev" == --switch ]; then COMPREPLY=($(compgen -W "$(__pff_profiles)" -- "$cur")) elif [[ "$cur" == -*s* ]]; then comp="${cur#-*s}"; opt="${cur:0:$((${#cur}-${#comp}))}" COMPREPLY=($(compgen -W "$(__pff_profiles "$opt")" -- "$cur")) fi } complete -F __pff_completion -o bashdefault -o default pff ' exit 0 fi source "$(dirname -- "$0")/lib/ulib/auto" || exit 1 urequire multiconf vcs javaproperties 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'. Les fichiers sont classés dans des profils spécifiques. Les profils reconnus sont: - Base est le profil des fichiers upstream non modifiés. Les fichiers origine sont intégrés dans ce profil. - Common est le profil des fichiers upstream modifiés. Tous les fichiers devraient être modifiés dans ce profil. - Les autres profils sont basés sur Common en priorité puis sur Base en cas de non existence dans Common. Il peut s'agir de profils comme prod ou test, qui contiennent des modifications spécifiques à différents cas d'utilisation. USAGE $scriptname [options] CONFIGURATION Le fichier $PFFCONF contient des variables qui conditionnent le comportement de pff: PROTECTS -- liste de spécifications de fichiers ou de répertoires à protéger lors de l'intégration. Ces spécifications sont au format rsync, e.g /dir/, /file, etc. FILTERS -- liste de définitions de filtres à appliquer aux fichiers lors de l'intégration. ces spécifications sont de la forme 'filespec:filter'. Les spécifications de fichier peuvent contenir des wildcards et sont de deux sortes: - sans chemin, e.g 'myfile' ou '*.c', tous les fichiers de ce nom correspondent - avec un chemin, e.g '/myfile', 'path/to/*.jsp', la correspondance est cherchée relativement au répertoire du projet. ainsi '/*.c' ne matche que les fichiers ayant l'extension .c situés dans le répertoire du projet Les filtres disponibles sont: nl2lf -- forcer le caractère de fin de ligne à LF nl2crlf -- forcer le caractère de fin de ligne à CR+LF nl2cr -- forcer le caractère de fin de ligne à CR normalize_properties -- normaliser fichier de propriétés java Il est possible de créer de nouveaux filtres en définissant des fonctions de la forme pff_filter_NAME(). Ces fonctions, si elles sont appelées sans argument, doivent filtrer l'entrée standard. Si un argument est spécifié, il faut filtrer ce fichier-là. NOMERGES -- liste de spécifications de fichiers qu'il ne faut pas chercher à fusionner. Les spécifications de fichier peuvent contenir des wildcards et sont de deux sortes: - sans chemin, e.g 'myfile' ou '*.c', tous les fichiers de ce nom correspondent - avec un chemin, e.g '/myfile', 'path/to/*.jsp', la correspondance est cherchée relativement au répertoire du projet. ainsi '/*.c' ne matche que les fichiers ayant l'extension .c situés dans le répertoire du projet NOUPSTREAM -- indiquer qu'il n'y a pas de distribution upstream ni de fichiers origine. pff est alors uniquement utilisé pour gérer des profils de fichier. Pour le moment, la seule différence fonctionnelle est que le mode des fichiers de pff/Base n'est pas forcé à 0444. Ainsi, ces fichiers sont traités au même titre que ceux du répertoire pff/Common Dans ce mode, on supporte aussi les liens vers des répertoires entiers: il suffit de créer le répertoire dans pff/Base avec un unique fichier .pffdir à l'intérieur. Lors du switch le répertoire correspondant est lié s'il existe dans un profil. NB: s'il existe d'autres fichiers dans le répertoire, ils sont ignorés. Cela permet d'avoir un ensemble de fichiers par défaut, mais les liens symboliques doivent être faits manuellement. 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 -H est requise. -H, --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 -H 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. -g, --patch [WORKDIR] Intégrer les modifications entrantes sur les fichiers nouvellement arrivés via l'option --new -o, --ours Utiliser la stratégie '-s recursive -X ours' en cas de conflit lors de l'intégration de la version. --ask-commit -c, --commit --no-commit Après l'intégration avec succès d'un patch, demander à l'utilisateur s'il veut faire commit & push dans git (--ask-commit, la valeur par défaut), le faire sans confirmation (--commit), ou ne jamais le faire (--no-commit) --no-push Ne pas faire de push après un commit. On peut aussi lancer la commande suivante avant pff: export UTOOLS_VCS_OFFLINE=1 -b, --add-global FILES... Ajouter/Identifier un fichier comme un fichier local pour tous les profils. Le fichier est copié dans le profil Base. --locals [WORKDIR] Lister les fichiers locaux --profiles [WORKDIR] Lister les profils valides -s, --switch PROFILE [WORKDIR] Basculer le profil courant -a, --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 -p, --profile PROFILE Pour l'option --edit, sélectionner le profil spécifié comme celui concerné pour les fichier mentionnés après cette option. Par exemple: $scriptname -e A -pprod B C -ptest D Edite le fichier A du profil courant, les fichiers B et C du profil prod et le fichier D du profil test. Le profil ALL est spécial et permet d'éditer le fichier dans tous les profils *où il existe*, excepté Base parce que ce profil contient des fichiers qui ne devraient pas être modifiés. Pour tous les autres profils, si le fichier n'existe pas dans le profil spécifié il est rajouté dans le profil avant son édition. Attention: pour que l'option -p/--profile soit correctement reconnue avec l'option -e/--edit, il faut que cette dernière option soit mentionnée en premier sur la ligne de commande. -P, --prod -T, --test -A, --all-profiles Raccourcis pour respectivement -pprod, -ptest et -pALL -d, --diff [DESTP [WORKDIR]] -d, --diff [SRCP DESTP [WORKDIR]] Afficher la différence entre entre deux profils. Avec la première syntaxe, comparer le profil DESTP au profil courant. Avec la deuxième syntaxe, comparer le profil DESTP au profil SRCP. -l, --list-names N'afficher que les noms des fichiers qui sont différents --infos [WORKDIR] Afficher des informations sur le projet courant: profils, fichiers locaux, profil courant, etc. C'est la commande par défaut. Les fichiers locaux sont taggés avec les valeurs suivantes ${COULEUR_ROUGE}P${COULEUR_NORMALE} il existe un patch pour ce fichier dans le profil courant ${COULEUR_BLEUE}*${COULEUR_NORMALE} ce fichier local est spécifique à ce profil $(get_color YELLOW)C${COULEUR_NORMALE} ce fichier local est spécifique au profil Common -l, --show-all Afficher tous les fichiers locaux au lieu de se contenter des fichiers modifiés dans le profil courant." } # 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/Common/ # 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 function flexists() { [ -e "$1" -o -L "$1" ] } 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 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" (for profile in "${PROFILES[@]}"; do echo "$profile"; done list_dirs "$pffdir/pff") | sort -u | grep -vxF Current } function get_user_profiles() { # afficher tous les profils modifiables du projet pff $1 (c'est à dire tous # les profils valides excepté Base) get_profiles "$@" | grep -vxF Base } function get_first_profile() { # afficher le premier profil autre que Base du projet pff $1 local profile profile="${PROFILES[0]}" if [ -z "$profile" -o "$profile" == Base ]; then get_user_profiles "$@" | head -n1 else echo "$profile" fi } function get_local_files() { # afficher tous les fichiers locaux exprimés relativement au répertoire du # projet pff $1 # pour les répertoires liés, ne lister que le fichier .pffdir local pffdir="$1" files file dirs dir isald files="$(find "$pffdir/pff/Base" -type f | sed "s|^$pffdir/pff/Base/||" | grep -v '/__pv-[^/_]*__[^/]*$')" dirs="$(echo "$files" | grep '/\.pffdir$' | sed 's/\.pffdir$//')" setx -a files=echo "$files" setx -a dirs=echo "$dirs" for file in "${files[@]}"; do isald= for dir in "${dirs[@]}"; do if [ "${file#$dir}" != "$file" ]; then isald=1 break fi done [ -z "$isald" -o "$file" == "${dir}.pffdir" ] && echo "$file" done } 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" Common "$2"; } function get_cfile() { get_pfile "$1" Current "$2"; } function get_vlfiles_nostrip() { # afficher tous les fichiers de version local pffdir="$1" rfile="$2" profile="${3:-Base}" version="$4" [ -d "$pffdir/pff/$profile" ] || return if [ -n "$version" ]; then if [ -n "$rfile" ]; then setx rfile=add_pv "$rfile" "$version" find "$pffdir/pff/$profile" \ -type f -path "$pffdir/pff/$profile/${rfile}" -o \ -type l -path "$pffdir/pff/$profile/${rfile}" else find "$pffdir/pff/$profile" \ -type f -name "__pv-${version}__*" -o \ -type l -name "__pv-${version}__*" fi else if [ -n "$rfile" ]; then setx rfile=add_pv "$rfile" "*" find "$pffdir/pff/$profile" \ -type f -path "$pffdir/pff/$profile/${rfile}" -o \ -type l -path "$pffdir/pff/$profile/${rfile}" else find "$pffdir/pff/$profile" \ -type f -name "__pv-*__*" -o \ -type l -name "__pv-*__*" fi fi } function get_vlfiles() { local pffdir="$1" rfile="$2" profile="${3:-Base}" version="$4" get_vlfiles_nostrip "$@" | sed "s|^$pffdir/pff/$profile/||" } function strip_pv() { local vlfile="$1" version="$2" vldir vlname if [[ "$vlfile" == */* ]]; then vldir="${vlfile%/*}/" vlname="${vlfile##*/}" else vldir= vlname="$vlfile" fi if [ -n "$version" ]; then vlname="${vlname#__pv-${version}__}" else vlname="${vlname#__pv-*__}" fi echo "$vldir$vlname" } function add_pv() { local vlfile="$1" version="$2" vldir vlname if [[ "$vlfile" == */* ]]; then vldir="${vlfile%/*}/" vlname="${vlfile##*/}" else vldir= vlname="$vlfile" fi echo "${vldir}__pv-${version}__${vlname}" } function is_nomerge() { local file="$1" pffdir="$2" local nomerge rfile setx rfile=get_rfile "$file" "$pffdir" setx file=basename -- "$rfile" # utilisé pour le match sur le nom du fichier for nomerge in "${NOMERGES[@]}"; do if [[ "$nomerge" == */* ]]; then # matcher sur le chemin relatif nomerge="${nomerge#/}" if eval "[[ $(qval "$rfile") == $(qwc "$nomerge") ]]"; then return 0 fi else # matcher uniquement sur le nom du fichier if eval "[[ $(qval "$file") == $(qwc "$nomerge") ]]"; then return 0 fi fi done return 1 } 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_user_profiles "$pffdir")" fi local vlfile rfile prefix pfile plink tmp for vlfile in "$@"; do setx rfile=strip_pv "$vlfile" for profile in "${profiles[@]}"; do prefix="$pffdir/pff/$profile" flexists "$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 [[ "$lfile" == */.pffdir ]]; then # répertoires entiers lfile="${lfile%/.pffdir}" src="${src%/.pffdir}" if [ -d "$pffdir/pff/$profile/$lfile" ]; then dest="$profile/$lfile" elif [ "$profile" != Common -a -d "$pffdir/pff/Common/$lfile" ]; then dest="Common/$lfile" else dest="Base/$lfile" fi elif [ -f "$pffdir/pff/$profile/$lfile" ]; then dest="$profile/$lfile" elif [ "$profile" != Common -a -f "$pffdir/pff/Common/$lfile" ]; then dest="Common/$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" # tous les fichiers du profil Base doivent être en lecture seule, sauf si # NOUPSTREAM=1 if [ -z "$NOUPSTREAM" ]; then find "$pffdir/pff/Base" -type f -perm /222 -exec chmod a-w '{}' + fi # Lancer les commandes AUTO_CMDS local cwd cmd if [ ${#AUTO_CMDS[*]} -gt 0 ]; then etitle "AutoCmds" cwd="$(pwd)" cd "$pffdir" for cmd in "${AUTO_CMDS[@]}"; do estep "\$ $cmd" eval "$cmd" done cd "$cwd" eend fi return 0 } 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" } ################################################################################ # Filtres function pff_filter_normalize_properties() { if [ $# -eq 0 ]; then __norm_properties else local mode r mode="$(fix_mode "$1")" norm_properties "$1"; r=$? unfix_mode "$1" "$mode" return $r fi } function pff_filter_nl2lf() { if [ $# -eq 0 ]; then _nl2lf else local mode r mode="$(fix_mode "$1")" doinplace "$1" _nl2lf; r=$? unfix_mode "$1" "$mode" return $r fi } function pff_filter_nl2crlf() { if [ $# -eq 0 ]; then _nl2crlf else local mode r mode="$(fix_mode "$1")" doinplace "$1" _nl2crlf; r=$? unfix_mode "$1" "$mode" return $r fi } function pff_filter_nl2cr() { if [ $# -eq 0 ]; then _nl2cr else local mode r mode="$(fix_mode "$1")" doinplace "$1" _nl2cr; r=$? unfix_mode "$1" "$mode" return $r fi } function apply_filter() { # Appliquer les filtres définis au fichier $1 dans le projet pff $2 # $3 est le nom du fichier sans préfixe, pour la sélection du filtre # retourner 0 si un filtre a été appliqué avec succès, 1 si une erreur s'est # produite, 2 si aucun filtre n'existe pour ce fichier local pfile="$1" pffdir="$2" local realfile="${3:-$pfile}" local rfile ffile filter r=2 filedir filespec setx rfile=get_rfile "$realfile" "$pffdir" setx file=basename -- "$rfile" # utilisé pour le match sur le nom du fichier for filter in "${FILTERS[@]}"; do splitpair "$filter" ffile filter if [[ "$ffile" == */* ]]; then # matcher sur le chemin relatif ffile="${ffile#/}" if eval "[[ $(qval "$rfile") == $(qwc "$ffile") ]]"; then "pff_filter_$filter" "$pfile" && r=0 || r=1 fi else # matcher uniquement sur le nom du fichier if eval "[[ $(qval "$file") == $(qwc "$ffile") ]]"; then "pff_filter_$filter" "$pfile" && r=0 || r=1 fi fi done return $r } ################################################################################ # 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 array_addu PROFILES Common conf_update "$workdir/$PFFCONF" PROFILES # 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" unwrap="$3" 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 ;; *.sh) die "Ce type d'archive ne contient pas l'information du type de distribution. Vous devez spécifier l'une des options -F ou -H" ;; *) die "L'extension de l'archive n'est pas reconnue. Vous devez spécifier l'une des options -F ou -H" ;; esac fi local -a tmpargs case "$archive" in *.sh) tmpargs=(--exclude cleanup.sh --exclude install.sh);; esac ac_set_tmpdir tmpd estep "Extraction de $(ppath "$archive" "$cwd")" extract_archive "$archive" "$tmpd" "${tmpargs[@]}" || 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")" if [ "$filename" == "$banv" -o "$filename" == "$ban" ]; then # le nom du répertoire correspond au nom de l'archive. cool! unwrap=1 elif [ "${filename%-$version}" == "$ban" ]; then # le nom du répertoire n'est pas contruit correctement # mais ce n'est pas grave (e.g ban-VERSION au lieu de # banVERSION comme l'archive) unwrap=1 else unwrap= fi 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 -F ou -H" 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_amue_clean() { # en mode distribution "patch", supprimer le contenu des répertoires # spécifiés s'ils sont relivrés [ "$DISTTYPE" == patch ] || return [ $# -gt 0 ] || set -- WEB-INF/{lib,classes/{gouv,com,org}} wsdl local dir for dir in "$@"; do if [ -n "$(/bin/ls -d "$srcdir/$dir"/* 2>/dev/null)" ]; then eval "rm -rf $dir/*" fi done } # compat function new_cmd_amue_clean_libs() { new_cmd_amue_clean WEB-INF/lib; } function new_cmd_amue_clean_wsdl() { new_cmd_amue_clean wsdl; } function new_cmd() { local autopatch="$1" version="$2" disttype="$3" unwrap="$4" merge_strategy="$5" commit_policy="$6"; shift; shift; shift; shift; shift; shift local archive="$1" pffdir="$2" ensure_pffdir pffdir "$pffdir" [ -n "$disttype" ] && DISTTYPE="$disttype" ## préparer la source: les fichiers entrants local srcdir full new__prepare_archive srcdir version "$archive" "$version" "$unwrap" [ "$DISTTYPE" == full ] && full=1 ## Lancer les commandes local cwd cmd if [ ${#NEW_CMDS[*]} -gt 0 ]; then etitle "NewCmds" cwd="$(pwd)" cd "$pffdir" for cmd in "${NEW_CMDS[@]}"; do estep "\$ $cmd" eval "$cmd" done cd "$cwd" eend fi ## 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 -f ORIGEXTS[@] prefix="$srcdir/" fnsrc="$fnsrc0" fosrc="$fosrc0" '{ found = 0 for (i = 1; i <= ORIGEXTS_count; i++) { sub("^" prefix, "") re_origext = qregexp(ORIGEXTS[i]) "(/|$)" if ($0 ~ re_origext) { 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[@]}" "${DEFAULT_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 src="$srcdir/$i" dest="$pffdir/$i" mkdirof "$dest"; r=$? edot $r "mkdirof $i"; [ $r -eq 0 ] || break cp "$src" "$dest"; r=$? edot $r "create $i"; [ $r -eq 0 ] || break apply_filter "$dest" "$pffdir"; r=$? [ $r -eq 0 -o $r -eq 1 ] && edot $r "filter $i" [ $r -eq 2 ] && r=0; [ $r -eq 0 ] || break done [ $r -eq 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 "update $i"; [ $r -eq 0 ] || break apply_filter "$dest" "$pffdir"; r=$? [ $r -eq 0 -o $r -eq 1 ] && edot $r "filter $i" [ $r -eq 2 ] && r=0; [ $r -eq 0 ] || break done [ $r -eq 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 "delete $i"; [ $r -eq 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" <"$fosrc0" awkrun -f ORIGEXTS[@] flsrc="$flsrc" flosrc="$flosrc" '{ for (i = 1; i <= ORIGEXTS_count; i++) { re_origext = qregexp(ORIGEXTS[i]) "(/|$)" if ($0 ~ re_origext) { orig = $0 local = gensub(re_origext, "\\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 }' # contrairement aux fichiers normaux, ajouter le contenu de fdeletes à fupdates # les fichiers de fdeletes sont des fichiers locaux non identifiés comme tels dans l'origine { grep -vxf "$fcreates" "$flsrc"; cat "$fdeletes"; } >"$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 "mkdirof $i"; [ $r -eq 0 ] || break cp "$src" "$dest"; r=$? edot $r "create $i"; [ $r -eq 0 ] || break apply_filter "$dest" "$pffdir"; r=$? [ $r -eq 0 -o $r -eq 1 ] && edot $r "filter $i" [ $r -eq 2 ] && r=0; [ $r -eq 0 ] || break add_global__link "$dest" "$pffdir" edot $r "add_global $i"; [ $r -eq 0 ] || break done [ $r -eq 0 ] || { eend $r; return 1; } for i in "${updates[@]}"; do # du fait qu'on intègre fdeletes dans fupdates, il est possible que # le fichier n'existe pas dans la source. de plus, l'association # dans losrcs n'existe pas non plus. dans ce cas, il suffit de # d'ignorer le fichier if [ -n "${losrcs[$i]+set}" ]; then src="$srcdir/${losrcs[$i]}" else src="$srcdir/$i" fi setx vlfile=add_pv "$i" "$version" dest="$pffdir/pff/Base/$vlfile" if flexists "$src"; then mkdirof "$dest"; r=$? edot $r "mkdirof $vlfile"; [ $r -eq 0 ] || break cp "$src" "$dest"; r=$? edot $r "create $vlfile"; [ $r -eq 0 ] || break apply_filter "$dest" "$pffdir" "$pffdir/pff/Base/$i"; r=$? [ $r -eq 0 -o $r -eq 1 ] && edot $r "filter $vlfile" [ $r -eq 2 ] && r=0; [ $r -eq 0 ] || break array_add vlfiles "$vlfile" fi done [ $r -eq 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 ask_yesno "Voulez-vous passer à l'intégration de cette version?" O && patch_cmd "$merge_strategy" "$commit_policy" "$pffdir" fi } #=========================================================== # pff --patch function patch_cmd() { local merge_strategy="$1" commit_policy="$2"; shift; shift local pffdir="$1" local gitproject was_patched eop_version local version profile rcfile curdir workdir controlfile control tmpfile local -a profiles vlfiles tmpfiles ensure_pffdir pffdir "$pffdir" # est-ce un projet suivi dans git? (cd "$pffdir"; git_check_gitvcs) && gitproject=1 || gitproject= array_from_lines profiles "$(get_user_profiles "$pffdir")" if array_contains profiles Common; then # toujours traiter Common en dernier array_del profiles Common array_add profiles Common fi case "$merge_strategy" in ours) merge_strategy=(-s recursive -X ours);; theirs) merge_strategy=(-s recursive -X theirs);; *) merge_strategy=();; esac while true; do # si pas de version en cours, il n'y a rien à patcher [ ${#PVERSIONS[*]} -gt 0 ] || break eop_version= for version in "${PVERSIONS[@]}"; do have_profile_vlfiles= have_base_vlfiles= for profile in "${profiles[@]}"; do array_from_lines vlfiles "$(get_vlfiles_nostrip "$pffdir" "" "$profile" "$version")" # filtrer les fichiers de NOMERGES mergefiles=() for vlfile in "${vlfiles[@]}"; do setx rfile=strip_pv "$vlfile" "$version" setx rfile=get_rfile "$rfile" "$pffdir" is_nomerge "$rfile" "$pffdir" && continue array_add mergefiles "$vlfile" done vlfiles=("${mergefiles[@]}") # on en a trouvé if [ ${#vlfiles[*]} -gt 0 ]; then have_profile_vlfiles=1 break fi done [ -n "$have_profile_vlfiles" ] && break array_from_lines vlfiles "$(get_vlfiles_nostrip "$pffdir" "" Base "$version")" # filtrer les fichiers de NOMERGES mergefiles=() for vlfile in "${vlfiles[@]}"; do setx rfile=strip_pv "$vlfile" "$version" setx rfile=get_rfile "$rfile" "$pffdir" is_nomerge "$rfile" "$pffdir" && continue array_add mergefiles "$vlfile" done vlfiles=("${mergefiles[@]}") have_base_vlfiles=1 break done if [ -n "$have_profile_vlfiles" ]; then # il faut patcher les fichiers du profil etitle "Intégration de la version $version dans le profil $profile" [ -n "$rcfile" ] || ac_set_tmpfile rcfile [ -n "$controlfile" ] || ac_set_tmpfile controlfile [ -n "$workdir" ] && ac_clean "$workdir" ac_set_tmpdir workdir curdir="$(pwd)" cd "$workdir" git init -q . git checkout -q --orphan upstream # rajouter les fichiers de Base dans la branche upstream for vlfile in "${vlfiles[@]}"; do setx bfile=strip_pv "$vlfile" "$version" setx bfile=get_bfile "$bfile" "$pffdir" setx rfile=get_rfile "$bfile" "$pffdir" mkdirof "$rfile" cp -a "$bfile" "$rfile" [ -z "$NOUPSTREAM" ] && chmod +w "$rfile" done git add -A [ -n "$(git status --porcelain)" ] && git commit -qm "Base" # créer la branche v$version depuis upstream git checkout -qb "v$version" # rajouter les fichiers de Common s'ils existent (sauf si la branche c'est Common ^^) if [ "$profile" != Common ]; then for vlfile in "${vlfiles[@]}"; do setx Cfile=strip_pv "$vlfile" "$version" setx Cfile=get_Cfile "$Cfile" "$pffdir" if flexists "$Cfile"; then setx rfile=get_rfile "$Cfile" "$pffdir" mkdirof "$rfile" cp -a "$Cfile" "$rfile" fi done git add -A [ -n "$(git status --porcelain)" ] && git commit -qm "Common" fi # rajouter les fichiers du profil for vlfile in "${vlfiles[@]}"; do setx pfile=strip_pv "$vlfile" "$version" setx pfile=get_pfile "$pfile" "$profile" "$pffdir" setx rfile=get_rfile "$pfile" "$pffdir" mkdirof "$rfile" cp -a "$pfile" "$rfile" done git add -A [ -n "$(git status --porcelain)" ] && git commit -qm "$profile" # rebasculer vers upstream et rajouter les fichiers de patch git checkout -q upstream for vlfile in "${vlfiles[@]}"; do setx rfile=strip_pv "$vlfile" "$version" setx rfile=get_rfile "$rfile" "$pffdir" mkdirof "$rfile" cp -L "$vlfile" "$rfile" done git add -A [ -n "$(git status --porcelain)" ] && git commit -qm "$version" # basculer vers la branche de version et tenter de merger upstream dans version git checkout -q "v$version" if git merge -q "${merge_strategy[@]}" --no-commit upstream; then # tout s'est bien passé [ -n "$(git status --porcelain)" ] && git commit -qm "v$version --> $profile" else # il y a eu une erreur. laisser l'utilisateur décider quoi faire echo >"$rcfile" "# [ -f /etc/bash.bashrc ] && . /etc/bash.bashrc [ -f ~/.bashrc ] && . ~/.bashrc $(qvals source "$ULIBDIR/ulib") urequire DEFAULTS function Commit() { echo COMMIT >$(qvals "$controlfile"); exit; } # raccourcis pour Commit function C() { Commit \"\$@\"; }; function c() { Commit \"\$@\"; }; function commit() { Commit \"\$@\"; } function Abort() { echo ABORT >$(qvals "$controlfile"); exit; } # raccourcis pour Abort function A() { Abort \"\$@\"; }; function a() { Abort \"\$@\"; }; function abort() { Abort \"\$@\"; } $(qvals cd "$workdir") $(qvals eerror "Une erreur s'est produite: consultez les fichiers concernés et faites les corrections nécessaires. En cas de conflit, vous devez les résoudre en examinant les lignes marquées <<<<<<< HEAD ======= >>>>>>> upstream puis en gardant la partie HEAD pour les modifications locales et upstream pour les modifications entrantes") $(qvals eimportant "Puis tapez ${COULEUR_VERTE}Commit${COULEUR_NORMALE} pour valider l'intégration de cette version") $(qvals eimportant "Sinon, tapez ${COULEUR_ROUGE}Abort${COULEUR_NORMALE} pour arrêter l'intégration de cette version") " >"$controlfile" "${SHELL:-bash}" --rcfile "$rcfile" control="$(<"$controlfile")" if [ -z "$control" ]; then # demander à l'utilisateur sa décision eerror "Vous n'avez pas terminé votre session par ${COULEUR_VERTE}Commit${COULEUR_NORMALE} ou ${COULEUR_ROUGE}Abort${COULEUR_NORMALE}" ask_yesno "Voulez-vous valider l'intégration de la version?" N && control=COMMIT || control=ABORT fi if [ "$control" == COMMIT ]; then array_from_lines tmpfiles "$(git status --porcelain | awk '{print substr($0, 4)}')" for tmpfile in "${tmpfiles[@]}"; do if grep -qE '^(<<<<<<<|=======|>>>>>>>)' "$tmpfile"; then ewarn "Vous avez choisi de valider l'intégration des modifications mais..." ewarn "$tmpfile: ce fichier semble encore contenir des marques de conflit" if ! ask_yesno -y "Etes-vous sûr d'avoir correctement édité le fichier?" N; then eecho "Annulation de l'intégration de la version" control=ABORT break fi fi done fi if [ "$control" == ABORT ]; then enote "Vous pouvez reprendre l'intégration de la version $version avec la commande pff -g" break # abort fi fi # récupérer les versions modifiées et supprimer les fichiers de patch for vlfile in "${vlfiles[@]}"; do setx pfile=strip_pv "$vlfile" "$version" setx pfile=get_pfile "$pfile" "$profile" "$pffdir" setx rfile=get_rfile "$pfile" "$pffdir" cp -a "$rfile" "$pfile" rm "$vlfile" done cd "$curdir" eend elif [ -n "$have_base_vlfiles" ]; then # il faut intégrer la nouvelle version dans Base etitle "Finaliser intégration de la version $version" for vlfile in "${vlfiles[@]}"; do setx bfile=strip_pv "$vlfile" "$version" [ -z "$NOUPSTREAM" ] && chmod +w "$bfile" mv "$vlfile" "$bfile" [ -z "$NOUPSTREAM" ] && chmod a-w "$bfile" done eop_version=1 VERSION="$version" array_del PVERSIONS "$VERSION" conf_update "$pffdir/$PFFCONF" VERSION PVERSIONS eend fi if [ -n "$gitproject" ]; then local commit default if [ -n "$eop_version" ]; then msg="Intégration de la version $version" else msg="Correction du profil $profile pour la version $version" fi commit="$commit_policy" if [ "$commit" == ask ]; then if [ -n "$eop_version" ]; then enote "Vous avez terminé l'intégration des patches de la version $version" default=O else einfo "Vous avez intégré les patches de la version $version pour le profil $profile" default=N fi ask_yesno "Voulez-vous enregistrer les modifications dans git?" $default || commit= fi if [ -n "$commit" ]; then git add -A && git commit -m "$msg" || return if [ -z "$UTOOLS_VCS_OFFLINE" -a -n "$(git_get_branch_remote)" ]; then git push fi fi fi done } #=========================================================== # 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 flexists "$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 flexists "$bfile" && [ -L "$pfile" -a -L "$cfile" ]; then : # ok pour les liens else # Création des liens pour $rfile mkdirof "$bfile" || return mv "$pfile" "$bfile" || return if [ -z "$NOUPSTREAM" ]; then chmod a-w "$bfile" || return fi [ -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 flexists "$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" | sed 's/\/.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 ensure_pffdir pffdir "$pffdir" autoinit "$pffdir" [ -n "$profile" ] || setx profile=get_first_profile "$pffdir" setx -a profiles=get_profiles "$pffdir" if ! array_contains profiles "$profile"; then if ! array_contains PROFILES "$profile"; then ewarn "$profile: ce profil n'existe pas" ask_yesno "Voulez-vous le créer?" O || return 1 array_addu PROFILES "$profile" conf_update "$pffdir/$PFFCONF" PROFILES fi 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 flexists "$pffdir/$rfile" || touch "$pffdir/$rfile" # vérifier la présence du lien global setx bfile=get_bfile "$pfile" "$pffdir" if ! flexists "$bfile"; then add_global__link "$pfile" "$pffdir" || return fi # vérifier la présence du lien local setx Pfile=get_pfile "$pfile" "$profile" "$pffdir" flexists "$Pfile" && return 0 # créer le fichier local setx Cfile=get_Cfile "$pfile" "$pffdir" mkdirof "$Pfile" if flexists "$Cfile"; then # copier depuis le profil Common par défaut cp "$Cfile" "$Pfile" else cp "$bfile" "$Pfile" [ -z "$NOUPSTREAM" ] && chmod +w "$Pfile" fi # 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 flexists "$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 profile="$1"; shift local pffdir file rfile pfile Pfile r local -a profiles edits args ensure_pffdir pffdir array_from_lines profiles "$(get_user_profiles "$pffdir")" [ -n "$profile" ] || setx profile=get_current_profile "$pffdir" enote "Dans le profil $profile:" r=0 while [ $# -gt 0 ]; do if [[ "$1" == -* ]]; then # nouvelle option local prev_profile="$profile" args=(+ -p:,--profile: profile=) parse_args "$@"; set -- "${args[@]}" [ "$profile" != "$prev_profile" ] && enote "Dans le profil $profile:" continue fi file="$1"; shift 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 } if [ "$profile" == ALL ]; then local tmpp for tmpp in "${profiles[@]}"; do setx Pfile=get_pfile "$pfile" "$tmpp" "$pffdir" [ -e "$Pfile" ] || continue estep "Edition de $(ppath "$Pfile")" array_add edits "$Pfile" done else 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" estep "Edition de $(ppath "$Pfile")" array_add edits "$Pfile" fi done if [ ${#edits[*]} -gt 0 ]; then "${EDITOR:-vi}" "${edits[@]}" || r=$? else r=2 fi return $r } #=========================================================== # pff --diff function diff_cmd() { local list_names="$1"; shift local pffdir profile srcp destp desc case $# in 0) ensure_pffdir pffdir setx srcp=get_current_profile "$pffdir" destp= ;; 1) if withpath "$1"; then ensure_pffdir pffdir "$1" setx srcp=get_current_profile "$pffdir" destp= else ensure_pffdir pffdir setx srcp=get_current_profile "$pffdir" destp="$1" fi ;; 2) if withpath "$2"; then ensure_pffdir pffdir "$2" setx srcp=get_current_profile "$pffdir" destp="$1" else ensure_pffdir pffdir srcp="$1" destp="$2" fi ;; *) ensure_pffdir pffdir "$3" srcp="$1" destp="$2" ;; esac [ "$destp" == "Common|Base" ] && destp= if [ -z "$destp" ]; then if [ "$srcp" == Base -o "$srcp" == Common ]; then desc="pff --diff $srcp Base" else desc="pff --diff $srcp ${destp:-"Common|Base"}" fi else desc="pff --diff $srcp $destp" fi local -a lfiles; local rfile bfile Cfile srcfile destfile setx -a lfiles=get_local_files "$pffdir" local -a diffcolor isatty && diffcolor=(--color) || diffcolor=(--no-color) for rfile in "${lfiles[@]}"; do setx srcfile=get_pfile "$pffdir/$rfile" "$srcp" "$pffdir" flexists "$srcfile" || continue if [ -n "$destp" ]; then setx destfile=get_pfile "$pffdir/$rfile" "$destp" "$pffdir" else setx Cfile=get_Cfile "$pffdir/$rfile" "$pffdir" setx bfile=get_bfile "$pffdir/$rfile" "$pffdir" # si on précise pas le profil destination, sélectionner dans l'ordre # Common ou Base if [ "$srcp" == Base -o "$srcp" == Common ]; then destfile="$bfile" elif flexists "$Cfile"; then destfile="$Cfile" else destfile="$bfile" fi fi if [ -n "$list_names" ]; then diff -q "$destfile" "$srcfile" >&/dev/null || echo "$srcfile" else [ -n "$desc" ] && echo "$desc" desc= if [ -n "$PFF_USE_REGULAR_DIFF" ]; then # au cas où git n'est pas disponible diff -ur "$destfile" "$srcfile" else git diff "${diffcolor[@]}" --no-index "$destfile" "$srcfile" fi fi done | page_maybe } #=========================================================== # pff --infos function infos_cmd() { local show_all="$1"; shift local pffdir="$1" local -a profiles vlfiles local rfile Pfile flag if find_pffdir pffdir "$pffdir"; then 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" setx Cfile=get_Cfile "$pffdir/$rfile" "$pffdir" if [[ "$rfile" == */.pffdir ]]; then # répertoires entiers rfile="${rfile%.pffdir}" Pfile="${Pfile%/.pffdir}" Cfile="${Cfile%/.pffdir}" if [ -d "$Pfile" ]; then flag="${COULEUR_BLEUE}*${COULEUR_NORMALE} " elif [ "$profile" != Common -a -d "$Cfile" ]; then flag="$(get_color YELLOW)C${COULEUR_NORMALE} " elif [ -z "$show_all" ]; then continue else flag=" " fi elif [ ${#vlfiles[*]} -gt 0 ]; then flag="${COULEUR_ROUGE}P${COULEUR_NORMALE} " elif [ -f "$Pfile" ]; then flag="${COULEUR_BLEUE}*${COULEUR_NORMALE} " elif [ "$profile" != Common -a -f "$Cfile" ]; then flag="$(get_color YELLOW)C${COULEUR_NORMALE} " elif [ -z "$show_all" ]; then continue 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" elif [ -f "${pffdir:-.}/$PFFCONF" ]; then : "${pffdir:=.}" conf_init "${PFFCONFVARS[@]}" source "$pffdir/$PFFCONF" estep "[Projet pff déployé] Version courante: ${COULEUR_BLEUE}$VERSION${COULEUR_NORMALE}" fi } ################################################################################ # 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? parse_mode= if [ "$1" == -e -o "$1" == --edit ]; then parse_mode=+ fi action=infos autopatch=1 version= disttype= ORIGEXTS=("${DEFAULT_ORIGEXTS[@]}") unwrap=auto merge_strategy= commit_policy=ask profile= alternate= args=($parse_mode --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 -H,--patch-archive disttype=patch --auto-unwrap unwrap=auto --no-unwrap unwrap= -E,--unwrap unwrap=1 -g,--patch action=patch -o,--ours merge_strategy=ours --theirs merge_strategy=theirs --ask-commit commit_policy=ask -c,--comit commit_policy=1 --no-commit commit_policy= --no-push UTOOLS_VCS_OFFLINE=1 -b,--add-global action=add-global --locals action=list-locals --profiles action=list-profiles -s,--switch action=switch -a,--add-local action=add-local -e,--edit action=edit -p:,--profile: profile= -P,--prod profile=prod -T,--test profile=test -A,--all-profiles profile=ALL -d,--diff action=diff --infos action=infos -l,--list-names,--show-all alternate=1 ) parse_args "$@"; set -- "${args[@]}" array_fix_paths ORIGEXTS case "$action" in init) init_cmd "$@";; new) new_cmd "$autopatch" "$version" "$disttype" "$unwrap" "$merge_strategy" "$commit_policy" "$@";; patch) patch_cmd "$merge_strategy" "$commit_policy" "$@";; 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 "$profile" "$@";; diff) diff_cmd "$alternate" "$@";; infos) infos_cmd "$alternate" "$@";; esac