Mit vereinten Kräften

Mercurial – verteilte Versionskontrolle leicht gemacht
Kommentare

Verteilte Versionskontrollsysteme sind eine der wichtigsten Tools, die Entwicklern heutzutage zur Verfügung stehen. Eines der modernsten Systeme dieser Art stellt Mercurial dar, das in diesem Artikel vorgestellt wird.

Ein Großteil der Arbeiten am Computer beinhaltet, dass die Arbeitsergebnisse in Dateien gespeichert werden. Selten entstehen die entsprechenden elektronischen Dokumente in einer einzelnen Bearbeitungssitzung, meistens werden die Dateien nach und nach erweitert (ein Programmierprojekt wird praktisch nie an einem Tag fertig, stattdessen wird inkrementell immer wieder Code hinzugefügt und geändert). Ist ein bestimmter Stand der Bearbeitung eines Projekts erreicht, ist es wünschenswert, ihn so speichern zu können, dass er später im Bedarfsfall wieder hergestellt werden kann, z. B. nach einer Codeänderung der jeweils letzte lauffähige Stand. Tatsächlich geht es auch selten um Stände von einzelnen Dateien, sondern um Stände von Dateimengen (Konfigurationen). Und oft wird an diesen Dateimengen nicht nur von einer Person an einem Arbeitsplatz gearbeitet, sondern von verschiedenen Personen an verschiedenen Arbeitsplätzen, und ein paralleler Zugriff soll unterstützt werden. Einen Versuch zur Lösung stellen (verteilte) Versionskontrollsysteme dar.
Das Problem kennt jeder, der mit Computern Dateien erstellt, insbesondere wenn er/sie programmiert, d. h. mit Quelltextdateien arbeitet. Ich erreiche einen bestimmten Stand (z. B. ein kompilierbares Programm, das bestimmte Tests passiert, einen abgestimmten Stand eines Projektdokuments o. Ä.), und möchte diesen dauerhaft festhalten. Was ich mir in diesem Zusammenhang wünschen würde, wäre:

• dass ich jederzeit zu dem ursprünglichen Zustand meines Dokuments (oder meiner Dokumente) zurückkehren kann.
• dass ich die Änderungen zwischen den verschiedenen Versionen der Dokumente, die ich erstellt habe, nachvollziehen kann.
• dass ich nachvollziehen kann, wer welche Änderungen vorgenommen hat.
• dass ich die Änderungen verschiedener Beteiligter zu einem konsistenten Stand zusammenführen kann.

Diese Anforderungen zu erfüllen, ist die Aufgabe der verschiedenen Ansätze zur Versionskontrolle.

Versionskontrolle „für Arme“ – Speichern von Kopien

Dieser Ansatz ist so naheliegend, dass jeder ihn wahrscheinlich schon verwendet hat. Wenn ein bestimmter Stand einer Datei erreicht ist, speichere ich einfach eine Sicherungskopie meines Stands. Um nicht in völliges Chaos zu versinken, empfiehlt es sich, ein durchgängiges Dateinamensschema für die Sicherungsstände zu verwenden, z. B. <Präfix>_<Version>_<Datum>.<Endung>. Für diesen Artikel etwa kann das Präfix „Versionskontrolle_mit_Mercurial“ verwendet werden. Da dieser Artikel mit LibreOffice geschrieben wird, ist die Endung „.odt“. Es ergeben sich dann für verschiedene Stände etwa diese Dateinamen:

Versionskontrolle_mit_Mercurial_v000_2013_10_07.odt
Versionskontrolle_mit_Mercurial_v001_2013_10_08.odt
Versionskontrolle_mit_Mercurial_v002_2013_10_13.odt
...

Trotz seiner Einfachheit wird dieses Verfahren gerne z. B. im Projektmanagement benutzt, um etwa verschiedene Stände von Excel-Tabellen oder Präsentationen zu verwalten. Es wird keine besondere Infrastruktur benötigt, und zumindest eine Anforderung (dass jede Version jederzeit zugreifbar bleibt), wird erfüllt. Allerdings hat das Verfahren einige Mängel.
So erfolgt die Versionierung durch die Vergabe des richtigen Dateinamens. Es obliegt dem Anwender, die Datei unter dem richtigen Namen mit der richtigen Version zu speichern. Außerdem kann im Rahmen von Kollaborationsszenarien nicht sichergestellt werden, dass die Versionsnummern eindeutig sind, wenn mehrere voneinander unabhängig arbeitende Mitarbeiter neue Versionen erzeugen. Es besteht die Notwendigkeit, dass die Versionierung von einer einzelnen Person/Instanz zentral ausgeführt wird. Diese muss ggf. auch konkurrierende Änderungen verschiedener Beteiligter einarbeiten. Hier liegen hohe Fehlerpotenziale.
Im Rahmen der Softwareentwicklung hat man es so gut wie immer mit Dateigruppen statt Einzeldateien zu tun. Betrachten wir etwa ein sehr kleines C++-Projekt mit nur drei Dateien: definitions.hh, mainprog.cc und Makefile (Listing 1a).

// file definitions.hh
const int MYCONSTANT = 1

// file mainprog.cc
#include “definitions.hh”
int main() {
  // do something
  return 0;
}

