Il y a très peu de temps, j’expliquais comment utiliser Git avec SVN dans Eclipse. C’est maintenant quelque chose que j’utilise au quotidien, que ce soit avec Eclipse ou IntelliJ IDEA, mon nouveau choucou. Dans un cas comme dans l’autre, on ne coupera pas à l’utilisation des « external tools » pour la configuration des commandes « git svn rebase/dcommit/etc. »

Après plusieurs semaines d’utilisation, j’ai voulu étendre cette utilisation à d’autres commandes, afin de voir si on pouvait pousser plus loin les interactions entre Git et SVN.

Références utiles

Avant toute chose, deux références qui m’ont bien aidé à comprendre les mécanismes de Git-SVN, outre de l’huile de coude et un dépôt SVN de test :

Au commencement était le clone

L’initialisation du repository avec « git svn clone » ne pose aucun soucis :

  • Elle positionne le master sur « trunk« , référencé en tant que « remotes/trunk » (y compris si ce n’est pas la dernière révision commitée), ce qui me paraît un bon comportement par défaut
  • Elle met en place des références sur les branches et tags SVN rencontrés (qui dépendent de la révision depuis laquelle on démarre), respectivement dans « remotes/branchname » et « remotes/tags/tagname« 

On se rend compte de ceci par un simple :

1
git branch -a

A ce niveau, on peut déplorer le choix de tout mettre en vrac à la racine de « remotes« . Si en plus de SVN, je veux travailler avec un (voire plusieurs) serveur Git distant, j’aimerais un préfixe au même titre pour les dépôt Git distants (comme « origin« , par exemple).

Afin de le résoudre, voici une petite alternative au clonage du repository SVN. Il faut tout d’abord créer un repository Git vide :

1
git svn init -s http://svn.courtine.org/monrepo

On édite ensuite manuellement le fichier de configuration « .git/config« . Dans l’exemple qui suit, je nomme mon repository « svn« , et je déplace les branches dans un sous répertoire, pour les distinguer du « trunk« . Il s’agit d’un choix personnel que vous pouvez adapter en fonction de vos besoins/conventions :

