GNU CVS: Quelltext Management

Autor: Matthias Kranz
(Quelle: Linux-Magazin 10/98 S.12 ff.)

Versionen, Releases, Revisionen, da kommt man ja schon bei der Begriffsbildung durcheinander. Wie soll man denn da in der Softwareentwicklung den Überblick behalten?

Zum Beispiel mit CVS, einem auf RCS aufsetzenden Frontend, das dieses um einige Möglichkeiten erweitert.

Das CVS Versions-Kontroll-System ist ein Programm zum Bearbeiten eines SoftwareProjekts oder überhaupt eines aus Text Dateien bestehenden Verzeichnis-Baums. Aus dieser Sicht betrachtet ist es auch überaus interessant für Web-Admins - mehr dazu später.

Installation

An dieser Stelle sei gesagt, daß die meisten Distributionen sozusagen von Haus aus das Concurrent Version System mitliefern. In der Regel handelt es sich hierbei nicht um die neueste (angeblich gibt es in der aktuellen Slackware 3.5 noch CVS 1.7), dafür aber um eine sicherlich stabile Version. Die gerade Ende Juli erschienene CVS 1.9.29 stellt z.B. einen großen Unterschied zur weit verbreiteten Version 1.9 dar. CVS hat sich inzwischen zu einem eigenständigen Paket entwickelt, welches nicht mehr auf ein zugrunde liegendes RCS oder diff zurückgreifen muß. Aktueller Maintainer ist Cyclic Software. Die Installation gestaltet sich durch die Verwendung der häufig benutzten autoconf-Methode einfach. Das Paket wird ausgepackt durch:

hdusel@sonja:~/developer/foo> tar -zxvf cvs-1.9.29.tar.gz

Dann wechselt man in das entpackte Verzeichnis und ruft das configure-Skript auf. Immer davon ausgehend, daß dieses erfolgreich beendet wird, folgt im Anschluß make und darauf ein make install.

hdusel@sonja:~/developer/foo> cd cvs 1.9.29
hdusel@sonja:~/developer/foo> ./configure
hdusel@sonja:~/developer/foo> make
hdusel@sonja:~/developer/foo> make install

In diesem Zusammenhang sei darauf hingewiesen, daß man schon beim Kompilieren den Umfang des CVS-Pakets bestimmt. Daß heißt man bestimmt an dieser Stelle, ob eine Client/Server-Version erstellt wird oder nicht. Plant man keinen Einsatz von Client/Server-CVS, kann man die ca. 30% kleinere Version bauen. Erreicht wird dies, indem man dem configure-Skript folgende Optionen übergibt:

hdusel@sonja:~/developer/foo> ./configure -disable-client -disable-server

Was ist CVS?

Vielleicht zunächst etwas zum Konzept von CVS (Concurrent Version System).

CVS verwaltet die Entwicklungsgeschichte (neudeutsch History) eines Verzeichnisbaums, der Quelldateien beinhaltet. Konkret werden zu jeder bearbeiteten Datei die sukzessiven Veränderungen gespeichert. Dabei wird jede Änderung eines Files mit der aktuellen Zeit und einem Hinweis auf den Urheber der Änderung versehen. Zusätzlich kann die Datei mit beschreibenden Hinweisen versehen werden. Denn grundsätzlich stellt sich jedem Entwickler, zumindest in einer größeren Gruppe die Frage, wer wie zuletzt ein File bearbeitet hat und warum.

Zentraler Bestandteil von CVS ist das Repository, in dem die Quelldateien liegen. Diese werden nicht direkt editiert. Statt dessen legt sich jeder beteiligte Entwickler ein eigenes Arbeitsverzeichnis an. In diesem kann er nun die Files beliebig editieren, Files hinzufügen oder löschen, usw. Entscheidend ist, daß diese Veränderungen immer nur die lokalen Kopien betreffen. Möchte man seine Version der Dateien in das Repository einspielen und damit jedem anderen Entwickler seine Veränderungen bekannt machen, müssen diese eingecheckt werden. Vor diesem Vorgang muß ein Update der eigenen Dateien erfolgen. Bei diesem Vorgang werden nur die Dateien aus dem Repository übernommen, die jüngeren Datums als die eigenen sind. Damit werden Inkonsistenzen vermieden. Schließlich könnten in der zwischenzeit auch mehrere Leute an der gleichen Datei gearbeitet haben. Nach dem erfolgreichen Update werden die Veränderungen in das Repository übertragen. Jedes künftige Auschecken oder Updaten erfolgt also dann mit der aktualisierten Version.

