» SelfLinux » Programmierung » CVS » Übersicht CVS » Abschnitt 6 SelfLinux-0.10.0
zurück   Startseite Kapitelanfang Inhaltsverzeichnis GPL   weiter

SelfLinux-Logo
Dokument Übersicht CVS - Abschnitt 6 Revision: 1.1.2.16
Autor:  Karl Fogel
Formatierung:  Matthias Hagedorn
Lizenz:  GPL
 

6 Eine Arbeitskopie auschecken

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

user@linux ~$ 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
user@linux ~$ ls
myproj/ was_myproj/
user@linux ~$ cd myproj
user@linux ~$ ls
CVS/ README.txt a-subdir/ b-subdir/ hello.c
user@linux ~$

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:

user@linux ~$ ls a-subdir
CVS/ subsubdir/ whatever.c
user@linux ~$ ls a-subdir/subsubdir/
CVS/ fish.c
user@linux ~$ 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:

user@linux ~$ cd CVS
user@linux ~$ ls
Entries Repository Root
user@linux ~$ cat Root
/usr/local/cvs
user@linux ~$ cat Repository
myproject
user@linux ~$

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:

user@linux ~$ cd CVS
user@linux ~$ cat Root
:pserver:jrandom@cvs.foobar.com:/usr/local/cvs
user@linux ~$ cat Repository
/usr/local/cvs/myproject
user@linux ~$

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:

user@linux ~$ 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

user@linux ~$ cd a-subdir/CVS
user@linux ~$ cat Root
/usr/local/cvs
user@linux ~$ cat Repository
myproj/a-subdir
user@linux ~$ cat Entries
/whatever.c/1.1.1.1/Sun Apr 18 18:18:22 1999//
D/subsubdir////
user@linux ~$

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.


6.1 Eine Veränderung einbringen

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

user@linux ~$ 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:

user@linux ~$ 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.



6.2 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:

user@linux ~$ 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:

user@linux ~$ 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:

user@linux ~$ 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:

user@linux ~$ 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 Aufgabe 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), 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:

user@linux ~$ 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:

user@linux ~$ 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.



6.3 CVS und implizite Argumente

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

user@linux ~$ cvs diff

ausgeführt anstatt

user@linux ~$ cvs diff hello.c

und

user@linux ~$ cvs update

anstatt von

user@linux ~$ 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:

user@linux ~$ 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
user@linux ~$

oder noch besser:

user@linux ~$ cvs -q update
M hello.c
M a-subdir/subsubdir/fish.c
M b-subdir/random.c
user@linux ~$
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:

user@linux ~$ cvs update hello.c b-subdir/random.c
M hello.c
M b-subdir/random.c
user@linux ~$

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

user@linux ~$ 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):

user@linux ~$ 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.



6.4 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:

user@linux ~$ 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
user@linux ~$ 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
user@linux ~$

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 Revisionsnummer 1.1 bekommen, diese aber - aus Gründen, die nur CVS weiß - als 1.1.1.1 in der Entries-Datei bis zum ersten Commit abgelegt wird.

Revisionsnummern

Jede Datei eines Projektes hat eine eigene Revisionsnummer. Wenn eine Datei wieder durch einen Commit zurückgesendet wird, wird die letzte Stelle der Revisionsnummer um eins inkrementiert. Daher haben die verschiedenen Dateien, die ein Projekt bilden, möglicherweise sehr unterschiedliche Revisionsnummern. Das bedeutet lediglich, dass einige Dateien öfter verändert (und durch Commit übertragen) wurden als andere.

(Sie werden sich vielleicht fragen, was es nun mit dem linken Teil der Revisionsnummern auf sich hat, wenn immer nur der rechte inkrementiert wird. Tatsächlich wird dieser Teil von CVS nicht automatisch inkrementiert, jedoch kann ein Benutzer dies anfordern. Dies ist eine selten benutzte Funktion und wird daher in diesem Kapitel nicht behandelt.)

Aus dem benutzten Beispielprojekt wurden gerade Veränderungen an drei Dateien durch einen Commit abgeschickt. Jede dieser Dateien hat nun die Revisionsnummer 1.2, jedoch haben die restlichen Dateien noch 1.1. Wird ein Checkout ausgeführt, werden nur die Dateien mit der höchsten Revisionsnummer geholt. Die nachfolgende Ausgabe zeigt, was der Benutzer qsmith sehen würde, wenn er zum aktuellen Zeitpunkt einen Ckeckout von myproject machen würde und die Revisionsnummern des Hauptverzeichnisses ausgibt:

user@linux ~$ cvs -q -d :pserver:qsmith@cvs.foobar.com:/usr/local/cvs co myproj
U myproj/README.txt
U myproj/hello.c
U myproj/a-subdir/whatever.c
U myproj/a-subdir/subsubdir/fish.c
U myproj/b-subdir/random.c
user@linux ~$ cd myproj/CVS
user@linux ~$ cat Entries
/README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
/hello.c/1.2/Mon Apr 19 06:35:15 1999//
D/a-subdir////
D/b-subdir////
user@linux ~$

Unter anderem hat die Datei hello.c nun die Revisionsnummer 1.2, während README.txt noch die ursprüngliche Revisionsnummer hat (Revision 1.1.1.1 oder auch 1.1).

Wenn er nun die Zeile

printf ("between hello and goodbye\n");

in hello.c einfügen würde und durch einen Commit an das Archiv sendet, wird die Revisionsnummer wiederum um eins erhöht:

user@linux ~$ cvs ci -m "added new middle line"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v <-- hello.c
new revision: 1.3; previous revision: 1.2
done
user@linux ~$

Nun hat hello.c die Revision 1.3, fish.c und random.c haben noch 1.2, und alle anderen Dateien haben 1.1.

Bemerkung
Hier wurde das Kommando mit cvs ci anstatt cvs commit angegeben. Die meisten CVS-Kommandos haben, um die Tipparbeit zu vereinfachen, Kurzformen. Von checkout, update und commit sind die Kurzformen co, up und ci. Eine Liste aller Kurzformen kann mit dem Befehl cvs --help-synonyms abgefragt werden.

In den meisten Fällen kann die Revisionsnummer einer Datei ignoriert werden. Meistens werden diese Nummern nur für interne Verwaltungsaufgaben von CVS selbst automatisch verwendet. Dennoch sind Revisionsnummern sehr nützlich, wenn man ältere Versionen einer Datei holen möchte (oder dagegen einen Diff macht).

Die Untersuchung der Entries-Datei ist nicht die einzige Möglichkeit, Revisionsnummern herauszubekommen. Dazu kann auch das status-Kommando verwendet werden.

user@linux ~$ cvs status hello.c
=====================================================
File: hello.c Status: Up-to-date

Working revision: 1.3 Tue Apr 20 02:34:42 1999
Repository revision: 1.3 /usr/local/cvs/myproj/hello.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

Dies gibt, wenn es ohne bestimmte Dateinamen aufgerufen wird, den Status aller Dateien eines Projektes aus:

user@linux ~$ cvs status
cvs status: Examining.
============================================
File: README.txt Status: Up-to-date

Working revision: 1.1.1.1 Sun Apr 18 18:18:22 1999
Repository revision: 1.1.1.1 /usr/local/cvs/myproj/README.txt,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

================================================
File: hello.c Status: Up-to-date
Working revision: 1.3 Tue Apr 20 02:34:42 1999
Repository revision: 1.3 /usr/local/cvs/myproj/hello.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

cvs status: Examining a-subdir
===============================================
File: whatever.c Status: Up-to-date

Working revision: 1.1.1.1 Sun Apr 18 18:18:22 1999
Repository revision: 1.1.1.1 /usr/local/cvs/myproj/a-subdir/whatever.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

cvs status: Examining a-subdir/subsubdir
===============================================
File: fish.c Status: Up-to-date

Working revision: 1.2 Mon Apr 19 06:35:27 1999
Repository revision: 1.2 /usr/local/cvs/myproj/
a-subdir/subsubdir/fish.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

cvs status: Examining b-subdir
==============================================
File: random.c Status: Up-to-date

Working revision: 1.2 Mon Apr 19 06:35:27 1999
Repository revision: 1.2 /usr/local/cvs/myproj/b-subdir/random.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
user@linux ~$

Ignorieren Sie einfach alle Teile, die Sie nicht verstehen. Tatsächlich gilt dies grundsätzlich für CVS. Oft wird die kleine Information, die Sie benötigen, von Unmengen an Informationen flankiert, die Sie entweder gar nicht interessieren oder vielleicht auch gar nicht verstehen. Das ist völlig normal. Suchen Sie sich einfach das heraus, was Sie brauchen, und ignorieren Sie den Rest.