# file Makefile
myexecutable.exe: definitions.hh mainprog.cc
$(CC) mainprog.cc -o myexecutable.exe
// file definitions_v000.hh 
const int MYCONSTANT = 1

// file mainprog_v000.cc
#include “definitions_v000.hh”
int main() {
  // do something
  return 0;
}

# file Makefile_v000
myexecutable.exe: definitions_v000.hh mainprog_v000.cc
$(CC) mainprog_v000.cc -o myexecutable.exe

Würde ein Versionierungsschema, das eine Versionsnummer im Dateinamen verwendet, so ergäbe sich für einen bestimmten Stand des Projekts das in Listing 1b gezeigte. Was passiert nun, wenn die Headerdatei angepasst wird? Es muss eine neue Version erzeugt werden: definitions_v001.hh. Da sich damit aber der Dateiname der Headerdatei geändert hat, muss auch mainprog_v000.cc angepasst werden, sodass die neue Version der Headerdatei inkludiert wird. Und mainprog_v000.cc muss umbenannt werden in mainprog_v001.cc, da es sich um eine neue Version handelt. Um die neue Konfiguration kompilieren zu können, wird dann aber eine neue Version von Makefile_v000 benötigt. Das heißt die Änderung einer einzigen Konstanten würde in diesem Fall bedeuten, dass drei Dateien umbenannt und angepasst werden müssen. Allgemein erzeugt dieses Verfahren für jeden Stand eine neue Datei und wird bei einer größeren Anzahl von Versionen schnell unübersichtlich.
Zusammenfassend lässt sich sagen: insbesondere im Zusammenhang mit Quellcodedateien gehört diese „Lösung“ eher zum „Problem Set“ als zum „Solution Set“.

Die ersten Lösungen: SCCS, RCS

Wie wir gesehen haben, ist es wünschenswert, beim Verwalten von Revisionen von Dateien sicherzustellen, dass die Dateinamen über die Revisionen hinweg konstant bleiben. Aus diesem Grund wurden die ersten Versionskontrollsysteme entwickelt. SCCS (Source Code Control System), das mit kommerziellen UNIX-Versionen ausgeliefert wird, verwaltet einzelne Dateien. Die Änderungsinformation wird dabei in einer speziellen SCCS-Datei gehalten. Für die Verwaltung einer Quellcodedatei myprog.c geht man etwa wie folgt vor. Zunächst wird eine korrespondierende SCCS-Datei erstellt:

admin -imyprog.c s.myprog.c

s.myprog.c enthält dann die benötigten Verwaltungsinformationen. Das Original von myprog.c sollte dann umbenannt werden, damit es nicht zu störenden Überlagerungen mit SCCS-Kommandos kommt.

mv myprog.c myprog.c.back

Für die Bearbeitung muss nun zunächst eine bestimmte Version angefordert werden. Dazu dient das Kommando get. Der Aufruf get -e s.myprog.c bewirkt, dass die neueste Version der Datei ausgecheckt wird (unter dem Namen myprog.c). Diese Datei kann jetzt editiert/geändert werden. Eine neue Revision der Datei wird dann mit dem Kommando delta erzeugt: delta s.myprog.c. Der Anwender wird dabei aufgefordert, Änderungsinformationen als Freitext einzugeben, anhand derer später nachvollzogen werden kann, was sich zwischen den Revisionen verändert hat. Mit dem Kommando sccsdiff können verschiedene Revisionen einer Datei verglichen werden. Auch die Änderungsinformationen können eingesehen werden. Mit SCCS ist es also möglich:

• verschiedene Versionen einer Datei unter Beibehaltung des Dateinamens zu verwalten.
• informelle Änderungsinformationen zu pflegen.
• inhaltliche Änderungen sowie Änderungsinformation zu recherchieren.

SCCS ist dabei für Quellcodedateien und Textdateien vorgesehen – Binärdateien werden nicht unterstützt. RCS (Revision Control System) arbeitet ähnlich wie SCCS und hat dieselben Einschränkungen (Verwaltung einzelner Dateien, nur Text-/Quellcodedateien).
Beide Systeme stellen gegenüber der manuellen Verwaltung (Speichern von Kopien mit jeweils neuen Namen) eine Verbesserung dar. Allerdings erfolgt die Versionierung immer nur auf einzelnen Dateien. Softwareprojekte bestehen im Allgemeinen aber aus einer Vielzahl von Dateien. Außerdem wird Software meist kollaborativ von mehreren Benutzern entwickelt. Ein Mehrbenutzerbetrieb wird von diesen Systemen aber nicht unterstützt. Entsprechend sind beide Systeme für die Softwareentwicklung nur bedingt geeignet.

Aufmacherbild: happy asian business team working in office von Shutterstock / Urheberrecht: Tom Wang

[ header = Seite 2: Multidateiverwaltung und Mehrbenutzerbetrieb: CVS ]

Multidateiverwaltung und Mehrbenutzerbetrieb: CVS

