Git
Chapters ▾ 2nd Edition

9.1 Git et les autres systèmes - Git comme client

Le monde n’est pas parfait. Habituellement, vous ne pouvez pas basculer immédiatement sous Git tous les projets que vous pourriez rencontrer. Quelques fois, vous êtes bloqué sur un projet utilisant un autre VCS et vous regrettez que ce ne soit pas Git. Dans la première partie de ce chapitre, nous traiterons de la manière d’utiliser git comme client pour les projets utilisant un autre système.

À un moment, vous voudrez convertir votre projet à Git. La seconde partie de ce chapitre traite la migration de votre projet dans Git depuis certains systèmes spécifiques et enfin par un script d’import personnalisé pour les cas non-standards.

Git comme client

Git fournit de si bonnes sensations aux développeurs que de nombreuses personnes ont cherché à l’utiliser sur leur station de travail, même si le reste de leur équipe utilise un VCS complètement différent. Il existe un certain nombre d’adaptateurs appelés « passerelles ». Nous allons en décrire certains des plus communs.

Git et Subversion

Aujourd’hui, la majorité des projets de développement libre et un grand nombre de projets dans les sociétés utilisent Subversion pour gérer leur code source. Il a été le VCS libre le plus populaire depuis une bonne décennie et a été considéré comme le choix de facto pour les projets open-source. Il est aussi très similaire à CVS qui a été le grand chef des gestionnaires de source avant lui.

Une des grandes fonctionnalités de Git est sa passerelle vers Subversion, git svn. Cet outil vous permet d’utiliser Git comme un client valide d’un serveur Subversion pour que vous puissiez utiliser les capacités de Git en local puis pousser sur le serveur Subversion comme si vous utilisiez Subversion localement. Cela signifie que vous pouvez réaliser localement les embranchements et les fusions, utiliser l’index, utiliser le rebasage et le picorage de commits, etc, tandis que vos collaborateurs continuent de travailler avec leurs méthodes ancestrales et obscures. C’est une bonne manière d’introduire Git dans un environnement professionnel et d’aider vos collègues développeurs à devenir plus efficaces tandis que vous ferez pression pour une modification de l’infrastructure vers l’utilisation massive de Git. La passerelle Subversion n’est que la première dose vers la drogue du monde des DVCS.

git svn

La commande de base dans Git pour toutes les commandes de passerelle est git svn. Vous préfixerez tout avec cette paire de mots. Les possibilités étant nombreuses, nous traiterons des plus communes pendant que nous détaillerons quelques petits modes de gestion.

Il est important de noter que lorsque vous utilisez git svn, vous interagissez avec Subversion qui est un système fonctionnant très différemment de Git. Bien que vous puissiez réaliser des branches locales et les fusionner, il est généralement conseillé de conserver votre historique le plus linéaire possible en rebasant votre travail et en évitant des activités telles qu’interagir dans le même temps avec un dépôt Git distant.

Ne réécrivez pas votre historique avant d’essayer de pousser à nouveau et ne poussez pas en parallèle dans un dépôt Git pour collaborer avec vos collègues développant avec Git. Subversion ne supporte qu’un historique linéaire et il est très facile de l’égarer. Si vous travaillez avec une équipe dont certains membres utilisent SVN et d’autres utilisent Git, assurez-vous que tout le monde n’utilise que le serveur SVN pour collaborer, cela vous rendra service.

Installation

Pour montrer cette fonctionnalité, il faut un serveur SVN sur lequel vous avez des droits en écriture. Pour copier ces exemples, vous avez besoin de faire une copie inscriptible d’un dépôt SVN de test accessible. Dans cette optique, vous pouvez utiliser un outil appelé svnsync qui est livré avec les versions les plus récentes de Subversion — il devrait être distribué avec les versions à partir de 1.4.

En préparation, créez un nouveau dépôt local Subversion :

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

Ensuite, autorisez tous les utilisateurs à changer les revprops — le moyen le plus simple consiste à ajouter un script pre-revprop-change qui renvoie toujours 0 :

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

Vous pouvez à présent synchroniser ce projet sur votre machine locale en lançant svnsync init avec les dépôts source et cible.

$ svnsync init file:///tmp/test-svn https://votre-serveur-svn.org/svn/

Cela initialise les propriétés nécessaires à la synchronisation. Vous pouvez ensuite cloner le code en lançant :

$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[…]

Bien que cette opération ne dure que quelques minutes, si vous essayez de copier le dépôt original sur un autre dépôt distant au lieu d’un dépôt local, le processus durera près d’une heure, en dépit du fait qu’il y a moins de 100 commits. Subversion doit cloner révision par révision puis pousser vers un autre dépôt — c’est ridiculement inefficace mais c’est la seule possibilité.

Démarrage

Avec des droits en écriture sur un dépôt Subversion, vous voici prêt à expérimenter une méthode typique. Commençons par la commande git svn clone qui importe un dépôt Subversion complet dans un dépôt Git local. Souvenez-vous que si vous importez depuis un dépôt Subversion hébergé sur Internet, il faut remplacer l’URL file://tmp/test-svn ci-dessous par l’URL de votre dépôt Subversion :

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
    A	m4/acx_pthread.m4
    A	m4/stl_hash.m4
    A	java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
    A	java/src/test/java/com/google/protobuf/WireFormatTest.java
…
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75

Cela équivaut à lancer git svn init suivi de git svn fetch sur l’URL que vous avez fournie. Cela peut prendre un certain temps. Le projet de test ne contient que 75 commits et la taille du code n’est pas extraordinaire, ce qui prend juste quelques minutes. Cependant, Git doit extraire chaque version, une par une et les valider individuellement. Pour un projet contenant des centaines ou des milliers de commits, cela peut prendre littéralement des heures ou même des jours à terminer.

La partie -T trunk -b branches -t tags indique à Git que ce dépôt Subversion suit les conventions de base en matière d’embranchement et d’étiquetage. Si vous nommez votre trunk, vos branches ou vos étiquettes différemment, vous pouvez modifier ces options. Comme cette organisation est la plus commune, ces options peuvent être simplement remplacées par -s qui signifie structure standard. La commande suivante est équivalente :

$ git svn clone file:///tmp/test-svn -s

À présent, vous disposez d’un dépôt Git valide qui a importé vos branches et vos étiquettes :

$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk

Il est important de remarquer comment cet outil sous-classe vos références distantes différemment. Voyons de plus près avec la commande Git de plomberie show-ref :

$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk

Git ne fait pas cela quand il clone depuis un serveur Git ; voici à quoi ressemble un dépôt avec des étiquettes juste après le clonage :

$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0

Git entrepose les étiquettes directement dans refs/tags, plutôt que de les traiter comme des branches distantes.

Valider en retour sur le serveur Subversion

Comme vous disposez d’un dépôt en état de marche, vous pouvez commencer à travailler sur le projet et pousser vos commits en utilisant efficacement Git comme client SVN. Si vous éditez un des fichiers et le validez, vous créez un commit qui existe localement dans Git mais qui n’existe pas sur le serveur Subversion :

$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)

Ensuite, vous avez besoin de pousser vos modifications en amont. Remarquez que cela modifie la manière de travailler par rapport à Subversion — vous pouvez réaliser plusieurs validations en mode déconnecté pour ensuite les pousser toutes en une fois sur le serveur Subversion. Pour pousser sur un serveur Subversion, il faut lancer la commande git svn dcommit :

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r77
    M	README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Cette commande rassemble tous les commits que vous avez validés par-dessus le code du serveur Subversion et réalise un commit sur le serveur pour chacun, puis réécrit l’historique Git local pour y ajouter un identifiant unique. Cette étape est à souligner car elle signifie que toutes les sommes de contrôle SHA-1 de vos commits locaux ont changé. C’est en partie pour cette raison que c’est une idée très périlleuse de vouloir travailler dans le même temps avec des serveurs Git distants. L’examen du dernier commit montre que le nouveau git-svn-id a été ajouté :

$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README

    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68

Remarquez que la somme de contrôle SHA qui commençait par 4af61fd quand vous avez validé commence à présent par 95e0222. Si vous souhaitez pousser à la fois sur un serveur Git et un serveur Subversion, il faut obligatoirement pousser (dcommit) sur le serveur Subversion en premier, car cette action va modifier vos données des commits.

Tirer des modifications

Quand vous travaillez avec d’autres développeurs, il arrive à certains moments que ce qu’un développeur a poussé provoque un conflit lorsqu’un autre voudra pousser à son tour. Cette modification sera rejetée jusqu’à ce qu’elle soit fusionnée. Dans git svn, cela ressemble à ceci :

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M	README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Pour résoudre cette situation, vous pouvez lancer la commande git svn rebase qui tire depuis le serveur toute modification apparue entre temps et rebase votre travail sur le sommet de l’historique du serveur :

$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a b34372b25ccf4945fe5658fa381b075045e7702a M	README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M	README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

À présent, tout votre travail se trouve au-delà de l’historique du serveur et vous pouvez effectivement réaliser un dcommit :

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r85
    M	README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Il est important de se souvenir qu’à la différence de Git qui requiert une fusion avec les modifications distantes non présentes localement avant de pouvoir pousser, git svn ne vous y contraint que si vos modifications provoquent un conflit (de la même manière que svn). Si une autre personne pousse une modification à un fichier et que vous poussez une modification à un autre fichier, votre dcommit passera sans problème :

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	configure.ac
Committed r87
    M	autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M	configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a9439312d80d5d43bb65d4a7d0389ed6d M	autogen.sh
First, rewinding head to replay your work on top of it...

Il faut s’en souvenir car le résultat de ces actions est un état du dépôt qui n’existait pas sur aucun des ordinateurs quand vous avez poussé. Si les modifications sont incompatibles mais ne créent pas de conflits, vous pouvez créer des défauts qui seront très difficiles à diagnostiquer. C’est une grande différence avec un serveur Git — dans Git, vous pouvez tester complètement l’état du projet sur votre système client avant de le publier, tandis qu’avec SVN, vous ne pouvez jamais être totalement certain que les états avant et après validation sont identiques.

Vous devrez aussi lancer cette commande pour tirer les modifications depuis le serveur Subversion, même si vous n’êtes pas encore prêt à valider. Vous pouvez lancer git svn fetch pour tirer les nouveaux commits, mais git svn rebase tire non seulement les commits distants mais rebase aussi vos commits locaux.

$ git svn rebase
    M	autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.

Lancer git svn rebase de temps en temps vous assure que votre travail est toujours synchronisé avec le serveur. Vous devrez cependant vous assurer que votre copie de travail est propre quand vous la lancez. Si vous avez des modifications locales, il vous faudra soit remiser votre travail, soit valider temporairement vos modifications avant de lancer git svn rebase, sinon la commande s’arrêtera si elle détecte que le rebasage provoquerait un conflit de fusion.

Le problème avec les branches Git

Après vous être habitué à la manière de faire avec Git, vous souhaiterez sûrement créer des branches thématiques, travailler dessus, puis les fusionner. Si vous poussez sur un serveur Subversion via git svn, vous souhaiterez à chaque fois rebaser votre travail sur une branche unique au lieu de fusionner les branches ensemble. La raison principale en est que Subversion gère un historique linéaire et ne gère pas les fusions comme Git y excelle. De ce fait, git svn suit seulement le premier parent lorsqu’il convertit les instantanés en commits Subversion.

Supposons que votre historique ressemble à ce qui suit. Vous avez créé une branche experience, avez réalisé deux validations puis les avez fusionnées dans master. Lors du dcommit, vous voyez le résultat suivant :

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	CHANGES.txt
Committed r89
    M	CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M	COPYING.txt
    M	INSTALL.txt
Committed r90
    M	INSTALL.txt
    M	COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Lancer dcommit sur une branche avec un historique fusionné fonctionne correctement, à l’exception que l’examen de l’historique du projet Git indique qu’il n’a réécrit aucun des commits réalisés sur la branche experience, mais que toutes les modifications introduites apparaissent dans la version SVN de l’unique commit de fusion.

Quand quelqu’un d’autre clone ce travail, tout ce qu’il voit, c’est le commit de la fusion avec toutes les modifications injectées en une fois, comme si vous aviez lancé git merge --squash. Il ne voit aucune information sur son origine ni sur sa date de validation.

Les embranchements dans Subversion

La gestion de branches dans Subversion n’a rien à voir avec celle de Git. Évitez de l’utiliser autant que possible. Cependant vous pouvez créer des branches et valider dessus dans Subversion en utilisant git svn.

Créer une nouvelle branche SVN

Pour créer une nouvelle branche dans Subversion, vous pouvez utiliser la commande git svn branch [nom de la branche] :

$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera) cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)

Cela est équivalent à la commande Subversion svn copy trunk branches/opera et réalise l’opération sur le serveur Subversion. Remarquez que cette commande ne vous bascule pas sur cette branche ; si vous validez, le commit s’appliquera à trunk et non à la branche opera.

