Verteilte Versionskontrolle (DVCS) mit Mercurial

Peter Arrenbrecht, August 2008
http://arrenbrecht.ch/

Einleitung

Verteilte Versionskontrolle (DVCS) ist trendy. Immer mehr Open-Source-Projekte stellen darauf um, und es hält auch in Firmen Einzug. Die Grundideen sind einfach: jeder hat immer die relevante Versionsgeschichte dabei, jeder arbeitet immer in einem ad-hoc Branch, und Merging ist darum eine ganz einfache und natürliche Sache. Welche Vorteile bringt das? Welche Probleme bereitet es? Was gibt es für Lösungen? Wir schauen uns das konkret an am Beispiel von Mercurial. Dieses ist mit Git und Bazaar eines der verbreitetsten DVCS und wird z.B. von Mozilla, OpenJDK und Netbeans eingesetzt.

Kursziel

Adressaten

Alle, die mit server-orientierter Versionskontrolle à la Subversion, CVS, Perforce etc. bereits Erfahrungen gemacht haben (oder die zumindest wissen, warum Versionskontrolle sinnvoll ist und sich jetzt endlich darum kümmern wollen). Wer bereits mit DVCS wie Git, Bazaar oder Darcs gearbeitet hat, wird sehen, wie sich Mercurial im Vergleich dazu anfühlt, aber bei den Grundlagen kaum Neues lernen.

Voraussetzungen

Wir versionieren ein kleines Projekt

Zunächst starten wir das Projekt. Typischerweise denken wir noch nicht an Versionskontrolle in diesem Moment, sondern legen einfach los:

$ mkdir hgdemo
$ cd hgdemo
$ cat >README <<-eof
    = hgdemo =

    Dies ist ein kleines Demo-Projekt zum Thema DVCS mit Mercurial.

    Es darf frei kopiert werden.
eof
$ cat >hgdemo.py <<eof
print "The numbers from 1 to 10" 
for i in range(1,11): print i,
print
eof
$ python hgdemo.py
The numbers from 1 to 10
1 2 3 4 5 6 7 8 9 10

Versionieren

hg init

Nun wollen wir aber sauber versionieren, denn es steht das erste Refactoring an: die Lizenz soll ausgelagert und besser definiert werden. Dazu wandeln wir einfach das aktuelle Verzeichnis um in eine Arbeitskopie (working copy) eines neuen Mercurial-Archivs (repository):

$ hg init

Das neue Archiv landet automatisch im Unterverzeichnis .hg/ (netterweise haben wir aber nur immer ein .hg/ Verzeichnis pro Arbeitskopie, nicht die verstreuten .svn/ wie bei Subversion):

$ ls -Ap
.hg/
hgdemo.py
README

Hier sehen wir bereits ein fundamentales Prinzip von DVCS in Aktion: jede Arbeitskopie hat immer ihr Archiv gleich mit dabei. Daher das Wort distributed in DVCS. Dafür stellt DVCS mächtige Mittel bereit, solche Archive to kopieren und zu synchronisieren. Mehr dazu ein bisschen später.

Im Moment ist das Archiv noch leer. Wie andere VCS protokolliert Mercurial erst, wenn wir es explizit dazu anweisen. Versuchen wir das sofort, so erhalten wir:

$ hg commit --message "erster Import" 
No username found, using 'hans@muehle' instead
nothing changed
~/.hgrc

Oops. Mercurial warnt uns, dass wir ihm noch nicht gesagt haben, unter welchem Namen es unsere Änderungen im Archiv protokollieren soll. Das holen wir gleich nach, denn sonst nimmt es einfach den aktuellen User- und Maschinennamen. Alle persönlichen Einstellungen für Mercurial schreiben wir nach ~/.hgrc:

$ echo "[ui]" >>~/.hgrc
$ echo "username = Hans Müller <hans.mueller@example.com>" >>~/.hgrc

Versuchen wir es erneut, so kommt immer noch nothing changed:

$ hg commit --message "erster Import" 
nothing changed
hg status

Warum? Listen wir den Zustand unserer Arbeitskopie auf, so sehen wir, dass Mercurial unsere Dateien noch nicht unter seine Fittiche genommen hat. ? bedeutet unbekannt (unknown):

$ hg status
? README
? hgdemo.py
hg add

