Wer schon länger programmiert wird zwei grundsätzliche Probleme kennen2.1:
Gestern hatte man eine funktionierende aber stinklangweilige Version seines Programmes. Heute hat man Zeit, also versucht man das Programm aufzupeppen. Doch irgendetwas geht schief und nichts läuft mehr. Doch wie herausfinden, was sich alles geändert hat? Woher die alte Version bekommen, die noch ging, wenn das letzte Backup mehrere Wochen alt ist?
Zwei (oder mehr) Programmierer arbeiten am selben Projekt. Doch irgendwann wird es notwendig eine bestimmte Datei zu ändern, dummerweise bei beiden. Die Aufgabe des Tages besteht nun darin die Änderungen des jeweils anderen zu finden und beide Versionen zu vereinen. Das ist eine Aufgabe, die allzu oft schwieriger zu lösen ist, als die Modifikation selbst es war.
Eine Versionsverwaltung kann helfen beide Probleme zu lösen.
Das erste, indem der Programmierer ältere Versionen seiner Dateien aus der Verwaltung holen kann. Er kann dann relativ leicht vergleichen, was sich in der Zwischenzeit geändert hat, meist gibt das einen recht guten Hinweis auf das Problem. Natürlich muss er sich daran erinnern seine Zwischenschritte in die Versionsverwaltung zu speichern. Ein Backup würde den selben Effekt haben, ein ``check-in'' Kommando ist jedoch meist schneller getippt als ein Backup (bei dem man ja mindestens noch einen Datenträger einlegen muss). Der Vorteil gegenüber einem Backup ist zudem noch, dass eine Versionsverwaltung jederzeit zur Verfügung steht, weiter zurück reichen kann als Backups und weniger Speicherplatz verbraucht2.2.
Das zweite Problem wird gelöst, indem entweder nur einem Entwickler zugleich Zugriff auf eine Datei gegeben wird oder die Versionsverwaltung Algorithmen kennt, mit denen sie automatisch Änderungen vereinen kann.
CVS steht für ``Concurrent Versions System''. Es besitzt sehr effektive Algorithmen zur Speicherung von Textdateien und zum Abgleich von Änderungen in der selben Datei2.3. Dateien werden hier nicht gesperrt2.4. Es eignet sich also recht gut in Projekten, in denen sehr viele Entwickler arbeiten, da sie sich nicht gegenseitig aus dem System aussperren können.
Ich werde hier die grundlegenden Kommandos von CVS vorstellen. Weitere Möglichkeiten finden sich in der zu CVS mitgelieferten Dokumentation.
CVS besitzt selbst keine Rechte-Verwaltung2.5. Es benutzt einfach die normalen Zugriffsrechte von Unix. Wenn ein Nutzer nicht auf die Dateien von CVS zugreifen kann, dann kann er sie auch nicht durch CVS erhalten
Normalerweise wird man daher pro Projekt ein CVSROOT2.6 aufsetzen. Ein Server ist für CVS ein Pfad, in dem es einige administrative Dateien für die eigene Verwaltung2.7 und weitere Repositories anlegt. Ein Repository ist physisch nichts weiter, als ein Pfad, in dem die Dateien und Verzeichnisse eines Quelltextbaumes abgelegt werden. Für jedes Sub-Projekt wird man in der Regel ein Repository anlegen2.8. Innerhalb eines Repositories dürfen beliebig viele weitere Dateien und Verzeichnisse existieren2.9.
Die von CVS verwalteten Dateien und Verzeichnisse sollte man normalerweise auch CVS überlassen. Man arbeitet mit lokalen Kopien im eigenen Verzeichnis.
In der lokalen Kopie wird man neben den normalen Dateien, welche man ja schon gewöhnt ist, noch ein Verzeichnis namens ``CVS'' finden. Die Dateien dort drin sollte man ganz in Ruhe lassen, das sind Dateien in denen CVS unter anderem speichert, wo sich der Server und das Repository befinden. Das zentrale Repository enthält die selbe Verzeichnisstruktur2.10 und die selben Dateien, nur um die Endung ``,v'' erweitert und in einem Format, das alle Versionen der Datei gleichzeitig speichert.
Grundsätzlich kann man jede Art von Datei über CVS verwalten, bei nicht-ASCII-Dateien kann es jedoch seinen Abgleich-Algorithmus nicht einsetzen und der Nutzer muss die Dateien selbst abgleichen.
Nach all der Theorie mal etwas praktisches. Wir setzen uns schrittweise einen eigenen CVS-Server auf.
Zunächst bereiten wir uns ein Verzeichnis für CVS vor. Wir legen einfach
einen Pfad an:
mkdir ~/cvstest
in diesem Verzeichnis werden wir unsere Experimente ablegen. Dieses
Verzeichnis muss nun noch auf CVS vorbereitet werden:
cvs -d ~/cvstest init
dieses Kommando wird das Repository CVSROOT in ~/cvstest anlegen, das die
administrativen Dateien von CVS enthält. Das ist alles, was nötig ist, um
eine lokale Versionsverwaltung einzurichten.
Beginnen wir mit der Programmierung2.11, für den Anfang dürfte ein Verzeichnis mit ein paar Text-Dateien (einfacher ASCII-Text!) genügen. Vor dem Import in CVS sollte das Verzeichnis aufgeräumt werden -- es sollten alle Dateien gelöscht werden, die aus anderen Dateien erneut (automatisch) erstellt werden können, also alle compilierten Objektdateien und alle Programme. Im Idealfall bleiben nur einfach Textdateien übrig2.12. Nun werden die Dateien importiert:
cvs -d ~/cvstest import -m "starte test-Projekt" \ testpro testpro startman muss dazu in dem Verzeichnis sein, aus dem die Quelldateien importiert werden sollen. Dieses Kommando wird nun das Repository ``testpro'' anlegen, den Dateien das Tag ``start'' geben und die Nachricht ``starte test-Projekt'' als ``"Anderungsnotiz'' eintragen.
Nun wird das ursprüngliche Verzeichnis irgendwohin verschoben, wo es sicher
ist (falls etwas schiefgegangen ist). Danach können wir das neue Repository
``auschecken''2.13:
cvs -d ~/cvstest checkout testpro
dadurch wird im aktuellen Verzeichnis ein Unterverzeichnis ``testpro''
angelegt, in dem sich eine lokale Kopie der im Repository
``testpro'' verwalteten Dateien befindet. Mit dieser Kopie kann man wie
gewohnt arbeiten, mit dem Unterschied, dass man zusätzlich noch alle
Möglichkeiten von CVS nutzen kann. Man kann übrigens beliebig viele Kopien in
verschiedene Verzeichnisse auschecken.
In den Befehlen oben wurde immer der Parameter ``-d'' angegeben. Dieser
Parameter sagt CVS, wo es nach dem CVSROOT-Verzeichnis zu suchen hat. Man kann
sich die Arbeit vereinfachen, indem man stattdessen die Shell-Variable CVSROOT
setzt:
export CVSROOT=~/cvstest
Um neue Dateien/Versionen aus dem Repository zu holen hat man nun zwei
Möglichkeiten. Zum einen kann man im übergeordneten Verzeichnis erneut
cvs checkout
aufrufen, dann müssen allerdings CVSROOT und Repository
angegeben sein, zum anderen kann man innerhalb seiner lokalen Kopie auch
cvs update
aufrufen (ohne weitere Parameter oder mit einem Dateinamen,
der ausgecheckt werden soll). Die zweite Möglichkeit hat den Vorteil, dass
man sich nicht um den Namen des Repositories und seine Position kümmern muss,
die erste hingegen checkt auch neue Verzeichnisse aus. In beiden Fällen
versucht CVS die lokale Version mit der Version im Repository abzugleichen.
Gelingt dies nicht gibt CVS eine Warnung aus und schreibt beide Versionen in
die Datei, der Nutzer muss den Konflikt nun von Hand auflösen.
Nachdem wir Änderungen an den Dateien vorgenommen haben soll die neue Version
natürlich eingecheckt werden, man ruft einfach cvs commit
in seiner
lokalen Kopie auf. CVS wird den Nutzer nun auffordern einen Kommentar für das
Logbuch zu schreiben, optional kann man diesen Kommentar auch gleich mit dem
Parameter -m "Kommentar"
angeben. Normalerweise gibt man an, welche
Änderungen man gemacht hat -- allerding als kurzen für Menschen
verständlichen Satz (a'la ``Fehlerkorrektur in der Tastaturbehandlung''), was
sich im Quelltext geändert hat kann sich schließlich jeder mit Zugriff auf
das Repository selbst ansehen (und andere bekommen auch keinen Zugriff auf das
Logbuch). Wenn man mit mehreren Entwicklern zusammen arbeitet sollte man
vorher außerdem ein cvs update
machen, um alle Änderungen der Anderen
in seiner lokalen Kopie zu haben, denn falls Konflikte auftreten müssen diese
immer lokal aufgelöst werden.
Wenn man mit mehreren anderen Programmierern zusammenarbeitet sollte man vor jedem commit sicherstellen, dass die Version, die man eincheckt auch funktioniert (oder mindestens compiliert und gelinkt werden kann). Ansonsten hält man die anderen mit Fehlern auf, die sie gar nicht verursacht haben und erzeugt so eine Menge Frust.
Unser Projekt wächst2.14. Es kommen einige Dateien hinzu, andere verschwinden wieder. Das sollte sich natürlich auch im Repository widerspiegeln. Um eine Datei zum Repository hinzuzufügen geben wir in dem Verzeichnis, in dem sie lokal liegt einfach cvs add <dateiname> ein. Um eine Datei zu entfernen wird sie zuerst lokal gelöscht und dann cvs add <dateiname> eingegeben. Da ältere Versionen des Repositories erhalten bleiben müssen wird die Datei nicht wirklich aus dem Repository gelöscht, sondern in ein Verzeichnis namens Attic verschoben.
Um den Unterschied zwischen zwei Versionen einer Datei zu sehen kann man mit cvs diff -r version1 [version2] dateiname herausfinden.
Den Status einer Datei kann man mit cvs status [-v] dateiname erfragen. CVS zeigt dann Daten, wie letzten Zugriff, aktuelle Version, Bearbeitungsstatus (ob die Datei lokal oder bei jemand anderes verändert wurde) und (mit -v) die aktuellen Tags (siehe Abschnitt ) an.
cvs tag release_1_1 [datei/verzeichnis]
Wird ein Verzeichnis (kein Parameter bedeutet ``.'') angegeben, läuft CVS rekursiv durch das Verzeichnis und markiert alle Dateien mit diesem Tag. Man sollte beachten, dass die Version im Repository das Tag erhält, die der aktuell ausgecheckten Version am nächsten ist (also, die die man zuletzt ausgecheckt hat), nicht die Version im lokalen Verzeichnis, man sollte also vorher ein cvs commit anschieben, um mit dem Repository synchron zu sein. Neben dem Kommando cvs tag gibt es noch cvs rtag, das genauso funktioniert aber die neueste Version im Repository kennzeichnet.
Oftmals hat man mehrere Versionen eines Projektes, die sich (teilweise) parallel Entwickeln2.15. Auch das läßt sich mit CVS verwalten. Dazu existieren die sog. Branches (eng. Zweig). Sie können mit dem Tag-Kommando angelegt werden, bekommen jedoch den Parameter -b. Das hat mehrere Effekte: das Tag bleibt über mehrere Versionen erhalten (deswegen oft auch ``sticky tag'' genannt) und die CVS-interne Versionsnummer wird um zwei Stellen erweitert2.16. Um einen bestimmten Branch auszuchecken gibt man bei cvs checkout einfach den Parameter -r tagname an.
Auf einer einzelnen lokalen Station macht CVS natürlich noch nicht sehr viel Sinn2.17. Richtig interessant wird CVS erst, wenn mehrere Entwickler über Netzwerke verteilt arbeiten können2.18.
CVS bietet zwei grundlegende Möglichkeiten des Netzwerkzugriffs: pserver und rsh2.19. PServer ist ein CVS-eigenes Protokoll, das über TCP/IP ein lokales CVS und einen CVS-Server verbindet. Rsh nutzt eine einfache remote-shell-Verbindung, um auf dem Server eine lokale CVS-Instanz aufzurufen.
Um CVS mitzuteilen, dass eine Netzwerkverbindunge gewünscht wird, erweitert
man den Pfad in CVSROOT:
:Methode:user@servername:/Pfad/zu/xvsroot
als Methode kann man pserver oder ext (die remote Shell
wird genutzt) angeben (wenn man es weglässt wird automatisch ext
angenommen). Ein Server kann natürlich beide Möglichkeiten parallel anbieten.
PServer ist eine relativ einfach und flexibel handhabbare Lösung, da ausser CVS und dem inetd keine weiteren Programme gebraucht werden. Allerdings braucht man auf dem Rechner, auf dem man es installiert root-Rechte. Verwandeln wir unser lokales CVS-Repository also in einen PServer:
Zuerst wird das CVS-PServer-Protokoll in /etc/services eingetragen:
cvspserver 2401/tcp
Jetzt richten wir den Server in /etc/inetd.conf ein:
cvspserver stream tcp nowait.400 root /usr/bin/cvs \ --allow-root=/home/konrad/cvstest pserverder Parameter -allow-root muss natürlich auf das wirkliche CVSROOT eingestellt werden.
Inetd muss nun neu gestartet werden2.20.
Da PServer die Authentifikation der Nutzer selbst vornimmt muss es eine eigene Passwort-Datei bekommen. Diese Datei sieht der Datei /etc/passwd ähnlich. Sie enthält Nutzernamen, ein Passwort und optional einen Nutzernamen, der für die jeweiligen Aktionen genutzt werden soll. CVS läuft nach wie vor mit den Unix-Rechten des jeweiligen Nutzers. Existiert ein Nutzername nicht im System muss also ein Nutzer angegeben werden, auf den umgeschaltet wird, wenn sich jemand mit dieser Kennung beim PServer anmeldet. Diese Passwort-Datei wird in $CVSROOT/CVSROOT/passwd gespeichert und kann etwa so aussehen:
cvsuser:R6bbk4fEhooo6:xtreme konrad:JRVoCn.TiPmZw anonymous:5hKlsZ6c3gNFQ:nobody
Wenn diese Datei existiert kann der PServer verwendet werden. $CVSROOT muss
nur auf den entsprechenden Wert gesetzt werden:
CVSROOT=:pserver:cvsuser@meinrechner:/home/konrad/cvstest
würde zum Beispiel dazu führen, dass CVS auf dem Rechner ``meinrechner'' mit
den Rechten von xtreme benutzt würde. Würde der Username ``konrad'' benutzt
wird CVS nach einem System-Nutzer ``konrad'' suchen und diesen benutzen. Doch
vorher muss man sich mit dem Kommando cvs login anmelden, dabei
wird das Passwort auf dem Server geprüft und verschlüsselt2.21 in ~/.cvspass gespeichert. Die passwd-Datei kann mit Tools wie mkpasswd oder
htpasswd erstellt werden. Wer keines der Tools hat kann auch die
Passwort-Strings aus /etc/passwd kopieren oder die obigen2.22.
Da die Zugriffsrechte von CVS-Nutzern sowieso durch deren Unix-Rechte bestimmt werden kann man auch gleich Shell-Accounts benutzen, um CVS dort hindurch zu ``tunneln''. Diese Idee wird von der Rsh-Methode umgesetzt. Dazu muss auf dem Server die Datei .rhosts im Homeverzeichnis des Nutzers oder /etc/rhosts existieren. Dort ist angegeben, von welchen Rechnern aus rsh benutzt werden darf. Man kann natürlich jedes andere Programm benutzen, das die selben Parameter wie rsh entgegennimmt und den selben Effekt (remote-Zugriff auf Shell) hat, dieses Programm kann man in die Variable $CVS_RSH eintragen2.23.
Der Vorteil dieser Methode ist, dass die Authentifikation externen Programmen überlassen werden kann. CVS arbeitet dann mit den Rechten, die ihm von dem externen Programm gegeben werden.
Die Arbeit für diese Methode beschränkt sich also auf das Installieren von rsh und der entsprechenden rhosts-Dateien. Der Rest wird vom Client gesteuert. Natürlich muss für jeden CVS-Nutzer ein entsprechender Unix-Account existieren.
Die beiden oben vorgestellten Methoden sind natürlich alles andere als sicher, wenn über größere Entfernungen oder auch nur innerhalb einer großen Organisation (wie zum Beispiel einer Hochschule) gearbeitet wird. Die Absicherung der CVS-Kommunikation sollte zwei wichtige Schritte beinhalten:
Sowohl auf dem Server, als auch auf dem Client muss dann natürlich ssh installiert sein2.24.
Für Open Source Projekte ist natürlich ein anonymer Zugang interessant. Viele Nutzer werden immer die neueste Version ausprobieren wollen (aus welchen Gründen auch immer). Das läßt sich am einfachsten über einen PServer erledigen. Oben habe ich schon einen Account mit dem Namen anonymous (oft wird auch ``cvs'' als Nutzername benutzt) und leerem Passwort angelegt, der auf nobody gemappt wird. Nobody ist normalerweise der Account im System, der freigegebene Dateien und Verzeichnisse zwar lesen, aber nicht schreiben kann (daher sollten die Repositories auch nur Schreibrecht für die jeweilige CVS-Gruppe bieten). Das genügt im Grunde schon für einen anonymous-Zugang, um sich aber gegen eventuelle Konfigurations-fehler abzusichern sollte man diesen Nutzer in die Datei $CVSROOT/CVSROOT/readers eintragen:
anonymous
Wichtig ist dabei auch das abschließende Newline2.25. Alle Nutzer, die in diese Datei eingetragen sind dürfen keine schreibenden Kommandos ausführen (wie commit oder tag).