Im vorangegangenen Beispiel besteht der interessante Teil aus den ersten drei Zeilen (die Leerzeile nicht mitgezählt) der Statusausgabe jeder Datei. Die erste Zeile ist die wichtigste; dort stehen der Dateiname und der Status der Datei innerhalb der Arbeitskopie. Zurzeit sind alle Dateien auf dem gleichen Stand mit dem Archiv, daher steht überall der Status Up-to-date. Wenn jedoch random.c modifiziert und noch nicht an das Archiv mittels Commit übertragen worden wäre, könnte dies so aussehen:

====================================================
File: random.c Status: Locally Modified

Working revision: 1.2 Mon Apr 19 06:35:27 1999
Repository revision: 1.2 /usr/local/cvs/myproj/b-subdir/random.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

Working revision und Repository revision zeigen an, ob die Datei mit dem Archiv übereinstimmt. Zurück bei der original Arbeitskopie (die Kopie von jrandom, welche die aktuellen Änderungen von hello.c noch nicht hat) wird Folgendes ausgegeben:

user@linux ~$ cvs status hello.c
==================================================
File: hello.c Status: Needs Patch

Working revision: 1.2 Mon Apr 19 02:17:07 1999
Repository revision: 1.3 /usr/local/cvs/myproj/hello.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
user@linux ~$

Dies besagt nun, dass jemand eine Veränderung an hello.c durchgeführt und mittels Commit eingefügt hat und damit die Revision des Archivs zu 1.3 wurde, diese Arbeitskopie aber noch Revision 1.2 hat. Die Zeile Status: Needs Patch besagt, dass beim nächsten Update die Änderungen vom Archiv geholt und mittels patch in die Arbeitskopie eingearbeitet würden.

Nehmen wir aber zunächst an, wir wüssten nichts von den Änderungen, die qsmith an hello.c durchgeführt hat und führen daher auch nicht status oder update aus. Stattdessen wird die Datei ebenfalls bearbeitet und eine geringfügig andere Veränderung an der gleichen Stelle der Datei durchgeführt. Dies führt zu dem ersten Konflikt.

Konflikte erkennen und auflösen

Einen Konflikt zu erkennen ist einfach. Wird update ausgeführt, gibt CVS diesbezüglich sehr eindeutige Meldungen aus. Doch lassen Sie uns zuerst einen Konflikt erzeugen. Dazu bearbeiten wir hello.c und fügen folgende Zeile ein:

printf ("this change will conflict\n");

und zwar genau an der Stelle, an der qsmith

printf ("between hello and goodbye\n");

einfügte. Zu diesem Zeitpunkt ist der Status unserer Kopie von hello.c

user@linux ~$ cvs status hello.c
===============================================
File: hello.c Status: Needs Merge

Working revision: 1.2 Mon Apr 19 02:17:07 1999
Repository revision: 1.3 /usr/local/cvs/myproj/hello.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
user@linux ~$

Das bedeutet, dass sowohl die Version der Datei im Archiv als auch die lokale Arbeitskopie verändert wurde und diese Veränderungen zusammengeführt werden müssen (merge). (CVS weiß noch nicht, dass diese Veränderungen einen Konflikt ergeben werden, da noch kein Update durchgeführt wurde.) Wird das Update durchgeführt, erscheint folgende Ausgabe:

user@linux ~$ cvs update hello.c
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.2
retrieving revision 1.3
Merging differences between 1.2 and 1.3 into hello.c
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in hello.c
C hello.c
user@linux ~$

Die letzte Zeile ist das kleine Geschenk von CVS. Das C in der ersten Spalte neben dem Dateinamen bedeutet, dass die Veränderungen zwar zusammengeführt wurden, aber ein Konflikt entstand. Die Datei hello.c beinhaltet nun beide Veränderungen:

#include <stdio.h>
void
main ()
{
printf ("Hello, world!\n");
<<<<<< hello.c
printf ("this change will conflict\n");
=======
printf ("between hello and goodbye\n");
>>>>>>> 1.3
printf ("Goodbye, world!\n");
}

Konflikte werden durch Konfliktmarkierungen in folgendem Format angezeigt:

<<<<<< (Dateiname)
die noch nicht durch Commit abgeschickten Änderungen der Arbeitskopie
blah blah lah

=======
die neuen Änderungen aus dem Archiv
blah blah blah
und so weiter
>>>>>>> (letzte Revisionsnummer des Archivs)

In der Entries-Datei wird ebenfalls vermerkt, dass sich die Datei derzeit in einem nur halbwegs fertigen Zustand befindet:

user@linux ~$ cat CVS/Entries
/README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
D/a-subdir////
D/b-subdir////
/hello.c/1.3/Result of merge+Tue Apr 20 03:59:09 1999//
user@linux ~$

Um den Konflikt zu beseitigen, muss die Datei so bearbeitet werden, dass der entsprechende Quelltext erhalten bleibt, die Konfliktmarkierungen entfernt werden und diese erneute Veränderung mittels Commit an das Archiv gesendet wird. Dies bedeutet nicht notwendigerweise, die eine Veränderung gegen die andere abwägen zu müssen; es könnte auch der gesamte Abschnitt (oder gar die gesamte Datei) neu geschrieben werden, weil eventuell beide Veränderungen nicht ausreichend sind. In diesem Fall soll die erste Veränderung den Zuschlag bekommen, jedoch mit etwas anderer Groß- und Kleinschreibung und Punktuation als die Version von qsmith:

user@linux ~$ emacs hello.c
(die Veränderungen anbringen ...)
user@linux ~$ cat hello.c
#include <stdio.h>
void
main ()
{
printf ("Hello, world!\n");
printf ("BETWEEN HELLO AND GOODBYE.\n");
printf ("Goodbye, world!\n");
}
user@linux ~$ cvs ci -m "adjusted middle line"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v <- hello.c
new revision: 1.4; previous revision: 1.3
done
user@linux ~$


6.5 Herausfinden, wer was gemacht hat: Log-Nachrichten lesen

Das Projekt hat mittlerweile einige Veränderungen durchgemacht. Möchte man nun einen Überblick darüber bekommen, was bisher geschah, so möchte man wahrscheinlich nicht jeden einzelnen Diff im Detail betrachten. Einfach die Log-Nachrichten durchlesen zu können, wäre ideal und kann auch einfach mit dem log-Kommando erreicht werden:

user@linux ~$ cvs log
(Seiten über Seiten an Ausgaben ausgelassen)

Die Log-Ausgabe ist tendenziell etwas ausführlich. Sehen wir uns die Log-Nachrichten nur für eine Datei an:

user@linux ~$ cvs log hello.c
RCS file: /usr/local/cvs/myproj/hello.c,v
Working file: hello.c
head: 1.4
branch:
locks: strict
access list:
symbolic names:
start: 1.1.1.1
jrandom: 1.1.1
keyword substitution: kv
total revisions: 5; selected revisions: 5
description:
----------------
revision 1.4
date: 1999/04/20 04:14:37; author: jrandom; state: Exp; lines: +1 -1
adjusted middle line
----------------
revision 1.3
date: 1999/04/20 02:30:05; author: qsmith; state: Exp; lines: +1 -0
added new middle line
----------------
revision 1.2
date: 1999/04/19 06:35:15; author: jrandom; state: Exp; lines: +1 -0
print goodbye too
----------------
revision 1.1
date: 1999/04/18 18:18:22; author: jrandom; state: Exp;
branches: 1.1.1;
Initial revision
----------------
revision 1.1.1.1
date: 1999/04/18 18:18:22; author: jrandom; state: Exp; lines: +0 -0
initial import into CVS
=========================================

Wie üblich, steht am Anfang eine Menge an Informationen, die einfach ignoriert werden kann. Die richtig guten Sachen kommen nach den Zeilen mit den Strichen, und das Format ist eigentlich selbsterklärend.

Wenn mehrere Dateien mit einem Commit abgeschickt wurden, erscheint dafür nur eine Log-Nachricht; dies kann beim Nachvollziehen von Veränderungen nützlich sein. Zum Beispiel haben wir zuvor fish.c und random.c gleichzeitig mit einem Commit abgeschickt. Dies geschah wie folgt:

user@linux ~$ cvs commit -m "filled out C code"
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

Das Ergebnis war, beide Dateien mit der gleichen Log-Nachricht durch commit abzuschicken: »filled out C code.« (So, wie es hier geschah, kamen damit beide Dateien von Revision 1.1 zu 1.2, aber dies ist nur ein Zufall. Hätte random.c Revision 1.29 gehabt, wäre daraus mit diesem Commit 1.30 geworden, und diese Revision 1.30 hätte die gleiche Log-Nachricht wie fish.c in der Revision 1.2 bekommen.)

Wird darauf cvs log angewendet, werden die gemeinsamen Log-Nachrichten angezeigt:

user@linux ~$ cvs log a-subdir/subsubdir/fish.c b-subdir/random.c
RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v
Working file: a-subdir/subsubdir/fish.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
start: 1.1.1.1
jrandom: 1.1.1
keyword substitution: kv
total revisions: 3; selected revisions: 3
description:
---------------
revision 1.2
date: 1999/04/19 06:35:27; author: jrandom; state: Exp; lines: +8 -1
filled out C code
---------------
revision 1.1
date: 1999/04/18 18:18:22; author: jrandom; state: Exp;
branches: 1.1.1;
Initial revision
----------------
revision 1.1.1.1
date: 1999/04/18 18:18:22; author: jrandom; state: Exp; lines: +0 -0
initial import into CVS
=================================================
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
Working file: b-subdir/random.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
start: 1.1.1.1
jrandom: 1.1.1
keyword substitution: kv
total revisions: 3; selected revisions: 3
description:
----------------
revision 1.2
date: 1999/04/19 06:35:27; author: jrandom; state: Exp; lines: +8 -1
filled out C code
----------------
revision 1.1
date: 1999/04/18 18:18:22; author: jrandom; state: Exp;
branches: 1.1.1;
Initial revision
----------------
revision 1.1.1.1
date: 1999/04/18 18:18:22; author: jrandom; state: Exp; lines: +0 -0
initial import into CVS
============================================

In dieser Ausgabe kann man sehen, dass die beiden Revisionen Teil des gleichen Commits waren. (Die Tatsache, dass die Zeitstempel der beiden Revisionen gleich sind, oder zumindest sehr nahe beieinander liegen, ist ein weiterer Beweis.)

Log-Nachrichten durchzusehen ist eine gute Methode, um einen Überblick darüber zu bekommen, was in einem Projekt vorgegangen oder was mit einer Datei zu einem bestimmten Zeitpunkt passiert ist. Es gibt auch freie Werkzeuge, um die rohe cvs log-Ausgabe in ein kompakteres und lesbareres Format umzuwandeln (wie beispielsweise das GNU ChangeLog-Format); diese Werkzeuge werden an dieser Stelle nicht behandelt, werden aber in  Kapitel 10 eingeführt.



6.6 Veränderungen untersuchen und zurücknehmen

Stellen Sie sich vor, dass qsmith in den Log-Nachrichten sieht, dass jrandom die letzten Veränderungen an hello.c vorgenommen hat:

revision 1.4
date: 1999/04/20 04:14:37; author: jrandom; state: Exp; lines: +1 -1
adjusted middle line

und sich fragt, was jrandom getan hat. Formal gesprochen fragt sich qsmith: Was ist der Unterschied zwischen meiner Revision (1.3) von hello.c und der darauf folgenden von jrandom (1.4)? Dies kann mit dem diff-Kommando herausgefunden werden, indem nun zwei unterschiedliche Revisionen durch die zusätzliche Kommandooption -r verglichen werden:

user@linux ~$ cvs diff -c -r 1.3 -r 1.4 hello.c
Index: hello.c
================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -c -r1.3 -r1.4
*** hello.c 1999/04/20 02:30:05 1.3
--- hello.c 1999/04/20 04:14:37 1.4
***************
*** 4,9 ****
main ()
{
printf ("Hello, world!\n");
! printf ("between hello and goodbye\n");
printf ("Goodbye, world!\n");
}

--- 4,9 ----
main ()
{
printf ("Hello, world!\n");
! printf ("BETWEEN HELLO AND GOODBYE.\n");
printf ("Goodbye, world!\n");
}

Auf diese Weise betrachtet ist die Veränderung sofort klar. Und weil die Revisionsnummern in chronologischer Reihenfolge angegeben werden (grundsätzlich eine gute Idee), wird auch der Diff in korrekter Reihenfolge gezeigt. Wird nur eine Revisionsnummer angegeben, benutzt CVS die Revision der aktuellen Arbeitskopie als zweites Argument.

Als qsmith diese Veränderung sieht, entscheidet er sich spontan zu Gunsten seiner eigenen Version und beschließt, ein undo durchzuführen - also eine Revision zurückzugehen.

Dies bedeutet aber nicht, dass er seine Revision 1.4 verlieren möchte. Obwohl es, rein technisch gesprochen, mit CVS sicherlich möglich wäre, diesen Effekt zu erreichen, gibt es dazu meist keinen Grund. Es ist vielmehr sinnvoller, Revision 1.4 in der Historie beizubehalten und eine neue Revision 1.5 zu erzeugen, die genau wie 1.3 aussieht. So wird das undo-Ereignis ein Teil der Historie der Datei.

Es bleibt nur die Frage, wie der Inhalt der Revision 1.3 wiederhergestellt werden und Revision 1.5 daraus entstehen kann?

In diesem speziellen Fall könnte qsmith die Datei einfach per Hand bearbeiten, den Stand der Revision 1.3 abbilden und wieder commit ausführen. Wenn die Veränderungen jedoch komplexer sind (wie sie es im wahren Leben normalerweise sind), ist der Versuch, die alte Version von Hand wiederherzustellen, hoffnungslos und fehlerträchtig. Daher soll qsmith CVS benutzen, um die ältere wiederherzustellen und erneut durch commit an das Archiv zu senden.

Es gibt dafür zwei gleichwertige Wege: den langsamen, mühevollen Weg und den schnellen, schönen Weg. Wir werden den langsamen und mühevollen zuerst betrachten.

Die langsame Methode des Zurücknehmens

Diese Methode verwendet die -p-Option für update in Verbindung mit -r. Die -p-Option sorgt dafür, dass der Inhalt der angegebenen Revision auf der Standardausgabe erscheint. An sich ist dies noch nicht sonderlich hilfreich; der Inhalt der Datei rauscht über den Bildschirm, und die Arbeitskopie bleibt unverändert. Wenn jedoch die Ausgabe in die Datei umgeleitet wird, enthält die Datei wieder den Inhalt der alten Revision. Es ist, als wäre die Datei von Hand zum alten Stand zurück bearbeitet worden.

Zuerst muss qsmith jedoch seine Arbeitskopie mit dem Archiv abgleichen:

user@linux ~$ cvs update
cvs update: Updating .
U hello.c
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
user@linux ~$ cat hello.c
#include <stdio.h>
void
main ()
{
printf ("Hello, world!\n");
printf ("BETWEEN HELLO AND GOODBYE.\n");
printf ("Goodbye, world!\n");
}

Als Nächstes führt er update -p aus, um sicherzustellen, dass die Revision 1.3 tatsächlich die ist, die er haben möchte:

user@linux ~$ cvs update -p -r 1.3 hello.c
=========================================
Checking out hello.c
RCS: /usr/local/cvs/myproj/hello.c,v
VERS: 1.3
***************
#include <stdio.h>
void
main ()
{
printf ("Hello, world!\n");
printf ("between hello and goodbye\n");
printf ("Goodbye, world!\n");
}

Huch, da sind noch ein paar überflüssige Zeilen am Anfang der Ausgabe. Diese kamen eigentlich nicht über die Standardausgabe, sondern über Standard-Error, sind also harmlos. Nichtsdestotrotz erschweren diese das Lesen und können mit -Q unterdrückt werden:

user@linux ~$ cvs -Q update -p -r 1.3 hello.c
#include <stdio.h>
void
main ()
{
printf ("Hello, world!\n");
printf ("between hello and goodbye\n");
printf ("Goodbye, world!\n");
}

Nun - dies ist genau das, was qsmith bekommen wollte. Der nächste Schritt ist, die Ausgabe mit Hilfe einer Unix-Ausgabeumleitung in die Datei der Arbeitskopie zu bekommen (dies erledigt das >):

user@linux ~$ cvs -Q update -p -r 1.3 hello.c > hello.c
user@linux ~$ cvs update
cvs update: Updating .
M hello.c
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir

Wenn nun update ausgeführt wird, wird die Datei als modifiziert markiert, was auch Sinn hat, da der Inhalt verändert wurde. Speziell ist der Inhalt der gleiche wie der der älteren Revision 1.3 (nicht dass CVS sich darüber bewusst wäre, dass diese identisch mit einer älteren Revision ist - CVS merkt nur, dass die Datei verändert wurde). Wenn qsmith ganz sichergehen wollte, könnte er einen Diff zur Überprüfung machen:

user@linux ~$ cvs -Q diff -c
Index: hello.c
=============================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.4
diff -c -r1.4 hello.c
*** hello.c 1999/04/20 04:14:37 1.4
--- hello.c 1999/04/20 06:02:25
***************
*** 4,9 ****
main ()
{
printf ("Hello, world!\n");
! printf ("BETWEEN HELLO AND GOODBYE.\n");
printf ("Goodbye, world!\n");
}
--- 4,9 ----
main ()
{
printf ("Hello, world!\n");
! printf ("between hello and goodbye\n");
printf ("Goodbye, world!\n");
}

Ja, dies ist genau das, was er wollte: eine klare Umkehrung - tatsächlich ist dies das Gegenteil des Diff, den er zuvor bekommen hatte. Zufrieden führt er einen Commit aus:

user@linux ~$ cvs ci -m "reverted to 1.3 code"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v <- hello.c
new revision: 1.5; previous revision: 1.4
done

Die schnelle Methode des Zurücknehmens

Die schnelle, schöne Methode des Update ist es, die Option -j (für join) zu dem update-Kommando zu verwenden. Diese Option verhält sich wie die -r-Option, da sie zwei Revisionsnummern als Argumente verwendet und bis zu zwei Mal -j angegeben werden kann.

CVS bestimmt dann die Unterschiede zwischen den beiden angegebenen Revisionen und wendet diese als Patch auf die fragliche Datei an. (Die Reihenfolge, in der die Revisionen angegeben werden, ist daher von entscheidender Bedeutung.)

Angenommen, die Kopie von qsmith ist aktuell, so folgt daraus, dass er einfach Folgendes ausführen könnte:

user@linux ~$ cvs update -j 1.4 -j 1.3 hello.c
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.4
retrieving revision 1.3
Merging differences between 1.4 and 1.3 into hello.c
user@linux ~$ cvs update
cvs update: Updating .
M hello.c
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
user@linux ~$ cvs ci -m "reverted to 1.3 code" hello.c
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v <-- hello.c
new revision: 1.5; previous revision: 1.4
done

Wenn nur eine Datei in einen vorherigen Zustand zurückgeführt werden soll, gibt es eigentlich keinen großen Unterschied zwischen der mühevollen und der schnellen Methode. Sie werden später im Buch sehen, dass die schnelle Methode wesentlich besser dazu geeignet ist, mehrere Dateien gleichzeitig zurückzuführen. In der Zwischenzeit können Sie einfach die Methode verwenden, die Ihnen am besten gefällt.

Zurückführung ist kein Ersatz für Kommunikation

Aller Wahrscheinlichkeit nach war das, was qsmith in diesem Beispiel getan hat, sehr gemein. Arbeitet man mit anderen Leuten an einem wirklichen Projekt und ist man der Meinung, dass jemand eine Veränderung eingebracht hat, die nicht so gut war, sollte man zuerst mit ihm oder ihr darüber reden. Vielleicht gibt es einen guten Grund für diese Veränderung, oder sie oder er hat einfach nicht genau darüber nachgedacht. Auf jeden Fall gibt es keinen Grund, diese sofort rückgängig zu machen. Alle Revisionen werden von CVS permanent gespeichert, und man kann daher jederzeit wieder zu einer älteren Revision zurückkehren, nachdem man sich mit dem entsprechend Verantwortlichen abgesprochen hat.

Wenn Sie ein Projektleiter sind und Abgabefristen einzuhalten haben oder meinen, das Recht und die Notwendigkeit dazu zu haben, dann machen Sie es so - aber schicken Sie direkt anschließend eine E-Mail an den Autor der zurückgenommenen Veränderung, und erklären Sie ihm, warum Sie es getan haben und was Ihrer Meinung nach korrigiert werden müsse, damit die Veränderung wieder einfließen kann.




zurück   Seitenanfang Startseite Kapitelanfang Inhaltsverzeichnis GPL   weiter