Mercurial nimmt – wie viele andere VCS auch – Dateien nicht automatisch auf. Stellen wir die Dateien also explizit unter Versionskontrolle:

$ hg add
adding README
adding hgdemo.py

Ohne weitere Argumente nimmt hg add rekursiv alle unbekannten Dateien auf. Wir sehen später, wie wir Build-Produkte filtern können. Zur Kontrolle listen wir alle Dateien mit hängigen Änderungen nochmals auf (A steht für add):

$ hg status
A README
A hgdemo.py
hg commit

Sieht gut aus, also protokollieren wir die Änderungen jetzt (neudeutsch committen). Dies erzeugt im Archiv ein sogenanntes changeset (manchmal auch einfach change, oder revision; geschrieben auch cset oder rev). Das ist eine Sammlung von zusammengehörigen Änderungen an einer Reihe von Dateien. Wie bei allen Versionskontroll-Systemen dokumentieren wir jedes Changeset mit einem Kommentar:

$ hg commit --message "erster Import" 

Die Arbeitskopie hat nun keine hängigen Änderungen mehr:

$ hg status
hg log

Sehen wir uns das Resultat in der Versionsgeschichte an:

$ hg log
changeset:   0:d8d194e06b45
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     erster Import

Die Zahl 0 in changeset: 0:... ist die Revisions-Nummer innerhalb dieses Archivs. Sie entspricht in etwa der Revisionsnummer in Subversion. Die darauf folgende Hex-Zahl ist eine ziemlich eindeutige Kennung des Changesets über Archive hinweg (dazu später mehr).

Backup

Wie oben erwähnt, ist unser neues Archiv ein Unterverzeichnis der Arbeitskopie. Hier liegt eine Gefahr. Man kann mit einem unbedachten rm -rf mein-neues-projekt schnell seine einzige Arbeitskopie und das ganze Archiv löschen. Dabei wollte man wohl einfach die Arbeitskopie löschen und neu auschecken nach einer unglücklichen Änderung. Das ist bei Subversion ungefährlich, wo man das Archiv separat anlegt.

hg clone

Mit Mercurial können wir das Archiv klonen (kontrolliert kopieren) um solchem Datenverlust vorzubeugen. Das geht sehr einfach mit:

$ hg clone --noupdate . ../hgdemo-repo

Dies erzeugt eine Kopie unseres Archivs. Die Option --noupdate unterdrückt die Arbeitskopie, die sonst am Ziel gleich wieder anhand des letzten Commits angelegt würde. Für eine reine Archiv-Kopie brauchen wird diese aber nicht. Wir erhalten:

$ ls -Ap ../hgdemo-repo
.hg/

Sinnvollerweise legen wir diese Kopie natürlich an einem Ort an, der regelmässig gesichert wird. Z.B. auf einem Server-Share.

Änderungen committen

Jetzt sind wir bereit, die Lizenz sauber versioniert einzuarbeiten:

$ cat >README <<-eof
    = hgdemo =

    Dies ist ein kleines Demo-Projekt zum Thema DVCS mit Mercurial.

    Es steht unter der BSD-Lizenz. Siehe COPYING.
eof
$ cat >COPYING <<-eof
    Redistribution and use in source and binary forms, with or
    without modification, are permitted provided that the following
    conditions are met...
eof

Nach diesen Änderungen können wir wieder den Zustand unserer Arbeitskopie sichten:

$ hg status
M README
? COPYING

M bedeutet geändert (modified), ? bedeutet wieder unbekannt. Wenn wir jetzt committen, protokolliert Mercurial nur die Änderung an README. Wir verwenden darum wiederum:

$ hg add
adding COPYING

und erhalten:

$ hg status
M README
A COPYING

Das committen wir:

$ hg commit --message "unter BSD-Liznz gestellt" 

und erhalten:

$ hg log
changeset:   1:13182acc9514
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     unter BSD-Liznz gestellt

changeset:   0:d8d194e06b45
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     erster Import
hg tip

Wir können auch nur den letzten Commit nochmals anschauen:

$ hg tip
changeset:   1:13182acc9514
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     unter BSD-Liznz gestellt

tip bedeutet in Mercurial immer den zuletzt dem Archiv zugefügten Commit. Wir können tip auch als Angabe für eine Revision verwenden. Folgendes ist demnach zu hg tip äquivalent:

$ hg log --rev tip
changeset:   1:13182acc9514
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     unter BSD-Liznz gestellt

Commit korrigieren

Hoppla. Schreibfehler im Commit-Kommentar: “Liznz”. Macht aber nix. Dies ist DVCS, und wir arbeiten ja immer noch in unserem lokalen, absolut privaten Archiv in .hg/. Da wir diese Arbeit noch mit niemandem geteilt haben – nicht mal mit der Backup-Kopie auf dem Share -, können wir gefahrlos den letzten Commit nochmals rückgängig machen.

Weil ich später etwas damit zeigen will, merken wir uns aber vorher kurz dieses unkorrigierte Archiv:

$ hg clone --noupdate . ../hgdemo-nofix
hg rollback

Jetzt machen wir rückgängig:

$ hg rollback
rolling back last transaction

und erhalten wieder die ursprüngliche Situation:

$ hg log
changeset:   0:d8d194e06b45
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     erster Import
$ hg status
M README
A COPYING

Darauf committen wir sauber:

$ hg commit --message "unter BSD-Lizenz gestellt" 
$ hg log
changeset:   1:dbca7b566804
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:45 2008 +0200
summary:     unter BSD-Lizenz gestellt

changeset:   0:d8d194e06b45
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     erster Import

Änderungen übertragen

hg outgoing

Auch der korrigierte Commit ist nun aber immer noch nur in unserem lokalen Archiv in .hg/. Im Backup-Archiv fehlt er noch. Das sehen wir so:

$ hg outgoing ../hgdemo-repo
comparing with ../hgdemo-repo
searching for changes
changeset:   1:dbca7b566804
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:45 2008 +0200
summary:     unter BSD-Lizenz gestellt
hg push

Wir übertragen ihn nun ins Backup-Archiv:

$ hg push ../hgdemo-repo
pushing to ../hgdemo-repo
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 2 changes to 2 files

(Analog dazu hätten wir auch mit hg pull from Backup-Archiv aus die Änderung rüberholen können.)

.hg/hgrc und [paths]default

Solche pushes werden wir noch oft tun, also wäre es simpler, wir könnten den Zielpfad fixieren. Das lässt sich pro Archiv festlegen in .hg/hgrc:

$ echo "[paths]" >>.hg/hgrc
$ echo "default = ../hgdemo-repo" >>.hg/hgrc

Damit klappt nun:

$ hg outgoing
comparing with /home/peo/dev/hg/workshop/tmp/out/hgdemo-repo
searching for changes
no changes found
$ hg push
pushing to /home/peo/dev/hg/workshop/tmp/out/hgdemo-repo
searching for changes
no changes found

Globale IDs

Wie erkennt Mercurial, welche Änderungen bei einem push noch übertragen werden müssen? Es verlässt sich dazu auf die globale ID der einzelnen Commits (auch node id, change id oder revision id). Diese ID ist ein kryptographisch guter Hash über folgende Informationen:

Jede Änderung an irgend einer dieser Angaben führt also dazu, dass Mercurial einen Commit als neu betrachtet. Das können wir konkret anhand des vorhin gesicherten Archivs mit dem unkorrigierten Schreibfehler im Commit-Text sehen:

$ hg incoming ../hgdemo-nofix
comparing with ../hgdemo-nofix
searching for changes
changeset:   1:13182acc9514
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     unter BSD-Liznz gestellt

Mercurial schlägt die von uns zuvor verworfene Fassung wieder vor. Dahinter steht ein zentrales Prinzip von Mercurial: die Geschichtsschreibung darf nicht unbemerkt manipuliert werden können. Und zur Geschichte gehören in Mercurial auch die Commit-Texte. Sobald ich also einen Commit einem anderen Archiv mitgeteilt habe, kann ich diesen nicht mehr verändern, ohne dass dies bemerkt wird.

Das geht noch weiter. Ich kann mit einer einzigen globalen ID dokumentieren, auf genau welchen Quelltextstand ich mich beziehe, inkl. seiner Geschichte. Das ist nicht fälschbar, solange der verwendete Hash-Algorithmus nicht angreifbar wird.

