Git-SVN – Soignez votre addiction à Git

Le vrai problème avec Git, c’est qu’une fois qu’on l’a utilisé c’est très dur de revenir vers un SCM non distribué comme Subversion, voire CVS.
La solution évidente est de lancer une migration vers Git (ou un autre SCM distribué comme Mercurial ou Bazaar, ne soyons pas sectaires). Toutefois cette migration  n’est pas toujours simple, voire possible, à mettre en place, pour diverses raisons qui n’ont habituellement rien à voir avec la technique.
Et si on pouvait avoir les avantages de Git sur son poste de développeur, tout en gardant le repository SVN central historique du projet ? Et bien si vous n’avez pas encore vu le titre de cet article, apprenez donc que c’est justement ce que propose Git-SVN.

Un petit avertissement au passage : cet article suppose un minimum de familiarité avec Git.

Git-SVN c’est quoi ?

Git-SVN est un ensemble de scripts qui permettent d’utiliser un repository Git local, et de le synchroniser avec un repository central Subversion. Malgré quelques limitations inhérentes à Subversion, on peut alors profiter de l’essentiel des fonctionnalités de Git, tout en gardant cette utilisation transparente pour les autres personnes de l’équipe, qui peuvent continuer à utiliser SVN comme avant.
Le but de cet article, plus que de présenter les 3 ou 4 commandes à connaître pour utiliser Git-SVN, sera de faire découvrir l’outil,  de comprendre son fonctionnement, et de bien saisir quelles sont les limites de son utilisation (et il y en a).
Pour plus de détails sur son utilisation, il y a la doc !

Avertissement

Si vous avez le choix, utilisez plutôt Git. Vraiment. Git-SVN a beau être un gros pas en avant par rapport à Subversion, ça reste une façade qui ne parviendra pas à masquer toutes les imperfections de Subversion, et qui ajoutera d’autres problématiques à prendre en compte.

Bon on y va maintenant ?

Les avertissements d’usage étant exprimés, on va maintenant passer à la pratique.
On va partir du principe que le point de départ est un projet Subversion existant, avec lequel on va s’interfacer avec Git. Si vous partez sur un nouveau projet, utilisez Git directement.

Installation

Git-SVN s’installe de la même façon que vous avez installé Git… Pour la plupart des distributions Linux, ou pour Cywin, installez le package git-svn.

Initialisation

On part d’un dossier vide, et on va créer un nouveau repository local Git, synchronisé avec le serveur SVN :

$ git svn init -s http://subversion-server/myproject/

Cette commande initialise un repository git vide, et ajoute les méta-données qui permettront de retrouver le serveur SVN pour se synchroniser.
Le paramètre -s indique que le repository SVN a une structure standard : trunk dans le sous-dossier trunk, branches dans branches et tags dans tags. Si ce n’est pas le cas, on peut remplacer le -s par -T, -b et -t pour indiquer respectivement l’emplacement du trunk, des branches et des tags. Les paramètres de ces commandes sont des dossiers, soit relatifs au repository SVN, soit absolus (une URI complète).

Intermède

Faisons une petite pause pour observer ce qu’a réalisé la commande ci-dessus, et peaufiner un peu les choses.
La commande a donc créé un nouveau dossier myproject. Celui-ci est vide à l’exception d’un sous-dossier .git, puisque c’est effectivement un repository Git ordinaire.
Mais alors quid des métadonnées SVN que j’ai mentionnées ? Celles qui nous intéressent sont présentes dans le fichier .git/config :

