Accueil > Recettes Techniques > Divers > Subversion : export des fichiers depuis une révision en bash

Subversion : export des fichiers depuis une révision en bash

Comme un svn diff mais avec les fichiers complets

mercredi 11 novembre 2009, par Julien Falconnet

Lorsqu’on travaille avec Subversion (SVN) la mise en production demande de passer par la commande svn export. Mais qu’en est il des mises à jour ? La procédure recommandée est de passer par diff et patch. Mais que faire lorsqu’il est impossible de passer par patch parce que l’on est limité à l’utilisation d’un FTP, par exemple ?

Et bien, on est bien embêté. En tout cas, c’était mon cas. Après quelques recherches sur Internet, difficile de trouver une solution satisfaisante. Aucun utilitaire ne semblant faire référence, et les scripts maison ne correspondant pas à mes besoin, j’ai décidé de mettre au point un petit utilitaire bash que je vous livre ici en GPL.

Comment exporter tous les fichiers qui ont changé depuis ma dernière livraison ? Voici une question qui a du tarauder un certain nombre de développeur au moins une fois. C’est vrai, après tout, on investit du temps sur l’utilisation d’un système de suivi de version (ici svn), si c’est pour en perdre encore lorsqu’il faut livrer, où est l’intérêt ? Pourquoi livrer la bibliothèque monstrueuse qu’on a du ajouter en révision 132 alors qu’on est révision 683 et qu’on fait une livraison en r650. Et je passe sur la question du client qui se demande pourquoi on lui renvoie une archive de 60 Mo alors qu’il n’a demandé que quelques corrections d’orthographe et de typographie ( si ! si ! ça pèse super lourd une virgule !).

En effet, svn export exporte toute le projet à une révision donnée. Et svn diff qui affiche bien les différences entre deux révisions ne produit que des sorties au format patch (il n’indique que les lignes qui ont été modifiées).

Après un certain nombre de recherches, j’ai été bien désespéré de voir que non seulement je n’étais pas le seul à buter sur ce problème, mais qu’en plus aucune solution ne semblait faire consensus. J’ai trouvé quelques solutions maisons et même des scripts entiers, mais aucuns ne correspondait à ce que je voulais faire, c’est-à-dire un simple export différentiel qui ressemblerait à un svn export (donc en bash).

J’ai donc décidé de me lancer et de créer cette perle rare. Comme j’en étais content et que je me suis dit que ça pourrait resservir, je vous la sert donc ci-dessous (en gpl). Vous noterez que je suis tellement persuadé de la dimension universelle de ce problème que j’ai décidé de le coder (et de le commenter en anglais).

Voici le code bash desvnxport.sh.

  1. #!/bin/sh
  2. # svnxport.sh
  3. #Export only modified files in SVN
  4. #
  5. #  Copyright (C) 2009 by Julien Falconnet
  6. #  http://www.falconnet.fr
  7. #
  8. #  This program is free software; you can redistribute it and/or modify
  9. #  it under the terms of the GNU General Public License as published by
  10. #  the Free Software Foundation; either version 2 of the License, or
  11. #  any later version.
  12. #
  13. #
  14. #BEWARE : This script does not operate correctly with files whose filename contains spaces
  15. # tests for parameters
  16. if [ ! $1 ];then echo "svnxport : No source specified. Needs : source revision target_directory";exit;fi
  17. if [ ! $2 ];then echo "svnxport : No revision specified. Needs : source revision target_directory";exit;fi
  18. if [ ! $3 ];then echo "svnxport : No target_directory specified. Needs : source revision target_directory";exit;fi
  19. # check if the target_directory allready exists
  20. if [ -d $3 ];then echo "svnxport : target_directory '$3' allready exists. Remove it or change target_directory parameter.";exit;fi
  21. echo "Processing : source($1), revision($2), target_directory($3)"
  22. # we use svn diff to select changed files and only keep those updated or added.
  23. # Then the 'for' separate status from filename (here is the problem with file with blanks)
  24. for myfile in `svn diff -r $2:HEAD --summarize $1 | grep -e '^M ' -e '^A '`
  25. do
  26. if  [  "$myfile" = "M"  -o  "$myfile" = "AM" -o "$myfile" = "A" -o "$myfile" = "." -o -d $myfile ]
  27. then
  28.         # we ignore the status, and the directory to update
  29.         continue
  30. else
  31.         #we focus on true changed files
  32.         #first we create needed directories for the current file
  33.         #note that we use a relative directory system
  34.         outfile=`echo $myfile |sed "s|$1||g"`
  35.         dir="$3/$outfile"
  36.         mkdir -p $(dirname $dir)
  37.         #then we export the file
  38.         svn export $myfile $3/$outfile >> /dev/null
  39.         echo "export $3/$outfile "
  40. fi
  41. done
  42. # List other files. Changed but not exported. Mainly the deleted ones.
  43. # Usefull to know which files should be removed and follow weird comportment
  44. echo "Watch for : "
  45. svn diff -r $2:HEAD --summarize $1 | grep -v -e 'M ' -e 'A ' |sed "s|$1||g"