Basculer de branche active

Git devine la branche cible des dcommits en se référant au sommet des branches Subversion dans votre historique — vous ne devriez en avoir qu’un et celui-ci devrait être le dernier possédant un git-svn-id dans l’historique actuel de votre branche.

Si vous souhaitez travailler simultanément sur plusieurs branches, vous pouvez régler vos branches locales pour que le dcommit arrive sur une branche Subversion spécifique en les démarrant sur le commit de cette branche importée depuis Subversion. Si vous voulez une branche opera sur laquelle travailler séparément, vous pouvez lancer :

$ git branch opera remotes/origin/opera

À présent, si vous voulez fusionner votre branche opera dans trunk (votre branche master), vous pouvez le faire en réalisant un git merge normal. Mais vous devez préciser un message de validation descriptif (via -m), ou la fusion indiquera simplement « Merge branch opera » au lieu d’un message plus informatif.

Souvenez-vous que bien que vous utilisez git merge qui facilitera l’opération de fusion par rapport à Subversion (Git détectera automatiquement l’ancêtre commun pour la fusion), ce n’est pas un commit de fusion normal de Git. Vous devrez pousser ces données finalement sur le serveur Subversion qui ne sait pas tracer les commits possédant plusieurs parents. Donc, ce sera un commit unique qui englobera toutes les modifications de l’autre branche. Après avoir fusionné une branche dans une autre, il est difficile de continuer à travailler sur cette branche, comme vous le feriez normalement dans Git. La commande dcommit qui a été lancée efface toute information sur la branche qui a été fusionnée, ce qui rend faux tout calcul d’antériorité pour la fusion. dcommit fait ressembler le résultat de git merge à celui de git merge --squash. Malheureusement, il n’y a pas de moyen efficace de remédier à ce problème — Subversion ne stocke pas cette information et vous serez toujours contraints par ses limitations si vous l’utilisez comme serveur. Pour éviter ces problèmes, le mieux reste d’effacer la branche locale (dans notre cas, opera) dès qu’elle a été fusionnée dans trunk.

Commandes Subversion

La boîte à outil git svn fournit des commandes de nature à faciliter la transition vers Git en mimant certaines commandes disponibles avec Subversion. Voici quelques commandes qui vous fournissent les mêmes services que Subversion.

L’historique dans le style Subversion

Si vous êtes habitué à Subversion, vous pouvez lancer git svn log pour visualiser votre historique dans un format SVN :

$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines

autogen change

------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines

Merge branch 'experiment'

------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines

updated the changelog

Deux choses importantes à connaître sur git svn log. Premièrement, à la différence de la véritable commande svn log qui interroge le serveur, cette commande fonctionne hors connexion. Deuxièmement, elle ne montre que les commits qui ont été effectivement remontés sur le serveur Subversion. Les commits locaux qui n’ont pas encore été remontés via dcommit n’apparaissent pas, pas plus que ceux qui auraient été poussés sur le serveur par des tiers entre-temps. Cela donne plutôt le dernier état connu des commits sur le serveur Subversion.

Annotations SVN

De la même manière que git svn log simule une commande svn log déconnectée, vous pouvez obtenir l’équivalent de svn annotate en lançant git svn blame [fichier]. Le résultat ressemble à ceci :

$ git svn blame README.txt
 2   temporal Protocol Buffers - Google's data interchange format
 2   temporal Copyright 2008 Google Inc.
 2   temporal https://code.google.com/apis/protocolbuffers/
 2   temporal
22   temporal C++ Installation - Unix
22   temporal =======================
 2   temporal
79    schacon Committing in git-svn.
78    schacon
 2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol
 2   temporal Buffer compiler (protoc) execute the following:
 2   temporal

Ici aussi, tous les commits locaux dans Git ou ceux poussés sur Subversion dans l’intervalle n’apparaissent pas.

Information sur le serveur SVN

Vous pouvez aussi obtenir le même genre d’information que celle fournie par svn info en lançant git svn info :

$ git svn info
Path: .
URL: https://schacon-test.googlecode.com/svn/trunk
Repository Root: https://schacon-test.googlecode.com/svn
Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029
Revision: 87
Node Kind: directory
Schedule: normal
Last Changed Author: schacon
Last Changed Rev: 87
Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)

Comme blame et log, cette commande travaille hors connexion et n’est à jour qu’à la dernière date à laquelle vous avez communiqué avec le serveur Subversion.

Ignorer ce que Subversion ignore

Si vous clonez un dépôt Subversion contenant des propriétés svn:ignore, vous souhaiterez sûrement paramétrer les fichiers .gitignore en correspondance pour vous éviter de valider accidentellement des fichiers qui ne devraient pas l’être. git svn dispose de deux commandes pour le faire. La première est git svn create-ignore qui crée automatiquement pour vous les fichiers .gitignore prêts pour l’inclusion dans votre prochaine validation.

La seconde commande est git svn show-ignore qui affiche sur stdout les lignes nécessaires à un fichier .gitignore qu’il suffira de rediriger dans votre fichier d’exclusion de projet :

$ git svn show-ignore > .git/info/exclude

De cette manière, vous ne parsemez pas le projet de fichiers .gitignore. C’est une option optimale si vous êtes le seul utilisateur de Git dans une équipe Subversion et que vos coéquipiers ne veulent pas voir de fichiers .gitignore dans le projet.

Résumé sur Git-Svn

Les outils git svn sont utiles si vous êtes bloqué avec un serveur Subversion pour le moment ou si vous devez travailler dans un environnement de développement qui nécessite un serveur Subversion. Il faut cependant les considérer comme une version estropiée de Git ou vous pourriez rencontrer des problèmes de conversion qui vous embrouilleront vous et vos collaborateurs. Pour éviter tout problème, essayez de suivre les principes suivants :

  • Gardez un historique Git linéaire qui ne contient pas de commits de fusion issus de git merge.

  • Rebasez tout travail réalisé en dehors de la branche principale sur celle-ci ; ne la fusionnez pas.

  • Ne mettez pas en place et ne travaillez pas en parallèle sur un serveur Git. Si nécessaire, montez-en un pour accélérer les clones pour de nouveaux développeurs mais n’y poussez rien qui n’ait déjà une entrée git-svn-id. Vous devriez même y ajouter un crochet pre-receive qui vérifie la présence de git-svn-id dans chaque message de validation et rejette les remontées dont un des commits n’en contiendrait pas.

