Kefk Network :  Open Source & Free Software |  About |  Wiki. Suchen | Index | Inhalt | Site Map | What's New?
Akteure | Geschichte | Freie Software | Open Source Software | Projekte | Ressourcen | Sammlungen.

 
Eine Übersicht über CVS
CVS-Grundlagen

Dieses Kapitel führt in die grundlegenden Konzepte von CVS ein und gibt dann eine tiefer gehende Erläuterung des täglichen CVS-Einsatzes. Nach dessen Lektüre sind Sie auf dem besten Wege, ein CVS-Guru zu werden.

Wenn Sie noch nie CVS (oder ein anderes Versionsverwaltungssystem) benutzt haben, kann es leicht sein, dass Sie durch einige der zu Grunde liegenden Annahmen ins Stolpern geraten. Was anfänglich anscheinend für die meiste Verwirrung sorgt ist, dass CVS für zwei scheinbar unabhängige Aufgaben eingesetzt wird: Historienverwaltung und Zusammenarbeit. Es zeigt sich jedoch, dass diese beiden Funktionen eng miteinander verbunden sind.

Die Historienverwaltung wurde notwendig, weil Benutzer den momentanen Zustand eines Programmes mit dem an einem bestimmten Zeitpunkt der Vergangenheit vergleichen wollten. Zum Beispiel bringt ein Programmierer während der Implementation einer neuen Funktion das Programm in einen ziemlich fehlerhaften Zustand, in dem das Programm auch wahrscheinlich erst einmal bleiben wird, bis diese neue Funktion endgültig fertig implementiert ist. Unglücklicherweise ist genau dies meist der Zeitpunkt, zu dem ein Benutzer die Beschreibung eines Fehlers in der letzten veröffentlichten Version einschickt. Um diesen Fehler zu finden (der auch in der aktuellen Entwicklerversion enthalten sein kann), muss das Programm zuerst wieder in einen benutzbaren Zustand gebracht werden.

Diesen Zustand wiederherzustellen stellt dann kein Problem dar, wenn die Historie zu den Quelltexten mit CVS verwaltet wird. Ein Entwickler kann einfach sagen: »Gib mir den Quelltext, wie er vor drei Wochen war«, oder vielleicht: »Gib mir den Quelltext so, wie er war, als wir die letzte öffentliche Version freigegeben haben.« Wenn Sie bisher noch nie einen so praktischen Zugriff auf frühere Versionen hatten, werden Sie überrascht sein, wie schnell man davon abhängig werden kann. Persönlich verwende ich eine Revisionskontrolle bei allen meinen Programmierprojekten - dies hat mich schon oft gerettet.

Um zu verstehen, was dies mit der Unterstützung der Zusammenarbeit zu tun hat, müssen wir zunächst die Mechanismen etwas näher betrachten, mit denen CVS es ermöglicht, dass mehrere Personen zusammen an einem Projekt arbeiten. Doch zuvor sehen wir uns einen Mechanismus an, den CVS nicht bietet (oder der zumindest nicht zu empfehlen ist): Dateisperren. Wenn Sie bereits andere Versionsverwaltungssysteme benutzt haben, werden Sie schon mit dem Entwicklungsmodell Sperren-Ändern-Freigeben vertraut sein, bei dem ein Entwickler zuerst den exklusiven Schreibzugriff auf die zu bearbeitende Datei (eine Sperre) bekommen muss, die Veränderungen anbringt und dann die Sperre wieder freigibt, damit andere Entwickler auf diese Datei zugreifen können. Wenn jemand anders bereits eine Sperre für diese Datei gesetzt hat, so muss er diese zuerst wieder freigeben, bevor man selbst eine Sperre setzen und Veränderungen anbringen kann. (In manchen Implementationen kann man diese Sperre auch »stehlen«, was aber für den anderen eine böse Überraschung und außerdem kein guter Stil ist!)

Dieses System ist dann brauchbar, wenn sich die Entwickler kennen, wissen, wer was zu einem bestimmten Zeitpunkt machen möchte, und, im Falle von Zugriffskonflikten, schnell miteinander kommunizieren können. Wenn jedoch die Entwicklergruppe zu groß wird oder zu weiträumig verstreut ist, knabbert die Verwaltung der Sperren an der eigentlichen Arbeitszeit; dies wird zu einem ständigen Problem und entmutigt viele, wirkliche Arbeit zu leisten.

CVS verfolgt einen ausgereifteren Ansatz. Anstatt von den Entwicklern zu verlangen, sich gegenseitig zu koordinieren, erlaubt CVS den Entwicklern gleichzeitiges Arbeiten, übernimmt die Integration der Veränderungen und behält mögliche Konflikte im Auge. Dieser Prozess benutzt das Kopieren-Modifizieren-Zusammenfassen-Modell, das wie folgt funktioniert:

  1. Entwickler A fordert eine Arbeitskopie von CVS an (ein Verzeichnisbaum, der alle Dateien eines Projektes enthält). Dies wird auch »Checking out« einer Arbeitskopie genannt, wie das Ausleihen eines Buches aus einer Bibliothek.
  2. Entwickler A arbeitet frei an seiner Arbeitskopie. Zum gleichen Zeitpunkt können auch andere Entwickler an ihren eigenen Arbeitskopien fleißig sein. Weil alle Kopien unabhängig voneinander sind, gibt es auch keine Konflikte - es ist so, als hätten alle Entwickler ihre eigene Kopie des gleichen Buches aus der Bibliothek, und sie alle schreiben, unabhängig voneinander, Kommentare an die Ränder oder bestimmte Seiten vollständig neu.
  3. Entwickler A beendet seine Veränderungen und sendet diese mit einer »Log-Nachricht«, also einem Kommentar, der beschreibt, was der Zweck der Veränderungen war, an den CVS-Server (commit). Dies ist damit vergleichbar, die Bibliothek darüber zu informieren, welche Veränderungen gemacht wurden und warum. Die Bibliothek lässt diese wiederum in eine Hauptkopie einfließen, wo sie damit für alle Zeit aufgezeichnet werden.
  4. In der Zwischenzeit können andere Entwickler CVS dazu veranlassen, die Bibliothek abzufragen, um herauszufinden, ob die Hauptkopie in jüngster Zeit verändert wurde. Ist dem so, aktualisiert CVS automatisch deren Arbeitskopie. (Dieser Teil grenzt an Magie und ist einfach wunderbar, ich hoffe, Sie wissen dies zu schätzen. Stellen Sie sich vor, wie anders die Welt wäre, wenn echte Bücher so funktionieren würden!)

Soweit es CVS betrifft, sind alle Entwickler eines Projektes gleich. Zu entscheiden, wann ein Commit oder eine Aktualisierung durchgeführt wird, ist eine Sache der persönlichen Einschätzung oder Projektregeln. Eine übliche Strategie bei Programmierprojekten ist es, immer eine Aktualisierung zu machen, bevor die Arbeit an größeren Veränderungen begonnen wird, und einen Commit erst dann zu machen, wenn die Veränderungen vollständig und getestet sind, sodass die Hauptkopie immer in einem funktionsfähigen Zustand ist.

Vielleicht fragen Sie sich, was passiert, wenn die Entwickler A und B in ihren Arbeitskopien unterschiedliche Veränderungen an dem gleichen Stück (Quell-)Text vornehmen und beide ihre Veränderungen mittels Commit abschicken? Dies wird Konflikt genannt und von CVS entdeckt, sobald Entwickler B, versucht seine Veränderungen abzuschicken. Anstatt Entwickler B zu erlauben fortzufahren, gibt CVS bekannt, dass es einen Konflikt gefunden hat, und setzt Konfliktmarkierungen (leicht zu erkennende Marken im Text) an die in Konflikt stehenden Stellen im Text seiner Kopie. Diese Stellen beinhalten beide Veränderungen und sind derart angeordnet, dass sie leicht verglichen werden können. Entwickler B muss sich nun alles noch einmal ansehen und eine neue Version abschicken, die den Konflikt auflöst. Vielleicht müssen die beiden Entwickler miteinander reden, um die Sache zu klären. CVS alarmiert nur die Entwickler über die Konflikte; es ist an den Menschen, diese tatsächlich zu lösen.

Was ist nun mit der Hauptkopie? In der offiziellen CVS-Terminologie wird diese das Archiv (Repository) eines Projektes genannt. Das Archiv ist schlicht nur ein Datei-/Verzeichnisbaum, der auf einem Server gespeichert ist. Ohne zu stark in die Tiefe der Struktur zu gehen (siehe jedoch Kapitel 4), werfen wir einen Blick darauf, was das Archiv leisten muss, um den Anforderungen des Checkout-Commit-Aktualisieren-Zyklus gerecht zu werden.

Stellen Sie sich folgendes Szenario vor:

  1. Zwei Entwickler, A und B, führen gleichzeitig einen Checkout des gleichen Projektes aus. Das Projekt befindet sich noch am Ausgangspunkt - es wurden noch von niemandem Veränderungen per Commit an das Archiv geschickt, sodass sich noch alle Dateien in ihrem ursprünglichen Zustand befinden.
  2. Entwickler A beginnt sofort mit seiner Arbeit und führt schon bald den ersten Commit seiner Änderungen aus.
  3. In der Zwischenzeit sieht Entwickler B fern.
  4. Entwickler A arbeitet, als ob es kein Morgen gäbe, und führt einen zweiten Commit für einen weiteren Satz von Veränderungen aus. Das Archiv enthält nun die Originaldateien, gefolgt von As zweitem Satz von Veränderungen, gefolgt von diesem Satz an Veränderungen.
  5. In der Zwischenzeit spielt Entwickler B Videospiele.
  6. Plötzlich schließt sich Entwickler C dem Projekt an und macht einen Checkout einer Arbeitskopie aus dem Archiv. Die Kopie von Entwickler C enthält As erste zwei Sätze von Veränderungen, weil diese schon im Archiv enthalten waren, als C für seine Arbeitskopie einen Checkout gemacht hat.
  7. Entwickler A arbeitet weiter wie von Geistern besessen, vollendet seinen dritten Satz an Veränderungen und führt abermals einen Commit aus.
  8. Zu guter Letzt, nichts ahnend von der jüngsten rasanten Aktivität, entscheidet Entwickler B, dass es Zeit wird, an die Arbeit zu gehen. Er kümmert sich nicht darum, eine Aktualisierung seiner Arbeitskopie durchzuführen; er fängt an, Dateien zu bearbeiten, von denen einige jene Dateien sein könnten, an denen A gearbeitet hat. Kurz darauf führt Entwickler B seinen ersten Commit dieser Veränderungen aus.

An diesem Punkt können nun zwei Dinge passieren. Wenn keine der von Entwickler B bearbeiteten Dateien von A bearbeitet wurde, dann ist der Commit erfolgreich. Wenn CVS jedoch merkt, dass einige der Dateien von B verglichen mit den aktuellen Dateien des Archivs veraltet sind und diese auch von B in seiner Arbeitskopie verändert wurden, informiert CVS B darüber, dass er eine Aktualisierung durchführen muss, bevor ein Commit durchgeführt werden kann.

Wenn Entwickler B die Aktualisierung durchführt, fügt CVS alle Veränderungen von A in Bs lokale Kopien der Dateien ein. Einige von As Veränderungen können mit Bs noch nicht abgeschickten Veränderungen in Konflikt geraten, manche nicht. Die Teile, welche nicht in Konflikt stehen, werden einfach ohne weitere Komplikationen in Bs Kopie eingefügt; die in Konflikt stehenden müssen zuerst von B bereinigt werden, bevor der Commit durchgeführt werden kann.

Wenn Entwickler C nun eine Aktualisierung durchführt, bekommt er mehrere Sätze an Veränderungen aus dem Archiv: den dritten Commit von A, den ersten von B und vielleicht den zweiten von B (wenn B die Konflikte aufgelöst hatte).

Damit CVS Veränderungen in der richtigen Reihenfolge an die Entwickler verteilen kann, deren Arbeitskopien unter Umständen unterschiedlich stark veraltet sind, muss das Archiv alle Commits seit Projektbeginn aufzeichnen. In der Praxis speichert das CVS-Archiv diese als aufeinander folgende Diffs. Daher kann CVS auch noch für sehr alte Arbeitskopien den Unterschied zwischen den Dateien der Arbeitskopien und dem aktuellen Stand des Archivs bestimmen und dadurch die Arbeitskopie effizient wieder auf den aktuellen Stand bringen. Für Entwickler ist es dadurch einfach, die Historie des Projektes einzusehen und zu jedem Zeitpunkt sogar sehr alte Arbeitskopien wieder zum Leben zu erwecken.

Obwohl das Archiv genau genommen das gleiche Resultat mit anderen Methoden erreichen könnte, ist das Abspeichern der Diffs eine einfache und intuitive Methode, die notwendige Funktionalität zu implementieren. Dieser Prozess hat den zusätzlichen Vorteil, dass CVS durch die korrekte Anwendung von patch jeden vorangegangenen Zustand des Verzeichnisbaumes wiederherstellen und damit jede Arbeitskopie von einem Zustand in einen anderen überführen kann. Es erlaubt jedem, einen Checkout des Projektes in einem womöglich vergangenen Zustand zu machen. Es kann ebenso die Unterschiede im diff-Format zwischen zwei Zuständen des Projektes aufzeigen, ohne dabei irgendeine Arbeitskopie zu beeinflussen.

Daher sind genau diese Funktionen, die den vernünftigen Zugriff auf die Historie eines Projektes zulassen, auch dafür nützlich, es einer dezentralen, unkoordinierten Entwicklergruppe zu ermöglichen, an einem Projekt zusammenzuarbeiten.

Die Details, wie ein Archiv angelegt wird, Benutzerzugriffe administriert werden und CVS-spezifische Dateiformate gehandhabt werden (diese werden in Kapitel 4 beschrieben), können Sie erst einmal außer Acht lassen. Im Augenblick konzentrieren wir uns darauf, wie Veränderungen an einer Arbeitskopie durchgeführt werden können.

Doch zuerst noch eine kurze Übersicht der Terminologie:

  • Revision - Eine Veränderung an einer Datei oder Menge von Dateien, die durch einen Commit abgeschlossen wurde. Eine Revision ist eine Momentaufnahme eines sich ständig verändernden Projektes.
  • Archiv - Die Hauptkopie, in der CVS die vollständige Revisionshistorie eines Projektes speichert. Jedes Projekt hat genau ein Archiv.
  • Arbeitskopie - Die Kopie, mit der gearbeitet wird und die tatsächlich verändert wird. Es kann mehrere Arbeitskopien eines bestimmten Projektes geben; im Allgemeinen hat jeder Entwickler seine eigene Kopie.
  • Checkout - Eine Arbeitskopie von dem Archiv anfordern. Die angeforderte Kopie stellt den Zustand des Projektes zu dem Zeitpunkt dar, zu dem die Kopie angefordert wurde; wenn Sie oder andere Entwickler Veränderungen vornehmen, müssen commit und update durchgeführt werden, um die eigenen Veränderungen zu »veröffentlichen« und die der anderen Mitentwickler sehen zu können.
  • Commit - Senden der eigenen Veränderungen zum zentralen Archiv. Auch Check-in genannt.
  • Log-Nachricht - Ein Kommentar der bei einem Commit einer Revision angehängt wird und die vorgenommenen Veränderungen beschreibt. Andere Entwickler können durch die Log-Nachrichten blättern und erhalten so die Antwort auf die Frage, was in dem Projekt passiert ist.
  • Aktualisierung (update) - Veränderungen von anderen Entwicklern vom Archiv in die eigene Arbeitskopie aufnehmen und aufzeigen, ob die eigene Arbeitskopie noch nicht durch commit abgeschickte Veränderungen enthält.
  • Konflikt - Situation, in der zwei Entwickler Veränderungen im gleichen Teil der gleichen Datei per commit abzuschicken versuchen. CVS bemerkt solche Konflikte und benachrichtigt die Entwickler, aber die Entwickler müssen diese selbst auflösen.
Ein Tag mit CVS

Der folgende Teil des Buches gibt eine Einführung in die grundlegende Benutzung von CVS, gefolgt von einer beispielhaften Sitzung, welche die typischsten CVS-Operationen beinhaltet. Im Laufe dessen werden wir auch beginnen, die interne Arbeitsweise von CVS zu betrachten.

Obwohl Sie zur Benutzung die Implementation von CVS nicht bis ins kleinste Detail verstehen müssen, ist ein Grundwissen über seine Funktionsweise unschätzbar wertvoll, um ein bestimmtes Ergebnis zu erzielen. CVS verhält sich eher wie ein Fahrrad als ein Auto, denn seine Mechanismen sind für jeden transparent, der einen aufmerksamen Blick darauf wirft. Wie mit einem Fahrrad kann man einfach aufspringen und sofort anfangen zu fahren. Wenn man sich jedoch einen Augenblick Zeit nimmt, um genauer zu betrachten, wie das Getriebe funktioniert, kann man wesentlich besser fahren. (Im Falle von CVS bin ich mir nicht sicher, ob diese Transparenz ein bewusstes Entwicklungsziel oder ein Unfall gewesen ist, aber es scheint eine Eigenschaft zu sein, die auf viele freie Programme zutrifft. Durchschaubare Implementationen haben den Vorteil, Benutzer dazu zu motivieren, zu dem Projekt beitragende Entwickler zu werden, indem sie von Anfang an mit den internen Prozessen konfrontiert werden.)

Unsere Führung findet in einer Unix-Umgebung statt. CVS läuft auch unter Windows oder dem Macintosh Betriebssystem, und Tim Endres von Ice Engineering hat sogar einen Java-Client geschrieben (siehe www.ice.com/java/jcvs/), der überall dort läuft, wo auch Java läuft. Dennoch wage ich die grobe Schätzung, dass die Mehrheit der CVS-Benutzer - derzeitig und potenziell - wahrscheinlich mit einer Unix Kommandozeilenumgebung arbeiten. Sollten Sie kein solcher sein, so sollten die Beispiele dieser Führung dennoch leicht auf andere Oberflächen übertragbar sein. Haben Sie die Konzepte einmal verstanden, können Sie sich an jede CVS-Oberfläche setzen und damit arbeiten (vertrauen Sie mir, ich habe dies schon oft gemacht).

Die Beispiele der Führung orientieren sich an Benutzern, die CVS für Programmierprojekte einsetzen werden. Trotzdem sind CVS-Operationen auf alle Textdokumente anwendbar, nicht nur auf Quelltexte.

In der Führung wird auch davon ausgegangen, dass Sie CVS bereits installiert haben (es ist bei vielen bekannten freien Unix-Systemen bereits enthalten, wodurch Sie es bereits haben könnten, ohne es zu wissen) und dass Sie Zugriff auf ein Archiv haben. Auch wenn Sie diese Voraussetzungen nicht erfüllen, können Sie dennoch von dieser Führung profitieren. In Kapitel 4 werden Sie lernen, wie man CVS installiert und wie Archive angelegt werden.

Davon ausgehend, dass CVS installiert ist, sollten Sie sich einen Augenblick Zeit nehmen, die Online-Dokumentation zu CVS zu finden. Gewöhnlich als das »Cederqvist« bekannt (nach Per Cederqvist, dem ursprünglichen Autor), liegt es dem Quelltextpaket von CVS bei und ist die wohl aktuellste verfügbare Referenz. Der Text ist im Texinfo-Format geschrieben und sollte auf Unix-Systemen in der »Info«-Hierarchie zu finden sein. Sie können dieses entweder mit dem Kommandozeilen info-Programm lesen

 

floss$ info cvs

oder durch die Tastenkombination Ctrl+H und dann »i« in Emacs. Wenn keines derer bei Ihnen funktioniert, fragen Sie den nächsten Unix-Guru (oder lesen Sie Kapitel 4, Installation). Wenn Sie regelmäßig mit CVS arbeiten wollen, sollten Sie auf jeden Fall das Cederqvist zur Hand haben.

CVS aufrufen

CVS ist ein einzelnes Programm, kann aber viele verschiedene Aktionen ausführen: Update, Commit, Verzweigung (Branch), Diff und so weiter. Wenn Sie CVS aufrufen, müssen Sie angeben, welche Aktion Sie ausführen wollen. Daraus folgt das Format für CVS-Aufrufe:

 

floss$ cvs Kommando

Zum Beispiel

 

floss$ cvs update

floss$ cvs diff

floss$ cvs commit

und so weiter. (Aber versuchen Sie nicht, eines dieser Kommandos in dieser Form auszuführen; solange Sie sich noch nicht in einer Arbeitskopie befinden, wird noch nichts passieren, wozu wir aber gleich kommen.)

Sowohl CVS als auch das Kommando können zusätzliche Optionen bekommen. Optionen, die das Verhalten von CVS unabhängig von dem auszuführenden Kommando verändern, heißen globale Optionen; kommandospezifische Optionen heißen einfach Kommandooptionen. Globale Optionen stehen immer links des Kommandos; Kommandooptionen rechts davon. Also ist bei

 

floss$ cvs -Q update -p

-Q eine globale Option und -p eine Kommandooption. (Falls es Sie interessiert, -Q bedeutet »leise« - also alle Diagnosemeldungen unterdrücken und Fehlermeldungen nur dann ausgeben, wenn das Kommando aus irgendeinem Grund gar nicht verarbeitet werden kann; -p bedeutet, das Ergebnis des Update auf der Standardausgabe auszugeben, anstatt es in Dateien zu schreiben.)

Zugriff auf ein Archiv

Bevor überhaupt irgendetwas ausgeführt werden kann, muss CVS der Ursprungsort des Archivs, auf das zugegriffen werden soll, mitgeteilt werden. Dies trifft dann nicht mehr zu, wenn schon eine Arbeitskopie durch einen Checkout geholt wurde - jede Arbeitskopie weiß, aus welchem Archiv sie stammt, wodurch CVS das Archiv automatisch aus einer bestimmten Arbeitskopie ableiten kann. Nehmen wir aber dennoch an, Sie haben noch keine Arbeitskopie und müssen daher CVS explizit mitteilen, wohin es sich wenden soll. Dies geschieht mit der globalen Option -d (-d steht für »directory«1, eine Option, für die es eine historische Begründung gibt, obwohl -r für »Repository« vielleicht besser gewesen wäre), gefolgt von dem Pfad zu dem Archiv. Nehmen wir zum Beispiel an, das Archiv befindet sich auf der lokalen Maschine in /usr/local/cvs (ein Standardort):

 

floss$ cvs -d /usr/local/cvs Kommando

In vielen Fällen befindet sich das Archiv jedoch auf einer anderen Maschine, und es muss daher über das Netzwerk zugegriffen werden. CVS stellt mehrere Netzwerkzugriffsmethoden zur Verfügung; welche eingesetzt werden soll, hängt von den Sicherheitsansprüchen des Archivservers ab (im Folgenden »der Server« genannt). Den Server für verschiedene Zugriffsmethoden einzurichten, wird in Kapitel 4 beschrieben; an dieser Stelle soll nur der Teil des Clients behandelt werden.

Glücklicherweise haben alle Netzwerkzugriffsmethoden eine gemeinsame Aufrufsyntax. Grundsätzlich muss zur Spezifikation eines nicht lokalen Archivs lediglich ein längerer Pfad zum Archiv angegeben werden. Zuerst wird die Zugriffsmethode angegeben, zu beiden Seiten mit Doppelpunkten abgetrennt, gefolgt von dem Benutzernamen und dem Servernamen (zusammengesetzt mit einem @-Zeichen), einem weiteren Doppelpunkt als Trenner und letztendlich dem Pfad des Archivverzeichnisses auf dem Server.

Betrachten wir die pserver-Zugriffsmethode, die für »passwort-authentisierten Server« steht:

 

floss$ cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs login

(Logging in to jrandom@cvs.foobar.com)

CVS password: (hier das CVS Passwort eingeben)

floss$

Der lange Archivname nach -d sagte CVS, die pserver-Zugriffsmethode mit dem Benutzernamen jrandom auf dem Server cvs.foobar.com zu verwenden, der ein CVS-Archiv in /usr/local/cvs hat. Übrigens muss der Hostname nicht »cvs.irgendetwas.com« lauten; dies ist eine übliche Übereinkunft, aber es hätte auch einfach folgendermaßen sein können:

 

floss$ cvs -d :pserver:jrandom@fisch.foobar.org:/usr/local/cvs Kommando

Das tatsächlich verwendete Kommando war login, das verifiziert, ob Sie autorisiert sind, mit dem Archiv zu arbeiten. Das login-Kommando fragt Sie anschließend nach einem Passwort und kontaktiert dann den Server, um das Passwort zu verifizieren. Guter Unix-Sitte folgend, hat cvs login keine Ausgabe, wenn das Login korrekt ablief; wenn es fehlschlägt, wird eine Fehlermeldung ausgegeben (zum Beispiel weil das Passwort falsch war).

Man muss sich von seiner lokalen Maschine nur einmal bei einem bestimmten CVS-Server anmelden. Nach einem erfolgreichen Login speichert CVS das Passwort in der Datei .cvspass in Ihrem Home-Directory. Diese Datei wird anschließend immer wieder eingelesen, wenn auf ein Archiv mit der pserver-Methode zugegriffen wird, wodurch login nur einmal beim ersten Zugriff auf einen bestimmten CVS-Server von einer bestimmten Client-Maschine aus durchgeführt werden muss. Natürlich kann cvs login jederzeit wiederholt werden, wenn sich zum Beispiel das Passwort geändert hat.

 

Bemerkung

pserver ist derzeit die einzige Zugriffsmethode, die ein erstes Login wie dieses benötigt; mit den anderen können direkt normale CVS-Kommandos ausgeführt werden.

Ist einmal die Authentifizierungsinformation in der .cvspass-Datei gespeichert, können andere CVS-Kommandos in der gleichen Kommandozeilensyntax ausgeführt werden:

 

floss$ cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs Kommando

Die pserver-Methode unter Windows anzuwenden kann einen weiteren Schritt benötigen. Windows kennt das Unix-Konzept der Home-Verzeichnisse nicht, weshalb CVS nicht weiß, wo die .cvspass-Datei abgespeichert werden soll. Hierzu muss explizit ein Verzeichnis angegeben werden. Normalerweise wird das Hauptverzeichnis der Festplatte C: als Home-Verzeichnis angegeben:

 

C:\WINDOWS> set HOME=C:

C:\WINDOWS> cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs login

(Logging in to jrandom@cvs.foobar.com)

CVS password: (hier Passwort eingeben)

C:\WINDOWS>

Jedes Verzeichnis des Dateisystems ist möglich. Netzwerklaufwerke sollten jedoch vermieden werden, da der Inhalt der .cvspass-Datei dann für jeden ersichtlich wäre, der Zugriff auf dieses Laufwerk hat.

Zusätzlich zu pserver unterstützt CVS die ext-Methode (die ein externes Programm zur Verbindung benutzt, bspw. rsh oder ssh), kserver (für das Kerberos-Sicherheitssystem Version 4) und gserver (welches das GSSAPI, oder auch Generic Security Services API, benutzt und auch Kerberos Version 5 oder größer verwenden kann). Diese Methoden sind ähnlich wie pserver, haben aber jede für sich ihre Eigenheiten.

Von diesen ist wohl die ext-Methode die üblichste. Wenn Sie sich an dem Server über rsh oder ssh anmelden können, können Sie die ext-Methode benutzen. Sie können dies folgendermaßen ausprobieren:

 

floss$ rsh -l jrandom cvs.foobar.com

Password hier Ihr Login-Passwort eingeben

Angenommen, Sie konnten sich mit rsh erfolgreich bei dem Server ein- und ausloggen, dann sind Sie nun wieder zurück auf Ihrer ursprünglichen Client-Maschine:

 

floss$ CVS_RSH=rsh; export CVS_RSH

floss$ cvs -d :ext:jrandom@cvs.foobar.com:/usr/local/cvs Kommando

Die erste Zeile setzt (in der Syntax der Unix Bourne-Shell) die CVS_RSH-Umgebungsvariable auf rsh, was CVS mitteilt, rsh als das Programm zur Verbindung zu benutzen. Die zweite Zeile kann irgendein CVS-Kommando sein; Sie werden nach Ihrem Passwort gefragt, sodass CVS das Login beim Server durchführen kann.

Wenn Sie eine C-Shell anstatt einer Bourne-Shell benutzen, versuchen Sie Folgendes:

 

floss% setenv CVS_RSH rsh

und unter Windows versuchen Sie dies:

 

C:\WINDOWS> set CVS_RSH=rsh

Der Rest dieser Führung verwendet die Bourne-Shell-Syntax; Sie können dies für Ihre Umgebung bei Bedarf umsetzen.

Um ssh (die Secure-Shell) anstatt von rsh zu benutzen, muss nur die Umgebungsvariable CVS_RSH entsprechend gesetzt werden:

 

floss$ CVS_RSH=ssh; export CVS_RSH

Lassen Sie sich nicht davon verwirren, dass die Variable CVS_RSH heißt, Sie ihren Inhalt aber auf ssh setzen. Es gibt historische Gründe dafür (die allumfassende Unix-Entschuldigung, ich weiß). CVS_RSH kann auf ein beliebiges Programm verweisen, welches das Login auf einer anderen Maschine sowie Kommandos ausführen und deren Ausgabe empfangen kann. Nach rsh ist ssh wohl das verbreitetste dieser Programme, obwohl es sicherlich noch andere gibt. Wichtig ist, dass diese Programme den Datenstrom in keiner Weise verändern dürfen. Dies disqualifiziert die Windows-NT rsh, weil diese zwischen den Unix- und DOS-Zeilenumbrüchen konvertiert (oder es zumindest versucht). Sie müssten sich in diesem Fall eine andere rsh für Windows besorgen oder eine andere Zugriffsmethode benutzen.

Die gserver- und kserver- Methoden werden nicht so oft wie die anderen benutzt und werden hier nicht behandelt. Diese sind bezüglich dessen, was bisher behandelt wurde, recht ähnlich; Näheres findet sich im Cederqvist.

Verwenden Sie nur ein Archiv und wollen nicht jedes Mal -d repos eingeben, können Sie einfach die CVSROOT-Umgebungsvariable (die vielleicht CVSREPOS hätte genannt werden sollen, doch dafür ist es nun zu spät) wie folgt setzen:

 

floss$ CVSROOT=/usr/local/cvs

floss$ export CVSROOT

floss$ echo $CVSROOT

/usr/local/cvs

floss$

oder vielleicht

 

floss$ CVSROOT=:pserver:jrandom@cvs.foobar.com:/usr/local/cvs

floss$ export CVSROOT

floss$ echo $CVSROOT

:pserver:jrandom@cvs.foobar.com:/usr/local/cvs

floss$

Der Rest dieser Führung geht davon aus, dass CVSROOT auf das Archiv verweist, sodass die Beispiele die -d Option nicht enthalten. Wenn auf mehrere Archive zugegriffen werden soll, sollte die CVSROOT-Umgebungsvariable nicht gesetzt werden und anstatt dessen mit -d repos das benötigte Archiv angegeben werden.

Ein neues Projekt beginnen

Wenn Sie den Umgang mit CVS erlernen wollen, um mit einem Projekt zu arbeiten, das bereits mit CVS verwaltet wird (das heißt, es befindet sich bereits irgendwo in einem Archiv), dann sollten Sie vielleicht mit dem Abschnitt »Eine Arbeitskopie auschecken« fortfahren. Möchten Sie allerdings existierende Quelltexte unter die Kontrolle von CVS stellen, ist dies der für Sie passende Abschnitt. Beachten Sie, dass immer noch davon ausgegangen wird, dass Sie Zugriff auf ein bereits bestehendes Archiv haben; wenn Sie zuerst eines anlegen müssen, lesen Sie bitte Kapitel 4.

Ein bestehendes Projekt in CVS zu übernehmen, wird importieren genannt. Das CVS-Kommando dazu lautet, wie Sie sich sicherlich bereits gedacht haben,

 

floss$ cvs import

abgesehen davon, dass es noch ein paar zusätzliche Optionen benötigt (und an der richtigen Stelle ausgeführt werden muss), um korrekt ausgeführt zu werden. Zuerst wechseln Sie in das Hauptverzeichnis Ihres Projektes:

 

floss$ cd myproj

floss$ ls

README.txt a-subdir/ b-subdir/ hello.c

floss$

Dieses Projekt besteht aus zwei Dateien - README.txt und hello.c - im Hauptverzeichnis, zuzüglich zwei Unterverzeichnissen - a-unterverzeichnis und b-unterverzeichnis - und noch einiger weiterer Dateien in den Unterverzeichnissen, die hier nicht angezeigt werden. Wenn ein Projekt importiert wird, importiert CVS alles aus der Verzeichnisstruktur, ausgehend von dem aktuellen Verzeichnis den ganzen Baum hinab. Daher sollten Sie sich vergewissern, dass sich nur solche Dateien in den Verzeichnissen befinden, die auch permanenter Bestandteil des Projektes werden sollen. Jegliche alten Sicherheitskopien, Schmierdateien und so weiter sollten entfernt werden.

Die allgemeine Syntax des import-Kommandos ist

 

floss$ cvs import -m "log nachr." projname hersteller-marke versions-marke

Die -m -Option (m = message, Nachricht) spezifiziert eine kurze Nachricht, die den Import beschreibt. Dies wird dann die erste Log-Nachricht des gesamten Projektes; jeder nachfolgende Commit wird ebenfalls eine eigene Log-Nachricht bekommen. Diese Nachrichten sind verpflichtend; wird die -m -Option nicht angegeben, startet CVS automatisch einen Texteditor (unter Verwendung der EDITOR Umgebungsvariablen), damit Sie eine Log-Nachricht eingeben können. Nachdem die Log-Nachricht abgespeichert wurde, wird der Import weiter durchgeführt.

Das nächste Argument der Kommandozeile ist der Projektname (hier wird »myproject« verwendet). Dies ist der Name, anhand dessen ein Checkout des Projektes aus dem Projektarchiv durchgeführt wird. (Was tatsächlich passiert ist, dass ein Verzeichnis mit diesem Namen im Archiv angelegt wird, doch mehr dazu in Kapitel 4). Der nun auszuwählende Name muss nicht der gleiche wie der des aktuellen Verzeichnisses sein, obwohl er es in den meisten Fällen wohl sein wird.

Die Argumente hersteller-marke und versions-marke dienen nur als Verwaltungsinformationen für CVS. Sie brauchen sich zu diesem Zeitpunkt nicht darum zu kümmern; es spielt praktisch keine Rolle, was Sie dafür wählen. In Kapitel 6 werden die seltenen Umstände beschrieben, unter denen diese relevant sind. Hier werden wir einen Benutzernamen und »start« für diese Argumente benutzen.

Wir können nun den Import starten:

 

floss$ cvs import -m "initial import into CVS" myproj jrandom start

N myproj/hello.c

N myproj/README.txt

cvs import: Importing /usr/local/cvs/myproj/a-subdir

N myproj/a-subdir/whatever.c

cvs import: Importing /usr/local/cvs/myproj/a-subdir/subsubdir

N myproj/a-subdir/subsubdir/fish.c

cvs import: Importing /usr/local/cvs/myproj/b-subdir

N myproj/b-subdir/random.c

 

No conflicts created by this import

floss$

Herzlichen Glückwunsch! Wenn Sie dieses oder ein ähnliches Kommando ausgeführt haben, haben Sie schlussendlich etwas ausgeführt, was das Archiv verändert.

Wenn Sie sich die Ausgabe des Import-Kommandos noch einmal durchlesen, werden Sie feststellen, dass CVS den Dateinamen einen einzelnen Buchstaben vorangestellt hat - in diesem Fall »N« für »neue Datei«. Die Verwendung eines einzelnen Buchstabens an der linken Position, um den Status einer Datei anzuzeigen, ist bei den Ausgaben eines Kommandos von CVS üblich. Wir werden dies auch später bei Checkout und Update sehen.

Sie könnten nun denken, dass Sie, nachdem Sie gerade das Projekt importiert haben, in den aktuellen Verzeichnissen sofort arbeiten können. Dies ist jedoch nicht der Fall. Das aktuelle Verzeichnis ist immer noch keine CVS-Arbeitskopie. Es war die Quelle für das import-Kommando, richtig, aber es wurde nicht alleine durch die Tatsache, in CVS importiert worden zu sein, auf magische Art und Weise in eine Arbeitskopie verwandelt. Um eine Arbeitskopie zu erhalten, müssen Sie eine aus dem Archiv auschecken.

Zuerst sollten Sie vielleicht jedoch den aktuellen Projektstamm sichern. Der Grund dafür ist, dass, wenn die Quelltexte einmal im CVS-Archiv liegen, Sie sich nicht selbst dadurch verwirren sollten, indem Sie Kopien von Dateien modifizieren, die nicht der Versionskontrolle unterliegen (und diese Veränderungen daher nicht Teil der Projekthistorie werden). Sie sollten von nun an alle Ihre Arbeiten an einer Arbeitskopie vornehmen. Sie sollten jedoch den gerade importierten Verzeichnisbaum noch nicht entfernen, da Sie noch nicht überprüft haben, ob das Archiv alle Dateien enthält. Natürlich können Sie sich dessen zu 99,999 Prozent sicher sein, weil der import-Befehl ohne Fehler ablief, doch warum etwas riskieren? Paranoia zahlt sich aus, wie jeder Programmierer weiß. Daher führen Sie etwa wie folgt aus:

 

floss$ ls

README.txt a-subdir/ b-subdir/ hello.c

floss$ cd ..

floss$ ls

myproj/

floss$ mv myproj was_myproj

floss$ ls

was_myproj/

floss$

So. Die Originaldateien sind noch vorhanden, sind aber durch den Namen klar als eine veraltete Version erkennbar, sodass sie nicht im Weg sind, wenn eine richtige Arbeitskopie geholt wird. Nun kann ein Checkout durchgeführt werden.

Eine Arbeitskopie auschecken

Das Kommando, um einen Checkout durchzuführen, ist genau das, was Sie sich sicherlich schon gedacht haben:

 

floss$ cvs checkout myproj

cvs checkout: Updating myproj

U myproj/README.txt

U myproj/hello.c

cvs checkout: Updating myproj/a-subdir

U myproj/a-subdir/whatever.c

cvs checkout: Updating myproj/a-subdir/subsubdir

U myproj/a-subdir/subsubdir/fish.c

cvs checkout: Updating myproj/b-subdir

U myproj/b-subdir/random.c

 

floss$ ls

myproj/ was_myproj/

floss$ cd myproj

floss$ ls

CVS/ README.txt a-subdir/ b-subdir/ hello.c

floss$

Achtung - Ihre erste Arbeitskopie! Der Inhalt ist genau derselbe wie der, den Sie gerade importiert haben, zuzüglich eines Unterverzeichnisses »CVS«. In diesem werden von CVS Informationen zur Versionskontrolle gespeichert. Genauer gesagt, es existiert nun in jedem Unterverzeichnis des Projektes ein CVS-Unterverzeichnis:

 

floss$ ls a-subdir

CVS/ subsubdir/ whatever.c

floss$ ls a-subdir/subsubdir/

CVS/ fish.c

floss$ ls b-subdir

CVS/ random.c

 

Tipp

Die Tatsache, dass CVS die Informationen zur Versionskontrolle in Unterverzeichnissen namens CVS ablegt, bedeutet, dass Ihr Projekt niemals eigene Unterverzeichnisse mit dem Namen CVS enthalten kann. Ich habe aber praktisch noch nie davon gehört, dass dies ein Problem gewesen wäre.

Bevor Dateien modifiziert werden, lassen Sie uns einen Blick in diese Blackbox werfen:

 

floss$ cd CVS

floss$ ls

Entries Repository Root

floss$ cat Root

/usr/local/cvs

floss$ cat Repository

myproject

floss$

Hier ist nichts besonders Mysteriöses. Die Datei Root verweist auf das Archiv, und die Datei Repository verweist auf ein Projekt innerhalb des Archivs. Lassen Sie es mich erklären, wenn dies auf Anhieb etwas verwirrend erscheint.

Die Terminologie von CVS sorgt seit langem für Verwirrung. Der Begriff »Archiv« wird für zwei unterschiedliche Dinge benutzt. Manchmal ist damit das Hauptverzeichnis eines Archivs gemeint (zum Beispiel /usr/local/cvs), das mehrere Projekte enthalten kann; die Datei Root verweist dorthin. Doch manchmal ist damit ein projektspezifisches Unterverzeichnis innerhalb des Archiv-Root gemeint (zum Beispiel /usr/local/cvs/myproject, /usr/local/cvs/deinprojekt, /usr/local/cvs/Fisch). Die Datei »Repository« innerhalb des CVS-Unterverzeichnisses hat diese Bedeutung.

Innerhalb dieses Buches bedeutet »Archiv« allgemein Root (also das übergeordnete Hauptarchiv), obwohl es gelegentlich auch ein projektspezifisches Unterverzeichnis bezeichnen kann. Sollte die eigentliche Intention nicht aus dem Kontext hervorgehen, wird dies im Text erklärt.

Beachten Sie, dass die Datei Repository manchmal mit einem absoluten Pfad anstatt eines relativen auf das Projekt verweist. Dies ist ein wenig redundant mit der Root Datei:

 

floss$ cd CVS

floss$ cat Root

:pserver:jrandom@cvs.foobar.com:/usr/local/cvs

floss$ cat Repository

/usr/local/cvs/myproject

floss$

In der Datei Entries werden Informationen über die einzelnen Dateien eines Projektes abgelegt. Jede Zeile beschäftigt sich dabei mit einer Datei, und es finden sich dort auch nur Einträge für die Dateien und Unterverzeichnisse des nächst übergeordneten Verzeichnisses. Hier die Haupt-CVS/Entries-Datei in myproject:

 

floss$ cat Entries

/README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//

/hello.c/1.1.1.1/Sun Apr 18 18:18:22 1999//

D/a-subdir////

D/b-subdir////

Jede Zeile folgt dem Format

 

/dateiname/revisionsnummer/Zeitstempel//

und die Zeilen der Verzeichnisse werden mit einem »D« eingeleitet. (CVS verwaltet keine Historie über Veränderungen der Verzeichnisse selbst, weshalb die Felder Revisionsnummer und Zeitstempel leer bleiben.)

Die Zeitstempel bezeichnen Datum und Uhrzeit der letzten Aktualisierung der Dateien in der Arbeitskopie (in universeller Zeit, nicht lokaler Zeit). Auf diese Weise kann CVS einfach unterscheiden, ob eine Datei seit dem letzten checkout, update oder commit verändert wurde. Wenn sich der Zeitstempel des Dateisystems von dem in der CVS/Entries-Datei unterscheidet, weiß CVS (ohne überhaupt das Archiv zu überprüfen), dass die Datei wahrscheinlich verändert wurde.

Betrachtet man die CVS/*-Dateien in den Unterverzeichnissen

 

floss$ cd a-subdir/CVS

floss$ cat Root

/usr/local/cvs

floss$ cat Repository

myproj/a-subdir

floss$ cat Entries

/whatever.c/1.1.1.1/Sun Apr 18 18:18:22 1999//

D/subsubdir////

floss$

stellt man fest, dass Root immer noch auf das gleiche Archiv verweist, Repository jedoch auf die Position des Verzeichnisses innerhalb des Projektes zeigt und die Entries-Datei andere Einträge enthält.

Direkt nach einem Import wird die Revisionsnummer einer jeden Datei des Projektes mit 1.1.1.1 angezeigt. Diese initiale Revisionsnummer ist eine Art Spezialfall, weshalb wir hier nicht näher darauf eingehen; wir werden uns näher mit Revisionsnummern beschäftigen, wenn ein Commit von ein paar Veränderungen durchgeführt wurde.

Version kontra Revision

Die von CVS intern verwalteten Revisionsnummern sind unabhängig von der Versionsnummer des Softwareproduktes, von dem diese ein Teil sind. Nehmen wir zum Beispiel ein aus drei Dateien bestehendes Projekt, deren Revisionsnummern am 3. Mai 1999 1.2, 1.7 und 2.48 waren. An diesem Tag wird eine neue Version dieser Software zusammengepackt und als SlickoSoft Version 3 freigegeben. Dies ist eine reine Marketingentscheidung und beeinflusst die CVS-Revisionen überhaupt nicht. Die CVS-Revisionsnummern sind für die Kunden nicht sichtbar (es sei denn, sie haben Zugriff auf das Archiv); die einzig sichtbare Nummer ist »3« in »Version 3«. Soweit es CVS betrifft, hätte die Version auch 1729 lauten können - die Versionsnummer (oder auch »Release«-Nummer) hat nichts mit der internen Verwaltung von Veränderungen durch CVS zu tun.

Um Verwirrung zu vermeiden, werde ich den Begriff »Revision« verwenden, um mich einzig auf die internen Revisionsnummern von Dateien unter der Kontrolle von CVS zu beziehen. Ich werde trotzdem CVS ein »Versionskontrollsystem« nennen, weil »Revisionskontrollsystem« doch etwas komisch klingt.

Eine Veränderung einbringen

Das Projekt, in seinem momentanen Zustand, macht noch nicht allzu viel. Hier ist der Inhalt von hello.c:

 

floss$ cat hello.c

#include <stdio.h>

void

main ()

{

printf ("Hello, world!\n");

}

Lassen Sie uns nun die erste Veränderung seit dem Import anbringen; es wird die Zeile

 

printf ("Goodbye, world!\n");

eingefügt, direkt nach Hello, world!. Starten Sie Ihren bevorzugten Texteditor und führen die Änderung durch:

 

floss$ emacs hello.c

...

Dies war eine recht simple Veränderung, eine, bei der man nicht so schnell vergessen kann, was man getan hat. Bei einem größeren und komplexeren Projekt ist es aber recht wahrscheinlich, dass man eine Datei bearbeitet, von etwas anderem unterbrochen wird und erst einige Tage später wieder dahin zurückkehrt und sich nun nicht mehr daran erinnern kann, was man tatsächlich oder ob überhaupt verändert hat. Dies bringt uns zur ersten Situation »CVS rettet Dein Leben«: die eigene Arbeitskopie mit dem Archiv vergleichen.

Herausfinden, was man selbst und andere getan haben: update und diff

Zuvor erwähnte ich Update als eine Methode, Veränderungen aus dem Archiv in die eigene Arbeitskopie einfließen zu lassen - also als eine Methode, die Veränderungen anderer Entwickler zu bekommen. Update ist jedoch etwas komplexer; es vergleicht den Gesamtzustand der Arbeitskopie mit dem Zustand des Projektes im Archiv. Auch wenn nichts im Archiv seit dem letzten Checkout verändert wurde, könnte sich dennoch etwas in der Arbeitskopie verändert haben, und update zeigt dies dann auch auf:

 

floss$ cvs update

cvs update: Updating .

M hello.c

cvs update: Updating a-subdir

cvs update: Updating a-subdir/subsubdir

cvs update: Updating b-subdir

Das M neben hello.c bedeutet, dass die Datei seit dem letzten Checkout modifiziert wurde und die Veränderungen noch nicht mit Commit in das Archiv eingebracht wurden.

Manchmal ist alles, was man möchte, herauszufinden, welche Dateien man bearbeitet hat. Möchte man jedoch einen detaillierteren Blick auf die Veränderungen werfen, kann man einen kompletten Report im diff-Format anfordern. Das diff-Kommando vergleicht die möglicherweise modifizierten Dateien der Arbeitskopie mit den entsprechenden Gegenstücken im Archiv und zeigt jegliche Unterschiede auf:

 

floss$ cvs diff

cvs diff: Diffing .

Index: hello.c

===================================================================

RCS file: /usr/local/cvs/myproj/hello.c,v

retrieving revision 1.1.1.1

diff -r1.1.1.1 hello.c

6a7

> printf ("Goodbye, world!\n");

cvs diff: Diffing a-subdir

cvs diff: Diffing a-subdir/subsubdir

cvs diff: Diffing b-subdir

Dies hilft schon weiter, auch wenn es durch eine Menge überflüssiger Ausgaben ein wenig obskur erscheinen mag. Für den Anfang können die meisten der ersten paar Zeilen ignoriert werden. Diese benennen nur die Datei des Archivs und zeigen die Nummer der letzten eingecheckten Revision. Unter bestimmten Umständen kann auch das eine nützliche Information sein (wir werden später genauer dazu kommen), sie wird aber nicht gebraucht, wenn man nur einen Eindruck davon bekommen möchte, welche Veränderungen an der Arbeitskopie stattgefunden haben.

Ein größeres Hindernis, den Diff zu lesen, stellen die Meldungen von CVS bei jedem Wechsel in ein Verzeichnis während des Updates dar. Dies kann während eines langen Updates bei großen Projekten nützlich sein, da es einen Anhaltspunkt bietet, wie lange das Update wohl noch dauern wird. Doch jetzt sind sie beim Lesen des Diff schlicht im Weg. Also sagen wir CVS mit der globalen -Q-Option, dass es nicht melden soll, wo es gerade arbeitet:

 

floss$ cvs -Q diff

Index: hello.c

===================================================================

RCS file: /usr/local/cvs/myproj/hello.c,v

retrieving revision 1.1.1.1

diff -r1.1.1.1 hello.c

6a7

> printf ("Goodbye, world!\n");

Besser - zumindest ist ein Teil der überflüssigen Ausgaben weg. Dennoch ist der Diff noch schwer zu lesen. Er sagt aus, dass an Zeile 6 eine neue Zeile hinzugekommen ist (was also jetzt Zeile 7 ist), und dass deren Inhalt

 

printf ("Goodbye, world!\n");

ist. Das vorangestellte »>« in dem Diff bedeutet, dass diese Zeile in der neuen Version vorhanden ist, nicht aber in der älteren.

Das Format kann jedoch noch lesbarer gemacht werden. Die meisten empfinden das »Kontext«-Diff-Format als leichter zu lesen, da es ein paar Kontextzeilen zu beiden Seiten einer Veränderung mit anzeigt. Kontext Diffs werden durch die zusätzliche Option -c zu diff erzeugt:

 

floss$ cvs -Q diff -c

Index: hello.c

===================================================================

RCS file: /usr/local/cvs/myproj/hello.c,v

retrieving revision 1.1.1.1

diff -c -r1.1.1.1 hello.c

*** hello.c 1999/04/18 18:18:22 1.1.1.1

--- hello.c 1999/04/19 02:17:07

***************

*** 4,7 ****

---4,8 ----

main ()

{

printf ("Hello, world!\n");

+ printf ("Goodbye, world!\n");

}

Nun, das ist Klarheit! Selbst wenn man nicht gewohnt ist, Kontext-Diffs zu lesen, macht ein kurzer Blick auf die vorangegangene Ausgabe offensichtlich, was passiert ist: eine neue Zeile wurde zwischen der Zeile, welche Hello, world! ausgibt und der abschließenden geschweiften Klammer hinzugefügt (das + in der ersten Spalte der Ausgabe markiert eine hinzugefügte Zeile).

Wir müssen Kontext-Diffs nicht perfekt lesen können, dies ist die Aufgaben von patch, es lohnt sich aber dennoch sich die Zeit zu nehmen, um eine zumindest ansatzweise Gewöhnung an dieses Format zu bekommen. Die ersten beiden Zeilen (nach der momentan nutzlosen Einleitung) sind

 

*** hello.c 1999/04/18 18:18:22 1.1.1.1

--- hello.c 1999/04/19 02:17:07

und sagen einem, was mit wem »gedifft« wurde. In diesem Fall wurde Revision 1.1.1.1 von hello.c mit einer modifizierten Version der gleichen Datei verglichen (daher gibt es keine Revisionsnummer für die zweite Zeile, weil die Veränderungen der Arbeitskopie noch nicht mit einem Commit in das Archiv aufgenommen wurden). Die Zeilen mit Sternchen und Strichen markieren Teile im späteren Teil des Diffs. Anschließend wird ein Teil der Originaldatei von einer Zeile mit Sternchen und einem eingefügten Zeilennummernbereich eingeleitet. Danach folgt eine Zeile mit Strichen mit möglicherweise anderen Zeilenummernbereichen, die einen Teil der modifizierten Datei einleiten. Diese Bereiche sind in sich kontrastierenden Paaren angeordnet (genannt »Hunks«2), die eine Seite von der alten Datei und die andere Seite von der neuen.

Dieser Diff besteht aus einem Hunk:

 

***************

*** 4,7 ****

--- 4,8 ----

main ()

{

printf ("Hello, world!\n");

+ printf ("Goodbye, world!\n");

}

Der erste Teil dieses Hunks ist leer, was bedeutet, dass keine Teile der Originaldatei entfernt wurden. Der zweite Teil zeigt an der entsprechenden Stelle der neuen Datei, dass eine Zeile eingefügt wurde; diese ist mit »+« markiert. (Wenn diff Auszüge einer Datei anführt, werden die ersten beiden linken Spalten für spezielle Codes reserviert, wie bspw. das »+«, sodass der gesamte Auszug um zwei Zeichen eingerückt erscheint. Die zusätzliche Einrückung wird natürlich entfernt, bevor der Diff wieder als Patch angewendet wird.)

Der Zeilennummernbereich zeigt den Bereich an, den der Hunk einschließt, samt der Zeilen aus dem Kontext. In der Originaldatei umfasste der Hunk die Zeilen 4 bis 7; in der neuen Datei sind dies die Zeilen 4 bis 8 (weil eine Zeile hinzugefügt wurde). Zu beachten ist, dass diff keine Ausschnitte aus der Originaldatei angezeigt hat, weil nichts entfernt wurde; es wurde nur der Bereich angezeigt und dann zu der zweiten Hälfte des Hunks übergegangen.

Hier noch ein zweiter Kontext-Diff eines meiner Projekte:

 

floss$ cvs -Q diff -c

Index: cvs2cl.pl

===================================================================

RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v

retrieving revision 1.76

diff -c -r1.76 cvs2cl.pl

*** cvs2cl.pl 1999/04/13 22:29:44 1.76

--- cvs2cl.pl 1999/04/19 05:41:37

***************

*** 212,218 ****

# can contain uppercase and lowercase letters, digits, '-',

# and '_'. However, it's not our place to enforce that, so

# we'll allow anything CVS hands us to be a tag:

! /^\s([^:]+): ([0-9.]+)$/;

push (@{$symbolic_names{$2}}, $1);

}

}

--- 212,218 ----

# can contain uppercase and lowercase letters, digits, '-',

# and '_'. However, it's not our place to enforce that, so

# we'll allow anything CVS hands us to be a tag:

! /^\s([^:]+): ([\d.]+)$/;

push (@{$symbolic_names{$2}}, $1);

}

}

Das Ausrufungszeichen zeigt an, dass die markierte Zeile zwischen der neuen und alten Datei unterschiedlich ist. Da keine »+«- oder »-«-Zeichen vorhanden sind, wissen wir, dass die Gesamtzeilenzahl der Datei gleich geblieben ist.

Hier ist noch ein Kontext-Diff des gleichen Projektes, diesmal ein wenig komplizierter:

 

floss$ cvs -Q diff -c

Index: cvs2cl.pl

===================================================================

RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v

retrieving revision 1.76

diff -c -r1.76 cvs2cl.pl

*** cvs2cl.pl 1999/04/13 22:29:44 1.76

--- cvs2cl.pl 1999/04/19 05:58:51

***************

*** 207,217 ****

}

else # we're looking at a tag name, so parse & store it

{

- # According to the Cederqvist manual, in node "Tags", "Tag

- # names must start with an uppercase or lowercase letter and

- # can contain uppercase and lowercase letters, digits, '-',

- # and '_'. However, it's not our place to enforce that, so

- # we'll allow anything CVS hands us to be a tag:

/^\s([^:]+): ([0-9.]+)$/;

push (@{$symbolic_names{$2}}, $1);

}

---- 207,212 ----

***************

*** 223,228 ****

--- 218,225 ----

if (/^revision (\d\.[0-9.]+)$/) {

$revision = "$1";

}

+

+ # This line was added, I admit, solely for the sake of a diff example.

# If have file name but not time and author, and see date or

# author, then grab them:

Dieser Diff hat zwei Hunks. Im ersten wurden fünf Zeilen entfernt (diese Zeilen sind nur im ersten Teil des Hunks zu sehen, und die Zeilenanzahl des zweiten Teils weist fünf Zeilen weniger auf). Eine ununterbrochene Zeile von Sternchen markiert die Grenze zwischen Hunks. Im zweiten Hunk ist zu sehen, dass zwei Zeilen hinzugefügt wurden: eine Leerzeile und ein sinnloser Kommentar. Zu beachten ist, wie die Zeilennummern durch die Effekte des ersten Hunks kompensiert werden. In der Originaldatei war der Zeilennummernbereich des zweiten Hunks 223 bis 228; in der neuen Datei, bedingt durch das Entfernen von Zeilen durch den ersten Hunk, ist der Zeilennummernbereich 218 bis 225.

Herzlichen Glückwunsch! Sie sind wahrscheinlich nun Experte im Lesen von Diffs, zumindest soweit Sie es aller Voraussicht nach benötigen werden.

CVS und implizite Argumente

Sie haben vielleicht bemerkt, dass bei jedem bisher verwendeten CVS-Kommando keine Dateien in der Kommandozeile angegeben wurden. Es wurde

 

floss$ cvs diff

ausgeführt anstatt

 

floss$ cvs diff hello.c

und

 

floss$ cvs update

anstatt von

 

floss$ cvs update hello.c

Das Prinzip das dahinter steht, ist, dass, wenn keine Dateinamen angegeben werden, CVS das Kommando auf alle Dateien anwendet, die dazu als sinnvoll erscheinen. Dies schließt auch Dateien in Unterverzeichnissen unterhalb des aktuellen Verzeichnisses ein; CVS durchläuft automatisch auch alle Unterverzeichnisse des Verzeichnisbaumes. Wenn zum Beispiel b-subdir/random.c und a-subdir/subsubdir/fish.c verändert wurden, wäre das Resultat von update folgendes:

 

floss$ cvs update

cvs update: Updating .

M hello.c

cvs update: Updating a-subdir

cvs update: Updating a-subdir/subsubdir

M a-subdir/subsubdir/fish.c

cvs update: Updating b-subdir

M b-subdir/random.c

floss$

oder noch besser:

 

floss$ cvs -q update

M hello.c

M a-subdir/subsubdir/fish.c

M b-subdir/random.c

floss$

 

Bemerkung

Die -q-Option ist eine Abschwächung der -Q-Option. Hätten wir -Q verwendet, hätte das Kommando keinerlei Ausgabe gehabt, da die Hinweise über Modifikationen als nicht essentielle Informationen gehandhabt werden. Die -q-Option ist weniger streng; Meldungen, die wir wahrscheinlich sowieso nicht sehen wollten, werden unterdrückt, und bestimmte nützlichere Meldungen werden durchgelassen.

Es können bei einem Update auch bestimmte Dateien angegeben werden:

 

floss$ cvs update hello.c b-subdir/random.c

M hello.c

M b-subdir/random.c

floss$

Tatsächlich ist es aber üblich, update ohne Angabe bestimmter Dateien zu starten. In den meisten Fällen wird man den gesamten Verzeichnisbaum auf einmal aktualisieren wollen. Zu beachten ist, dass alle bisherigen Updates nur zeigten, dass einige Dateien lokal modifiziert wurden, weil sich bisher noch nichts im Archiv verändert hat. Wenn weitere Entwickler mit einem zusammen an dem Projekt arbeiten, ist es immer möglich, dass update Veränderungen aus dem Archiv holt und in die lokalen Dateien einfließen lässt. In diesem Fall kann es etwas nützlicher sein, die Dateien zum Update explizit zu benennen.

Das gleiche Prinzip kann auch auf andere CVS-Kommandos angewendet werden. Zum Beispiel können die Veränderungen für eine Datei nach der anderen mit diff betrachtet werden

 

floss$ cvs diff -c b-subdir/random.c

Index: b-subdir/random.c

===================================================================

RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v

retrieving revision 1.1.1.1

diff -c -r1.1.1.1 random.c

*** b-subdir/random.c 1999/04/18 18:18:22 1.1.1.1

--- b-subdir/random.c 1999/04/19 06:09:48

***************

*** 1 ****

! /* A completely empty C file. */

--- 1,8 ----

! /* Print out a random number. */

!

! #include <stdio.h>

!

! void main ()

! {

! printf ("a random number\n");

! }

oder es können alle Veränderungen auf einmal angezeigt werden (bleiben Sie sitzen, dies wird ein großer Diff):

floss$ cvs -Q diff -c

Index: hello.c

===================================================================

RCS file: /usr/local/cvs/myproj/hello.c,v

retrieving revision 1.1.1.1

diff -c -r1.1.1.1 hello.c

*** hello.c 1999/04/18 18:18:22 1.1.1.1

--- hello.c 1999/04/19 02:17:07

***************

*** 4,7 ****

--- 4,8 ----

main ()

{

printf ("Hello, world!\n");

+ printf ("Goodbye, world!\n");

}

Index: a-subdir/subsubdir/fish.c

===================================================================

RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v

retrieving revision 1.1.1.1

diff -c -r1.1.1.1 fish.c

*** a-subdir/subsubdir/fish.c 1999/04/18 18:18:22 1.1.1.1

--- a-subdir/subsubdir/fish.c 1999/04/19 06:08:50

***************

*** 1 ****

! /* A completely empty C file. */

--- 1,8 ----

! #include <stdio.h>

!

! void main ()

! {

! while (1) {

! printf ("fish\n");

! }

! }

Index: b-subdir/random.c

===================================================================

RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v

retrieving revision 1.1.1.1

diff -c -r1.1.1.1 random.c

*** b-subdir/random.c 1999/04/18 18:18:22 1.1.1.1

--- b-subdir/random.c 1999/04/19 06:09:48

***************

*** 1 ****

! /* A completely empty C file. */

--- 1,8 ----

! /* Print out a random number. */

!

! #include <stdio.h>

!

! void main ()

! {

! printf ("a random number\n");

! }

Wie aus den Diffs klar hervorgeht, ist dieses Projekt produktionsreif. Machen wir also einen Commit der Änderungen in das Archiv.

Commit durchführen

Das commit-Kommando schickt Veränderungen an das Archiv. Werden keine Dateien angegeben, sendet commit alle Veränderungen an das Archiv; ansonsten kann einer oder können mehrere Dateinamen für den Commit angegeben werden (die anderen Dateien werden in diesem Fall ignoriert).

Hier wird eine Datei direkt und zwei werden indirekt an commit übergeben:

 

floss$ cvs commit -m "print goodbye too" hello.c

Checking in hello.c;

/usr/local/cvs/myproj/hello.c,v <-- hello.c

new revision: 1.2; previous revision: 1.1

done

floss$ cvs commit -m "filled out C code"

cvs commit: Examining .

cvs commit: Examining a-subdir

cvs commit: Examining a-subdir/subsubdir

cvs commit: Examining b-subdir

Checking in a-subdir/subsubdir/fish.c;

/usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v <-- fish.c

new revision: 1.2; previous revision: 1.1

done

Checking in b-subdir/random.c;

/usr/local/cvs/myproj/b-subdir/random.c,v <-- random.c

new revision: 1.2; previous revision: 1.1

done

floss$

Nehmen Sie sich einen Augenblick Zeit, um die Ausgaben sorgfältig zu lesen. Das meiste ist selbsterklärend. Es fällt jedoch auf, dass die Revisionsnummern inkrementiert wurden (wie zu erwarten war), die original Revisionen aber mit 1.1 anstatt 1.1.1.1, wie in der anfänglich erwähnten Entries-Datei, angezeigt wurden.

Es gibt eine Erklärung für diese Diskrepanz, auch wenn es nicht sonderlich wichtig ist. Dies betrifft die besondere Bedeutung, die CVS der Revision 1.1.1.1 beimisst. In den meisten Fällen kann man sagen, dass Dateien bei einem Import die Revisionsnumm