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.
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
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.
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.
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.
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.