Si vous suivez ces principes, le travail avec un serveur Subversion peut être supportable. Cependant, si le basculement sur un vrai serveur Git est possible, votre équipe y gagnera beaucoup.

Git et Mercurial

L’univers des systèmes de gestion de version distribués ne se limite pas à Git. En fait, il existe de nombreux autres systèmes, chacun avec sa propre approche sur la gestion distribuée des versions. À part Git, le plus populaire est Mercurial, et ces deux-ci sont très ressemblants par de nombreux aspects.

La bonne nouvelle si vous préférez le comportement de Git côté client mais que vous devez travailler sur un projet géré sous Mercurial, c’est que l’on peut utiliser Git avec un dépôt géré sous Mercurial. Du fait que Git parle avec les dépôts distants au moyen de greffons de protocole distant, il n’est pas surprenant que cette passerelle prenne la forme d’un greffon de protocole distant. Le projet s’appelle git-remote-hg et peut être trouvé à l’adresse https://github.com/felipec/git-remote-hg.

git-remote-hg

Premièrement, vous devez installer git-remote-hg. Cela revient simplement à copier ce fichier quelque part dans votre chemin de recherche, comme ceci :

$ curl -o ~/bin/git-remote-hg \
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg

…en supposant que ~/bin est présent dans votre $PATH. git-remote-hg est aussi dépendant de la bibliothèque Mercurial pour Python. Si Python est déjà installé, c’est aussi simple que :

$ pip install mercurial

Si Python n’est pas déjà installé, visitez https://www.python.org/ et récupérez-le.

La dernière dépendance est le client Mercurial. Rendez-vous sur https://www.mercurial-scm.org/ et installez-le si ce n’est pas déjà fait.

Maintenant, vous voilà prêt. Vous n’avez besoin que d’un dépôt Mercurial où pousser. Heureusement, tous les dépôts Mercurial peuvent servir et nous allons donc simplement utiliser le dépôt "hello world" dont tout le monde se sert pour apprendre Mercurial :

$ hg clone https://selenic.com/repo/hello /tmp/hello

Démarrage

Avec un dépôt « côté serveur » maintenant disponible, détaillons un flux de travail typique. Comme vous le verrez, ces deux systèmes sont suffisamment similaires pour qu’il y ait peu de friction.

Comme toujours avec Git, commençons par cloner :

$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Notez bien que pour travailler avec un dépôt Mercurial, on utilise la commande standard git clone. C’est dû au fait que git-remote-hg travaille à un niveau assez bas, en utilisant un mécanisme similaire à celui du protocole HTTP/S de Git. Comme Git et Mercurial sont tous les deux organisés pour que chaque client récupère une copie complète de l’historique du dépôt, cette commande réalise rapidement un clone complet, incluant tout l’historique du projet.

La commande log montre deux commits, dont le dernier est pointé par une ribambelle de refs. En fait, certaines d’entre elles n’existent par vraiment. Jetons un œil à ce qui est réellement présent dans le répertoire .git :

$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files

Git-remote-hg essaie de rendre les choses plus idiomatiquement Git-esques, mais sous le capot, il gère la correspondance conceptuelle entre deux systèmes légèrement différents. Par exemple, le fichier refs/hg/origin/branches/default est un fichier Git de références, qui contient le SHA-1 commençant par « ac7955c », qui est le commit pointé par master. Donc le répertoire refs/hg est en quelque sorte un faux refs/remotes/origin, mais il contient la distinction entre les marque-pages et les branches.

Le fichier notes/hg est le point de départ pour comprendre comment git-remote-hg fait correspondre les empreintes des commits Git avec les IDs de modification de Mercurial. Explorons-le un peu :

$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9

Donc, refs/notes/hg pointe sur un arbre qui correspond dans la base de données des objets de Git à une liste des autres objets avec des noms. git-ls-tree affiche le mode, le type, l’empreinte de l’objet et le nom de fichier des articles d’un arbre. Quand nous creusons un de ces articles, nous trouvons à l’intérieur un blob appelé « ac9117f » (l’empreinte SHA-1 du commit pointé par master), avec le contenu « 0a04b98 » (qui est l’ID de la modification Mercurial au sommet de la branche default).

La bonne nouvelle est que nous n’avons quasiment pas à nous soucier de tout ceci. Le mode de travail ne sera pas très différent de celui avec un serveur distant Git.

Il reste une chose à gérer avant de passer à la suite : les fichiers ignore. Mercurial et Git utilisent un mécanisme très similaire pour cette fonctionnalité, mais il est très probable que vous ne souhaitez pas valider un fichier .gitignore dans un dépôt Mercurial. Heureusement, Git dispose d’un moyen d’ignorer les fichiers d’un dépôt local et le format Mercurial est compatible avec Git. Il suffit donc de le copier :

$ cp .hgignore .git/info/exclude

Le fichier .git/info/exclude se comporte simplement comme un fichier .gitignore, mais n’est pas inclus dans les commits.

Déroulement

Supposons que nous avons travaillé et validé quelques commits sur la branche master et que nous sommes prêts à pousser ce travail sur un dépôt distant. Notre dépôt ressemble actuellement à ceci :

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Notre branche master est en avance de deux commits par rapport à origin/master, mais ces deux commits n’existent que sur notre machine locale. Voyons si quelqu’un d’autre a poussé son travail dans le même temps :

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87  master     -> origin/master
   ac7955c..df85e87  branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Comme nous avons utilisé l’option --all, nous voyons les références « notes » qui sont utilisées en interne par git-remote-hg, mais nous pouvons les ignorer. Le reste était attendu ; origin/master a avancé d’un commit et notre historique a divergé. À la différence d’autres systèmes que nous décrivons dans ce chapitre, Mercurial est capable de gérer les fusions, donc ce que nous allons faire n’a rien d’extraordinaire.

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
*   0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Parfait. Nous lançons les tests et tout passe, et nous voilà prêts à partager notre travail avec l’équipe :

$ git push
To hg::/tmp/hello
   df85e87..0c64627  master -> master

C’est fini ! Si vous inspectez le dépôt Mercurial, vous verrez que le résultat se présente comme attendu :

$ hg log -G --style compact
o    5[tip]:4,2   dc8fa4f932b8   2014-08-14 19:33 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   64f27bcefc35   2014-08-14 19:27 -0700   ben
| |    Update makefile
| |
| o  3:1   4256fc29598f   2014-08-14 19:27 -0700   ben
| |    Goodbye
| |
@ |  2   7db0b4848b3c   2014-08-14 19:30 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

La modification numérotée 2 a été faite par Mercurial et celles numérotées 3 et 4 ont été faites par git-remote-hg, en poussant les commits réalisés avec Git.

