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.
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.
|
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.
|
Sie haben vielleicht bemerkt, dass bei jedem bisher verwendeten
CVS-Kommando keine Dateien in der Kommandozeile angegeben wurden. Es
wurde
ausgeführt anstatt
user@linux ~$
cvs diff hello.c
|
und
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.
|
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 ~$
|
|
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.
|
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.
|
|