Pour ceux que ça intéressent, deux points ont été compliqués. D’abord, le fait qu’en bash le for découpe sur les espaces (et non pas sur les retours à la ligne) : Un coup sur deux j’avais le statut, ce qui était pénible puisque je ne voulais traiter que les A et les M et pas les D. J’ai faillit passé par un marqueur et finalement j’ai trouvé plus élégant de passer par un grep (qui s’intercale avant le découpage du for).

L’autre soucis a été la création de l’arborescence. En effet j’ai été déçu de voir que ni touch, ni mkdir, ni svn export n’étaient capables de créer des sous répertoires en même temps que le répertoire père. Du coup j’ai été obligé de rajouter cette boucle toute laide de mkdir. [edit ; grâce au post de Rodney Amato j’ai put trouver le paramètre -p de mkdir qui remplace avantageusement la boucle en question]

Par contre, du coup, les espaces dans les noms de fichiers sont découpés par le for et les fichiers correspondants ne sont pas exportés par le script. Il faudrait remplacer les espaces dans les fichier à la volée avant le for et les re-remplacer après mais c’était tellement inélégant que je m’y suis refusé. D’autant que cela ne me sert pas puisque je ne mets jamais d’espace dans mes noms de fichiers (c’est trop laid). Par contre si quelqu’un à une solution élégante pour contourner ce problème je suis preneur.

A bientôt...


Parmi les scripts intéressant que j’ai trouvé, celui ci est celui qui m’a le plus inspiré. Hélas il était en php et faisait plus que ce que je voulais. Mais pour ceux qui cherchent une solution en php plutôt qu’en bash, je pense qu’elle est valide :http://forum.phpfrance.com/vos-contributions/export-fichier-depuis-subversion-pour-mise-production-backup-inclus-t241495.html

Les hooks SVN auraient pu être une autre piste intéressante mais demandent de modifier le dépot, donc des droits étendus. (une présentation rapide en français)

Merci aussi à ce post de Rodney Amato qui m’a apporté la révélation du mkdir -p.