Die lokale Revisionsnummer (0, 1, ...) hingegen ist nur ein Hilfsmittel, damit man auf der Kommandozeile eine Version in einem bestimmten Archiv einfacher benennen kann. Derselbe Commit kann in verschiedenen Archiven aber verschiedene lokale Nummern erhalten.

Build-Identifikation

Wenn ich also in mein Programm einbetten will, aus welchem Stand es compiled wurde, reicht dazu die globale ID zum Compile-Zeitpunkt. Das deckt mit einer Angabe sämtliche versionierten Quellen ab und gilt in jedem der verteilten Archive. Darum ist es unter Mercurial auch nicht üblich, in den Quellen selbst die aktuelle Revisionsnummer etc. zu hinterlegen (es ist aber möglich mit der keywords extension).

Verwandt dazu ist in Mercurial auch das Code-Signing mit digitalen Signaturen. Da die globale ID sicher ist, unterschreibt man einfach diese ID.

hg id

Die globale ID erhalten wir mit:

$ hg identify --id
dbca7b566804

Wir automatisieren das entsprechend:

$ cat >build.sh <<-eof
    #! /bin/bash
    rm -rf temp; mkdir temp
    touch temp/__init__.py
    echo "VERSION = \"$(hg identify --id)\"" >temp/buildinfo.py
eof
$ chmod +x build.sh
$ ./build.sh

und verwenden die Version im Programm:

$ mv hgdemo.py hgdemo.py~
$ cat >hgdemo.prefix~ <<eof
from temp import buildinfo
print "Mercurial demo (version %s)" % buildinfo.VERSION
eof
$ cat hgdemo.prefix~ hgdemo.py~ >hgdemo.py
$ cat >hgdemo.sh <<-eof
    #! /bin/bash
    PYTHONPATH=. python hgdemo.py
eof
$ chmod +x hgdemo.sh
$ ./hgdemo.sh
Mercurial demo (version dbca7b566804)
The numbers from 1 to 10
1 2 3 4 5 6 7 8 9 10

Was haben wir geändert?

$ hg status
M hgdemo.py
? build.sh
? hgdemo.prefix~
? hgdemo.py~
? hgdemo.sh
? temp/__init__.py
? temp/__init__.pyc
? temp/buildinfo.py
? temp/buildinfo.pyc

Wenn wir jetzt hg add ausführen, wird Mercurial alle temporären Dateien auch aufnehmen. Das können wir umgehen mit:

$ hg add build.sh hgdemo.sh

Ausblenden von temporären Dateien

.hgignore

Aber eigentlich möchten wir Mercurial ein für alle Mal mitteilen, dass das temp-Verzeichnis nicht versioniert werden soll. Das tun wir in der pro Archiv zentralen .hgignore-Datei. Mercurial kennt hier zwei Formen: regular expressions (default) und globs. Details dazu erhält man mit man hgignore.

Wir blenden in diesem Beispiel temp (nicht aber z.B. xy/temp) und alle *~-Backupdateien aus:

$ cat >.hgignore <<-eof
    syntax: regexp
    ^temp/
    syntax: glob
    *~
eof

und erhalten:

$ cp hgdemo.py hgdemo.py~
$ hg status
M hgdemo.py
A build.sh
A hgdemo.sh
? .hgignore

Die Datei .hgignore wird auch versioniert. Damit sind die Ignore-Regeln in allen Kopien des Archivs verfügbar:

$ hg add .hgignore

Commits aufteilen

Diese Änderung umfasst mehrere Aspekte:

hg commit [file…]

Netterweise folgen die Grenzen der Aspekte auch Dateigrenzen. Somit können wir sie sauber einzeln committen. Wir geben dazu bei hg commit einfach die gewünschten Dateien mit (beachte, dass in bash die tab completion intelligent reagiert und nur geänderte Dateien vorschlägt):

$ hg status
M hgdemo.py
A .hgignore
A build.sh
A hgdemo.sh
$ hg commit --message ".hgignore für /temp und *~" .hgignore
$ hg status
M hgdemo.py
A build.sh
A hgdemo.sh
$ hg commit --message "build.sh für temp/buildinfo.py" build.sh
$ hg status
M hgdemo.py
A hgdemo.sh

Lange Commit-Texte

hg commit --logfile

