Das DVCS-Universum besteht nicht nur aus nur Git. In diesem Bereich gibt es viele andere Systeme, jedes hat seinen eigenen Ansatz, wie eine verteilte Versionskontrolle zu funktionieren hat. Neben Git ist Mercurial am populärsten und die beiden sind sich in vielerlei Hinsicht sehr ähnlich.
Die gute Nachricht, wenn Sie Gits clientseitiges Verhalten bevorzugen, aber mit einem Projekt arbeiten, dessen Quellcode mit Mercurial verwaltet wird, dann ist es möglich, Git als Client für ein von Mercurial gehostetes Repository zu verwenden. Da die Art und Weise, wie Git über Remotes mit Server-Repositorys kommuniziert, sollte es nicht überraschen, dass diese Bridge als Remote-Helfer implementiert ist. Der Name des Projekts lautet git-remote-hg und ist unter https://github.com/felipec/git-remote-hg zu finden.
Zuerst müssen Sie git-remote-hg installieren. Im Wesentlichen geht es darum, die Datei irgendwo in Ihrem Pfad abzulegen, so wie hier:
$ curl -o ~/bin/git-remote-hg \
https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg
…vorausgesetzt (in einer Linux-Umgebung), ~/bin
ist in Ihrem $PATH
.
Git-remote-hg hat noch eine weitere Abhängigkeit: die mercurial
Library für Python.
Wenn Sie Python installiert schon haben, ist das einfach:
$ pip install mercurial
Wenn Sie Python noch nicht installiert haben, besuchen Sie https://www.python.org/ und besorgen Sie es sich zuerst.
Als Letztes brauchen Sie den Mercurial-Client. Gehen Sie zu https://www.mercurial-scm.org/ und installieren Sie ihn, falls Sie es noch nicht getan haben.
Jetzt sind Sie bereit zu abrocken. Alles, was Sie benötigen, ist ein Mercurial-Repository, auf das Sie zugreifen können. Glücklicherweise kann sich jedes Mercurial-Repository so verhalten, also verwenden wir einfach das „hello world“-Repository, das jeder benutzt, um Mercurial zu lernen:
$ hg clone http://selenic.com/repo/hello /tmp/hello
Nun, da wir über ein geeignetes „serverseitiges“ Repository verfügen, können wir einen typischen Workflow durchlaufen. Wie Sie sehen werden, sind diese beiden Systeme ähnlich genug, dass es keine große Überschneidungen gibt.
Wie immer mit Git, wir klonen zuerst:
$ 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
Wie Sie sehen, verwendet man bei der Arbeit mit einem Mercurial-Repository den Standardbefehl git clone
.
Das liegt daran, dass git-remote-hg auf einem relativ niedrigen Level arbeitet und einen ähnlichen Mechanismus verwendet, wie es die Implementierung des HTTP/S-Protokolls in Git ist (Remote-Helfer).
Da Git und Mercurial beide so konzipiert sind, dass jeder Client eine vollständige Kopie der Repository-Historie hat, erstellt dieser Befehl relativ schnell einen vollständigen Klon, einschließlich der gesamten Projekthistorie.
Der log-Befehl zeigt zwei Commits, von denen der letzte von einer ganzen Reihe von Refs angeführt wird.
Wie sich herausstellt, sind einige davon nicht wirklich da.
Werfen wir einen Blick darauf, was sich wirklich im .git
Verzeichnis befindet:
$ 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 versucht sich idiomatisch (begrifflich) an Git anzunähern, aber im Hintergrund verwaltet es die konzeptionelle Zuordnung zwischen zwei leicht unterschiedlichen Systemen.
Im Verzeichnis refs/hg
werden die aktuellen Remote-Referenzen gespeichert.
Zum Beispiel ist die refs/hg/origin/branches/default
eine Git ref-Datei, die das SHA-1 enthält und mit „ac7955c“ beginnt. Das ist der Commit, auf den master
zeigt.
Das Verzeichnis refs/hg
ist also eine Art gefälschtes refs/remotes/origin
, aber es unterscheidet zusätzlich zwischen Lesezeichen und Zweigen.
Die Datei notes/hg
ist der Ausgangspunkt dafür, wie git-remote-hg Git-Commit-Hashes auf Mercurial-Changeset-IDs abbildet.
Lassen Sie uns ein wenig experimentieren:
$ 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
So zeigt refs/notes/hg auf einen Verzeichnisbaum, der in der Git-Objektdatenbank eine Liste anderer Objekte mit Namen ist.
git ls-tree
gibt Modus, Typ, Objekt-Hash und Dateiname für Elemente innerhalb eines Baums aus.
Sobald wir uns auf eines der Baumelemente festgelegt haben, stellen wir fest, dass sich darin ein „ac9117f“ Blob (der SHA-1-Hash des Commit, auf den master
zeigt) befindet. Inhaltlich ist er identisch mit „0a04b98“ (das ist die ID des Mercurial-Changesets an der Spitze der default
Branch).
Die gute Nachricht ist, dass wir uns darüber meistens keine Sorgen machen müssen. Der typische Arbeitsablauf unterscheidet sich nicht wesentlich von der Arbeit mit einem Git-Remote.
Noch eine Besonderheit, um die wir uns kümmern sollten, bevor wir fortfahren: Die Auslassungen.
Mercurial und Git verwenden dafür einen sehr ähnlichen Mechanismus, aber es ist durchaus möglich, dass Sie eine .gitignore
Datei nicht wirklich in ein Mercurial Repository übertragen wollen.
Glücklicherweise hat Git eine Möglichkeit, Dateien zu ignorieren, die lokal in einem On-Disk-Repository liegen. Das Mercurial-Format ist kompatibel mit Git, so dass Sie es nur kopieren müssen:
$ cp .hgignore .git/info/exclude
Die Datei .git/info/exclude
verhält sich wie eine .gitignore
, wird aber nicht in den Commits aufgenommen.
Nehmen wir an, wir haben einige Arbeiten erledigt und einige Commits auf den master
Branch gemacht und Sie sind so weit, ihn in das Remote-Repository zu pushen.
Nun sieht unser Repository momentan so aus:
$ 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
Unser master
Branch ist zwei Commits vor dem origin/master
, aber diese beiden Commits existieren nur auf unserem lokalen Rechner.
Schauen wir mal nach, ob jemand anderes zur gleichen Zeit wichtige Arbeit geleistet hat:
$ 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
Da wir das --all
Flag verwendet haben, sehen wir die „notes“ Refs, die intern von git-remote-hg verwendet werden, die wir aber ignorieren können.
Den Rest haben wir erwartet. origin/master
ist um einen Commit fortgeschritten. Unser Verlauf hat sich dadurch verändert.
Anders als bei anderen Systemen, mit denen wir in diesem Kapitel arbeiten, ist Mercurial in der Lage, Merges zu verarbeiten, so dass wir nichts Ausgefallenes tun müssen.
$ 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
Perfekt. Wir führen die Tests durch und alles passt, also sind wir so weit, dass wir unsere Arbeit mit dem Rest des Teams teilen können:
$ git push
To hg::/tmp/hello
df85e87..0c64627 master -> master
Das wars! Wenn Sie einen Blick auf das Mercurial-Repository werfen, werden Sie feststellen, dass genau das getan wurde, was wir erwarten durften:
$ 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
Das Change-Set mit der Nummer 2 wurde von Mercurial vorgenommen, und die Change-Sets mit der Nummer 3 und 4 wurden von git-remote-hg durchgeführt, indem Commits mit Git gepusht wurden.
Git hat nur eine Art von Branch: eine Referenz, die sich verschiebt, wenn Commits gemacht werden. In Mercurial wird diese Art von Referenz als „bookmark“ (dt. Lesezeichen) bezeichnet, und sie verhält sich ähnlich wie ein Git-Branch.
Das Konzept von Mercurial eines „Branchs“ ist höher gewichtet.
Der Branch, auf den ein Changeset durchgeführt wird, wird zusammen mit dem Changeset aufgezeichnet, d.h. er befindet sich immer im Repository-Verlauf.
Hier ist ein Beispiel für einen Commit, der auf dem develop
Branch gemacht wurde:
$ 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
Achten Sie darauf, dass die Zeile mit „branch“ beginnt. Git kann das nicht wirklich nachahmen (und muss es auch nicht; beide Arten von Zweigen können als Git ref dargestellt werden), aber git-remote-hg muss den Unterschied erkennen, denn für Mercurial ist er wichtig.
Das Anlegen von Mercurial-Lesezeichen ist so einfach wie das Erstellen von Git-Branches. Auf der Seite vom Git machen Sie:
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
* [new branch] featureA -> featureA
Das ist alles, was es dazu zu sagen gibt. Auf der Mercurial-Seite sieht es dann folgendermaßen aus:
$ 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
Beachten Sie den neuen [featureA]
Tag auf Revision 5.
Die verhalten sich genau wie Git-Branches auf der Git-Seite, mit einer Ausnahme: Sie können ein Lesezeichen auf der Git-Seite nicht löschen (das ist eine Einschränkung des Remote-Helfers).
Sie können auch an einem „schwergewichtigen“ Mercurial-Branch arbeiten: Bringen Sie einfach einen Branch in den branches
Namensraum:
$ 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
So sieht das dann auf der Mercurial-Seite aus:
$ 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'
[...]
Der Branch-Name „permanent“ wurde mit dem Change-Set 7 eingetragen.
Seitens von Git ist die Arbeit mit einem dieser Branch-Stile die gleiche: Einfach auschecken, committen, fetchen, mergen, pullen, und pushen, wie Sie es üblicherweise machen würden. Eine Sache, die Sie wissen sollten, ist, dass Mercurial das Überschreiben der Historie nicht unterstützt, sondern nur hinzufügt. Das Mercurial-Repository sieht nach einem interaktiven Rebase und einem Force-Push so aus:
$ 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
Die Changesets 8, 9 und 10 wurden angelegt und gehören zum permanent
Branch, aber die alten Changesets sind immer noch vorhanden.
Das kann für Ihre Teamkollegen, die Mercurial verwenden, sehr verwirrend sein, also versuchen Sie es zu vermeiden.
Git und Mercurial sind sich ähnlich genug, um über die eigene Umgebung hinaus schmerzlos zu arbeiten. Wenn Sie vermeiden, den Verlauf zu ändern, der Ihren Computer verlässt (was allgemein empfohlen wird), merken Sie wahrscheinlich nicht einmal, dass am anderen Ende Mercurial verwendet wird.