Branches et marque-pages

Git n’a qu’un seul type de branche : une référence qui se déplace quand des commits sont ajoutés. Dans Mercurial, ce type de référence est appelé « marque-page » et se comporte de la même manière qu’une branche Git.

Le concept de « branche » dans Mercurial est plus contraignant. La branche sur laquelle une modification est réalisée est enregistrée avec la modification, ce qui signifie que cette dernière sera toujours présente dans l’historique du dépôt. Voici un exemple d’un commit ajouté à la branche develop :

$ hg log -l 1
changeset:   6:8f65e5e02793
branch:      develop
tag:         tip
user:        Ben Straub <ben@straub.cc>
date:        Thu Aug 14 20:06:38 2014 -0700
summary:     More documentation

Notez la ligne qui commence par « branch ». Git ne peut pas vraiment répliquer ce comportement (il n’en a pas besoin ; les deux types de branches peuvent être représentés par une ref Git), mais git-remote-hg a besoin de comprendre cette différence, puisque qu’elle a du sens pour Mercurial.

La création de marque-pages Mercurial est aussi simple que la création de branches Git. Du côté Git :

$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
 * [new branch]      featureA -> featureA

C’est tout ce qui est nécessaire. Du côté Mercurial, cela ressemble à ceci :

$ hg bookmarks
   featureA                  5:bd5ac26f11f9
$ hg log --style compact -G
@  6[tip]   8f65e5e02793   2014-08-14 20:06 -0700   ben
|    More documentation
|
o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| |    update makefile
| |
| o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |    goodbye
| |
o |  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Remarquez la nouvelle étiquette [featureA] sur la révision 5. Elle se comporte exactement comme une branche Git du côté Git, avec une exception : vous ne pouvez pas effacer un marque-page depuis le côté Git (c’est une limitation des greffons de gestion distante).

Vous pouvez travailler aussi sur une branche « lourde » Mercurial : placez une branche dans l’espace de nom branches :

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch]      branches/permanent -> branches/permanent

Voici à quoi ça ressemble du côté Mercurial :

$ hg branches
permanent                      7:a4529d07aad4
develop                        6:8f65e5e02793
default                        5:bd5ac26f11f9 (inactive)
$ hg log -G
o  changeset:   7:a4529d07aad4
|  branch:      permanent
|  tag:         tip
|  parent:      5:bd5ac26f11f9
|  user:        Ben Straub <ben@straub.cc>
|  date:        Thu Aug 14 20:21:09 2014 -0700
|  summary:     A permanent change
|
| @  changeset:   6:8f65e5e02793
|/   branch:      develop
|    user:        Ben Straub <ben@straub.cc>
|    date:        Thu Aug 14 20:06:38 2014 -0700
|    summary:     More documentation
|
o    changeset:   5:bd5ac26f11f9
|\   bookmark:    featureA
| |  parent:      4:0434aaa6b91f
| |  parent:      2:f098c7f45c4f
| |  user:        Ben Straub <ben@straub.cc>
| |  date:        Thu Aug 14 20:02:21 2014 -0700
| |  summary:     Merge remote-tracking branch 'origin/master'
[...]

Le nom de branche « permanent » a été enregistré avec la modification marquée 7.

Du côté Git, travailler avec les deux styles de branches revient au même : employez les commandes checkout, commit, fetch, merge, pull et push comme vous feriez normalement. Une chose à savoir cependant est que Mercurial ne supporte pas la réécriture de l’historique mais seulement les ajouts. Voici à quoi ressemble le dépôt Mercurial après un rebasage interactif et une poussée forcée :

$ hg log --style compact -G
o  10[tip]   99611176cbc9   2014-08-14 20:21 -0700   ben
|    A permanent change
|
o  9   f23e12f939c3   2014-08-14 20:01 -0700   ben
|    Add some documentation
|
o  8:1   c16971d33922   2014-08-14 20:00 -0700   ben
|    goodbye
|
| o  7:5   a4529d07aad4   2014-08-14 20:21 -0700   ben
| |    A permanent change
| |
| | @  6   8f65e5e02793   2014-08-14 20:06 -0700   ben
| |/     More documentation
| |
| o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
| |\     Merge remote-tracking branch 'origin/master'
| | |
| | o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| | |    update makefile
| | |
+---o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |      goodbye
| |
| o  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Les modifications 8, 9 et 10 ont été créées et appartiennent à la branche permanent mais les anciennes modifications sont toujours présentes. Ça a toutes les chances de perdre vos collègues qui utilisent Mercurial, donc c’est à éviter à tout prix.

Résumé Mercurial

Git et Mercurial sont suffisamment similaires pour que le travail pendulaire entre les deux se passe sans accroc. Si vous évitez de modifier l’historique qui a déjà quitté votre machine (comme il l’est recommandé), vous pouvez tout simplement ignorer que le dépôt distant fonctionne avec Mercurial.

Git et Perforce

Perforce est un système de version très populaire dans les environnements professionnels. Il existe depuis 1995, ce qui en fait le système le plus ancien abordé dans ce chapitre. Avec cette information en tête, il apparaît construit avec les contraintes de cette époque ; il considère que vous êtes toujours connecté à un serveur central et une seule version est conservée sur le disque dur local. C’est certain, ses fonctionnalités et ses contraintes correspondent à quelques problèmes spécifiques, mais de nombreux projets utilisent Perforce là où Git fonctionnerait réellement mieux.

Il y a deux options pour mélanger l’utilisation de Perforce et de Git. La première que nous traiterons est le pont « Git Fusion » créé par les développeurs de Perforce, qui vous permet d’exposer en lecture-écriture des sous-arbres de votre dépôt Perforce en tant que dépôts Git. La seconde s’appelle git-p4, un pont côté client qui permet d’utiliser Git comme un client Perforce, sans besoin de reconfigurer le serveur Perforce.

Git Fusion