Diese letzte Änderung nehmen wir als zusammengehörig an. Wir möchten sie aber gerne mit etwas mehr Text beschreiben als bisher. Rufen wir hg commit ohne --message auf, so started Mercurial den System-Editor zur Eingabe des Kommentars (oder den in der HGEDITOR-Variablen konfigurierten Editor). Die erste Zeile gilt dabei als Zusammenfassung, die in kompakten Logs isoliert angezeigt werden kann. Alternativ dazu kann man den Text aus einer Datei lesen lassen. Das tun wir hier:

$ cat >/tmp/my-commit-text <<-eof
    Version wird beim Start ausgegeben
    * hgdemo.py gibt die Version aus temp/buildinfo.py aus
    * hgdemo.sh führt hgdemo.py mit dem korrekten PYTHONPATH aus
eof
$ hg commit --logfile /tmp/my-commit-text

Standardmässig sehen wir nur die Zusammenfassung:

$ hg tip
changeset:   4:1837573691a3
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:46 2008 +0200
summary:     Version wird beim Start ausgegeben

Den gesamten Text erhalten wir so:

$ hg tip --template "{desc}\n" 
Version wird beim Start ausgegeben
* hgdemo.py gibt die Version aus temp/buildinfo.py aus
* hgdemo.sh führt hgdemo.py mit dem korrekten PYTHONPATH aus

Solche Templates werden wir gleich intensiver nutzen.

Output-Format von Logs ändern

Nun haben wir schon eine längliche Versionsgeschichte:

$ hg log
changeset:   4:1837573691a3
tag:         tip
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:46 2008 +0200
summary:     Version wird beim Start ausgegeben

changeset:   3:02ca4c15fb57
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:46 2008 +0200
summary:     build.sh für temp/buildinfo.py

changeset:   2:99d768161921
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:45 2008 +0200
summary:     .hgignore für /temp und *~

changeset:   1:dbca7b566804
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:45 2008 +0200
summary:     unter BSD-Lizenz gestellt

changeset:   0:d8d194e06b45
user:        Hans Müller <hans.mueller@example.com>
date:        Fri Aug 22 14:23:44 2008 +0200
summary:     erster Import
hg log --template

Mittels Templates lässt sich das kompakter darstellen:

$ hg log --template="{rev}\t{desc|firstline} - {author|user}\n" 
4    Version wird beim Start ausgegeben - hans
3    build.sh für temp/buildinfo.py - hans
2    .hgignore für /temp und *~ - hans
1    unter BSD-Lizenz gestellt - hans
0    erster Import - hans
Templater

Details zur Templater-Syntax findet man im Templater-Kapitel im mercurial book.

[ui]logtemplate

Ich persönlich finde das viel übersichtlicher und habe darum dieses Format als Standard etabliert:

$ echo "[ui]" >>~/.hgrc
$ echo "logtemplate = \"{rev}\t{desc|firstline} - {author|user}\n\"" >>~/.hgrc
$ hg log
4    Version wird beim Start ausgegeben - hans
3    build.sh für temp/buildinfo.py - hans
2    .hgignore für /temp und *~ - hans
1    unter BSD-Lizenz gestellt - hans
0    erster Import - hans

(In Wirklichkeit habe ich am Ende noch {date|age} drin. Das gibt noch das Alter in sinnvollen Grössenordnungen aus. Ist hier nicht aktiv, da es mühsam zum Diffen ist.)

[alias]

Dazu habe ich mir noch Aliase eingerichtet mit anderen Formaten und Limits. Dazu muss man aber zunächst mal die alias extension aktivieren:

$ echo "[extensions]" >>~/.hgrc
$ echo "hgext.alias =" >>~/.hgrc

Dann kann man Aliase einrichten:

$ echo "[alias]" >>~/.hgrc
$ echo "last = log --limit 15" >>~/.hgrc
$ echo "long = log --limit 15 --template=\"{rev}\t{desc|tabindent} - {author|user}\n\"" >>~/.hgrc
$ echo "revs = log --limit 15 --template=\"{rev}:{node|short} {desc|firstline} - {author|user}\n\"" >>~/.hgrc
$ hg last
4    Version wird beim Start ausgegeben - hans
3    build.sh für temp/buildinfo.py - hans
2    .hgignore für /temp und *~ - hans
1    unter BSD-Lizenz gestellt - hans
0    erster Import - hans
$ hg long
4    Version wird beim Start ausgegeben
    * hgdemo.py gibt die Version aus temp/buildinfo.py aus
    * hgdemo.sh führt hgdemo.py mit dem korrekten PYTHONPATH aus - hans
