326 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
 | |
| urequire multiconf
 | |
| 
 | |
| PFF_ORIGEXT=pff
 | |
| PFF_CONF=.pff.conf # ne pas modifier
 | |
| DEFAULT_PFF_ORIGEXTS=(".$ORIGEXT" .origine .default)
 | |
| DEFAULT_PFF_PROTECTS=(/.git/ .svn/ /pff/ "/$PFF_CONF")
 | |
| 
 | |
| PFF_CONFVARS=(
 | |
|     "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"
 | |
|     "PROTECTS=//Fichiers locaux à protéger lors de l'intégration e.g /dir/, /file, etc."
 | |
|     "MKDIRS//Répertoires qui doivent toujours exister"
 | |
|     "FILTERS//Filtres appliqués aux fichiers lors de l'intégration"
 | |
|     "NOMERGES=//Fichiers qu'il ne faut pas chercher à fusionner"
 | |
| )
 | |
| 
 | |
| # 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/$PFF_CONF" -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 "${PFF_CONFVARS[@]}"
 | |
|         source "$pffdir/$PFF_CONF"
 | |
|         local "$destvar"; upvar "$destvar" "$pffdir"
 | |
|         return
 | |
|     fi
 | |
|     local msg="Projet pff introuvable (utiliser --init ?)"
 | |
|     [ -n "$2" ] && die "$2: $msg" || die "$msg"
 | |
| }
 | |
| 
 | |
| function pff_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 pff_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 pff_get_user_profiles() {
 | |
|     # afficher tous les profils modifiables du projet pff $1 (c'est à dire tous
 | |
|     # les profils valides excepté Base)
 | |
|     pff_get_profiles "$@" | grep -vxF Base
 | |
| }
 | |
| 
 | |
| function pff_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
 | |
|         pff_get_user_profiles "$@" | head -n1
 | |
|     else
 | |
|         echo "$profile"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| function pff_get_local_files() {
 | |
|     # afficher tous les fichiers locaux exprimés relativement au répertoire du
 | |
|     # projet pff $1
 | |
|     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 pff_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 pff_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 pff_get_bfile() { pff_get_pfile "$1" Base "$2"; }
 | |
| function pff_get_Cfile() { pff_get_pfile "$1" Common "$2"; }
 | |
| function pff_get_cfile() { pff_get_pfile "$1" Current "$2"; }
 | |
| 
 | |
| function pff_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
 | |
|             find "$pffdir/pff/$profile" \
 | |
|                  -type f -path "$pffdir/pff/$profile/${rfile}__pv-${version}__" -o \
 | |
|                  -type l -path "$pffdir/pff/$profile/${rfile}__pv-${version}__"
 | |
|         else
 | |
|             find "$pffdir/pff/$profile" \
 | |
|                  -type f -name "*__pv-${version}__" -o \
 | |
|                  -type l -name "*__pv-${version}__"
 | |
|         fi
 | |
|     else
 | |
|         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-*__"
 | |
|         else
 | |
|             find "$pffdir/pff/$profile" \
 | |
|                  -type f -name "*__pv-*__" -o \
 | |
|                  -type l -name "*__pv-*__"
 | |
|         fi
 | |
|     fi
 | |
| }
 | |
| function pff_get_vlfiles() {
 | |
|     local pffdir="$1" rfile="$2" profile="${3:-Base}" version="$4"
 | |
|     pff_get_vlfiles_nostrip "$@" | sed "s|^$pffdir/pff/$profile/||"
 | |
| }
 | |
| 
 | |
| function pff_is_nomerge() {
 | |
|     local file="$1" pffdir="$2"
 | |
|     local nomerge rfile
 | |
|     setx rfile=pff_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
 | |
|             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 pff_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 "$(pff_get_user_profiles "$pffdir")"
 | |
|     fi
 | |
|     local vlfile rfile prefix pfile plink tmp
 | |
|     for vlfile in "$@"; do
 | |
|         rfile="${vlfile%__pv-*__}"
 | |
|         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 pff_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=pff_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 pff_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 mkdir
 | |
|     [ -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
 | |
|     find "$pffdir/pff/Base" -type f -perm /222 -exec chmod a-w '{}' +
 | |
|     # Créer les répertoires de MKDIRS
 | |
|     for mkdir in "${MKDIRS[@]}"; do
 | |
|         mkdir -p "$pffdir/$mkdir"
 | |
|     done
 | |
|     return 0
 | |
| }
 | |
| 
 | |
| function pff_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=pff_get_first_profile "$pffdir"
 | |
|         [ -n "$profile" ] || profile=Base
 | |
|         enote "Autosélection du profil $profile"
 | |
|         pff_select_profile "$profile" "$pffdir"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| function pff_autofix() {
 | |
|     pff_autoinit "$1"
 | |
|     pff_autoselect "$1"
 | |
| }
 |