Natürlich ist das Arbeiten nicht nur für große Software-Entwicklergruppen interessant. Man findet sehr schnell weitere Anwendungsgebiete in denen eine Versionskontrolle und ein Versionsmanagement sinnvoll sein können, z.B. beim Erstellen und Verwalten von vielen Web-Seiten. Nach der schnöden einführenden Theorie (wat mutt, dat mutt), nun die praktische Anwendung. Mögliche Probleme oder Schwierigkeiten werden nun an den entsprechenden Stellen erläutert.

Einrichten des Repository

Wie schon oben erwähnt geht beim Concurrent Version System nichts ohne das Repository. Bevor wir allerdings zur Einrichtung eines solchen kommen, setzen wir noch eine Environment-Variable - das erleichtert die Arbeit ungemein. Dazu geben Benutzer der csh (oder einer ihrer Abkömmlinge) ein:

hdusel@sonja:~/developer/foo> setenv CVSROOT /pfad/zum/repository

oder User der bash

hdusel@sonja:~/developer/foo> export CVSROOT=/pfad/zum/repository

ein. Wird das Arbeiten mit CVS zum täglichen Bestandteil sollte man die Variable entsprechend in den Init-Skripten der Shells setzen. CVSROOT muß korrekt gesetzt werden, ansonsten kommt es z.B. zu solchen Fehlermeldungen:

hdusel@sonja:~/developer/foo> cvs checkout module
cvs checkout: No CVSROOT specified! Please use the '-d' option
cvs [checkout abborted]: or set the CVSROOT environment variable.

Tja, relativ aussagekräftig. Alternativ kann man also mit der '-d'-Option den CVS-Pfad explizit setzen. Dieser überschreibt auch eine möglicherweise gesetzte Environment-Variable. Zu Testzwecken könnte man jetzt also ein Verzeichnis anlegen, in dem unser Repository beheimatet sein soll. Im folgenden wird es sich um /home/developer/repository handeln.

Initialisieren des Repository

Vor der ersten Benutzung von CVS müssen wir natürlich zunächst festlegen, was überhaupt verwaltet werden soll. Dazu initialisieren wir in einem ersten Schritt das Repository:

hdusel@sonja:~/developer/foo> cd /home/developer/repository
hdusel@sonja:~/developer/foo> cvs init

Im Verzeichnis ist nun ein Unterverzeichnis CVS-ROOT erzeugt worden. Es ist unter anderem Heimat für diverse generierte Hilfsdateien. Im nächsten Schritt werden die Dateien des Verzeichnisses /home/developer/to-import/ unter CVS-Kontrolle gebracht. CVS findet über die Environment-Variable das Ziel.

hdusel@sonja:~/developer/foo> cd to_import
hdusel@sonja:~/developer/foo> cvs import -m ´Kommentar´ src_import tag1 tag2

Durch diese Operation wurde im Repository ein Verzeichnis src-import erzeugt, in dem nun sämtliche Dateien abgelegt sind. Sie haben alle die Endung ,v' bekommen und bei näherer Betrachtung mit einem Editor fällt auf, daß sie mit Verwaltungs-Informationen erweitert worden sind. Verzichtet man auf obige m-Option und den dazugehörigen Kommentar, wird automatisch ein Editor geöffnet. Dieser kann über die Environment-Variable CVSEDITOR gesetzt werden, ansonsten wird EDITOR ausgewertet. tagl und tag2 sind sogenannte Vendor- und Release-Tags und zwingend vorgeschrieben. Nähere Informationen dazu folgen im zweiten Teil dieses Kurses. Vorerst kann man sie nach Belieben setzen, z.B. ein login- oder Namenskürzel angeben.

