Versionsverwaltung II

Martin Kompf

Das Concurrent Version System (CVS) ist das wohl am weitesten verbreitete Versionsverwaltungstool, insbesondere für Open Source Projekte.

CVS wurde usprünglich aus RCS (siehe dazu Teil I) weiterentwickelt und eignet sich vor allem für große Projekte, an denen viele Entwickler mitarbeiten. Im Gegensatz zu RCS erlaubt es eine echte parallele und verteilte Entwicklung und bietet nicht nur eine (eingeschränkte) Sicht auf einzelne Dateien, sondern auf komplette Module. Ein Modul besteht dabei aus einer Menge von Dateien und Verzeichnissen.

Die nachfolgende Beispiele setzen voraus, dass bereits ein funktionsfähiges CVS-Repository vorhanden ist. Dieses Repository liegt auf einem zentralen Server und enthält sämtliche Versionen aller Dateien und Module, die durch CVS verwaltet werden. Die Frage, wie ein solches Repository erstellt und administriert wird, kann in dieser kurzen Einführung nicht besprochen werden. Allen Lesern, die sich mit dieser Frage und weitergehenden Themen in Bezug auf CVS beschäftigen wollen, sei die Online-Ausgabe des Buches Open Source-Projekte mit CVS (auch in deutscher Übersetzung vorliegend) empfohlen.

Angenommen, Sie wollen an einem Projekt namens Graph mitarbeiten, für das es bereits ein entsprechendes CVS-Modul gibt. Zunächst muss eine eigene Arbeitskopie dieses Moduls erstellt werden:

$ cvs checkout Graph

Dies erzeugt im momentanen Arbeitsverzeichnis das Unterverzeichnis Graph, in dem die zu Graph gehörenden Dateien (eventuell organisiert in mehreren Unterverzeichnissen) stehen:

$ cd Graph
$ ls
CVS          DataSet.cpp  DataSet.h    Graph.cpp  Graph.h      Makefile 

Das Verzeichnis CVS enthält Daten, die von CVS intern verwendet werden und sollte daher nicht modifiziert oder gelöscht werden.

Nun kann die Arbeitskopie modifiziert werden. Angenommen, Sie haben die Funktionalität von Graph.cpp verbessert, dann wollen Sie sicher auch alle anderen Entwickler davon profitieren lassen. Dies erfolgt durch ein Commit auf Graph.cpp:

$ cvs commit Graph.cpp

Es wird jetzt zunächst ein Texteditor gestartet, in dem man eine kurze Beschreibung der Änderungen eingeben kann. Nach Verlassen des Editors gibt CVS die Meldung aus:

Checking in Graph.cpp;
/home/CVSRepository/Graph/Graph.cpp,v  <--  Graph.cpp
new revision: 1.2; previous revision: 1.1
done

Die Änderungen an der lokalen Datei sind damit erfolgreich auch im CVS-Repository vollzogen worden, Graph.cpp bekommt die neue Versionsnummer 1.2.

Im Gegensatz zu RCS war die Datei Graph.cpp nicht mit einem Lock versehen worden, d.h. andere Entwickler hätten ebenfalls an dieser Datei Veränderungen vornehmen können. Was passiert jedoch in dem Moment, wenn deren Änderungen auch ins Repository übertragen werden sollen? Nehmen wir an, ein zweiter Entwickler an einem anderen Rechner hat ebenfalls Graph.cpp verändert. Nach erfolgreichem Test will er jetzt committen:

% cvs commit Graph.cpp
cvs server: Up-to-date check failed for `Graph.cpp'
cvs [server aborted]: correct above errors first!

Diese Fehlermeldung bedeutet erst einmal nur, dass seine Arbeitskopie von Graph.cpp nicht mehr auf neuestem Stand ("Up-to-date") ist. Er muss erst die neueste Version aus dem Repository holen. Dazu verwendet er jetzt nicht mehr checkout wie am Anfang, sondern update:

% cvs update Graph.cpp
retrieving revision 1.1
retrieving revision 1.2
Merging differences between 1.1 and 1.2 into Graph.cpp
rcsmerge: warning: conflicts during merge
cvs server: conflicts found in Graph.cpp
C Graph.cpp

CVS hat nun versucht, seine Arbeitskopie mit der Kopie im Repository zu mischen ("merge"). Dabei wendet CVS eine Algorithmus an, der versucht, soviel Arbeit wie möglich dem Anwender abzunehmen und sowohl die Änderungen der Arbeitskopie als auch des Repositories in die neue Version einfließen zu lassen. Es wenn dieser Automatismus wie im Beispiel versagt, muss der Entwickler selbst Hand anlegen und die Datei nochmals bearbeiten. Danach kann endlich der Commit ausgeführt werden:

% cvs commit Graph.cpp
Checking in Graph.cpp;
/home/CVSRepository/Graph/Graph.cpp,v  <--  Graph.cpp
new revision: 1.3; previous revision: 1.2
done

Etwas später ist unser Modul Graph "fertig" entwickelt und getestet (Softwareentwickler wissen, dass nie irgendein Programm wirklich fertig ist...) und soll ausgeliefert werden. Dazu stellen wir zunächst sicher, dass die Arbeitskopie auf neuestem Stand ist:

$ cvs update .

(Der Punkt "." bezeichnet dabei das momentane Arbeitsverzeichnis, welches immer noch Graph sein sollte.) Dann setzen wir ein Tag auf das komplette auszuliefernde Modul:

$ cvs tag -F RELEASE_1_0 .
cvs server: Tagging .
T DataSet.cpp
T DataSet.h
T Graph.cpp
T Graph.h
T Makefile

Damit können wir später jederzeit nachvollziehen, welches die erste Releaseversion unseres Moduls war. Mit

$ cvs update -r RELEASE_1_0 .

läßt sich auch nach einem Jahr noch der exakte Bearbeitungsstand aller im Release 1.0 vorhandenen Dateien rekonstruieren, selbst wenn das Modul inzwischen viel weiter entwicklelt worden ist.

Die dargestellte Benutzung von cvs entspricht der Kommandozeilenversion, wie sie auf den meisten Plattformen (Windows, Unix) vorhanden ist. Es gibt auch diverse CVS-Clients mit einer grafischen Benutzeroberfläche, wie WinCVS für Windows oder jCVS für Java. Das CVS-Repository (der CVS-Server) sollte jedoch immer auf einem Unix-Rechner liegen, hierfür bietet sich Linux an. Weiterführende Informationen zum Thema CVS, Links zu den diversen Clients und vieles mehr sind auf der CVS-Homepage zusammengestellt.