Perforce fournit un produit appelé Git Fusion (disponible sur https://www.perforce.com/git-fusion), qui synchronise un serveur Perforce avec des dépôts Git du côté serveur.

Installation

Pour nos exemples, nous utiliserons la méthode d’installation de Git Fusion la plus facile qui consiste à télécharger une machine virtuelle qui embarque le daemon Perforce et Git Fusion. Vous pouvez obtenir la machine virtuelle depuis https://www.perforce.com/downloads/Perforce/20-User, et une fois téléchargée, importez-la dans votre logiciel favori de virtualisation (nous utiliserons VirtualBox).

Au premier lancement de la machine, il vous sera demandé de personnaliser quelques mots de passe pour trois utilisateurs Linux (root, perforce et git), et de fournir un nom d’instance qui peut être utilisé pour distinguer cette installation des autres sur le même réseau. Quand tout est terminé, vous verrez ceci :

L’écran de démarrage de la machine virtuelle Git Fusion.
Figure 145. L’écran de démarrage de la machine virtuelle Git Fusion.

Prenez note de l’adresse IP qui est indiquée ici, car nous en aurons besoin plus tard. Ensuite, nous allons créer l’utilisateur Perforce. Sélectionnez l’option « Login » en bas de l’écran et appuyez sur Entrée (ou connectez-vous en SSH à la machine), puis identifiez-vous comme root. Ensuite, utilisez ces commandes pour créer un utilisateur :

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

La première commande va ouvrir un éditeur VI pour personnaliser l’utilisateur, mais vous pouvez accepter les valeurs par défaut en tapant :wq et en appuyant sur Entrée. La seconde vous demandera d’entrer le mot de passe deux fois. C’est tout ce qu’il faut faire depuis une invite de commande, et on peut quitter la session.

L’action suivante consiste à indiquer à Git de ne pas vérifier les certificats SSL. L’image Git Fusion contient un certificat, mais celui-ci ne correspond pas au domaine de l’adresse IP de votre machine virtuelle, donc Git va rejeter la connexion HTTPS. Pour une installation permanente, consultez le manuel Perforce Git Fusion pour installer un certificat différent ; pour l’objet de notre exemple, ceci suffira :

$ export GIT_SSL_NO_VERIFY=true

Maintenant, nous pouvons tester que tout fonctionne correctement.

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

La machine virtuelle contient un projet exemple que vous pouvez cloner. Ici, nous clonons via HTTPS, avec l’utilisateur john que nous avons créé auparavant ; Git demande le mot de passe pour cette connexion, mais le cache d’identifiant permettra de sauter cette étape par la suite.

Configuration de Fusion

Une fois que Git Fusion est installé, vous désirerez sûrement modifier la configuration. C’est assez facile à faire via votre client Perforce favori ; rapatriez simplement le répertoire //.git-fusion du serveur Perforce dans votre espace de travail. La structure du fichier ressemble à ceci :

$ tree
.
├── objects
│   ├── repos
│   │   └── [...]
│   └── trees
│       └── [...]
│
├── p4gf_config
├── repos
│   └── Talkhouse
│       └── p4gf_config
└── users
    └── p4gf_usermap

498 directories, 287 files

Le répertoire objects est utilisé en interne par Git Fusion pour faire correspondre les objets Perforce avec Git et vice versa et il n’y a pas lieu d’y toucher. Il y a un fichier p4gf_config global dans ce répertoire, ainsi qu’un fichier pour chaque dépôt. Ce sont les fichiers de configuration qui déterminent comment Git Fusion se comporte. Examinons le fichier à la racine :

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

Nous ne nous étendrons pas sur les significations des différents paramètres, mais on voit que c’est un simple fichier INI, du même style que ceux utilisés par Git. Ce fichier spécifie les options globales, qui peuvent être surchargées par chaque fichier de configuration spécifique à un dépôt, tel que repos/Talkhouse/p4gf_config. Si vous ouvrez ce fichier, vous verrez une section [@repo] contenant des paramétrages différents des paramètres globaux par défaut. Vous verrez aussi des sections ressemblant à ceci :

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

C’est la correspondance entre une branche Perforce et une branche Git. Le nom de la section est libre, du moment qu’il est unique. git-branch-name vous permet de convertir un chemin du dépôt qui serait encombrant sous Git en quelque chose de plus utilisable. L’entrée view contrôle comment les fichiers Perforce sont transformés en dépôts Git, en utilisant la syntaxe standard de description de vue. Des correspondances multiples peuvent être indiquées, comme dans cet exemple :

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

De cette manière, si votre montage d’espace de travail normal change de structure de répertoires, vous pouvez répliquer cette modification dans le dépôt Git.

Le dernier fichier que nous examinerons est users/p4gf_usermap, qui fait correspondre les utilisateurs Perforce avec les utilisateurs Git, et qui n’est même pas nécessaire. Quand une modification Perforce est convertie en commit Git, le comportement par défaut de Git Fusion consiste à rechercher l’utilisateur Perforce et à utiliser son adresse de courriel et son nom complet comme champs d’auteur/validateur dans Git. Dans l’autre sens, le comportement par défaut consiste à rechercher l’utilisateur Perforce correspondant à l’adresse de courriel stockée dans le champ auteur du commit Git et de soumettre une modification avec cet identifiant (si les permissions l’accordent). Dans la plupart des cas, ce comportement suffira, mais considérons tout de même le fichier de correspondance suivant :

john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"

Chaque ligne est de la forme <utilisateur> <courriel> <nom complet> et crée une correspondance unique. Les deux premières lignes font correspondre deux adresses de courriel distinctes avec le même utilisateur Perforce. C’est utile si vous avez créé des commits Git sous plusieurs adresses de courriel (ou modifié votre adresse de courriel), mais que vous voulez les faire correspondre au même utilisateur Perforce. À la création d’un commit Git depuis une modification Perforce, la première ligne correspondant à l’utilisateur Perforce est utilisée pour fournir l’information d’auteur à Git.

Les deux dernières lignes masquent les noms réels de Bob et Joe dans les commits Git créés. C’est très utile si vous souhaitez ouvrir les sources d’un projet interne, mais que vous ne souhaitez pas rendre public le répertoire de vos employés. Notez que les adresses de courriel et les noms complets devraient être uniques, à moins que vous ne souhaitiez publier tous les commits Git avec un auteur unique fictif.

Utilisation

Perforce Git Fusion est une passerelle à double-sens entre les contrôles de version Perforce et Git. Voyons comment cela se passe du côté Git. Nous supposerons que nous avons monté le projet « Jam » en utilisant le fichier de configuration ci-dessus, et que nous pouvons le cloner comme ceci :

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://ben@10.0.1.254':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

La première fois que vous le faites, cela peut durer un certain temps. Ce qui se passe, c’est que Git Fusion convertit toutes les modifications concernées de l’historique Perforce en commits Git. Cela se passe localement sur le serveur, donc c’est plutôt rapide, mais si votre historique est long, ce n’est pas immédiat. Les récupérations subséquentes ne lancent que des conversions incrémentales, ce qui devrait correspondre à la vitesse native de Git.

Comme vous pouvez le voir, notre dépôt ressemble complètement à un autre dépôt Git. Il y a trois branches et Git a utilement créé une branche master locale qui suit la branche origin/master. Travaillons un peu et créons une paire de commits :

# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Nous avons deux nouveaux commits. Maintenant, vérifions si quelqu’un d’autre a aussi travaillé :

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15  master     -> origin/master
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Il semble bien ! Ça n’apparaît pas sur cette vue, mais le commit 6afeb15 a en fait été créé en utilisant un client Perforce. Il ressemble juste à un commit normal du point de vue de Git, ce qui est exactement l’effet recherché. Voyons comment le serveur Perforce gère le commit de fusion :

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b  master -> master

Git pense que ça a marché. Voyons l’historique du fichier README du point de vue de Perforce, en utilisant la fonctionnalité de graphe de révision de p4v :

Graphe de révision de Perforce résultant d’une poussée depuis Git.
Figure 146. Graphe de révision de Perforce résultant d’une poussée depuis Git.

Si vous n’avez jamais vu ceci auparavant, cela peut dérouter, mais c’est une vue similaire à la vue graphique de l’historique Git. Nous visualisons l’historique du fichier README, donc l’arbre de répertoire en haut à gauche ne montre que ce fichier, aux endroits où il apparaît dans différentes branches. En haut à droite, nous avons le graphe visuel des relations entre les différentes révisions du fichier et la vue en grand du graphe en bas à droite. Le reste de l’écran concerne la visualisation des détails pour la révision sélectionnée (2 dans ce cas).

Une chose à noter est que le graphe ressemble exactement à celui de l’historique Git. Perforce n’avait pas de branche nommée pour stocker les commits 1 et 2, il a donc créé une branche « anonymous » dans le répertoire .git-fusion pour la gérer. Cela arrivera aussi pour des branches Git nommées qui ne correspondent pas à une branche Perforce nommée (et que vous pouvez plus tard faire correspondre à une branche Perforce en utilisant le fichier de configuration).

Tout ceci se passe en coulisse, mais le résultat final est qu’une personne dans l’équipe peut utiliser Git, une autre Perforce et aucune des deux n’a à se soucier du choix de l’autre.

Résumé Git-Fusion

Si vous avez accès (ou pouvez avoir accès) à un votre serveur Perforce, Git Fusion est un excellent moyen de faire parler Git et Perforce ensemble. Cela nécessite un peu de configuration, mais la courbe d’apprentissage n’est pas très raide. C’est une des rares sections de ce chapitre où il est inutile de faire spécifiquement attention à ne pas utiliser toute la puissance de Git. Cela ne signifie pas que Perforce sera ravi de tout ce que vous lui enverrez — si vous réécrivez l’historique qui a déjà été poussé, Git Fusion va le rejeter — Git Fusion cherche vraiment à sembler naturel. Vous pouvez même utiliser les sous-modules Git (bien qu’ils paraîtront étranges pour les utilisateurs Perforce), et fusionner les branches (ce qui sera enregistré comme une intégration du côté Perforce).

Si vous ne pouvez pas convaincre un administrateur de votre serveur d’installer Git Fusion, il existe encore un moyen d’utiliser ces outils ensemble.

Git-p4

Git-p4 est une passerelle à double sens entre Git et Perforce. Il fonctionne intégralement au sein de votre dépôt Git, donc vous n’avez besoin d’aucun accès au serveur Perforce (autre que les autorisations d’utilisateur, bien sûr). Git-p4 n’est pas une solution aussi flexible ou complète que Git Fusion, mais il permet tout de même de réaliser la plupart des activités sans être invasif dans l’environnement serveur.

Note

Vous aurez besoin de l’outil p4 dans votre de chemin de recherche pour travailler avec git-p4. À la date d’écriture du livre, il est disponible à https://www.perforce.com/downloads/Perforce/20-User.

Installation

Pour l’exemple, nous allons lancer le serveur Perforce depuis l’image Git Fusion, comme indiqué ci-dessus, mais nous n’utiliserons pas le serveur Git Fusion et nous dialoguerons avec la gestion de version Perforce directement.

Pour utiliser le client p4 en ligne de commande (dont git-p4 dépend), vous devrez définir quelques variables d’environnement :

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
Démarrage

Comme d’habitude avec Git, la première commande est un clonage :

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master

Cela crée ce qui en parlé Git s’appelle un clone « superficiel » (shallow) ; seule la toute dernière révision Perforce est importée dans Git ; souvenez-vous que Perforce n’a pas été pensé pour fournir toutes les révisions à chaque utilisateur. C’est suffisant pour utiliser Git comme client Perforce, mais pour d’autres utilisations, ce n’est pas assez.

Une fois que c’est terminé, nous avons un dépôt Git complètement fonctionnel.

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from the state at revision #head

Notez le dépôt p4 distant pour le serveur Perforce, mais tout le reste ressemble à un clone standard. En fait, c’est trompeur ; ce n’est pas réellement dépôt distant.

$ git remote -v

Il n’y a pas du tout de dépôt distant. Git-p4 a créé des références qui représentent l’état du serveur et celles-ci ressemblent à des références de dépôts distants dans git log, mais elles ne sont pas gérées par Git lui-même et vous ne pouvez pas pousser dessus.

Utilisation

Donc, travaillons un peu. Supposons que vous avez progressé sur une fonctionnalité très importante et que vous êtes prêt à la montrer au reste de votre équipe.

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

Nous avons réalisé deux nouveaux commits qui sont prêts à être soumis au serveur Perforce. Vérifions si quelqu’un d’autre a poussé son travail entre temps.

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)
$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Il semblerait que ce soit le cas, et master et p4/master ont divergé. Le système de branchement de Perforce ne ressemble en rien à celui de Git, donc soumettre des commits de fusion n’a aucun sens. Git-p4 recommande de rebaser vos commits et fournit même un raccourci pour le faire :

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Vous pouvez déjà le deviner aux messages affichés, mais git p4 rebase est un raccourci pour git p4 sync suivi de git rebase p4/master. C’est légèrement plus intelligent que cela, spécifiquement lors de la gestion de branches multiples, mais ça correspond bien.

