Die Community zu .NET und Classic VB.
Menü

Versionsverwaltung mit Mercurial (Teil 1)

 von 

Einleitung 

Wird von der Entwicklung von (grossen) Softwareprojekten gesprochen, fällt oft das Wort Versionsverwaltung, oder auch SCM (software configuration management). Diese Kurzeinführung soll zeigen, dass sich solche Systeme durchaus auch für Kleinprojekte eignen und das Leben des Softwareentwicklers erheblich erleichtern können.

Werkzeuge für diesen Zweck gibt es viele, der Fokus dieses Tutorials liegt jedoch auf dem Tool Mercurial (dt. Quecksilber mit dem chemischen Formelzeichen Hg). Alternativen wären beispielsweise:

  • CVS (concurrent versions system)
  • SVN (Subversion)
  • Git
  • Bazaar

Dieses Tutorial ist in drei Teile gegliedert:

  • Teil 1 behandelt die grundlegenden Funktionen von Mercurial.
  • Teil 2 geht auf die Arbeit mit mehreren Repositories ein.
  • Teil 3 stellt weitere nützliche Funktionen vor.

Viel Spass beim Lesen wünscht

Philipp Burch

Zweck der Versionsverwaltung  

Als Erstes sollte geklärt werden, wozu eine Versionsverwaltung dient. Dazu soll ein einfaches Beispiel dienen, wie es bestimmt die meisten Entwickler bereits erlebt haben:

Man entwickelt ein Programm, führt verschiedenste Tests durch und wird es früher oder später produktiv einsetzen. Während des Einsatzes zeigen sich die Schwächen des Programms und man schreibt ein entsprechendes Update. Nach einiger Zeit merkt man, dass das Programm seit der Aktualisierung bei bestimmten Operationen ein unerwartetes Verhalten zeigt. Vor dem Update war das noch in Ordnung! Was nun? Die Version von vor dem Update existiert natürlich nicht mehr.

Eine Lösung für dieses Problem wäre natürlich, man hätte vor dem Ändern des Codes ein Backup desselben abgelegt. Doch wer denkt schon daran? "Es ist ja nur eine kleine Änderung."

Genau hier kommt nun die Versionsverwaltung ins Spiel: Es sollte möglich sein, nach jeder in sich abgeschlossenen Änderung am Programmcode die aktuelle Version zu speichern und für späteren Zugriff zu archivieren. Eben diese Archivierung einer Version ist eine der Kernaufgaben eines Versionsverwaltungssystems.

Installation  

Die aktuellste Version von Mercurial kann unter folgendem Link heruntergeladen werden:

mercurial.selenic.com

Es sind kompilierte Pakete für Linux, Mac OSX und Windows verfügbar, ebenso wie der in Python geschriebene Quellcode. Linuxbenutzer finden mit grosser Wahrscheinlichkeit auch ein "mercurial" Paket in der Paketverwaltung ihrer Distribution.

Kommandozeilenversion  

Um vollen Zugriff auf alle Möglichkeiten von Mercurial zu erhalten, sollte die Bedienung in der Kommandozeile (CLI, Terminal, Konsole, Eingabeaufforderung, cmd) vorgenommen werden. Dieses Tutorial verweist jeweils auf die Befehle wie sie in der Kommandozeile eingegeben werden müssen. Grafische Tools verwenden in der Regel Menüpunkte mit identischen oder zumindest sehr ähnlichen Bezeichnungen.

Grundlagen

Die Beispiele in diesem Tutorial sind in einer Bash-shell unter Ubuntu (Linux) erstellt worden. Die Syntax mag sich nicht jedem Leser auf den ersten Blick erschliessen, daher soll hier ein kleiner Überblick über die hier oft verwendeten Kommandos gegeben werden:

tutorial$ echo "test" > test.txt

"tutorial" repräsentiert das aktuelle Verzeichnis. Dies ist zur Orientierung, damit bei Verwendung von mehreren Ordnern klar ist, in welchem Verzeichnis der präsentierte Befehl ausgeführt wird. Das Dollarzeichen ($) markiert den Anfang der Befehlseingabe. Alles bis und mit dem Dollarzeichen darf nicht eingegeben werden.