1
2
3
4
5
[svn-remote "svn"]
	url = http://svn.courtine.org/monrepo
	fetch = trunk:refs/remotes/svn/trunk
	branches = branches/*:refs/remotes/svn/branches/*
	tags = tags/*:refs/remotes/svn/tags/*

Pour mettre à jour ces branches distantes avec cette nouvelle convention de nommage (et positionner master sur le « trunk« ), il suffit alors de lancer à la racine du repository :

1
git svn fetch -r startrev:HEAD

Avec « git branch -a« , on vérifie que le résultat est bien conforme à nos attentes.

Vérifier le contenu de branches/tags SVN

Pour maintenir ces « remotes » à jour, il suffit de lancer :

1
git svn fetch

On peut ensuite faire toutes les opérations que l’on ferait habituellement avec Git sur des dépôts distants. En particulier créer des branches locales liées à des branches SVN distantes :

1
2
git branch feature_locale remotes/svn/branches/feature
git checkout feature_locale

Je déconseille de donner à une branche locale le même nom qu’une branche SVN. Même si ça semble fonctionner, cela provoque des avertissements dans la console Git sur « l’ambiguïté du nom » dans les commandes ultérieures qui l’utiliseront.

Manipuler le SVN à distance depuis Git

Maintenant, si je modifie cette branche locale fraîchement créée, je peux commiter localement ces modifications, puis les « pousser » vers SVN. Ces modifications seront bien commitées (au sens SVN du terme) dans la branche « feature » et non dans le trunk :

1
git svn dcommit

Si on revient sur master pour faire une autre évolution, la commiter et l’envoyer à Subversion, ce traitement sera correctement effectué sur « trunk« .

On peut également créer à distance une nouvelle branche SVN :

1
git svn branch nom_branche

ou encore créer un tag :

1
git svn branch --tag "version_1.0"

Je n’ai pas poussé mes investigations sur ce sujet, mais il n’est à ma connaissance pas possible de synchroniser les tags Git avec les tags SVN. Il faut créer explicitement les tags SVN.

Les tags SVN sont d’ailleurs vus comme des branches, placées dans un répertoire différent. Donc, contrairement aux tags Git qu’il n’est pas possible d’écraser (sauf à le forcer avec « -f« ), l’écrasement d’un tag SVN se fait sans préavis :

1
2
3
4
5
git branch v1 remotes/svn/tags/version_1.0
git checkout v1
# Modifications quelconques...
git commit -am "Hérésie : modification d'un tag !"
git svn dcommit

Merge, rebase, et autres gitteries : terrain TRES glissant !

Même si tout cela est possible, il ne faut pas abuser de la synchronisation SVN-Git. Car il ne faut pas perdre de vue que le modèle de fusion de SVN n’est pas du tout le même que celui de Git (et est beaucoup moins puissant). On a donc très vite fait de casser le serveur SVN si on « dcommit » n’importe quoi.

C’est en particulier pour cela que l’on se synchronise avec SVN par « rebase » plutôt que par « merge« . Lorsque l’on fait ensuite un « dcommit« , on préserve la linéarité de l’historique SVN.

Je recommande donc vivement à tous ceux qui veulent s’y essayer de créer un repository SVN de test pour bien voir quelles en sont les limites. En voici quelques unes, que j’ai pu rencontrer lors de mes tests :

Le merge simple

La branche « master » (« trunk« ) a évolué, de même que ma branche « evolution » (éventuellement liée à une branche SVN). Cette évolution étant terminée, je fais ma fusion dans Git avant de la pousser vers SVN :

1
2
3
git checkout master
git merge evolution -m "Fusion de la branche evolution..."
git svn dcommit

Alors que l’historique complet est bien conservé dans Git, tous les commits de la branche « evolution » sont agrégés dans un unique commit SVN, perdant ainsi tout l’historique de cette branche. D’où l’importance :

  • d’avoir gardé une trace dans SVN de cette branche, surtout si elle concerne une grosse évolution
  • de mettre un commentaire de merge pertinent et complet, pour palier à la perte de l’historique détaillé (le message par défaut s’il n’est pas précisé étant « Merge branch evolution »).

Le merge multiple

Même problème que pour le merge simple… mais multiplié par le nombre de branches fusionnées simultanément.

Le merge « fast-forward »

Celui-là, c’est ma bête noire !

1
2
3
4
5
6
7
8
git svn branch evo
git branch evolution remotes/svn/branches/evo
git checkout evolution
# Modifications de la branche evolution
git commit -am "Nouvelle evolution"
git svn dcommit # Commit dans la branche "evo"
git checkout master
git merge evolution # Drame

« master » n’ayant pas bougé depuis la création de la branche « evolution« , Git choisit de faire un « merge fast-forward« . Dans la pratique, la référence « HEAD » de la branche « master » va simplement être déplacée pour pointer vers le dernier commit de la branche « evolution« . Du point de vue de Git, il n’y a rien de choquant : c’est un de ses mécanismes de base.

Le problème advient lors de la prochaine modification de « master » que l’on veut pousser vers SVN. Contrairement à ce que l’on attend, la modification se fera sur la branche « evo » et non sur le « trunk« .

En effet, on se situe « logiquement » dans Git au niveau de la branche « evolution » (dans laquelle on a rapatrié la référence « HEAD » de « master« ). Cependant, même si ce comportement est logique et explicable, il est véritablement gênant.

Le rebase

Si le « rebase » a lieu en dehors de tout commit SVN, ça ne pose pas de problème. Dans le cas contraire, c’est une très mauvaise idée… y compris sous Git d’ailleurs ! Une fois les modifications exposées à l’opprobre publique sur un dépôt partagé, on ne doit pas y retoucher, sous peine de risquer de casser les travaux de ses collègues.

S’agissant donc de toute manière d’une mauvaise pratique, je n’ai pas tenté le « commit SVN » suivi d’un « rebase Git » suivi d’un nouveau « commit SVN » aux forceps… mais je doute fort que les choses se passent correctement.

Conclusion

L’intégration de SVN dans Git permet vraiment de faire un grand choses, mais il faut l’utiliser avec précaution… d’autant plus que le risque potentiel est énorme en cas d’erreur de manipulation sur le serveur SVN. A moins de vraiment savoir ce que l’on fait, je recommande donc de se cantonner à des manipulations simples.

Si j’apprécie beaucoup Git-SVN qui me permet de travailler avec Git sur un projet dont la gestion de configuration « officielle » est assurée par SVN, ça reste quelque chose d’un peu bancal, qui ne permet pas de bénéficier de toutes les fonctionnalités de Git, par rapport à une gestion de configuration 100% Git.

C’est également un bon moyen d’assurer une transition. En phase de qualification de Git par exemple, rien n’empêche de travailler avec Git en local, et deux « remotes » pour la centralisation des sources : le serveur SVN historique, ainsi qu’un serveur Git flambant neuf !