À présent, notre historique est linéaire à nouveau et nous sommes prêts à remonter nos modifications sur Perforce. La commande git p4 submit va essayer de créer une nouvelle révision Perforce pour chaque commit Git entre p4/master et master. Son lancement ouvre notre éditeur favori et le contenu du fichier ouvert ressemble à ceci :

# A Perforce Change Specification.
#
#  Change:      The change number. 'new' on a new changelist.
#  Date:        The date this specification was last modified.
#  Client:      The client on which the changelist was created.  Read-only.
#  User:        The user who created the changelist.
#  Status:      Either 'pending' or 'submitted'. Read-only.
#  Type:        Either 'public' or 'restricted'. Default is 'public'.
#  Description: Comments about the changelist.  Required.
#  Jobs:        What opened jobs are to be closed by this changelist.
#               You may delete jobs from this list.  (New changelists only.)
#  Files:       What opened files from the default changelist are to be added
#               to this changelist.  You may delete files from this list.
#               (New changelists only.)

Change:  new

Client:  john_bens-mbp_8487

User: john

Status:  new

Description:
   Update link

Files:
   //depot/www/live/index.html   # edit


######## git author ben@straub.cc does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html  2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html   2014-08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="https://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

C’est quasiment le même contenu qu’on verrait en lançant p4 submit, mis à part le bloc à la fin que git-p4 a utilement inclus. Git-p4 essaye d’honorer vos réglages Git et Perforce individuellement quand il doit fournir un nom pour un commit ou une modification, mais dans certains cas, vous voudrez le modifier. Par exemple, si le commit Git que vous importez a été écrit par un contributeur qui n’a pas de compte utilisateur dans Perforce, vous voudrez tout de même que la modification résultante ait l’air d’avoir été écrite par lui, et non par vous.

Git-p4 a importé le message du commit Git comme contenu de la modification Perforce, donc tout ce qu’il nous reste à faire et de sauvegarder et de quitter, deux fois (une fois par commit). La sortie qui en résulte ressemble à ceci :

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

À la sortie, c’est comme si nous avions fait un git push, ce qui est l’analogie la plus proche avec ce qui s’est réellement passé.

Notez aussi que durant ce processus, les commits Git sont transformés en modifications Perforce ; si vous voulez les comprimer en une seule modification, vous pouvez le faire avec un rebasage interactif avant de lancer git p4 submit. Notez aussi que les empreintes SHA-1 de tous les commits qui ont été soumis comme modifications ont changé ; c’est parce que git-p4 ajoute une ligne à la fin de chaque message de validation qu’il convertit :

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title

    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

Que se passe-t-il si vous essayez de soumettre un commit de fusion ? Essayons pour voir. Voici la situation dans laquelle nous sommes :

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
*   1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Les historiques Git et Perforce divergent à partir de 775a46f. Le côté Git contient deux commits, puis un commit de fusion avec la tête Perforce, puis un autre commit. Nous allons essayer de les soumettre au sommet d’un seul changement côté Perforce. Voyons ce qui se passerait si nous essayions de soumettre cet historique maintenant :

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

L’option -n est un raccourci pour --dry-run qui essaie d’afficher ce qui se passerait si la commande submit était réellement lancée. Dans ce cas, il semble que nous créerions trois modifications Perforce, ce qui correspond aux trois commits sans fusion qui n’existent pas encore sur le serveur Perforce. Cela ressemble exactement à ce que nous souhaitons, voyons comment cela se passe :

$ git p4 submit
[…]
$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Notre historique est devenu linéaire, comme si nous avions rebasé avant de soumettre (ce qui est exactement ce qui s’est passé). Cela signifie que vous êtes libre de créer, modifier, jeter et fusionner les branches du côté Git sans crainte que votre historique deviennent à un moment incompatible avec Perforce. Si vous pouvez le rebaser, vous pourrez le reporter dans le serveur Perforce.

Branche

Si votre projet Perforce a de multiples branches, vous n’êtes pas malchanceux ; git-p4 peut gérer cette configuration d’une manière similaire à Git. Supposons que votre dépôt Perforce ait la forme suivante :

//depot
  └── project
      ├── main
      └── dev

Et supposons que vous ayez une branche dev qui contient une view spec qui ressemble à ceci :

//depot/project/main/... //depot/project/dev/...

Git-p4 peut détecter automatiquement cette situation et faire ce qu’il faut :

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev

    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev
$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

Notez le déterminant « @all » ; il indique à git-p4 de cloner non seulement la dernière modification pour ce sous-arbre, mais aussi toutes les modifications qui ont déjà touché à ces chemins. C’est plus proche du concept de clone dans Git, mais si vous travaillez sur un projet avec un long historique, cela peut prendre du temps à se terminer.

L’option --detect-branches indique à git-p4 d’utiliser les spécifications de branche de Perforce pour faire correspondre aux références Git. Si ces correspondances ne sont pas présentes sur le serveur Perforce (ce qui est une manière tout à fait valide d’utiliser Perforce), vous pouvez dire à git-p4 ce que sont les correspondances de branches, et vous obtiendrez le même résultat :

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

Renseigner la variable de configuration git-p4.branchList à main:dev indique à git-p4 que main et dev sont toutes deux des branches et que la seconde est la fille de la première.

Si nous lançons maintenant git checkout -b dev p4/project/dev et ajoutons quelques commits, git-p4 est assez intelligent pour cibler la bonne branche quand nous lançons git-p4 submit. Malheureusement, git-p4 ne peut pas mélanger les clones superficiels et les branches multiples ; si vous avez un projet gigantesque et que vous voulez travailler sur plus d’une branche, vous devrez lancer git p4 clone une fois pour chaque branche à laquelle vous souhaitez soumettre.

Pour créer ou intégrer des branches, vous devrez utiliser un client Perforce. Git-p4 ne peut synchroniser et soumettre que sur des branches préexistantes, et il ne peut le faire qu’avec une modification linéaire à la fois. Si vous fusionnez deux branches dans Git et que vous essayez de soumettre la nouvelle modification, tout ce qui sera enregistré sera une série de modifications de fichiers ; les métadonnées relatives aux branches impliquées dans cette intégration seront perdues.

Résumé Git et Perforce

Git-p4 rend possible l’usage des modes d’utilisation de Git avec un serveur Perforce, et ce, de manière plutôt réussie. Cependant, il est important de se souvenir que Perforce gère les sources et qu’on ne travaille avec Git que localement. Il faut rester vraiment attentif au partage de commits Git ; si vous avez un dépôt distant que d’autres personnes utilisent, ne poussez aucun commit qui n’a pas déjà été soumis au serveur Perforce.

Si vous souhaitez mélanger l’utilisation de Git et de Perforce comme clients pour la gestion de source sans restriction et si vous arrivez à convaincre un administrateur de l’installer, Git Fusion fait de Git un client de premier choix pour un serveur Perforce.

scroll-to-top