"echo" gibt einfach das folgende Argument wieder aus. Das Grösser-als-Zeichen (>) leitet eben diese Ausgabe in die Datei "test.txt" weiter. Nach diesem Befehl ist also eine Datei "test.txt" im aktuellen Verzeichnis "tutorial" mit dem Inhalt "test" vorhanden.

tutorial$ cat test.txt
test

"cat" gibt den Inhalt einer Datei aus. Alternativ kann mit cat auch in eine Datei geschrieben werden:

tutorial$ cat > test.txt
Eine Datei mit einer
und noch einer Zeile

Die Eingabe muss mit EOF (End of file, Ctrl-D) abgeschlossen werden. Der Vorteil gegenüber "echo" ist, dass damit auch problemlos mehrere Zeilen geschrieben werden können. Zuletzt gibt es noch den Operator >> als Alternative zu >, welcher die Ausgabe des vorangegangenen Befehls an das Ende der Datei anhängt, anstatt diese zu überschreiben.

Installation prüfen

Zum Test, ob Mercurial korrekt installiert ist, kann folgende Eingabe getätigt werden:

$ hg version
Mercurial Distributed SCM (version 1.9.1)
(see http://mercurial.selenic.com for more information)

Copyright (C) 2005-2011 Matt Mackall and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Sollte statt der Versionsinformation eine Fehlermeldung angezeigt werden, dass hg nicht gefunden wurde, dann ist die Installation noch nicht vollständig. Unter Windows ist die häufigste Ursache dafür, dass der Pfad zur ausführbaren hg.exe nicht in der PATH-Umgebungsvariablen eingetragen ist.

Grafische Werkzeuge  

TortoiseHg

TortoiseHg ist eine bekannte graphische Benutzeroberfläche für Mercurial. Es läuft ebenfalls unter Windows, Mac OSX und Linux, wird aber primär unter Windows eingesetzt. Einmal installiert, stehen die Funktionen des Programms direkt im Kontextmenü des Windows-Explorers zur Verfügung.

Integration in Entwicklungsumgebungen

Viele Entwicklungsumgebungen wie beispielsweise Netbeans oder Eclipse bringen bereits Unterstützung für Mercurial mit. Für Visual Studio ist VisualHG und HgSccPackage verfügbar.

Konfiguration: .hg/hgrc  

Mercurial kann mit vielen Einstellungen konfiguriert werden. Dafür werden verschiedene Konfigurationsdateien verwendet, üblicherweise hgrc genannt. Eine detaillierte Beschreibung der Dateistruktur und möglicher Einstellungen kann in der hgrc-manpage nachgelesen werden. Die wichtigsten Optionen werden in diesem Tutorial in den passenden Abschnitten vorgestellt.

Wenn es klemmt: hg help  

Die Verwendung von Mercurial ist nicht kompliziert, doch es kommt vor, dass man die Funktionsweise eines Befehls gerade nicht präsent hat. In diesem Fall hilft oft die eingebaute Hilfe von Mercurial, welche mittels

$ hg help

aufgerufen werden kann. Für nähere Informationen zu einem Befehl kann dieser angehängt werden, beispielsweise:

$ hg help init

Abkürzungen  

Die Befehle von Mercurial haben in der Regel kurze, einprägsame Namen. Trotzdem ist es nett, nicht jedesmal den ganzen Befehl ausschreiben zu müssen. Daher reicht es, so viele Zeichen einzugeben, dass die Auswahl eindeutig wird:

$ hg he
hg: command 'he' is ambiguous:
    heads help
$ hg hel
Mercurial Distributed SCM

list of commands:

 add          add the specified files on the next commit
 addremove    add all new files, delete all missing files
 annotate     show changeset information by line for each file

# ...
# usw.

Begrifflichkeiten  

Bevor es losgeht sollen hier noch einige Begriffe kurz erklärt werden, die im weiteren Verlauf des Tutorials oft verwendet werden. Weitere Informationen dazu finden sich im Mercurial-eigenen Glossar, abrufbar mittels

test$ hg help glossary

Repository

Als Repository, abgekürzt auch Repo wird der Speicher der Versionshistorie eines Arbeitsverzeichnisses bezeichnet. In Mercurial ist diese Ablage üblicherweise der .hg-Ordner innerhalb des Arbeitsverzeichnisses.

Arbeitsverzeichnis

Dieses Verzeichnis enthält die in einem Repository verwalteten Dateien in einer bestimmten Version. Es enthält damit jene Dateien, mit welchen gearbeitet wird, daher der Name.

Einchecken, committen

Ist eine Änderung durchgeführt, so muss diese in das Repo übernommen werden. Diesen Vorgang bezeichnet man als einchecken oder auch commit.

Auschecken, updaten

Das Gegenstück zum Einchecken ist das Auschecken. Bei diesem Vorgang werden in einem Repo gespeicherte Änderungen in das Arbeitsverzeichnis übernommen.

Erstellen eines Repositories: hg init  

Kommen wir also zur Sache. Der erste wichtige Befehl ist

test$ hg init   # "test" ist das aktuelle Verzeichnis

Und es passiert... nichts. Zumindest auf den ersten Blick. hg init erzeugt ein neues Repository im aktuellen Verzeichnis. Das Repository (auch als "Repo" abgekürzt) wird in Mercurial durch den Ordner mit dem Namen ".hg" repräsentiert. Unixoide Betriebssysteme behandeln grundsätzlich alle Datei- und Verzeichnisnamen als versteckt, wenn sie mit einem Punkt beginnen. Unter Windows wird das "versteckt"-Attribut gesetzt. Anzeigen lässt sich das Verzeichnis aber natürlich dennnoch:

test$ ls -al
total 12
drwxrwxr-x 3 phip phip 4096 2012-01-19 15:01 .
drwxr-xr-x 4 phip phip 4096 2012-01-19 15:01 ..
drwxrwxr-x 3 phip phip 4096 2012-01-19 15:01 .hg

Warum diese ganze Geheimniskrämerei? Ganz einfach: Im Normalfall sollte man es tunlichst vermeiden, in diesem Verzeichnis irgendwas zu verändern. Die Ausnahme dieser Regel ist die "hgrc"-Datei, welche zur Konfiguration des Repositories dient und etwas später vorgestellt wird.

An dieser Stelle soll noch kurz auf die Tragweite dieses Befehls eingegangen werden: Das Repo erstreckt sich über das Verzeichnis mit dem .hg-Ordner und sämtliche Unterverzeichnisse. Es ist also kein Problem, innerhalb des Wurzelverzeichnisses weitere Unterordner zu erstellen. Im Gegensatz zu beispielsweise Subversion hält Mercurial alle Informationen über ein Repo in seinem Wurzelverzeichnis. Es ist also absolut normal, dass nachträglich erstellte Unterverzeichnisse keine .hg-Ordner mehr enthalten.

Dateien hinzufügen: hg status; hg add  

Im letzten Abschnitt haben wir ein leeres Repository erstellt und initialisiert. Nun sollen hier natürlich auch Dateien eingefügt werden. Dabei gibt es einen wichtigen Punkt zu beachten: Ein Repo besteht primär aus den Daten im .hg-Verzeichnis, also aus der Versionshistorie und weiteren Mercurial-eigenen Informationen. Daneben gibt es eine "Working copy", also eine Arbeitskopie, in welcher die vollständigen Daten in einer bestimmten Version vorliegen. Dieses Arbeitsverzeichnis besteht aus allen anderen Dateien und Verzeichnissen neben dem .hg-Ordner.

Wenn wir nun also mit der Arbeit beginnen und eine Datei erzeugen

test$ echo "ActiveVB" > avb.txt
test$ ls -l
total 12
-rw-rw-r-- 1 phip phip 9 2012-01-19 15:40 avb.txt

dann weiss Mercurial eigentlich noch überhaupt nichts davon. Dieser Zustand lässt sich auch anzeigen:

test$ hg status
? avb.txt

Das Fragezeichen (?) zeigt an, dass in der Arbeitskopie eine unversionierte Datei vorhanden ist. Um Mercurial mitzuteilen, dass es diese Datei bitte auch in die Versionierung aufnehmen soll, müssen wir sie explizit hinzufügen:

test$ hg add avb.txt 
test$ hg status
A avb.txt

Nun zeigt Mercurial A als Status der Datei an. Das bedeutet, dass sie beim nächsten Commit (siehe nächster Abschnitt) im aktuellen Zustand versioniert werden soll.

Änderungen (lokal) übernehmen: hg commit  

Bis jetzt ist Mercurial informiert, dass wir eine Datei "avb.txt" erstellt haben, welche in das Repository aufgenommen werden soll. Es existiert jedoch nach wie vor keine Version, welche diese Datei tatsächlich enthält. Dies lässt sich mit

test$ hg commit -m 'Testdatei erstellt.'
test$ hg status

ändern. hg commit übernimmt alle offenen Änderungen wie sie von hg status aufgeführt werden und speichert sie als neue Version ab. Nach diesem Befehl entspricht das Arbeitsverzeichnis wieder der aktuellen Version, daher liefert hg status eine leere Liste.

Die Option -m weist den Befehl an, das nächste Argument als Commit-Message zu verwenden. Diese Mitteilung dient als kurze Beschreibung, was geändert worden ist. Wird die Option weggelassen, wird der konfigurierte Editor gestartet um eine Mitteilung eingeben zu können.

Bei einer neuen Installation von Mercurial kann es vorkommen, dass bei hg commit ein Fehler ausgegeben wird, weil kein Benutzername angegeben wurde:

test$ hg commit
abort: no username supplied (see "hg help config")

Der Name ist jedoch erforderlich, um den Urheber einer Änderung anzuzeigen (siehe hg log). Es wäre nun möglich, den Benutzer bei jedem Commit explizit mit der Option -u zu spezifizieren:

test$ hg commit -u phip

Das ist jedoch relativ mühsam und kann dazu führen, dass der gleiche Benutzer nicht immer genau gleich benannt ist. Einfacher ist es, den Benutzernamen in einer der eingangs erwähnten hgrc-Konfigurationsdateien zu speichern. Dazu kann zum Beispiel die Repository-eigene Datei verwendet werden, welche im .hg-Ordner abgelegt ist. Sollte sie noch nicht existieren, kann sie einfach erzeugt werden. Wichtig: Der Dateiname muss exakt hgrc lauten, ohne Dateierweiterung. Um nun also einen Benutzernamen "phip" zu speichern, sollte der Dateiinhalt so aussehen:

test$ cat > .hg/hgrc 
[ui]
username = phip

Neben .hg/hgrc gibt es noch weitere Ablageorte für hgrc-Dateien um systemweite Konfigurationen vorzunehmen. Weitere Informationen dazu finden sich in der Hilfe zu hgrc:

test$ hg help hgrc

Dateien entfernen: hg remove  

Während der (Software-)entwicklung kommt es auch vor, dass Dateien von einem Projekt entfernt werden müssen. Dies lässt sich, ganz intuitiv, mittels hg remove bewerkstelligen:

test$ hg remove avb.txt 
test$ hg status
R avb.txt
test$ ls -l
total 0

Der Befehl entfernt die Datei im Arbeitsverzeichnis und markiert sie als entfernt (R) für einen kommenden commit-Vorgang.

test$ hg commit -m 'Testdatei entfernt.'
test$ hg status
test$ ls -l
total 0

Bei hg add wurde das Fragezeichen als Markierung von noch nicht versionierten Dateien erwähnt. Das Gegenstück dazu ist das Ausrufezeichen für versionierte Dateien, welche nicht mehr vorhanden sind. Dazu benötigen wir erst eine Datei:

test$ echo "abc" > test.txt
test$ hg add test.txt 
test$ hg commit -m 'Weitere Testdatei erstellt.'
test$ ls -l
total 12
-rw-rw-r-- 1 phip phip 4 2012-01-19 17:12 test.txt

Um sie auch gleich wieder zu entfernen:

test$ rm test.txt 
test$ hg status
! test.txt

Die Datei muss nachträglich noch mittels hg remove explizit entfernt werden, damit eine Änderung mittels hg commit eingecheckt werden kann:

test$ hg commit -m 'Datei wieder entfernt.'
nothing changed (1 missing files, see 'hg status')
test$ hg remove test.txt 
test$ hg commit -m 'Datei wieder entfernt.'
test$ hg status

Hinzufügen und entfernen kombinieren: hg addremove  

Jede Datei nach dem Erstellen/Entfernen einzeln zu bearbeiten mag etwas mühsam erscheinen. Das ist es auch, daher existiert der Befehl addremove, welcher, wie der Name unschwer vermuten lässt, eine Kombination aus add und remove darstellt. Dennnoch ist er mehr als das, wie die Hilfe dazu zeigt:

test$ hg help addremove
hg addremove [OPTION]... [FILE]...

add all new files, delete all missing files

    Add all new files and remove all missing files from the repository.

    New files are ignored if they match any of the patterns in ".hgignore". As
    with add, these changes take effect at the next commit.

    Use the -s/--similarity option to detect renamed files. With a parameter
    greater than 0, this compares every removed file with every added file and
    records those similar enough as renames. This option takes a percentage
    between 0 (disabled) and 100 (files must be identical) as its parameter.
    Detecting renamed files this way can be expensive. After using this
    option, "hg status -C" can be used to check which files were identified as
    moved or renamed.

    Returns 0 if all files are successfully added.

options:

 -s --similarity SIMILARITY  guess renamed files by similarity (0<=s<=100)
 -I --include PATTERN [+]    include names matching the given patterns
 -X --exclude PATTERN [+]    exclude names matching the given patterns
 -n --dry-run                do not perform actions, just print output

[+] marked option can be specified multiple times

use "hg -v help addremove" to show global options

Die einfachste Variante des Befehls kommt ohne jegliche Argumente aus:

test$ echo "Datei1" > datei1.txt
test$ echo "Datei2" > datei2.txt
test$ hg status
? datei1.txt
? datei2.txt
test$ hg addremove
adding datei1.txt
adding datei2.txt
test$ hg status
A datei1.txt
A datei2.txt
test$ hg commit -m 'Zwei neue Dateien.'

Beim Entfernen von Dateien verhält sich der Befehl genau gleich, alle verschwundenen (!) Files werden entsprechend aus dem Repo verbannt:

test$ rm datei1.txt 
test$ hg status
! datei1.txt
test$ hg addremove
removing datei1.txt
test$ hg status
R datei1.txt
test$ hg commit -m 'Und eine wieder weg.'

Die Funktion ist damit besonders nützlich, wenn ganze Ordner(-strukturen) eingefügt oder gelöscht werden.

hg addremove kann aber noch mehr: Mercurial kennt die Befehle hg copy und hg rename um Dateien zu kopieren, bzw. zu verschieben/umzubenennen. Dabei wird die Revision auch als solche Aktion gespeichert, nicht als entfernen und neu erstellen desselben Dateiinhalts. Doch weil ein einfaches cp (kopieren), bzw. mv (umbenennen) schlicht viel schneller ist, kann sich addremove auch darum kümmern:

test$ mv datei2.txt neuer_name.txt
test$ hg status
! datei2.txt
? neuer_name.txt
test$ hg addremove
removing datei2.txt
adding neuer_name.txt
recording removal of datei2.txt as rename to neuer_name.txt (100% similar)
test$ hg status
A neuer_name.txt
R datei2.txt
test$ hg commit -m 'Umbenannt.'

Wie der Auszug der Hilfe etwas weiter oben zeigt, kann das Verhalten von hg addremove mit weiteren Argumenten gesteuert werden. Insbesondere können anstelle von allen Dateien im Repository nur solche gewählt werden, die in ein gegebenes Muster passen. Weiterhin ist es möglich, Abkürzungen nicht nur für identische, sondern auch für ähnliche Dateien zu speichern. Für nähere Informationen zu diesen Optionen sei hierbei auf die oben dargestellte Hilfe verwiesen.

Änderungsliste anzeigen: hg log  

Nachdem nun einige Änderungen an einem Repository vorgenommen wurden, kann man sich diese mehr oder weniger detailliert anzeigen lassen. Für eine einfache Übersicht mit allen Revisionen genügt:

test$ hg log
changeset:   6:781433f769b3
tag:         tip
user:        phip
date:        Sun Jan 22 17:14:20 2012 +0100
summary:     Umbenannt.

changeset:   5:ca85e2bc86ea
user:        phip
date:        Sun Jan 22 17:07:40 2012 +0100
summary:     Und eine wieder weg.

changeset:   4:030e26631791
user:        phip
date:        Sun Jan 22 17:06:53 2012 +0100
summary:     Zwei neue Dateien.

# Weitere Zeilen entfernt

Für jede Revision werden folgende Informationen angezeigt:

  • changeset : Die Nummer (vor dem Doppelpunkt) und der Hash der Revision.
  • tag : Bezeichnung der Revision, falls vorhanden.
  • user : Name des Benutzers, welcher die Revision eingecheckt hat.
  • date : Datum und Uhrzeit der Änderung.
  • summary : Erste Zeile der Commit-Mitteilung, wie nach dem -m angegeben.

Einige Worte zu changeset: Mercurial verwendet die Bezeichnung changeset für einen Satz von Änderungen zwischen zwei Revisionen des Repositories. Jedes changeset hat sowohl eine Nummer, wie auch einen Hash. Dabei ist zu beachten, dass nur der Hash zwischen verschiedenen Klonen des Repositories (siehe unten) eindeutig ist.

Auffallend ist bei der Ausgabe, dass die letzte Änderung zuoberst ausgegeben wird. Bei wenigen Einträgen ist das nicht weiter problematisch, doch ab einer bestimmten Länge der Liste passt die Ausgabe nicht mehr in den Puffer des Terminals und die obersten (neusten) Einträge werden nicht mehr angezeigt. Abhilfe lässt sich mit der Option -r schaffen:

test$ hg log -r 0:
changeset:   0:088f46fcb897
user:        phip
date:        Thu Jan 19 17:02:24 2012 +0100
summary:     Testdatei erstellt.

changeset:   1:975ad67fcaf4
user:        phip
date:        Thu Jan 19 17:04:06 2012 +0100
summary:     Testdatei entfernt.

# Einige Einträge dazwischen entfernt

changeset:   5:ca85e2bc86ea
user:        phip
date:        Sun Jan 22 17:07:40 2012 +0100
summary:     Und eine wieder weg.

changeset:   6:781433f769b3
tag:         tip
user:        phip
date:        Sun Jan 22 17:14:20 2012 +0100
summary:     Umbenannt.

Die Option -r erwartet als nächstes Argument eine Liste von Revisionen, welche ausgegeben werden sollen. Die Angabe 0: in diesem Beispiel bedeutet, dass alle Revisionen von 0 (erste Änderung) bis tip (letzte Änderung) angezeigt werden sollen. Die Reihenfolge der Angaben bestimmt dabei auch die Reihenfolge der Ausgaben, somit würde hg log -r tip:0 wieder die ursprüngliche Ausgabe erzeugen. Anstelle von tip und 0 lassen sich natürlich auch beliebige Revisionsnummern dazwischen angeben um den Bereich einzuschränken:

test$ hg log -r 5:tip
changeset:   5:ca85e2bc86ea
user:        phip
date:        Sun Jan 22 17:07:40 2012 +0100
summary:     Und eine wieder weg.

changeset:   6:781433f769b3
tag:         tip
user:        phip
date:        Sun Jan 22 17:14:20 2012 +0100
summary:     Umbenannt.

hg log kennt noch eine Reihe weiterer Optionen, eine davon ist -v, womit detailliertere Informationen zu jeder Änderung angezeigt werden können. Einerseits werden damit in jedem Eintrag auch die veränderten Dateien aufgelistet, andererseits wird jeweils die komplette Commitmessage ausgegeben, nicht nur die erste Zeile davon:

test$ hg log -v -r 5:
changeset:   5:ca85e2bc86ea
user:        phip
date:        Sun Jan 22 17:07:40 2012 +0100
files:       datei1.txt
description:
Und eine wieder weg.


changeset:   6:781433f769b3
tag:         tip
user:        phip
date:        Sun Jan 22 17:14:20 2012 +0100
files:       datei2.txt neuer_name.txt
description:
Umbenannt.

Für eine Erklärung der weiteren Optionen soll auch hier auf die (übrigens sehr umfangreiche) Hilfe verwiesen werden. Neben hg help log geben die Befehle hg help revisions und hg help revsets Aufschluss darüber, wie Listen von Revisionen (-r-Option) angegeben werden können.

Zusätzlich soll an dieser Stelle noch die Erweiterung (siehe weiter unten) graphlog erwähnt werden, welche zusätzlich zu den einzelnen changesets auch den Revisionsgraphen anzeigen kann.

Änderungen zwischen Versionen anzeigen: hg diff  

Nachdem wir nun eine Liste von Änderungen in einem Repo anzeigen lassen können, wäre es natürlich auch wünschenswert, die exakten Unterschiede zwischen zwei Revisionen sehen zu können. Dazu lässt sich hg diff verwenden, welcher als Ausgabe die Differenz im unified diff-Format liefert. In der einfachsten Form zeigt der Befehl die Änderungen der Arbeitskopie gegenüber dem Repository an:

test$ hg diff
test$ echo "Neue Zeile" >> neuer_name.txt 
test$ cat neuer_name.txt 
Datei2
Neue Zeile
test$ hg diff
diff -r 781433f769b3 neuer_name.txt
--- a/neuer_name.txt    Sun Jan 22 17:14:20 2012 +0100
+++ b/neuer_name.txt    Mon Jan 23 06:12:56 2012 +0100
@@ -1,1 +1,2 @@
 Datei2
+Neue Zeile

Ist die Arbeitskopie aktuell, liefert hg diff keine Ausgabe wie in der ersten Zeile zu sehen ist.

Um die Änderungen einer bestimmten Revision zu sehen, kann das Argument -c zusammen mit einer Revisionsnummer verwendet werden:

test$ hg commit -m 'Zeile ergänzt.'
test$ cat > andere_datei.txt
Andere Datei
Neue Zeile
test$ hg add andere_datei.txt 
test$ hg commit -m 'Andere Datei erstellt.'
test$ hg diff -c 7
diff -r 781433f769b3 -r 204c5be58c52 neuer_name.txt
--- a/neuer_name.txt    Sun Jan 22 17:14:20 2012 +0100
+++ b/neuer_name.txt    Mon Jan 23 06:27:10 2012 +0100
@@ -1,1 +1,2 @@
 Datei2
+Neue Zeile

Zusätzlich ist es auch möglich, die Änderungen zwischen zwei weiter entfernten Revisionen anzuzeigen:

test$ hg log -r 5:
changeset:   5:ca85e2bc86ea
user:        phip
date:        Sun Jan 22 17:07:40 2012 +0100
summary:     Und eine wieder weg.

changeset:   6:781433f769b3
user:        phip
date:        Sun Jan 22 17:14:20 2012 +0100
summary:     Umbenannt.

changeset:   7:204c5be58c52
user:        phip
date:        Mon Jan 23 06:27:10 2012 +0100
summary:     Zeile ergänzt.

changeset:   8:614d933bb214
tag:         tip
user:        phip
date:        Mon Jan 23 06:30:22 2012 +0100
summary:     Andere Datei erstellt.

test$ hg diff -r 5 -r 7
diff -r ca85e2bc86ea -r 204c5be58c52 datei2.txt
--- a/datei2.txt    Sun Jan 22 17:07:40 2012 +0100
+++ /dev/null    Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-Datei2
diff -r ca85e2bc86ea -r 204c5be58c52 neuer_name.txt
--- /dev/null    Thu Jan 01 00:00:00 1970 +0000
+++ b/neuer_name.txt    Mon Jan 23 06:27:10 2012 +0100
@@ -0,0 +1,2 @@
+Datei2
+Neue Zeile

Angemerkt sei an dieser Stelle, dass die Ausgabe von hg diff problemlos auch an andere Tools weitergeleitet werden kann um sie etwas schöner darzustellen. Beispielsweise colordiff zum Einfärben der veränderten Zeilen:

test$ hg diff -r 5 -r 7 | cdiff

Alte Versionen zurückholen: hg update; hg revert  

Bis jetzt tätigten wir alle Änderungen an der Arbeitskopie nur in "Vorwärtsrichtung", wir haben immer die aktuelle Version verwendet und daran gearbeitet. Selbstverständlich ist es jedoch jederzeit möglich, die Arbeitskopie auf eine ältere Revision zu setzen. Dazu dient hg update mit Angabe der entsprechenden Zielrevision:

test$ ls -l
total 24
-rw-rw-r-- 1 phip phip 24 2012-01-23 06:30 andere_datei.txt
-rw-rw-r-- 1 phip phip 18 2012-01-23 06:12 neuer_name.txt
test$ hg update -r 4
2 files updated, 0 files merged, 2 files removed, 0 files unresolved
test$ ls -l
total 24
-rw-rw-r-- 1 phip phip 7 2012-01-23 18:39 datei1.txt
-rw-rw-r-- 1 phip phip 7 2012-01-23 18:39 datei2.txt
test$ hg status

Die Arbeitskopie ist nun auf Revision 4 "aktualisiert" und man kann wie gewohnt damit arbeiten. Dabei sollte jedoch Folgendes beachtet werden: Wird an einer alten Revision etwas geändert und eingecheckt, dann ist die Versionshistorie nicht mehr linear. Konkret wird eine neue Kopfversion (ein head) erzeugt, welche später typischerweise wieder mit der bisherigen Kopfversion verschmolzen (merged) werden muss. Mehr zu diesem Thema wird im Abschnitt hg merge behandelt, wenn konkurrenzierende Zugriffe diskutiert werden.

Um wieder zur Kopfversion (tip) zurückzukehren, genügt ein Aufruf von hg update ohne Argument:

test$ hg update
2 files updated, 0 files merged, 2 files removed, 0 files unresolved
test$ ls -l
total 24
-rw-rw-r-- 1 phip phip 24 2012-01-23 19:05 andere_datei.txt
-rw-rw-r-- 1 phip phip 18 2012-01-23 19:05 neuer_name.txt

Dass man ein ganzes Repo auf einen alten Stand zurücksetzen will, kommt eher selten vor. Öfters sollen Versionen von einzelnen Dateien zurückgeholt werden. Dies funktioniert nicht mit hg update, sondern mit hg revert:

test$ cat neuer_name.txt 
Datei2
Neue Zeile
test$ hg revert -r 6 neuer_name.txt 
test$ cat neuer_name.txt 
Datei2
test$ hg status
M neuer_name.txt

Hier ist ein wesentlicher Unterschied zu hg update zu beobachten: Wird die ganze Arbeitskopie auf eine ältere Revision zurückgesetzt, gibt es keine offenen Änderungen. Genauer gesagt dürfen vor einem hg update in der Regel auch keine offenen Änderungen vorhanden sein. Bei hg revert ist das anders: Dabei wird ausschliesslich der Dateiinhalt durch die alte Version ersetzt und muss (nach etwaigen Änderungen) eingecheckt werden. Daher wird neuer_name.txt im Beispiel oben nach dem Zurücksetzen als M odifiziert ausgegeben.

Ein anderer Anwendungsfall für hg revert ergibt sich auch während der ganz normalen Entwicklung: Angenommen, man hat seine Software auf einem bestimmten Stand und diesen als Revision eingecheckt. Nun will man auf dieser Basis einen Test für eine zusätzliche Funktion durchführen, allerdings mit der Option, bei einem Fehlschlag wieder auf die ursprüngliche Version zurückzukehren. Dies geht dann sehr einfach:

test$ hg status
M neuer_name.txt
test$ cat neuer_name.txt 
Datei2
test$ hg revert neuer_name.txt 
test$ cat neuer_name.txt
Datei2
Neue Zeile
test$ hg status
? neuer_name.txt.orig
test$ rm neuer_name.txt.orig 

hg revert ohne Angabe einer Version setzt eine veränderte Datei (oder alle Dateien im Repo mit der Option --all) auf die aktuelle Revision des Repositories zurück. Dabei werden standardmässig Backupkopien mit der Endung .orig angelegt, ausser bei Verwendung der Option --no-backup.

Ausblick  

Bis zu dieser Stelle wurde die Arbeit mit einem einzelnen Repo erklärt. Die wahre Stärke von Versionsverwaltungssystemen zeigt sich jedoch erst beim Zusammenspiel von verschiedenen Personen, welche (gleichzeitig) an den selben Daten arbeiten. Wie dabei vorgegangen werden kann, zeigt Teil 2 des Tutorials.

Ihre Meinung  

Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.