[svn-remote "svn"]
url = http://subversion-server/myproject
fetch = trunk:refs/remotes/trunk
branches = branches/*:refs/remotes/*
tags = tags/*:refs/remotes/tags/*

On y voit l’adresse du serveur, et la configuration des différents types de branches (pour rappel le trunk et les tags SVN sont des branches ordinaires qui suivent juste une certaine convention de nommage).

Le trunk SVN sera donc récupéré dans la branche git ref/remotes/trunk.
Les branches, elles, iront directement en vrac dans ref/remotes/*. Aïe. C’est mal. Pour rappel la convention pour les branches remote d’un repository distant appelé origin sont de la forme refs/remotes/origin/*. Ici, si on ajoute par la suite un autre repository remote, ça va être le gros mélange dans les branches des différents remotes. On va donc arranger un peu ça, et mettre toutes les branches remote du serveur Subversion dans un sous-dossier :

[svn-remote "svn"]
url = http://subversion-server/myproject
fetch = trunk:refs/remotes/svn/trunk
branches = branches/*:refs/remotes/svn/branches/*
tags = tags/*:refs/remotes/svn/tags/*

Récupération de l’historique

Maintenant qu’on a bien configuré notre repository local, on va récupérer l’historique de notre serveur Subversion.

$ git svn fetch

Le premier appel à cette commande va récupérer l’ensemble des révisions et des branches du projet. Un serveur Subversion n’étant pas conçu pour ça, il n’y a pas d’autre solution que de récupérer toutes les révisions une par une, en commençant par la toute première, puis en appliquant les révisions les unes à la suite des autres. Pour un gros repository, ça peut être long. Très long. Ça peut aller jusqu’à plusieurs jours pour un repository avec des dizaines de milliers de révisions.

Si on est pressé par le temps, et qu’on n’a pas besoin de tout l’historique des révisions, on peut limiter le nombre de commits récupérés. Pour cela, ajouter le paramètre -r qui précise la première révision à récupérer. Dans ce cas, faire bien attention à préciser une révision antérieure à la création des branches et tags que l’on va effectivement utiliser.
En effet Subversion ne sauvegarde pas le “point de départ” des branches, donc il faut que Git-SVN ait suffisamment de données pour reconstituer ce point dans l’arborescence Git.

On pourrait aussi préciser un filtre sur les branches à récupérer, mais je ne le conseille pas : ce n’est pas tellement plus rapide à initialiser, mais par contre ça sera pénible par la suite pour créer ou suivre de nouvelles branches.

Une chose importante à noter est que, comme les tags Subversion sont en réalité des branches, ils sont récupérés sous la forme de branches Git dans refs/remotes/svn/tags/*.

Manipulations locales

À ce stade, on a un vrai repository Git local, qu’on va pouvoir manipuler localement comme n’importe quel repository Git, avec les commandes habituelles : git status, git log, git add, git rm, git commit, etc. Créez des branches locales autant que vous voulez. Par contre pour rapatrier vos branches locales dans les branches partagées, privilégiez le rebase plutôt que le merge : on verra plus bas que le détail de la branche de feature seront sinon perdues.

Synchronisation avec le repository Subversion distant

Pour récupérer les modifications distantes (équivalent d’un git fetch), on utilise la commande :

$ git svn fetch

Cette commande met à jour les branches remote, mais ne touche pas les branches locales, exactement comme le fait git fetch.

Pour mettre à jour la branche locale courante, on peut lancer la commande suivante :

$ git svn rebase

Cette commande est à peu près équivalente à la commande git pull –rebase. Elle effectue un git svn fetch (pour la branche courante uniquement), suivi d’un git rebase de la branche locale sur la branche remote.
Il n’existe pas d’équivalent du git pull standard, puisque les commits de merge (commits avec plusieurs parents) ne sont de toutes façons pas gérés par Subversion.

Pour envoyer les modifications locales vers le serveur Subversion (équivalent d’un git push), on utilise :

$ git svn dcommit

Attention, cette commande est assez particulière. Contrairement à un git push, les commits envoyés sont détruits, puis recréés à partir des données récupérées de Subversion, comme si on avait fait un rebase dessus. Ceci permet de s’assurer que le repository local représente bien l’état du repository Subversion, en ajoutant les données de la révision subversion dans le commentaire, mais aussi et surtout, ça permet de se débarrasser des fonctionnalités de Git non supportées par Subversion, comme par exemple les commits de merge.

Manipulations de branches et de tags

Pour créer une branche Subversion, utiliser :

$ git svn branch ma-branche

Pour un tag :

$ git svn tag mon-tag

Ces commandes manipulent directement le repository distant. Pas besoin donc d’effectuer un dcommit par la suite.

Pour modifier une branche distante, le fonctionnement est identique à la manipulation d’une branche remote Git : on crée une branche locale à partir de la branche distante, on modifie, on commite et on push/dcommit.

Limitations

On a vu les manipulations qui marchent. Maintenant attardons-nous un peu sur les points auxquels il faudra faire plus attention.

Merges

Comme on a pu le voir, la limitation la plus impactante est due à l’absence de gestion des merges par Subversion, qui occasionne des rebase à tout va.

En l’absence de suivi des merges, le suivi des reports de modifications d’une branche à l’autre est compliqué (un peu comme dans subversion seul, en fait) : difficile après un merge de savoir quelles modifications ont été reportées ou non.
La solution, non optimale, que je préconise pour garder un suivi correct des reports de corrections d’une branche de recette à une branche de développement est l’utilisation de cherry-picks : les commits sont copiés un par un d’une branche à l’autre. Ainsi on n’a certes pas les infos du merge, comme la branche d’origine, mais on peut voir les modifications mieux qu’avec un gros commit qui regroupe toutes les modifications.

Rebases

En règle générale, il faut être bien conscient de quels commits ont déjà été synchronisés avec SVN, et surtout ne jamais les modifier. Avec Git c’est déjà mal. Avec Git-SVN ça tue des chatons. Il y a vraiment moyen de pourrir un repository Subversion avec des rebases mal maîtrisés.

Commits non atomiques

Un autre point d’attention est le fait que la commande git svn dcommit n’est pas atomique. Il faut bien être conscient qu’elle peut en fait effectuer plusieurs envois (svn commit) successifs, intercalés par des fetch et des rebase. Il faut donc éviter d’envoyer ses modifications en même temps qu’un autre utilisateur. Il m’est déjà arrivé de voir deux commits fusionnés localement en un (un local et un distant) dans un cas comme ça.

Branches de tracking

L’information de la branche remote associée à une branche locale n’est pas persistée. La branche SVN à prendre en compte lors d’un git svn rebase ou git svn dcommit est trouvée à l’exécution : c’est la branche remote la plus “proche” du HEAD, la première trouvée en redescendant l’arbre.
On peut ainsi avoir quelques surprises. Par exemple en appelant par erreur un git svn dcommit depuis une branche locale créée depuis le master, on va mettre à jour la branche trunk avec le contenu de la branche locale.
La meilleure façon de ne pas se tromper est d’utiliser les commandes remote uniquement depuis les branches locales créées à partir d’une branche remote. Ainsi pour créer une nouvelle branche subversion, utiliser d’abord git svn branch, puis créer la branche locale à partir de la branche distante nouvellement créée.

Partage de repository

Un gros problème, c’est que le hash d’un commit issu de Git-SVN n’est pas unique. Ainsi si deux développeurs utilisent Git-SVN à partir du même repository Subversion, les commits n’auront pas les mêmes identifiants. On ne pourra donc pas partager simplement une branche de travail entre deux développeurs.

Conclusion

J’espère que vous avez pu voir que cet outil est puissant, mais potentiellement dangereux si mal utilisé. Gardez donc à l’esprit que c’est un outil de transition, pas une solution à long terme. Et migrez vers Git !

VN:R_U [1.9.22_1171]
Rating: +1 (from 1 vote)
Share
Ce contenu a été publié dans Outils. Vous pouvez le mettre en favoris avec ce permalien.

Une réponse à Git-SVN – Soignez votre addiction à Git

  1. Benoit Lateltin dit :

    Pour combiner le git svn init et le git svn fetch j’utilise :
    git svn clone -s http://server/myproject -r XXXXX:HEAD.
    Et je conseille aussi vivement l’option -r avec XXXXX correspondant à un numéro de version car comme tu l’as dit, la première récupération peut être très très longue !

    VN:R_U [1.9.22_1171]
    Rating: 0 (from 0 votes)

Laisser un commentaire