DevOps, CI/CD et conteneurs
De Subversion à Git : concepts, migration et habitudes d'équipe
Intermédiaire12 min de lectureMis à jour
Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise
Quitter Subversion, c'est mener deux chantiers à la fois : convertir fidèlement l'historique, et faire évoluer les réflexes d'une équipe dont le modèle mental suppose un serveur central unique. Ce guide compare honnêtement les deux modèles, traduit le vocabulaire commande par commande, déroule une conversion complète avec git svn, branches, tags et auteurs compris, puis propose un plan de bascule qui ne perd aucun travail.
Ce que vous allez apprendre
- Opposer les modèles centralisé et distribué et expliquer ce que chacun privilégie
- Traduire les commandes, les conventions d'arborescence et les propriétés Subversion en leurs équivalents Git
- Convertir un dépôt avec git svn, fichier d'auteurs et arborescences non standard compris
- Transformer les répertoires de branches et de tags SVN importés en vraies branches Git et en tags annotés
- Planifier la bascule, vérifier le résultat et faire évoluer les habitudes héritées de SVN
Subversion fait encore tourner une grande quantité de code en production, et les projets qui l'utilisent sont rarement les petits. Une migration tombe donc sur des équipes qui traînent dix ans de révisions, une arborescence qui s'est éloignée des conventions, des scripts de build qui analysent les numéros de révision, et des développeurs qui n'ont jamais travaillé sans un serveur central pour arbitrer. La mécanique de conversion de l'historique est la moitié facile ; la moitié difficile, c'est que Git demande de repenser ce qu'est un commit et le moment où il devient visible par les autres.
Ce guide traite les deux moitiés. Il commence par la différence conceptuelle, parce que l'essentiel des frictions d'une migration en découle, puis traduit le vocabulaire, puis déroule une vraie conversion avec git svn, y compris les points qui piègent tout le monde — le fichier d'auteurs, les arborescences non standard, et la transformation des répertoires importés en véritables branches et tags. Il se termine par la bascule et par les habitudes à changer dès le premier jour.
Ce qui distingue réellement les deux modèles
Subversion n'a qu'un dépôt. Il vit sur un serveur, il contient tout l'historique et il attribue à chaque commit un numéro de révision séquentiel, qui augmente à l'échelle du dépôt entier. Votre working copy est un checkout d'un chemin à une révision donnée, et ne contient aucun historique. Committer implique de contacter le serveur ; s'il est injoignable, ou si vous n'avez pas les droits d'écriture, vous ne pouvez pas committer.
Git donne à chaque développeur un dépôt complet. Cloner copie toute la base d'objets : l'historique, les branches et les tags sont tous locaux. Committer écrit dans votre propre dépôt et n'implique personne d'autre. Partager est un acte distinct et explicite — push — et recevoir en est un autre, fetch. Comme un commit est identifié par le hash de son contenu et de toute son ascendance, il n'y a pas de compteur global à incrémenter, donc pas besoin de coordinateur.
Ce seul changement structurel produit l'essentiel des différences pratiques :
- Créer une branche ne coûte rien. Dans Subversion, une branche est une copie d'un répertoire côté serveur ; dans Git, c'est un fichier qui contient un hash de commit. Les équipes qui créaient une branche par release se mettent à en créer une par changement.
- L'historique est un graphe, pas une ligne. Les numéros de révision de Subversion impliquent un ordre total. Les commits Git ont des parents, un merge en a deux, et « qui est arrivé en premier » n'a de sens que le long d'un chemin du graphe.
- La plupart des opérations sont hors ligne et instantanées.
log,diff,blame,checkoutetcommitlisent des données locales. Sur un gros dépôt, la différence est spectaculaire. - Les checkouts partiels ne sont pas la norme. Subversion permet d'extraire un seul sous-répertoire. Git clone tout, ce qui explique pourquoi les monorepos énormes remplis de binaires demandent de la réflexion (sparse checkout, partial clone, Git LFS).
- L'historique peut être réécrit. Amend, rebase et squash n'ont pas d'équivalent dans Subversion. Ils sont puissants avant le partage et perturbateurs après : c'est une nouvelle règle que l'équipe devra apprendre.
Aucun des deux modèles n'est meilleur dans l'absolu. L'autorité centrale de Subversion simplifie le contrôle d'accès répertoire par répertoire et offre le verrouillage des fichiers impossibles à fusionner. Git y renonce en échange de la vitesse, du travail hors ligne et d'un modèle de branches qui a rendu praticables la revue de code moderne et l'intégration continue.
Traduire le vocabulaire
L'essentiel de la correspondance au quotidien devient mécanique dès que l'on admet qu'une commande SVN se traduit généralement par deux commandes Git, puisque les étapes locale et distante sont séparées.
| Subversion | Git | Remarque |
|---|---|---|
svn checkout URL | git clone URL | Le clone apporte tout l'historique, pas une seule révision. |
svn update | git fetch puis git merge (ou git pull) | Fetch ne touche jamais à vos fichiers. |
svn commit | git commit puis git push | Le commit reste local tant qu'il n'est pas poussé. |
svn status | git status | Git montre aussi ce qui est indexé. |
svn diff | git diff / git diff --staged | Deux diffs, parce que Git a un index. |
svn add / svn rm | git add / git rm | git add indexe aussi les modifications. |
svn revert FILE | git restore FILE | Annule les modifications non commitées. |
svn merge -c 4711 | git cherry-pick <hash> | Applique un changement ailleurs. |
svn copy ^/trunk ^/branches/x | git switch -c x | Aucun aller-retour avec le serveur. |
svn switch ^/branches/x | git switch x | Changer de branche est une opération locale sur les fichiers. |
svn log -v | git log --stat | Ajoutez --graph pour voir les merges. |
svn blame | git blame | Même principe, exécuté en local. |
propriété svn:ignore | fichier .gitignore | Un fichier suivi, pas une métadonnée. |
svn:externals | submodule, subtree ou dépendance de paquet | Pas de conversion automatique. |
svn lock | verrouillage Git LFS, ou rien | Git n'a pas de verrouillage natif. |
numéro de révision r4711 | hash de commit a1b2c3d | Utilisez git describe pour des noms lisibles. |
Deux conventions méritent une remarque. Les répertoires trunk, branches et tags de Subversion relèvent de la pure convention — le serveur n'y voit que des répertoires ordinaires, et c'est pourquoi une copie fait office de branche. Git possède des refs de branche et de tag à part entière : après la conversion, ces répertoires disparaissent et deviennent des refs. Par ailleurs, les tags Subversion sont des répertoires modifiables dans lesquels il arrive qu'on committe ; les tags Git sont censés être des pointeurs permanents, si bien qu'un « tag » ayant reçu des commits après sa création doit plutôt être converti en branche.
Choisir sa méthode de conversion
Trois outils couvrent presque tous les cas.
git svn est livré avec Git, ne demande aucune installation supplémentaire et réalise un import fidèle, révision par révision. C'est le bon choix par défaut pour un dépôt unique allant jusqu'à quelques dizaines de milliers de révisions, et il peut aussi servir de passerelle bidirectionnelle pendant une période de transition.
svn2git est une fine surcouche de git svn qui automatise la conversion des branches et des tags décrite plus bas. Pratique, mais comprenez ce qu'il fait — vous devrez de toute façon inspecter le résultat. Un outil C++ distinct, portant le même nom et développé pour de grandes migrations open source, lit directement un fichier de dump Subversion et se montre bien plus rapide sur les très gros dépôts, au prix d'un fichier de règles à écrire.
reposurgeon est l'option lourde : il charge l'historique sous une forme modifiable pour vous permettre de réécrire les auteurs, corriger les messages, supprimer des chemins ou scinder des dépôts. Sortez-le quand l'historique est vraiment en désordre.
Constituer le fichier d'auteurs
Subversion enregistre un nom d'utilisateur ; Git enregistre un nom et une adresse e-mail sur chaque commit. Sans correspondance, chaque commit historique est attribué à jdoe <jdoe@localhost>, ce qui casse git shortlog, le filtrage de blame et tout outil qui relie les commits à des comptes. Construisez la correspondance avant de commencer.
# Every distinct committer in the repository's history
svn log --quiet https://svn.example.com/shop \
| awk '/^r[0-9]+ \| /{print $3}' \
| sort -u > authors-raw.txt
Transformez cette liste en authors.txt, une ligne par utilisateur :
jdoe = Jane Doe <[email protected]>
amartin = Alex Martin <[email protected]>
build = Build Service <[email protected]>
(no author) = Unknown <[email protected]>
N'oubliez pas la ligne (no author) : les révisions créées par l'administrateur du dépôt ou par les premiers imports n'ont souvent pas d'auteur, et git svn s'interrompt dès qu'il rencontre un nom d'utilisateur absent du fichier. Demandez à chacun de vérifier sa propre ligne — une adresse qui ne correspond pas à son identité Git fera ressembler ses commits historiques et ses nouveaux commits à ceux de deux personnes différentes.
Lancer la conversion
Pour un dépôt qui respecte l'arborescence standard :
git svn clone https://svn.example.com/shop \
--stdlayout \
--authors-file=authors.txt \
--no-metadata \
--prefix=svn/ \
shop-git
--stdlayout est un raccourci pour --trunk=trunk --branches=branches --tags=tags. --no-metadata omet la ligne git-svn-id: qui serait sinon ajoutée à chaque message de commit ; omettez cette option si vous comptez continuer à committer vers Subversion via la passerelle, car celle-ci a besoin de cette ligne. --prefix=svn/ regroupe les refs importées sous refs/remotes/svn/, ce qui lève toute ambiguïté au moment du nettoyage décrit plus bas.
Les arborescences non standard sont courantes. Indiquez les chemins explicitement, et répétez l'option quand les branches vivent à plusieurs endroits :
git svn clone https://svn.example.com/legacy \
--trunk=main/src \
--branches=branches --branches=feature-branches \
--tags=releases \
--authors-file=authors.txt \
--ignore-paths='^(vendor/binaries|docs/generated)/' \
legacy-git
--ignore-paths prend une expression régulière et constitue le moyen le plus simple de laisser hors du nouvel historique les artefacts générés, les binaires tiers ou un dump de base de données commité par erreur. Pour n'importer que l'historique récent, ajoutez -r 40000:HEAD : vous obtenez une conversion rapide dont le premier commit est un snapshot, l'ancien serveur restant disponible en lecture seule pour tout ce qui est plus ancien.
Si l'import s'arrête — panne réseau, auteur manquant —, corrigez la cause et reprenez avec git svn fetch dans le dépôt partiellement converti. Il repart de la dernière révision convertie au lieu de tout recommencer.
Convertir les refs importées en branches et en tags
Après le clone, git branch -a affiche des entrées comme remotes/svn/trunk, remotes/svn/release-2.4 et remotes/svn/tags/v2.4.0. Ce sont des remote-tracking refs, pas de vraies branches, et les pousser vers un nouveau serveur ne sert à rien. Convertissez-les :
cd shop-git
# SVN tag directories become annotated Git tags
git for-each-ref --format='%(refname:short) %(objectname)' refs/remotes/svn/tags \
| while read -r ref sha; do
name="${ref#svn/tags/}"
git tag -a "$name" -m "Imported from Subversion tag $name" "$sha"
git branch -r -d "$ref"
done
# The remaining SVN branches become local branches
git for-each-ref --format='%(refname:short) %(objectname)' refs/remotes/svn \
| while read -r ref sha; do
name="${ref#svn/}"
[ "$name" = trunk ] && continue
git branch "$name" "$sha"
git branch -r -d "$ref"
done
git branch -m master main # if trunk was checked out as master
Avant de pousser, faites le ménage que l'ancien dépôt ne permettait jamais. Convertissez les propriétés d'ignore de Subversion en un vrai fichier, et ajoutez les entrées qui étaient jusque-là gérées par le serveur :
git svn show-ignore >> .gitignore
git add .gitignore && git commit -m "Add .gitignore from svn:ignore properties"
Cherchez ensuite ce qui alourdit le dépôt. git count-objects -vH indique sa taille, et ce pipeline liste les plus gros blobs jamais commités :
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '$1=="blob" {print $3, $4}' | sort -nr | head -20
Si le haut de cette liste est occupé par des archives de release ou des jars de bibliothèques tierces, retirez-les avec git filter-repo --path <dir> --invert-paths avant le premier push. Réécrire l'historique après coup invalide tous les clones.
Vérifier puis basculer
Ne faites jamais confiance à une conversion que vous n'avez pas vérifiée. Trois vérifications peu coûteuses détectent presque tout :
Comparer les têtes
Exportez la tête du trunk depuis Subversion et celle demaindepuis Git dans deux répertoires, puis lancezdiff -ren ignorant.svnet.git. Les seules différences doivent être les fichiers que vous avez délibérément exclus.Comparer les nombres
Comparezsvn log --quiet | grep -c '^r'àgit rev-list --count --all. Ils ne correspondront pas exactement — les merges et les chemins exclus modifient la forme de l'historique —, mais un écart d'un ordre de grandeur signifie que les options d'arborescence étaient fausses.Reconstruire chaque tag
Extrayez deux ou trois tags de release et lancez le build. Une release que vous ne pouvez pas reconstruire depuis le nouveau dépôt, c'est une migration qui a échoué sans bruit.
Pour la bascule elle-même, gelez les écritures plutôt que de faire tourner les deux systèmes en parallèle indéfiniment. Annoncez une date, passez le dépôt Subversion en lecture seule avec un hook pre-commit, lancez un dernier git svn fetch pour récupérer les dernières révisions, refaites la conversion des branches et des tags, puis poussez :
git remote add origin [email protected]:acme/shop.git
git push --all origin
git push --tags origin
Une courte période de passerelle est raisonnable si quelques personnes ne peuvent pas basculer tout de suite. Avec les métadonnées conservées (sans --no-metadata), git svn rebase rapatrie les nouvelles révisions SVN dans le clone Git, et git svn dcommit rejoue les commits Git locaux sous forme de révisions SVN. Considérez-la comme une concession de deux semaines, pas comme une architecture : la passerelle ne fonctionne qu'à partir d'un dépôt dont l'historique n'a pas été réécrit, et écrire des deux côtés invite à la divergence.
Mettez à jour tout l'aval dans la même fenêtre. Les jobs de build qui faisaient référence à ${SVN_REVISION} ont besoin d'un nouveau schéma de version — git describe --tags ou le hash court. Les liens vers r4711 dans le gestionnaire de tickets cassent. Les scripts de release qui faisaient un svn copy pour créer un tag font désormais git tag -a puis git push origin <tag>. Le guide sur le pipeline de livraison présente des stratégies de versionnage qui fonctionnent avec des hashes plutôt qu'avec des compteurs.
Ce que l'équipe doit désapprendre
Trois habitudes expliquent presque toute la confusion des débuts, et les nommer à l'avance épargne quinze jours de questions au support.
« Committer » ne veut plus dire « partager ». Les développeurs habitués à Subversion soit gardent leurs changements pour eux pendant des jours parce que committer était public, soit committent et se demandent pourquoi personne ne voit leur travail. Dites-le explicitement : committez tôt et souvent en local, poussez quand le travail forme un tout cohérent.
« Mettre à jour » ne réécrit plus vos fichiers dans votre dos. git fetch est sans danger et peut être lancé en permanence ; c'est le merge ou le rebase qui touche au working tree.
Les branches servent aux changements, pas aux releases. Le réflexe hérité de Subversion veut qu'une branche soit un événement lourd, soumis à approbation. Choisissez un modèle et mettez-le par écrit — le guide sur les workflows de branches compare les options réalistes, et le trunk-based development est en général le changement le plus léger par rapport aux habitudes d'une équipe Subversion.
Accompagnez la transition concrètement : une page de correspondance des commandes affichée au mur, et une personne désignée pour répondre aux questions sur Git pendant le premier mois. Assurez-vous que tout le monde lit la partie des bases de Git consacrée à l'annulation des changements, sinon « comment je récupère mon fichier ? » sera la question du jour, tous les jours.
Pièges à anticiper
- Un auteur manquant interrompt l'import après des heures. Générez la liste d'abord et incluez
(no author). - Des tags dans lesquels on a commité. Ils sont convertis en tags pointant sur le dernier état, et les révisions intermédiaires sont perdues sans avertissement. Cherchez avec
svn log ^/tagsles chemins de tags ayant plus d'une révision et convertissez-les en branches. - Les répertoires vides disparaissent. Git suit des fichiers, pas des répertoires. Si le build a besoin d'un répertoire vide, ajoutez-y un fichier
.gitkeep. - Fins de ligne et propriétés de fichiers.
svn:eol-styledevient une entrée.gitattributes(* text=auto), etsvn:executablecorrespond au mode du fichier. Créez le.gitattributesdans le premier commit après la conversion plutôt que de découvrir le problème plus tard sur une machine Windows. - Personne n'est responsable de l'ancien serveur. Décidez qui maintient l'instance Subversion en lecture seule, pour combien de temps, et où se trouvent ses sauvegardes. « On le décommissionnera le trimestre prochain » signifie souvent qu'il disparaîtra sans la moindre annonce.
Points clés à retenir
- Subversion centralise l'historique sur un serveur et numérote les révisions globalement ; Git donne à chaque développeur l'historique complet, et c'est pourquoi créer une branche, consulter le log et committer sont des opérations locales et instantanées.
- La plupart des commandes se traduisent une pour deux : un commit SVN devient un commit Git plus un push, et un update SVN devient un fetch plus un merge.
- Constituez le fichier d'auteurs avant de convertir, incluez
(no author), et créez un miroir local du dépôt si le serveur est lent. git svn clone --stdlayoutgère les dépôts ordinaires ;--trunk,--branches,--tagset--ignore-pathsexplicites gèrent le reste, et l'import peut reprendre avecgit svn fetch.- Les refs importées ne sont pas de vraies branches : convertissez les répertoires de tags en tags annotés et les répertoires de branches en branches, ajoutez un
.gitignoreet un.gitattributes, et éliminez les gros binaires avant le premier push. - Vérifiez en comparant les têtes, le nombre de commits et en reconstruisant les tags publiés ; ensuite gelez Subversion, poussez une seule fois et gardez la période de passerelle courte.
- Consacrez autant d'attention au changement d'habitudes — committer n'est pas partager, fetch est sans danger, les branches ne coûtent rien — qu'à la conversion elle-même.
Sources et lectures complémentaires
Questions fréquentes
- Faut-il conserver tout l'historique Subversion ou repartir de zéro ?
- Les deux se défendent. Une conversion complète préserve blame, bisect et l'archéologie du code, ce qui compte pour un code métier qui vit longtemps, mais elle prend plus de temps et embarque chaque gros binaire jamais commité. Partir d'un dépôt vide avec un unique commit d'import est rapide et propre, et fonctionne si l'ancien serveur reste en ligne en lecture seule pour l'archéologie. Tranchez en vous demandant à quelle fréquence quelqu'un lit réellement un historique vieux de plus de deux ans.
- Combien de temps dure une conversion avec git svn ?
- L'outil parcourt l'historique Subversion révision par révision, à travers le réseau : comptez en gros d'une à quelques révisions par seconde. Un dépôt de 50 000 révisions peut facilement tourner toute la nuit. Lancez-la sur une machine proche du serveur SVN, utilisez si possible un miroir local créé avec svnsync, et ne supposez jamais qu'elle se terminera dans une fenêtre de maintenance prévue pour un après-midi.
- Que deviennent les svn:externals ?
- Rien d'automatique : git svn les ignore. Un external qui pointe vers un autre projet devient en général une dépendance résolue par Maven, npm ou le gestionnaire de paquets de votre langage, ce qui est presque toujours la meilleure réponse. Si vous avez vraiment besoin d'un checkout imbriqué, un submodule Git ou un subtree merge reproduit le comportement, mais examinez chaque external individuellement plutôt que de les convertir mécaniquement.
- A-t-on encore besoin du verrouillage de fichiers après le passage à Git ?
- Git n'a pas d'équivalent à svn lock, car son modèle de merge suppose du texte que l'on peut combiner. Les équipes qui verrouillaient des fichiers binaires — maquettes, tableurs, documents générés — devraient les passer sur Git LFS, qui propose un workflow de verrouillage, ou les garder dans un système conçu pour les binaires. Verrouiller du code source était le plus souvent une habitude à perdre plutôt qu'une exigence à reproduire.