Versionsverwaltung mit Mercurial (Teil 1)
von Philipp Burch
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
Inhalt
- Zweck der Versionsverwaltung
- Installation
- Kommandozeilenversion
- Grafische Werkzeuge
- Konfiguration: .hg/hgrc
- Wenn es klemmt: hg help
- Abkürzungen
- Begrifflichkeiten
- Erstellen eines Repositories: hg init
- Dateien hinzufügen: hg status; hg add
- Änderungen (lokal) übernehmen: hg commit
- Dateien entfernen: hg remove
- Hinzufügen und entfernen kombinieren: hg addremove
- Änderungsliste anzeigen: hg log
- Änderungen zwischen Versionen anzeigen: hg diff
- Alte Versionen zurückholen: hg update; hg revert
- Ausblick
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:
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.