CVS (Concurrent Versions System) stellte einen wichtigen Fortschritt im Bereich der Versionskontrolle dar. CVS stellte insbesondere die Verwaltung von Dateimengen und Mehrbenutzerbetrieb bereit.
Im Unterschied zu den vorgenannten Systemen verwaltet CVS unter Versionskontrolle gestellte Dateien zentral in einem so genannten Repository, das von einem Server verwaltet wird – CVS ist insofern eine Client-Server-Anwendung. Für die Arbeit mit CVS muss zunächst ein entsprechendes Repository für das Projekt angelegt werden. Dazu kann zunächst ein leeres Repository angelegt oder ein bestehendes Verzeichnis importiert werden.
Um jetzt mit den Dateien aus dem Projekt zu arbeiten, muss der jeweilige Anwender zunächst das entsprechende Verzeichnis auschecken (cvs co). Als Resultat entsteht ein lokales Verzeichnis, das die entsprechenden Dateien enthält. Der Anwender kann jetzt Änderungen in seiner ausgecheckten Version vornehmen. Neben dem Ändern von bestehenden Dateien kann er Dateien hinzufügen (cvs add <Dateiname>) oder entfernen (cvs remove <Dateiname>). Alle diese Änderungen geschehen zunächst nur lokal. Nach Abschluss der Änderungen muss der Anwender seine Änderungen zum Server in das Repository synchronisieren (cvs commit).
Aufgrund des Mehrbenutzerbetriebs kann es hier jetzt zu Konflikten kommen, wenn mehrere Benutzer gleichzeitig Änderungen an denselben Dateien vornehmen. Ruft man cvs commit auf, kann es passieren, dass Änderungen anderer, die in der Zwischenzeit vorgenommen wurden, verloren gehen. Um solche Probleme zu minimieren, war es bei der Arbeit mit CVS wichtig, zunächst möglicherweise von anderen Anwendern durchgeführte Änderungen in die lokale Kopie zu synchronisieren (cvs update). Während der Ausführung des Updatekommandos werden die jeweilige lokale und die serverseitige Datei verglichen. Als Resultat des Updates kann es zu Konflikten kommen, diese werden in der entsprechenden Datei ausgezeichnet. Das sieht dann etwa so aus:

<<<<<<< definitions.hh
const int MYCONSTANT = 2
=======
const char MYCONSTANT = 1
>>>>>>> 1.4

So ein Konflikt tritt z. B. auf, wenn lokal in definitions.hh MYCONSTANT auf 2 erhöht wurde, während auf dem Server eine neue Version erstellt wurde, die den Datentyp von MYCONSTANT auf char ändert. Es ist dann Aufgabe des Anwenders, diese Konflikte händisch aufzulösen.
Ein neues Konzept waren in CVS so genannte Branches. Die Grundidee an dieser Stelle ist, dass es nicht nur einen, sondern mehrere Entwicklungszweige eines Projekts geben kann. So ist es z. B. verbreitet, dass verschiedene Produktversionen gepflegt werden müssen. Ein Produkt könnte z. B. die freigegebenen Versionen 1, 2 und 3 haben, und eine neue Version 4 könnte sich in Entwicklung befinden. Bei Anwendern könnte nun etwa die Version 2 installiert sein: Beim Betrieb würden nun Fehler in Version 2 gefunden. Ein Update auf die neueste Version wäre aus verschiedenen Gründen nicht möglich, etwa weil sich die Schnittstellen mit Version 3 geändert haben. Es muss also die Möglichkeit geben, verschiedene Entwicklungszweige zu pflegen, sodass Bugfixes für Version 2 durchgeführt und gepflegt werden können.

Abb. 1: Branches in CVS

Abbildung 1 illustriert die Situation. Die grünen Kästen auf der linken Seite stellen den Hauptstrang der Entwicklung dar. Es werden dann zwei Versionen freigegeben (1.0 und 2.0). Dazu wird der Befehl cvs branch verwendet, der dazu führt, dass eigene Entwicklungsstränge abgezweigt („gebrancht“ werden). Sie haben jetzt ihre eigene Historie und können unabhängig vom Hauptstrang gepflegt werden. Die Versionen 1.1 und 2.1 stellen dabei Bugfixes für die Versionen 1.0 und 2.0 dar. Diese müssen im Hauptstrang (in CVS „Head“ genannt) möglicherweise gar nicht durchgeführt werden, weil durch Restrukturierung, Änderung der Architektur o. Ä. dort die entsprechenden Fehler gar nicht existieren.

Abb. 2: Mergen in CVS