Auschecken eines Projektes

Zum Anlegen eines lokalen Arbeitsverzeichnisses benutzt man das checkout-Kommando. Auf diese Art und Weise kann man beliebig oft den Inhalt des Repositories reproduzieren. Man legt in seinem Heimat-Verzeichnis beispielsweise das Verzeichnis project an und führt das Folgende aus:

hdusel@sonja:~/developer/foo> cd ~/project
hdusel@sonja:~/developer/foo> cvs checkout src_import
cvs checkout: Updatig src_import
U src_import/datei1.c
U src_import/datei2.c
U src_import/datei3.c
U src_import/datei4.c

Dann wird im Verzeichnis project das Unterverzeichnis src-import mit den entsprechenden Dateien angelegt. Diesen Vorgang kann man auch genauer speziflzieren. Bei Bedarf ist es z.B. über die Angabe eines Tags möglich, die beim Einchecken entsprechend markierte Datei auszulesen.

hdusel@sonja:~/developer/foo> cvs checkout -r tag1 src_import

Oder es sollen beispielsweise alle Dateien von gestern ausgecheckt werden:

hdusel@sonja:~/developer/foo> cvs checkout -D yesterday src_import

Dateien verändern

Jetzt arbeitet man also mit seinen und an seinen Dateien, entwickelt vielleicht sogar innerhalb einer größeren Gruppe und nun stellt sich natürlich die Frage: Wie werden die Dateien konsistent gehalten bzw. wie befördert man seine veränderten Files wieder in das Repository? Im schwierigsten Fall haben ja zwei oder mehr Programmierer am gleichen Source-File gearbeitet. Wessen Veränderungen haben die höchste Priorität? Nun, CVS begegnet dieser Problematik zunächst einmal damit, daß man gezwungen wird vor dem Einchecken, dem sogeannten committen, ein Update seines Arbeitsverzeichnisses durchzuführen. Versucht man eine Datei einzuchecken, die zwischenzeitlich von jemandem erneuert worden ist, erhält man eine Fehlermeldung:

hdusel@sonja:~/developer/foo> cvs commit datei4.h
cvs commit datei4.h
cvs commit: Up-to-date check failed for ´datei4.h´
cvs [commit aborted]: correct above errors first!

Man wird also aufgefordert, zunächst ein Update vorzunehmen. Nun denn, nichts leichter als das.

hdusel@sonja:~/developer/foo> cvs update
cvs update
cvs update: Updating
RCS file: /home/developer/repository/src_import/datei4.h,v
receiving revision 1.1.1.1
retreiving revision 1.2
Merging differences between 1.1.1.1 and 1.2 into datei4.h
M datei4.h

Dieses Updaten verlief ohne Probleme, daß heißt die veränderte Datei datei4.h aus dem Repository konnte ohne Konflikte mit der Arbeitskopie verschmolzen werden. Dieses Verfahren hat sich in der Praxis als relativ zuverlässig erwiesen. Man sollte natürlich an dieser Stelle Vorsicht walten lassen und nach Merging-Meldungen anschliessend überprüfen, ob sich die neu entstandenen Files immer noch kompilieren lassen (im Falle der Verwaltung von Source-Codes). Verläuft das Verschmelzen allerdings nicht reibungslos, wird man von CVS durch Konflikt-Meldungen darauf aufmerksam gemacht.

hdusel@sonja:~/developer/foo> cvs update
cvs update: Updating
RCS file: /home/developer/repository/src_import/datei4.h,v
retrieving revision 1.1.1.1
retrieving revision 1.2
Merging differences between 1.1.1.1 and 1.2 into datei4.h
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in datei4.h
C datei4.h