3    build.sh für temp/buildinfo.py - hans
2    .hgignore für /temp und *~ - hans
1    unter BSD-Lizenz gestellt - hans
0    erster Import - hans
$ hg revs
4:1837573691a3 Version wird beim Start ausgegeben - hans
3:02ca4c15fb57 build.sh für temp/buildinfo.py - hans
2:99d768161921 .hgignore für /temp und *~ - hans
1:dbca7b566804 unter BSD-Lizenz gestellt - hans
0:d8d194e06b45 erster Import - hans

Theorie: DVCS und die verbreitetsten Vertreter

Was ist DVCS?

Verbreitetste Vertreter

Wir branchen einen Release, machen Bugfixes und mergen diese zurück

Release branchen und stabilisieren

Branch für den Release:

$ hg clone . ../hgdemo-rel
updating working directory
6 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd ../hgdemo-rel

Testen, stabilisieren, etc.

$ ./build.sh
$ ./hgdemo.sh
Mercurial demo (version dbca7b566804)
The numbers from 1 to 10
1 2 3 4 5 6 7 8 9 10
$ sed -i hgdemo.py -e "s/to 10/to 10:/" 
$ ./hgdemo.sh
Mercurial demo (version dbca7b566804)
The numbers from 1 to 10:
1 2 3 4 5 6 7 8 9 10
$ hg commit --message "Nachricht korrigiert" 
hg tag

Release ist ok:

$ hg tag 0.1
$ hg tip
6    Added tag 0.1 for changeset d4f21b377ff6 - hans

Release ziehen:

$ ./build.sh
$ ./hgdemo.sh
Mercurial demo (version dbca7b566804)
The numbers from 1 to 10:
1 2 3 4 5 6 7 8 9 10

Entwicklung läuft weiter

$ cd ../hgdemo
$ echo "print \"So cool!\"" >>hgdemo.py
$ ./hgdemo.sh
Mercurial demo (version dbca7b566804)
The numbers from 1 to 10
1 2 3 4 5 6 7 8 9 10
So cool!
$ hg commit --message "So cool!" 

Bugfix aus Release mergen

hg pull

Bugfix abholen:

$ hg pull ../hgdemo-rel
pulling from ../hgdemo-rel
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)

Der Branch ist nun in unserem Archiv enthalten:

$ hg heads
7    Added tag 0.1 for changeset d4f21b377ff6 - hans
5    So cool! - hans

Glog:

$ echo "[extensions]" >>~/.hgrc
$ echo "hgext.graphlog =" >>~/.hgrc
$ hg glog
o  7    Added tag 0.1 for changeset d4f21b377ff6 - hans
|
o  6    Nachricht korrigiert - hans
|
| @  5    So cool! - hans
|/
o  4    Version wird beim Start ausgegeben - hans
|
o  3    build.sh für temp/buildinfo.py - hans
|
o  2    .hgignore für /temp und *~ - hans
|
o  1    unter BSD-Lizenz gestellt - hans
|
o  0    erster Import - hans

Merge:

$ hg merge
merging hgdemo.py
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

Testen:

$ ./hgdemo.sh
Mercurial demo (version dbca7b566804)
The numbers from 1 to 10:
1 2 3 4 5 6 7 8 9 10
So cool!

Wenn OK, commit:

$ hg commit --message "Merge von Version 0.1" 
$ hg glog
@    8    Merge von Version 0.1 - hans
|\
| o  7    Added tag 0.1 for changeset d4f21b377ff6 - hans
| |
| o  6    Nachricht korrigiert - hans
| |
o |  5    So cool! - hans
|/
o  4    Version wird beim Start ausgegeben - hans
|
o  3    build.sh für temp/buildinfo.py - hans
|
o  2    .hgignore für /temp und *~ - hans
|
o  1    unter BSD-Lizenz gestellt - hans
|
o  0    erster Import - hans

Wir schauen uns das in einigen Mercurial-GUIs an

Theorie: Wie funktioniert das Merge-Tracking bei Mercurial?

TODO

Rest-Programm

Wenn die Zeit reicht:

Ideen: