Het DVCS universum is groter dan alleen Git. Eigenlijk zijn er vele andere systemen in deze ruimte, elk met hun eigen aanpak om hoe op de juiste manier om te gaan met gedistribueerd versiebeheer. Buiten Git, is de meest populaire Mercurial, en de twee zijn op vele vlakken erg vergelijkbaar.
Het goede nieuws is, als je het gedrag van Git aan de kant van het werkstation de voorkeur geeft, maar als je werkt met een project waarvan de broncode wordt beheerd met Mercurial, dat er een manier is om Git als een client voor een repository die op een Mercurial-host draait te gebruiken. Omdat de manier voor Git om te praten met server repositories via remotes loopt, moet het niet als een verrassing komen dat deze brug ("bridge") geïmplementeerd is als een remote helper. De naam van het project is git-remote-hg, en het kan worden gevonden op https://github.com/felipec/git-remote-hg.
Allereerst moet je git-remote-hg installeren. Dit houdt niet veel meer in dan het bestand ergens op je pad neer te zetten, op deze manier:
$ curl -o ~/bin/git-remote-hg \
https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg
…aangenomen dat ~/bin
op je $PATH
staat.
Git-remote-hg heeft een andere afhankelijkheid: de mercurial
library voor Python.
Als je Python geïnstalleerd hebt, is dit zo simpel als:
$ pip install mercurial
(Als je geen Python geïnstalleerd hebt, bezoek dan https://www.python.org/ en haal dat eerst op.)
Het laatste wat je nodig hebt is de Mercurial client. Ga naar https://www.mercurial-scm.org/ en installeer dit als je dat al niet hebt gedaan.
Nu is alles klaar voor gebruik. Al wat je nodig hebt is een Mercurial repository waar je naar kunt pushen. Gelukkig kan elke Mercurial repository zich op deze manier gedragen, dus we hoeven alleen de "hello world" repository die iedereen gebruikt om Mercurial te leren gebruiken:
$ hg clone http://selenic.com/repo/hello /tmp/hello
Nu we een passende ``server-side'' repository hebben, kunnen we een normale workflow gaan volgen. Zoals je zult zien zijn deze twee systemen voldoende vergelijkbaar dat er niet veel wrijving zal zijn.
Zoals altijd met Git, gaan we eerst clonen:
$ 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
Je zult opgemerkt hebben dat je bij het werken met een Mercurial repository het standaard git clone
commando gebruikt.
Dat is omdat git-remote-hg op een behoorlijk laag niveau werkt, en gebruik maakt van een vergelijkbaar mechanisme als het HTTP/S protocol dat in Git geïmplementeerd is (remote helpers).
Omdat Git en Mercurial beide zijn ontworpen vanuit het principe dat elk werkstation een volledige kopie van de historie van de repository heeft, maakt dit commando een volledige kloon, inclusief alle historie van het project en doet dit redelijk snel.
Het log commando laat twee commits zien, maar naar de laatste daarvan wordt door een hele sloot refs verwezen.
Nu is het zo dat een aantal van deze er eigenlijk helemaal niet zijn.
Laten we kijken naar wat er eigenlijk in de .git
directory staat:
$ 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 probeert dingen meer letterlijk op z’n Gits te maken, maar onder de motorkap beheert het de conceptuele mapping tussen twee marginaal verschillende systemen.
De refs/hg
directory is waar de echte remote refs worden opgeslagen.
Bijvoorbeeld, de refs/hg/origin/branche/default
is een Git ref bestand die de SHA-1 bevat die begint met `ac7955c'', wat de commit is waar `master
naar wijst.
Dus de refs/hg
directory is een soort van nep refs/remotes/origin
, maar het heeft het toegevoegde onderscheid tussen boekenleggers ("bookmarks") en branches.
Het notes/hg
bestand is het beginpunt van hoe git-remote-hg Git commit hashes mapt op Mercurial changeset IDs.
Laten we dit een beetje verkennen:
$ 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
Dus refs/notes/hg
wijst naar een tree, wat in de Git object database een lijst van andere objecten met namen is.
git ls-tree
produceert de mode, type, object hash en bestandsnamen voor items in een tree.
Zodra we gaan graven naar een van de tree items, vinden we dat hierbinnen een blog zit met de naam ac9117f'' (de SHA-1 hash van de commit waar
0a04b98'' (wat de ID is van de Mercurial changeset aan de punt van de master
naar wijst), met de inhoud default
-branch).
Het goede nieuwe is dat we ons over het algemeen hierover geen zorgen hoeven te maken. De typische workflow zal niet veel verschillen van het werken met een Git remote.
Er is een extra onderwerp waar we even aandacht aan moeten schenken voordat we doorgaan: ignores.
Mercurial en Git gebruiken een erg vergelijkbare mechanisme hiervoor, maar het is erg aannemelijk dat je een .gitignore
niet echt in een Mercurial repository wilt committen.
Gelukkig heeft Git een manier om bestanden te negeren die lokaal is voor een repository die op schijf staat, en het formaat van Mercurial is compatibel met die van Git, dus je moet het ernaartoe kopiëren:
$ cp .hgignore .git/info/exclude
Het .git/info/exclude
bestand gedraagt zich als een .gitignore
, maar wordt niet in commits meegenomen.
Laten we aannemen dat we wat werk gedaan hebben en een aantal commits op de master
-branch uitgevoerd hebben, en je bent klaar om dit naar de remote repository te pushen.
Zo ziet onze repository eruit op dit moment:
$ 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
Onze master
-branch loopt twee commits voor op origin/master
, maar deze twee commits bestaan alleen op onze lokale machine.
Laten we kijken of iemand anders belangrijk werk heeft gedaan in de tussentijd:
$ 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
Omdat we de --all
vlag gebruikt hebben, zien we de `notes'' refs die intern gebruikt worden door git-remote-hg, maar deze kunnen we negeren.
De rest is zoals we hadden verwacht; `origin/master
is een commit naar voren gegaan, en onze histories zijn nu uiteengelopen.
In tegenstelling tot andere systemen waar we mee werken in dit hoofdstuk, is Mercurial in staat om merges te verwerken, dus we gaan nu geen moeilijke dingen doen.
$ 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
Perfect. We laten de tests draaien en alles slaagt, dus we zijn klaar om ons werk te delen met de rest van het team:
$ git push
To hg::/tmp/hello
df85e87..0c64627 master -> master
Dat is alles! Als je de Mercurial repository bekijkt, zul je zien dat dit gedaan heeft wat we mogen verwachten:
$ 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
De wijzigingsset ("changeset") met nummer 2 is gemaakt door Mercurial, en de changesets nummers 3 en 4 zijn door git-remote-hg gemaakt, door het pushen van de met Git gemaakte commits.
Git heeft maar een soort branch: een referentie die verplaatst wordt als commits worden gemaakt. In Mercurial, worden deze soorten referenties een ``bookmark'' genoemd, en het gedraagt zich grotendeels vergelijkbaar met een branch in Git.
Het concept van een `branch'' in Mercurial heeft iets meer voeten in de aarde.
De branch waar een changeset op is gebaseerd wordt opgeslagen met de changeset, wat inhoudt dat het altijd in de historie van de repository aanwezig is.
Hier is een voorbeeld van een commit die gemaakt is op de `develop
-branch:
$ hg log -l 1
changeset: 6:8f65e5e02793
branch: develop
tag: tip
user: Ben Straub <[email protected]>
date: Thu Aug 14 20:06:38 2014 -0700
summary: More documentation
Merk de regel op die begint met ``branch''. Git kan dit niet echt simuleren (en hoeft dit ook niet; beide soorten branches kunnen in Git als een ref worden weergegeven), maar git-remote-hg moet het onderscheid kunnen maken, omdat Mercurial hier om geeft.
Het aanmaken van Mercurial bookmarks is net zo eenvoudig als het maken van Git branches. Aan de Git kant:
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
* [new branch] featureA -> featureA
En dat is alles wat nodig is. Aan de kant van Mercurial ziet het er zo uit:
$ 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
Merk de nieuwe [featureA]
tag in revisie 5 op.
Deze gedragen zich precies als Git branches aan de Git kant, met een uitzondering: je kunt een bookmark niet van de Git kant verwijderen (dit is een beperking van remote helpers).
Je kunt ook op een `zwaargewicht'' Mercurial branch werken: gewoon een branch in de `branches
naamsruimte ("namespace") zetten:
$ 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
En hier is hoe het er aan de kant van Mercurial uit ziet:
$ 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 <[email protected]>
| date: Thu Aug 14 20:21:09 2014 -0700
| summary: A permanent change
|
| @ changeset: 6:8f65e5e02793
|/ branch: develop
| user: Ben Straub <[email protected]>
| 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 <[email protected]>
| | date: Thu Aug 14 20:02:21 2014 -0700
| | summary: Merge remote-tracking branch 'origin/master'
[...]
De branchnaam ``permanent'' is opgeslagen met de changeset gemarkeerd met 7.
Aan de kant van Git, is het werken met beide stijlen branch gelijk: gewoon checkout, commit, fetch, merge, pull en push zoals je gewoonlijk zou doen. Een ding wat je moet weten is dat Mercurial het overschrijven van historie niet ondersteunt, alleen eraan toevoegen. Dit is hoe onze Mercurial repository eruit ziet na een interactieve rebase en een force-push:
$ 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
Changesets 8, 9 en 10 zijn gemaakt voor en behoren bij de permanent
-branch, maar de oude changesets zijn er nog steeds.
Dit kan erg verwarrend worden voor je teamgenoten die Mercurial gebruiken, dus probeer dit te vermijden.