CVS meldet in diesem Fall also, daß es nicht in der Lage war, die beiden verschiedenen Dateien zusammenzufügen und legt beide Varianten, entsprechend markiert, in der betreffenden Datei ab. Nun bleibt es dem Programmierer überlassen, den Inhalt zu überprüfen und anschliessend die Datei einzuchecken. Was in diesem Fall zu tun ist erfahren sie weiter unten im Abschnitt Konflikte lösen. Wurde eine Datei, die man nur importiert, selbst aber gar nicht editiert, inzwischen verändert, wird sie einfach aktualisiert. CVS macht uns durch verschiedene Meldungen darauf aufmerksam. Wie oben zu sehen, steht am Anfang der Zeile vor jeder Datei ein einzelner Buchstabe. Dabei steht U für Update, M für Modified und C für Conflict. Durch das M wird angezeigt, daß man selbst als letzter die Datei bearbeitet hat und etwaige Änderungen allen anderen noch nicht bekannt sind. Denn, daß sollte man sich immer wieder klar machen, diese Aktionen betreffen und manipulieren zunächst nur die Files im eigenen Arbeitsverzeichnis.

Veränderungen untersuchen

In jedem von uns steckt ein gewisser Grad an Neugier - beim einen mehr, beim anderen weniger. Jetzt beschäftigt uns allerdings die Frage, was zwischen verschiedenen Revisionen passiert ist. Was macht den Unterschied zwischen 1.57 und 1.58 aus? Dazu verwendet man das CVS-Kommando log (wie überraschend!).

hdusel@sonja:~/developer/foo> cvs log datei1.c
RCS file: /home/developer/repository/src_import/datei1.c,v
Working file: datei1.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
tag2: 1.1.1.1
tag1: 1.1.1
keyword substitution: kv
total revisions: 3; selected revisions: 3
description:
------------
revision 1.2
date: 1998/07/12 13:22:02; author: developer; state: Exp; lines: +3 -0
mkr
------------
revision 1.1
date: 1998/07/12 12:41:47; author: developer; state: Exp;
initial revision
------------
revision 1.1.1.1 date: 1998/07/12 12:41:47; author: developer; state: Exp; lines: +0 -0
Kommentar
===========================================================================

Liest man die geballte Information von unten nach oben wird einem das Prinzip schnell klar. Kommentar aus der letzten Zeile dürfte uns vom Anlegen des Repository bekannt sein. Wer sich hier für die Details interessiert, sei auf das CVS-Manual verwiesen. Vielleicht nur so viel: Bei Revision 1.2 folgt in der nächsten Zeile zunächst der genaue Zeitpunkt der Vänderung sowie der Autor des Files. In der folgenden Zeile steht der Log-Kommentar, der beim commit-Vorgang im Editor eingegeben wurde. In diesem Fall nicht sonderlich aussagekräftig. Interessiert man sich für bestimmte Aktionen im Detail, hat man nun mit Hilfe des diff-Befehls die Möglichkeit sich genauestens zu informieren.

hdusel@sonja:~/developer/foo> cvs diff -c -r 1.1 -r 1.2 datei1.c
Index: datei1.c
====================================================================
RCS file: /home/developer/repository/src_import/datei1.c,v
retrieving revision 1.1
retrieving revision 1.2
diff -c -r1.1 -r1.2
*** datei1.c 1998/07/12 12:41:47 1.1
--- datei1.c 1998/07/12 13:22:03 1.2

Die Option -c steht dabei für ein etwas ausführlicheres Format, die beiden folgenden -r spezifizieren die gewünschten Revisionen. Hier lassen sich auch größere Abschnitte auswählen. Schaut man sich die Ausgabe genauer an, so erkennt man folgendes System. Die Sterne *** kennzeichnen die ältere Version der Datei. In diesem Fall war es also so, daß die anfangs leere Datei beim Sprung von Version 1.1 nach 1.2 um die Zeilen 1-3

+ /* Einfache Beispieldatei */
+
+ /* Ein anderer Entwickler veraendert diese Datei. */

ergänzt wurden. Benutzern des patch-Programms dürfte dies bekannt vorkommen.

Konflikte lösen

Nein, hier findet man sich nicht in einer NachmittagsTalkshow wieder. Unser Augenmerk liegt auf Merging-Konflikten, die wie oben erwähnt, beim update Befehl auftreten können. Sie treten nur dann auf, wenn man einen Abschnitt einer Datei editiert hat, der gleichzeitig auch von einem anderen Entwickler bearbeitet wurde. Zur Erinnerung sehen wir uns die zweite Update-Meldung an (siehe oben). Die Datei wurde durch ein C am Zeilenanfang gekennzeichnet.

<<<<<<<<<< datei4.h
/* Dies ist eine Beispieldatei */
/* Hier wurde sie auch editiert, aber anders. */
======
/* Dies eine Beispieldatei */
/* Und hier wurde sie zum ersten Mal verändert. */
>>>> 1.2

Von <<<<<<<< datei4.h angefangen bis zu ======= sieht man den eigenen Teil, daran anschliessend bis >>>> 1.2 den Teil eines anderen. In diesem Fall handelt es sich nur um unterschiedliche Kommentar-Zeilen. Ist aber Source-Code verändert worden, muß man sich eine Lösung des Problems, eventuell in Absprache mit dem anderen Entwickler, überlegen. Nach der Anpassung löscht man noch die Markierungen und nun steht einem commit nichts mehr im Wege.

hdusel@sonja:~/developer/foo> cvs commit datei4.h
Checking in datei4.h;
/home/developer/repository/src_import/datei4.h,v <- datei4.h
new revision: 1.3; previous revision: 1.2
done

Wie schon gewohnt wird der Editor aufgerufen und man ist aufgefordert einen Kommentar einzugeben.

Dateien hinzufügen oder löschen

Für CVS bedeutet das Löschen oder Hinzufügen von Dateien nichts anderes als eine Änderung am bestehenden Datenbestand. Das hat auch einen einleuchtenden Grund. Wie schon erwähnt ist es ja unter CVS möglich, auf beliebige Versionen einzelner Dateien zurückzugreifen.

Das heißt, man kann z.B. nach Belieben eine lauffähige Variante seines Programms auschecken ohne auf aktuellere, aber möglicherweise fehlerbehaftete Teile Rücksicht nehmen zu müssen. Nun kann es ja durchaus vorkommen, daß in dieser Version Dateien vorhanden sind, die in der weiteren Entwicklung wieder verschwunden sind. Sie werden aber weiterhin mitverwaltet und existieren immer noch im Repository, auch wenn sie beim jetzigen Checkout nicht mehr auftauchen würden. Ebenso weiß CVS nichts von neuen Dateien, die lediglich im Arbeitsverzeichnis, aber noch nicht im Repository auftauchen. Diese müssen zunächst mit einem add hinzugefügt und dann mit dem schon bekannten commit-Befehl eingecheckt werden.

hdusel@sonja:~/developer/foo> cvs add datei5.c
cvs add: scheduling file ´datei5.c´ for addition
cvs add: use ´cvs commit´ to add this file permanently
hdusel@sonja:~/developer/foo> cvs commit
/home/developer/repository/src_import/datei5.c,v <- datei5.v
initial revision: 1.1
done

Wie man erkennen kann, beginnt jede Datei mit der initialen Revisionsnummer 1. 1. Das Löschen funktioniert ganz ähnlich und wenn man das zugrundeliegende Konzept von CVS verstanden hat, kann man sich schon vorstellen, wie es funktioniert. Zunächst löscht man mit Hilfe des gewohnten Unix-Befehls rm die Datei aus seinem Arbeitsverzeichnis. Anschließend markiert man sie zum Löschen. Durch Ausführen des commit-Befehls wird die Datei endgültig entfernt. Mit endgültig ist es diesem Fall nur gemeint, daß sie beim nächsten checkout bzw. update nicht mehr erscheint. In diesem Zusammenhang kann man sich diese Eigenschaft von CVS praktisch zunutze machen. Möchte oder muß man die Veränderungen, die man an einer Datei vorgenommen hat, rückgängig machen, sozusagen ein Undo ausführen, löscht man die entsprechende Datei einfach aus seinem Verzeichnis und führt dann den update-Befehl aus. CVS erkennt, daß eine Datei fehlt und man bekommt das File in seinem aktuellsten Zustand.

Komplizierter wird da schon ein Umbenennen von Files. Eine mögliche Lösung in diesem Fall ist mit den schon bekannten Kommandos zu erreichen. Zuerst gibt man der entsprechenden Datei einen neuen Namen. Im Anschluß muß man nun die alte Version aus dem Repository löschen. Dazu dient ja der rm Befehl.

Natürlich darf man nicht vergessen, das neu benannte File mit Hilfe des add-Kommandos dem Repository hinzuzufügen und die gesamte Aktion mit einem commit abzuschliessen. Nicht sehr elegant und auch mit einem Nachteil behaftet, denn die Log-Daten der Datei werden natürlich nicht übernommen.

Ein anderer Fall tritt auf, wenn man eine neue Datei in seinem Arbeitsverzeichnis angelegt hat und dann bewußt oder unbewußt cvs update ausführt. In diesem Fall steht CVS vor einem Problem und es gibt ein ? gefolgt vom entsprechenden Dateinamen aus.

Dokumentieren ist alles

Wie schon weiter oben angekündigt sind die Kommentare, die verschieden CVS-Befehle verlangen, in ihrer Bedeutung nicht zu unterschätzen. Ist man alleiniger Benutzer von CVS und verwaltet möglicherweise einen kleinen Baum von Web-Seiten mit Hilfe dieses Utilities, kann man natürlich durchaus auch über einen längeren Zeitraum den Überblick behalten. Arbeiten aber mehrere Menschen an einem SoftwareProjekt oder zieht sich ein umfangreiches womöglich über Jahre hinweg, sind prägnante Dokumentationen unerläßlich. Man hat im Prinzip zwei Möglichkeiten, um unter CVS Informationen über ältere Versionen von Dateien zu bekommen. Zum einen gibt es ja das log-Kommando und zum anderen diff. Bei letzterem hat man dann sogar die Möglichkeit sich die gesamte Veränderung im Detail zu betrachten. Aus diesem Grund ist es sinnvoll, aussagekräftige Formulierungen in allgemeinverständlicherweiser Sprache als log zu verwenden und sich nicht in die konkrete Implementierung zu vertiefen.

Verwaltung mit Tags

Es wurde ja schon erwähnt, daß ein wichtiger Vorteil der Versionsverwaltung von CVS darin liegt, jederzeit Zugriff auf den älteren Stand einer Entwicklung zu haben. So kann man sehr einfach, sobald man beispielsweise einen stabilen Zustand seiner Software erreicht hat, das komplette Paket mit einem Tag markieren und ist nun immer in der Lage, genau diese Version wieder auszuchecken.

hdusel@sonja:~/developer/foo> cvs tag release-1-0 .
cvs tag: Tagging .
T datei1.c
T datei2.c
T datei3.c
T datei4.c
T datei5.c

An strategisch wichtigen Punkten in der Entwicklung eines Projektes (in diesem Fall einem Release) ist es sinnvoll nicht nur einzelne Dateien, sondern wie in diesem Fall ein gesamtes Verzeichnis zu markieren. Hier lautet es release-1-0 und ab jetzt ist es möglich über die Angabe dieses Tags alle obigen Dateien im jetzigen Zustand zu erhalten. Der Befehl status mit der Option -v zeigt einem ausführlich die entsprechenden Markierungen einer Datei.

hdusel@sonja:~/developer/foo> cvs status -v datei4.h
==========================================================
File: datei4.h Status: Up-to-date

Working revision: 1.3 Sun Jul 12 14:25:11 1998
Repository revision 1.3
/home/developer/repository/src_import/datei4.h,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

Existing Tags:
release-1-0 (revision: 1.3)
tag2 (revision: 1.1.1.1)
tag1 (revision: 1.1.1)

Diese Seite wurde am 03.05.2003 um 14:01 Uhr das letzte mal bearbeitet.