Abbildung 2 zeigt eine andere Situation. Hier ist es so, dass der Bugfix, der in Version 1.1 gemacht wurde, auch Code im Head betrifft. Dazu wird das Kommando cvs merge benutzt, das die im Branch gemachten Änderungen auf den aktuellen Stand des Heads anwendet, wobei eine neue Version im Head entsteht. Da Head und Branches unabhängig voneinander bearbeitet werden können, kann es hier wieder zu Konflikten kommen – diese werden wie oben markiert und müssen ggf. von Hand aufgelöst werden.
Wie schon gesagt, erlaubt CVS das Arbeiten mit Mengen/Gruppen von Dateien. In CVS bekommen die Dateien aber nichtsdestotrotz eigene Versionsnummern. Abbildung 3 zeigt die Versionshistorie von vier Dateien eines Projekts, dem Hauptprogramm mainprog.cc, der Schnittstellendeklaration interface.hh, der Schnittstellenimplementierung interface.cc und dem dazugehörigen Makefile. Die Vertikale gibt die Zeitachse an. Wie man sieht, verlaufen die Versionen der verschiedenen Dateien nicht synchron, sondern zu einem bestimmten Zeitpunkt haben die Dateien wahrscheinlich verschiedene Versionen. Ein bestimmter Zustand des Gesamtprogramms entspricht dabei immer einer Konfiguration (man könnte auch von einer Baseline sprechen) verschiedener Dateiversionen. Eine solche Konfiguration (z. B. der Zeitpunkt, an dem das Programm erstmals eine bestimmte Funktionalität erreicht hat) ist hier gelb gekennzeichnet. Mit dem Befehl cvs tag ist es möglich, für eine solche Konfiguration (in CVS: Tag) einen symbolischen Namen zu vergeben, anhand derer diese wiederhergestellt werden kann. Ohne diese Tags kann immer nur der letzte Stand aller Dateien eines Branches wiederhergestellt werden.

Abb. 3: Tags in CVS

CVS gestattet darüberhinaus das Verwalten von Binärdateien. CVS erfreute sich als freie Software gerade im Open-Source-Bereich bei vielen Projekten großer Beliebtheit, ist mittlerweile aber veraltet. CVS wird nicht weiterentwickelt.

Subversion

Subversion (SVN) stellt einen Versuch dar, wesentliche Mängel von CVS zu beheben, ohne das grundlegende Arbeitsprinzip von CVS zu verändern. Entsprechend war einer der Slogans der Entwicklungsziele, SVN sei „CVS done right“. Ähnlich wie bei Java versucht wurde, eine Syntax ähnlich zu C++ zu verwenden, verwendet SVN eine Befehlssyntax, die der von CVS sehr ähnlich ist. SVN stellt gegenüber CVS in vielen Bereichen eine große Verbesserung dar. Einige Punkte verdienen besonders hervorgehoben zu werden:

• Subversions Versionierung bezieht sich nicht auf Einzeldateien, sondern auf das gesamte Projekt. Bei jeder Änderung entsteht eine neue Revision des gesamten Repositorys. Damit ist es im Unterschied zu CVS möglich, jeden eingecheckten Stand problemlos wiederherzustellen.
• In Subversion gibt es grundsätzlich keinen Unterschied zwischen einem Tag (s. o.) und einem Branch. Beides wird intern durch eine Kopie eines bestimmten Stands des Projekts in einen neuen Pfad im Repository realisiert.
• Subversion erlaubt das Löschen von Verzeichnissen (das war in CVS nicht möglich, einmal angelegte Verzeichnisse blieben immer erhalten, es war nur ein Löschen enthaltener Dateien möglich).
• Die Verwaltung von Binärdateien ist unter Subversion deutlich einfacher als unter CVS, sie können wie alle anderen Dateien verwaltet werden.
• Durch die Ablage von Metadaten in den ausgecheckten Kopien in speziellen Datenbanken (aktuell in einer SQLite-Datenbank) können viele Operationen (Anzeige lokaler Änderungen etc. durchgeführt werden, ohne mit dem Server zu kommunizieren, was insbesondere bei entfernt gehosteten Repositories von Vorteil ist.
• Subversion prüft vor dem Check-in, ob es serverseitige Änderungen gibt, und verlangt vom Anwender, diese vor dem Check-in in die lokale Kopie zu synchronisieren. Dadurch wird verhindert, dass aus Versehen die Änderungen eines anderen einfach überschrieben werden – eine sehr gefährliche Schwachstelle bei CVS.

Alles in allem kann gesagt werden, dass Subversion die bei der Entwicklung zugrunde liegenden Ziele, nämlich die Beseitigung grundlegender Mängel von CVS und die Verbesserung der Benutzbarkeit und Performance, erreicht hat. Wie CVS ist Subversion freie Software und wird sowohl in Open-Source-Projekten als auch in Unternehmen gern und viel benutzt.

[ header = Seite 3: Probleme mit den bisherigen Ansätzen]

Probleme mit den bisherigen Ansätzen

Die zuletzt beschriebenen Systeme sind Client-Server-Anwendungen. Die entscheidenden Verwaltungsinformationen befinden sich auf der Serverseite. Das impliziert, dass eine Nutzung wesentlicher Aspekte der Systeme im Offlinebetrieb nicht möglich ist.
Ein weiteres Problem besteht darin, dass lokale Weiterentwicklungen einzelner Beteiligter an einem Softwareprojekt eine hohe Komplexität erreichen können. Auch hier ergeben sich die Anforderungen, die allgemein an die Versionskontrolle gestellt wurden. Insbesondere möchte der Anwender die Möglichkeit haben, lokale Stände sichern zu können. Mit den beschriebenen Ansätzen hat er aber nur die Möglichkeit, seine Änderungen im zentralen Repository zu sichern. Und das sollte erst geschehen, wenn seine Entwicklung die Reife hat, um sie zu veröffentlichen, um die Integrität des Repositorys nicht zu verletzen. Will er also lokale, nicht komplette Änderungen zwischenspeichern, muss er wieder mit Sicherungskopien oder ähnlichen Verfahren arbeiten, deren Mängel schon am Anfang dieses Artikels diskutiert wurden.
Hinzu kommt, dass zentralisierte Systeme große Schwierigkeiten bereiten, wenn separate Änderungen verschiedener Entwickler synchronisiert werden sollen. Das hat etwas mit der benutzten Form der Datenhaltung zu tun. Zentralisierte Systeme speichern Revisionen von Dateien. Beim Einchecken von Änderungen wird der Stand der Datei auf dem Server mit dem neuen Stand verglichen und der neue Stand eingecheckt, sodass eine neue Dateiversion entsteht. Der Schwerpunkt liegt also nicht auf den Änderungen, sondern auf den Unterschieden zwischen Dateiversionen. Schon das einfache Verschieben einer Funktion oder Methode innerhalb der Quelldatei wird nicht als solches erkannt. Stattdessen taucht die entsprechende Passage gegenüber der alten Version als gelöscht und an der neuen Position als neu auf. In aktuelleren Versionen von Subversion ist dieses Problem im Wesentlichen behoben, d. h. SVN erkennt in solchen Fällen, dass der betreffende Code nur verschoben wurde. In CVS und früheren Versionen von SVN war das aber nicht der Fall.
Ungeachtet der Weiterentwicklungen von Subversion bleibt aber bestehen, dass Offlinebetrieb und lokale Versionsverwaltung nicht gut unterstützt werden.

DVCS (Git, Mercurial, Fossil)

Linux wurde lange Zeit ohne Versionskontrollsystem entwickelt (stattdessen wurde mit Tar-Balls und Patches gearbeitet). Zu einem späteren Zeitpunkt wurde dann Bitkeeper verwendet – ein kommerzielles Versionskontrollsystem, das in der Lage war, große Änderungsmengen vieler verschiedener Entwickler effizient zu verwalten. Im Unterschied zu den genannten Systemen wird Bitkeeper aufgrund seiner Architektur als „Distributed Version Control System“ bezeichnet. Als sich abzeichnete, dass Bitkeeper nicht mehr kostenlos zur Verfügung stehen würde, wurde nach Alternativen gesucht. Im Resultat entstanden Git (entwickelt unter Federführung von Linus Torvalds) und Mercurial (ein parallel entstandenes Projekt, initiiert von Matt Mackall). Beide Systeme sind sehr leistungsfähig und werden in großen Open-Source-Projekten verwendet (Git für Linux, Mercurial für die Programmiersprache Python).
Beide Systeme sind ausgereift und offensichtlich praxistauglich. Generell wird Git als etwas mächtiger und Mercurial als etwas einfacher in der Benutzung eingestuft. Beide Systeme sind freie Software.
Ein weiteres interessantes System in diesem Zusammenhang ist Fossil, ebenfalls ein DCVS, das neben den reinen Features eines DCVS zusätzlich auch Unterstützung für Bugtracking und die Verwaltung von Wikis bereitstellt. Fossil ist insbesondere „self-contained“, lässt sich dementsprechend auch problemlos etwa auf Medien wie USB-Sticks o. Ä. nutzen, und zeichnet sich durch eine sehr einfache Benutzung aus.

Unterschiede zu klassischen VCS
DVCS unterscheiden sich zu ihren Vorgängern im Wesentlichen in folgenden Punkten:
1. DCVS erlauben die Trennung von lokaler Verwaltung und globaler Veröffentlichung, und erlauben, die Möglichkeiten der Versionskontrolle auch lokal zu verwenden.
2. DCVS unterstützen paralleles, dezentrales Arbeiten.
3. DCVS verwalten im Wesentlichen nicht Dateiversionen, sondern Änderungsmengen (Change Sets).

Einrichtung eines Mercurial Repositorys (Bitbucket)

Da in diesem Artikel Mercurial im Mittelpunkt steht, wird hier zunächst beschrieben, wie ein entsprechendes Repository auf Bitbucket eingerichtet werden kann. Das ist streng genommen nicht unbedingt notwendig – es ist auch möglich, rein lokal mit Mercurial zu arbeiten. Die typischen Arbeitsabläufe ergeben sich aber eigentlich erst, wenn sowohl mit lokalen als auch entfernten Repositories gearbeitet wird. Bitbucket erlaubt es, kostenfrei private Repositories für bis zu fünf Mitglieder einzurichten. Es ist daher für erste Gehversuche mit Mercurial ideal geeignet.
Geht man auf die Homepage von Bitbucket, erhält man zunächst die Möglichkeit, sich mit einem bestehenden Konto einzuloggen, oder sich über „Sign in“ zu registrieren.

Abb. 4: Registrierung bei Bitbucket

Abbildung 4 zeigt den Registrierungsdialog bei Bitbucket. Hier sind die üblichen Informationen einzutragen, Name, E-Mail, Passwort. Als Accounttyp sollte „Team“ angegeben werden, wenn das Repository von einem Team administriert werden soll, ansonsten reicht für unsere Zwecke „Individual“. Es können auch existierende Accounts (Google, Facebook, Twitter, GitHub) verwendet werden. Abbildung 5 zeigt den Bildschirm für die Anlage eines neuen Repositorys. In diesem Fall entscheiden wir und für die Option „Create your first repository“, anstatt ein bestehendes Verzeichnis als Basis für das neue Repository zu verwenden. Abbildung 6 zeigt die Optionen für die Anlage des neuen Repositorys. Als Typ tragen wir hier „Mercurial“ ein. Im folgenden Bildschirm (Abb. 7) entscheiden wir uns dafür, ein neues Projekt anzulegen. Die nächste Seite (Abb. 8) gibt dann Tipps, wie weiter fortgefahren werden sollte. Die beschriebenen Tipps sind sinnvoll und können von uns übernommen werden. Voraussetzung ist, das Mercurial auf dem eigenen Rechner installiert wurde (Kasten: „Installation von Mercurial“).

Abb. 5: Anlage eines neuen Repositorys

Abb. 6: Optionen für die Anlage eines neuen Repositorys

Abb. 7: „Add some code“

Abb. 8: „Clone your new repo“

Installation von Mercurial
Mercurial lässt sich unter den gängigen Betriebssystemen mit relativ wenig Aufwand installieren. Ubuntu/Debian: sudo apt-get install mercurial. Einzelheiten finden sich hier. Windows/Mac: Downloads finden sie hier.

[ header = Seite 4: Arbeiten mit Mercurial ]

Arbeiten mit Mercurial

Wir zeigen jetzt exemplarisch ein paar typische Abläufe in der Arbeit mit Mercurial:

• Klonen eines Repositorys
• Einstellen von Dateien unter lokale Verwaltung
• Einchecken von Änderungen (lokal)
• Übertragen von Änderungen in das Bitbucket-Repository
• Kollaboration

Wir folgen zunächst den Empfehlungen der Seite und klonen das Repository, um eine lokale Arbeitskopie zu erhalten (als Benutzer „gharlane“) (Listing 2).

wolfgang@metellus:~/projects/hg-cos$ mkdir demoproject
wolfgang@metellus:~/projects/hg-cos$ cd demoproject/ wolfgang@metellus:~/projects/hg-cos/demoproject$ hg clone https://bitbucket.org/gharlane/replacenonascii
HTTP-Autorisierung erforderlich
Bereich: Bitbucket.org HTTP
Benutzer: gharlane
Passwort:
Zielverzeichnis: replacenonascii
Keine Änderungen gefunden
Aktualisiere auf Zweig default
0 Dateien aktualisiert, 0 Dateien zusammengeführt, 0 Dateien entfernt, 0 Dateien ungelöst

Im Resultat erhalten wir ein Unterverzeichnis /replacenonascii/. Das Kommando hg clone hat ein eigenes lokales Repository als komplette Kopie des serverseitigen Repositorys erzeugt. Das Repository auf dem Server enthält nichts – entsprechend auch keine Änderungen.
Jetzt ist es an der Zeit, dem neuen Repository Inhalte hinzuzufügen. Listing 3 zeigt ein kleines C-Programm, das benutzt werden kann, um Nicht-ASCII-Zeichen durch ein anderes Zeichen zu ersetzen.

/* replacenonascii.c
 *
 * Program to filter non-ascii character sequences
 * and replace them by replacement characters
 *
 */

#include <stdio.h>

int main() {
  int c;
  int outC;
  c = getchar();
  while (c != EOF) {
    if (c > 127) {
      outC = '_';
    }
    else {
      outC = c;
    }
    putchar(outC);
    c = getchar();
  }
  return 0;
}

Wir speichern dieses Programm in dem Verzeichnis /replacenonascii/. Dann prüfen wir den Status unseres Repositorys mit dem Kommando hg status:

wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg status
? replacenonascii.c
wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$

Mercurial zeigt die gerade angelegte Datei mit einem Fragezeichen an. Das bedeutet, dass die Datei Mercurial nicht bekannt ist. Um das zu ändern, müssen wir die Datei mit dem Kommando hg add bekannt machen: wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg add replacenonascii.c. Eine erneute Statusprüfung

wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg status
A replacenonascii.c

zeigt die Datei jetzt mit einem großen A (für Added) an. Um die Datei jetzt endgültig erstmals einzustellen, benutzen wir das Kommando hg commit:

wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg commit -u gharlane

Um an solchen Stellen nicht immer wieder erneut den Usernamen eintragen zu müssen, empfiehlt es sich, die Datei .hgrc im Unterverzeichnis /.hg/ anzupassen. Dazu muss die Datei um folgende Zeilen erweitert werden:

[ui]
username = Firstname Lastname <firstname.lastname@example.net>
verbose = True

Als Ergebnis des commit-Kommandos öffnet sich der konfigurierte Editor und fordert zur Eingabe einer Beschreibung der Änderung auf – wir tragen hier einfach nur „initial“ ein. Anschließend speichern wir die Datei. Mercurial quittiert das mit der Meldung: „Änderungssatz 0 erzeugt:8975aae814e1“. Damit befindet sich die Datei jetzt unter (lokaler) Verwaltung von Mercurial.
Wir nehmen jetzt eine Änderung an unserer Datei vor: statt des „magic characters“ „_“ benutzen wir eine Konstante REPLACEMENT. Die geänderte Datei zeigt Listing 4.

/* replacenonascii.c
 *
 * Program to filter non-ascii character sequences
 * and replace them by replacement characters
 *
 */

#include <stdio.h>

const char REPLACECHAR = '_';

int main() {
  int c;
  int outC;
  c = getchar();
  while (c != EOF) {
    if (c > 127) {
      outC = REPLACECHAR;
      }
    else {
      outC = c;
    }
    putchar(outC);
    c = getchar();
  }

  return 0;
}

Nach dem Speichern der Änderung überprüfen wir wieder den Status. Die geänderte Datei ist mit einem großen M gekennzeichnet (für Modified). Was sich gegenüber der vorherigen Version im Repository geändert hat, können wir mit hg diff ermitteln (Listing 5).

wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg diff 
diff -r 8975aae814e1 replacenonascii.c 
--- a/replacenonascii.c	Sun Nov 03 16:01:49 2013 +0100 
+++ b/replacenonascii.c	Sun Nov 03 16:18:54 2013 +0100 
@@ -7,13 +7,15 @@ 
 
 #include <stdio.h> 
 
+const char REPLACECHAR = '_'; 
+ 
 int main() { 
   int c; 
   int outC; 
   c = getchar(); 
   while (c != EOF) { 
     if (c > 127) { 
-      outC = '_'; 
+      outC = REPLACECHAR; 
       } 
     else { 
       outC = c; 
wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$

Im Ergebnis des Kommandos sieht man, was geändert wurde: Zeilen, die mit einem Pluszeichen beginnen, wurden seit der letzten Version hinzugefügt, Zeilen, die mit einem Minuszeichen beginnen, entfernt. Da unsere Datei bereits unter Kontrolle von Mercurial steht, gäbe es hier auch die Möglichkeit, mit hg revert den vorherigen Stand wiederherzustellen.
Wir checken auch diese Änderung wieder mit hg commit ein. Als Beschreibung geben wir dieses Mal „new constant REPLACECHAR“ ein. Mercurial meldet daraufhin: „Änderungssatz 1 erzeugt:413cdedd1212“.
Es ist wichtig, sich klarzumachen, dass wir keine Versionen von Dateien erzeugt haben, sondern dass wir Änderungssätze generiert und eingestellt haben.
Bis jetzt haben wir nur lokal gearbeitet – trotzdem konnten wir bereits wichtige Features eines Versionskontrollsystems (Änderungsverfolgung, Möglichkeit der Wiederherstellung) nutzen. Jetzt sollen die lokalen Änderungen aber veröffentlicht werden, um sie mit anderen teilen zu können. Dazu benutzen wir das Kommando hg push (Listing 6).

wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg push 
Übertrage nach https://bitbucket.org/gharlane/replacenonascii
HTTP-Autorisierung erforderlich
Bereich: Bitbucket.org HTTP
Benutzer: gharlane
Passwort:
Suche nach Änderungen
2 Änderungssätze gefunden
Entfernt: adding changesets
Entfernt: adding manifests
Entfernt: adding file changes
Entfernt: added 2 changesets with 2 changes to 1 files

Für die Übertragung müssen wir erst Login und Passwort eingeben. Die zwei Änderungssätze werden im entfernten Repository hinzugefügt und sind damit für andere zugreifbar.
Damit der Anwender „slashgordon“ auf das Repository zugreifen kann, laden wir ihn ein. Dazu gehen wir auf die Seite des Repositorys (https://bitbucket.org/gharlane/replacenonascii) und wählen dort SEND INVITATION (Abb. 9) und geben im folgenden Dialog die E-Mail-Adresse des Users ein, der eingeladen werden soll, betätigen den Button ADD, stellen den Zugriff auf „WRITE“ und betätigen den Button SHARE. Er erhält anschließend eine entsprechende Benachrichtigung per E-Mail.

Abb. 9: „Invite users to this repo“

Wir arbeiten jetzt mit einem weiteren Anwender. Der Anwender „slashgordon“ erzeugt wie schon beschrieben eine lokale Kopie mit hg clone. Er hat damit Zugriff auf die entsprechenden Verwaltungsinformationen des Repositorys. Er fügt jetzt seinerseits eine Änderung hinzu, die es erlaubt, über einen Kommandozeilenparameter ein anderes Ersatzzeichen zu verwenden (Listing 7).

/* replacenonascii.c
 *
 * Program to filter non-ascii character sequences
 * and replace them by replacement characters
 *
 */

#include <stdio.h>

const char REPLACECHAR = '_';

void processFile(char replaceChar) {
  int c;
  int outC;
  c = getchar();
  while (c != EOF) {
    if (c > 127) {
      outC = replaceChar;
      }
    else {
      outC = c;
    }
    putchar(outC);
    c = getchar();
  }
}

int main(int argc, char *argv[]) {
  int i;
  char replaceChar;
  for (i=1; i<argc; i++) {
    printf("i=%d argv[i]=%sn", i, argv[i]);
    if (0 == strcmp("--replacechar", argv[i])) {
      i++;
      replaceChar = argv[i][0];
    }
  }
  processFile(replaceChar);

  return 0;
}

Durch Ausführen der Kommandos hg commit und hg push (wie schon beschrieben) werden die Änderungen zum lokalen und zum Serverrepository synchronisiert. In der Zwischenzeit hat der Anwender „gharlane“ in seinem Repository ebenfalls eine Änderung gemacht, wobei er nur den Kommentar am Anfang der Datei etwas angepasst hat:

/* replacenonascii.c
 *
 * Program to filter non-ascii character sequences
 * and replace them by replacement character '_' 
...

Das diff-Kommando zeigt wieder die Änderung an, danach checkt er die Änderung in das lokale Repository ein. Anschließend versucht er, seine Änderung in das entfernte Repository zu übertragen.

wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg push
Übertrage nach https://bitbucket.org/gharlane/replacenonascii
...
Suche nach Änderungen
new remote heads on branch 'default'
new remote head f15aa665cfcb
Abbruch: push creates new remote head f15aa665cfcb!
(you should pull and merge or use push -f to force)

Was hier geschehen ist, ist, dass die Übertragung verweigert wurde. Das Problem ist, dass sich der letzte Änderungssatz des lokalen und des Servererpositorys unterscheiden. Mercurial empfiehlt, den Konflikt mit pull und merge aufzulösen.

wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg pull 
...
Hole von https://bitbucket.org/gharlane/replacenonascii
Suche nach Änderungen
Füge Änderungssätze hinzu
Füge Manifeste hinzu
Füge Dateiänderungen hinzu
Fügte 1 Änderungssätze mit 1 Änderungen an 1 Dateien hinzu (+1 Köpfe)
("hg heads" zeigt alle Köpfe, nutze "hg merge" um sie zusammenzuführen)

Es existieren jetzt also mehrere Köpfe (Heads) des Änderungsstapels, d. h. konkurrierendere Änderungen, die wir uns auch anschauen können (Listing 8).

wolfgang@metellus:~/projects/hg-cos/demoproject/replacenonascii$ hg heads
Änderung:        3:f44add1b3c5a
Marke:           tip
Vorgänger:       1:413cdedd1212
Nutzer:          slashgordon
Datum:           Sun Nov 03 18:49:49 2013 +0100
Dateien:         replacenonascii.c
Beschreibung:
command line parameter for replacement character


Änderung:        2:f15aa665cfcb
Nutzer:          Wolfgang Schmidt  <gharlane@web.de>
Datum:           Mon Nov 04 22:54:53 2013 +0100
Dateien:         replacenonascii.c
Beschreibung:
changed comment

Um die beiden Änderungssätze zusammmenzuführen, kann das merge-Kommando benutzt werden. Mercurial hat die Änderungen zusammengeführt, sodass es jetzt wieder nur einen Kopf gibt. Damit ist der Konflikt aufgelöst. Infolgedessen sieht die Datei jetzt aus wie in Listing 9 und enthält beide Änderungen (den Kommentar von „gharlane“ und den Argumentparser von „slashgordon“). Die Änderung kann jetzt mit commit in das lokale Repository eingestellt und mit push auf das Bitbbucket Repository gestellt werden.

/* replacenonascii.c
 *
 * Program to filter non-ascii character sequences
 * and replace them by replacement character '_' 
 *
 */

#include <stdio.h>

const char REPLACECHAR = '_';

void processFile(char replaceChar) {
  int c;
  int outC;
  c = getchar();
  while (c != EOF) {
    if (c > 127) {
      outC = replaceChar;
      }
    else {
      outC = c;
    }
    putchar(outC);
    c = getchar();
  }
}

int main(int argc, char *argv[]) {
  int i;
  char replaceChar;
  for (i=1; i<argc; i++) {
    printf("i=%d argv[i]=%sn", i, argv[i]);
    if (0 == strcmp("--replacechar", argv[i])) {
      i++;
      replaceChar = argv[i][0];
    }
  }
  printf("replaceChar=%cn", replaceChar); 
  processFile(replaceChar);

  return 0;
}

Damit endet unser kleiner Rundgang durch Mercurial. Natürlich konnte nur ein Bruchteil der Möglichkeiten von Mercurial angesprochen werden. Für die weitergehende Beschäftigung mit Mercurial sei auf die entsprechende Dokumentation des Projekts und das Tutorial „Subversion Reeducation“ von Joe Spolsky (teilweise etwas polemisch, aber unterhaltsam) verwiesen.

Fazit

Im Allgemeinen ist die Verwendung von Versionskontrollsystemen zumindest für die Softwareentwicklung einer manuellen Verwaltung vorzuziehen. Zentralisierte Versionskontrollsysteme wie SVN sind leistungsfähig und gut zu benutzen. DVCS wie Mercurial bieten aber gerade für die verteilte Entwicklung einige Vorteile, weil sie das parallele unabhängige Arbeiten mehrerer Entwickler besser unterstützen. Insbesondere die Änderungskontrolle innerhalb der lokalen Repositories stellt hier eine entscheidende Verbesserung dar. Da Mercurial es auch erlaubt, ohne einen Server zu arbeiten, stellt es für einen einzelnen Entwickler durch das Entfallen des Aufwands für die Einrichtung und die Wartung eines Servers auch eine interessante Alternative dar. Dabei ist Mercurial in der Benutzung prinzipiell nicht komplizierter als SVN, wenn man die grundlegende Arbeitsweise verstanden hat. Es lohnt sich also, sich mit Mercurial oder verwandten Systemen wie Git oder Fossil zu beschäftigen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -