SVN : Purger un repository de ses anciennes révisions

Subversion (SVN) est un système de gestion de versions opensource permettant avec simplicité le travail collaboratif sur un projet.
Chaque collaborateur travaille alors sur une copie locale, et le serveur SVN comprend un dossier de référence appelé le repository, ou le dépôt.
Chaque commit effectué, que ce soit pour ajouter, supprimer, renommer, modifier un fichier, entraîne la création d’une nouvelle révision du projet.
Le problème c’est que le repository grossit au fur et à mesure des révisions.
Il arrive donc parfois que l’on veuille gagner de l’espace disque en purgeant les révisions du projet, et puis honnêtement, qui aurait l’idée de revenir à la version 3 d’un projet qui en est à sa version 2712. 🙂

Quelques commandes SVN qui pourront nous être utiles

svnlook:

Cette commande n’effectue aucune modification sur le dépôt. Elle permet d’examiner le contenu du dépôt. Associé à des sous commande, elle donne des renseignements précis et précieux. Par contre , elle utilise le chemin du dépôt, et non pas l’adresse, et ne peut donc être utilisée que sur le serveur où se situe le dépôt.

  • ex: svnlook youngest , donne le numéro de la dernière révision du projet
  • ex: svnlook uuid , donne le numéro d’identifiant unique du projet SVN. Cet identifiant est utilisé par les clients SVN pour retrouver le bon dépôt.

Il y en a évidemment d’autres, je vous invite à les découvrir ici.

svnadmin:

Cette commande permet de travailler sur le dépôt (création, surveillance, réparation, ..)
Tout comme svnlook elle utilise le chemin du dépôt, et non pas l’adresse, et ne peut donc être utilisée que sur le serveur où se situe le dépôt.
Les deux principales sous commandes que nous allons aborder ici sont le dump et le load.

svnadmin dump
svnadmin dump permet de décharger tout ou partie d’un dépôt

svnadmin dump CHEMIN_DÉPÔT [-r BAS[:HAUT]] [OPTIONS]

Pour décharger totalement un dépôt , la commande est la suivante:

svnadmin dump CHEMIN_DÉPÔT > monfichier.dump

(Le dépôt est alors pris dans son intégralité).
Deux options permettent de modifier le comportement, il s’agit de –deltas et de –incremental.
L’option –incremental qui force la première révision à ne contenir que les fichiers et répertoires modifiés lors de cette révision (au lieu de l’ensemble de l’arborescence) au même format que les autres révisions d’un flux dump. Cela permet d’avoir un fichier de dump relativement petit, mais il faut que le dépôt dans lequel on rechargera soit déjà au courant de l’arborescence. (Non utilisable dans notre cas)
Dans l’exemple suivant on décharge le dépôt à partir de la révision 21 jusque la dernière, et ceci de façon incrémentale.

svnadmin dump CHEMIN_DÉPÔT -r 21:HEAD --incremental > monfichier.dump

L’option –deltas indique à svnadmin dump de générer uniquement le delta des éléments concernés par rapport à leur version antérieure. Cela réduit la taille du fichier de dump, mais elle peut avoir d’autres inconvénients. Il faut donc bien se renseigner avant de l’utiliser (dans notre cas, l’option m’a semblé tout a fait correspondre au besoin).

svnadmin load
svnadmin load charge un flux dump et propage les nouvelles révisions dans le système de fichiers du dépôt.

svnadmin load CHEMIN_DÉPÔT [OPTIONS]

Pour charger un dépôt, la commande est la suivante:

svnadmin load CHEMIN_DÉPÔT < monfichier.dump

Parmi les options celle qui nous intéresse est –force-uuid, car dans ce cas le load utilise le UUID qu’il trouve dans le flux (donc dans le fichier de dump) si il existe. Dans notre cas cela permettra de conserver le même UUID que précédemment et donc de permettre à nos différents développeurs de continuer de faire des update comme avant. Dans le cas contraire un checkout s’imposerait.

Quelques explications sur le fichier de dump

L’entête

L’entête du dump comprend les informations suivantes:

  • SVN-fs-dump-format-version: 2
  • UUID: <UUID>

C’est à dire respectivement,le numéro de version du format de DUMP, généralement le « 2 » sauf cas particulier , et le numéro d’identifiant unique du dépôt SVN.

Les informations minimales d’une révision

Chaque révision est formatée dans le fichier de dump au minimum de la manière suivante:

  • Revision-number: xx => Le numéro de la révision
  • Prop-content-length: xx => la longueur en caractères que va prendre les propriétés de contenus (les clés, identifiées par la lettre K)
  • Content-length: xx => la longueur en caractères que va prendre le contenu (les valeurs, identifiées par la lettre V)

puis pour chaque propriété:

  • K x => avec x le nombre de caractères de la clé
  • svn:<label> => le nom de la propriété concernée avec pouvant prendre les valeurs log, author, date, ..
  • V y => avec y le nombre de caractères de la valeur
  • <value> => avec la valeur de votre choix

Exemple:

Revision-number: 1
Prop-content-length: 92
Content-length: 92

K 7
svn:log
V 18
Revision archived.
K 10
svn:author
V 9
misslemon
K 8
svn:date
V 27
$(date +"%Y-%m-%dT%T")
PROPS-END

La procédure mise en place

Venons en enfin au fait. 🙂
Le script mis en place prend en argument les paramètres de <CHEMIN-DU-DEPOT> et de <NUMERO-DE-REVISION>, numéro le plus bas que l’on souhaite conserver.
Elle fait elle même appel à un second script gen-empty-revs.sh permettant de générer un fichier dump composé de révision vides pour les révisions que l’on souhaite supprimer.
En effet, l’option –incremental nous aurait permis de renuméroter les révisions, mais nous ne pouvons pas nous permettre de perdre l’arborescence, il serait impossible de recharger le dump par la suite. Nous aurions des erreur du type:

<<< Début d'une nouvelle transaction basée sur la révision xxx
svnadmin: File not found: transaction '0-0', path 'site/www/dossier/fichier.ext'
     * édition de : site/www/dossier/fichier.ext ...

Nous avons donc choisi de supprimer les anciennes révisions et de les remplacer par des révisions vides, qui seront beaucoup moins gourmandes en place.
Pour lancer le script:

sh monscript  

Voici le script:

#!/bin/sh
# Usage: sh svnpurge repository-whole-path max-rev-to-backup
#

[ $# -lt 2 ] && {
        echo Usage: $0 repository-whole-path max-rev-to-backup
        exit 1
}

ARCHIVE_UPTO=$2
REPONAME=$1

ARCHIVE_FROM=`expr $ARCHIVE_UPTO + 1`
#il est possible à ce niveau de faire un backup du DEPOT; à vous de voir
OLD_UUID=`svnlook uuid ${REPONAME}`
bash gen-empty-revs.sh ${ARCHIVE_UPTO} ${OLD_UUID} > gen-dump
svnadmin dump ${REPONAME} -r $ARCHIVE_FROM:HEAD --deltas >> gen-dump
rm -rf ${REPONAME}
svnadmin create ${REPONAME}
svnadmin load --force-uuid ${REPONAME} < gen-dump

Et voici le script gen-empty-revs:

# Usage: gen-empty-revs.sh uuid max-rev-no
#

## Generate dump header
function dump_header ()
{
# $1 must contain repo UID

cat <<-DUMP_HEADER
SVN-fs-dump-format-version: 2

UUID: $1

DUMP_HEADER
}

## Revison-number blocks
#  $1 must contain a revision number
function dump_rev ()
{
cat <<-REVNUM_BLOCKS
Revision-number: $1
Prop-content-length: 92
Content-length: 92

K 7
svn:log
V 18
Revision archived.
K 10
svn:author
V 9
misslemon
K 8
svn:date
V 27
$(date +"%Y-%m-%dT%T.445155Z")
PROPS-END

REVNUM_BLOCKS
}

[ $# -lt 2 ] && {
        echo Usage: $0 upto_rev_num repo_uuid
        exit 1
}

dump_header $2
for n in `seq 1 $1`
do
        dump_rev $n
done

Source:
Le livre de référence sur svn, et en français , sur le site HumBug.
L’article qui m’a permis de résoudre mes problèmes et de comprendre le fonctionnement sur le site o’reilly.com et dans lequel j’ai repris le script gen-empty-revs.
La documentation apache sur le format des fichiers de dump.