Messages

  • Bonjour, merci pour le script.
    Je voudrais dire, que dans l’expression reguliere j’ai rajouté ^ avant A et M.

    svn diff -r $2:HEAD —summarize $1 | grep -e ’^M ’ -e ’^A ’

    C’est me permis éviter l’export de tout l’ensemble de projet à la fin d’exécution de script.

    Testé sur Mac 10.5.

    Fedir

  • Bonjour Julien,

    Merci pour ton script qui correspond à ce que je cherchais.

    En ce qui concerne le problème récurrent (et ô combien pénible...) de la gestion des noms de fichiers contenant des espaces, tu peux utiliser la commande bash "while read" (+ pipe ou fichier) au lieu du classique "for" qui, en effet, posera toujours problème.

    Exemples d’utilisation dans un répertoire contenant des fichiers avec des espaces :

    $ for i in `ls -1` ; do file "$i" ; done

    Ne marche pas avec les fichiers ayant des espaces...

    $ ls -1 | while read i ; do file "$i" ; done

    Fonctionne avec les fichiers ayant des espaces... (sans utiliser de fichier temporaire).

    $ ls -1 > temp.txt ; while read i ; do file "$i" ; done < temp.txt

    Fonctionne avec les fichiers ayant des espaces... (en écrivant le résultat de `ls -1` dans un fichier temporaire).

    Note 1 : l’exemple utilise `ls -1` et non le simple `ls` pour être certain d’avoir un seul nom de fichier par ligne (important pour que "while read" fonctionne...)

    Note 2 : il est très important de protéger la variable $i par des guillemets dans la boucle, sinon les espaces poseront toujours problème et le "while read" n’aura servi à rien... De même pour tout ton (tes) script(s), il est fortement recommandé de protéger toutes les variables $1, $2, $3, $myfile et $outfile par des guillemets pour les mêmes raisons. ;-)

    Note 3 : l’exemple numéro 2 (avec un pipe) posera un problème de visibilité d’éventuelles variables à l’intérieur de la boucle depuis l’extérieur car l’utilisation d’un pipe déclenche l’exécution de la boucle dans un sous-process par le shell, sous-process qui empêchera l’export des variables en sortie de la boucle (ça va ? J’ai perdu personne ? :-). Dans ce cas, l’utilisation de l’exemple 3 avec un fichier temporaire est la solution possible à ma connaissance. Exemples :

    $ flag=0 ; ls -1 | while read i ; do file "$i" ; flag=1 ; done ; echo "flag=$flag"

    flag=0 # Et oui ! C’est très déroutant !

    $ flag=0 ; ls -1 > temp.txt ; while read i ; do file "$i" ; flag=1 ; done < temp.txt ; echo "flag=$flag"

    flag=1 # Ouf ! Cette fois, c’est bon !

    Remarque : je conseille au passage la lecture (longue mais très enrichissante) de l’excellent "Avanced Bash-Scripting Guide" :

    http://tldp.org/LDP/abs/abs-guide.pdf

  • Bonjour et merci pour ta petite contribution.

    Je me suis rendu compte que mon besoin n’était pas le tiens et j’ai scripté ma propre boucle, mais je pense que nos problématiques se rejoignent.

    Ma problématique est d’exporter dans une une archive ZIP le contenu d’un DIFF entre deux versions (pour MAJ via FTP/SSH par exemple) que je puisse envoyer par mail.

    Voilà le bout de code que je me suis donc pondu après lecture de ton article :)

    Il est peut être nécessaire d’affiner le pattern du awk à l’affichage réel obtenu dans vos conditions (c’est l’espace entre la "lettre" et le nom du fichier) qui varie peut être d’une console/distrib à l’autre.

    Ainsi, en le mettant dans un des répertoires du PATH ou en l’ajoutant comme alias dans un .profile par exemple : "alias zipdiff=’sh /CHEMIN/VERS/zipdiff.sh"
    Je peux l’utiliser depuis le dossier concerné juste après un svn up par exemple.

    En espérant qu’il puisse servir à d’autres !

    Bonne journée.

    XDjuj.

    # !/bin/sh
    ###
    # zipdiff.sh
    # ZIPPER les fichiers (et l’arbo correspondante) entre deux versions SVN
    ###
    # Usage depuis le dossier actif : sh zipdiff.sh revMIN revMAX
    # Régler l’entrée
    if [ ! $1 ] ;then echo "Usage : sh zipdiff.sh revMIN revMAX" ;exit ;fi
    if [ ! $2 ] ;then echo "Usage : sh zipdiff.sh revMIN revMAX" ;exit ;fi
    # Définir les petites variables qui vont bien
    DATE=`date +%Y%m%d-%Hh%Mm%Ss` ;
    NOM=$DATE-DIFF-$1_$2 ;
    

    # Boucler et zipper sur le dossier actif
    for ladiff in `svn diff -r$1 :$2 —summarize | grep -e ’^M’ -e ’^A’ | awk -F" " ’print $2’`
    do
    zip -r .zip $ladiff
    done
    # Exporter les logs (comme ça on voit AUSSI s’il y a eu des REMOVE)
    echo "$DATE : DIFF entre $1 et $2" > $NOM.log ;
    svn diff -r$1 :$2 —summarize >> $NOM.log
    zip $NOM.zip $NOM.log

  • http://www.epershand.net/developpement/outils/tortoise-svn-export-differentiel

    Cette procédure fait elle ce que tu attends ? Si c’est le cas, tortoise n’étant qu’un client, il suffit de lire dans les logs du serveur subversion la liste des commandes envoyées par le client tortoise pour obtenir facilement un script shell paramétrable avec des commandes svn pures qui font ce que tu attends.

  • Hi Julien,

    Thank you very much for this article. I’ve been looking for a script just like this for a while !

    I hope you don’t mind I’ve made a few adjustments for my personal needs and blogged about them here

    Thanks again

    Stephen

  • Hello Julien,

    J’étais en train de me chercher comment trouver une solution à ce problème et en cherchant la solution je tombe sur un post de toi !! Quelle heureuse coincidence ! Bon, je vais construire ma solution à partir de ton script, merci encore !

    N’hésites pas à m’envoyer un mail un de ces 4 ;)