|
Es gibt auch eine einfachere, wenn auch leicht beschränkende Variante
des Vorangegangenen. Bei ihr frieren die Entwickler am Zweig ihre
Arbeit ein, wenn die Hauptversion eine Verschmelzung durchführt,
worauf die Entwickler der Hauptversion einen völlig neuen Zweig
abspalten, der dann den alten ersetzt. Die Entwickler des alten
Zweiges wechseln zu dem neuen über und fahren mit der Arbeit fort.
Dieser Zyklus wird wiederholt, bis es keinen Bedarf mehr für den Zweig
gibt. Das Ganze geht in etwa so (in Steno - wir setzen wie immer
voraus, dass jrandom@floss die Hauptversion bearbeitet und
qsmith@paste den Zweig):
user@linux floss$
cvs tag -b BRANCH-1
user@linux paste$
cvs checkout -r BRANCH-1 myproj
|
Die Arbeit sowohl an der Hauptversion als auch am Zweig wird
aufgenommen; irgendwann beratschlagen sich die Entwickler und
beschließen, dass es Zeit ist, die Änderungen aus dem Zweig in die
Hauptversion einfließen zu lassen:
user@linux paste$
cvs ci -m "committing all uncommitted changes"
user@linux floss$
cvs update -j BRANCH-1
|
Alle Änderungen werden aus dem Zweig übernommen; die Entwickler am
Zweig unterbrechen ihre Arbeit, während die Entwickler der
Hauptversion alle Konflikte auflösen, den Commit vornehmen, eine
Markierung setzen und einen neuen Zweig erzeugen:
user@linux floss$
cvs ci -m "merged from BRANCH-1"
user@linux floss$
cvs tag merged-from-BRANCH-1
user@linux floss$
cvs tag -b BRANCH-2
|
jetzt schalten die Entwickler des (alten) Zweiges ihre Arbeitskopien
auf den neuen Zweig um; sie wissen, dass sie keine noch nicht per
Commit bestätigten Änderungen verlieren, denn als die Verschmelzung
begonnen wurde, waren sie up-to-date, und der neue Zweig stammt aus
einer Hauptversion, welche die Änderungen des alten Zweigs übernommen
hat:
user@linux paste$
cvs update -r BRANCH-2
|
Das Ganze wird so endlos fortgesetzt, man muss nur BRANCH-1 durch
BRANCH-2 und (vorher) BRANCH-2 durch BRANCH-3 ersetzen.
Ich nenne das die Technik »fliegender Fisch«, denn der Zweig
»entspringt« mehrfach der Hauptversion, reist ein kurzes Stück und
»taucht« dann wieder in sie ein. Der Vorteil dieses Ansatzes ist,
dass er einfach ist (die Hauptversion übernimmt immer alle Änderungen
des jeweiligen Zweigs) und dass die Entwickler der Zweigversion nie
Konflikte auflösen müssen - sie bekommen einfach jedes Mal einen neuen
sauberen Zweig ausgehändigt, an dem sie dann arbeiten. Der Nachteil
ist, dass die »abgespaltenen« Entwickler natürlich jedes Mal untätig
herumsitzen müssen, während ihre Änderungen in die Hauptversion
propagiert werden - und das kann beliebig viel Zeit in Anspruch
nehmen, abhängig davon, wie viele Konflikte aufgelöst werden müssen.
Ein weiterer Nachteil liegt darin, dass dann viele kleine unbenutzte
Zweige herumliegen anstelle von vielen unbenutzten nichtverzweigenden
Marken. Wie auch immer - wenn es Sie nicht stört, Millionen winziger,
überflüssiger Zweige zu haben und Sie weitgehend reibungslose
Zusammenführungen zu schätzen wissen, dann ist »fliegender Fisch« der
- was die mentale Buchhaltung angeht - leichteste Weg.
Egal wie Sie nun vorgehen, Sie sollten immer versuchen, die Trennung
so kurz wie möglich zu halten. Wenn Zweig und Hauptversion zu lange
ohne Verschmelzung laufen, können sie schnell nicht nur unter
textuellem, sondern auch unter semantischem Auseinanderdriften leiden.
Änderungen, die nur textuell in Konflikt stehen, sind sehr leicht
aufzulösen. Änderungen, die auf Grund unterschiedlicher Konzepte in
Konflikt stehen, erweisen sich häufig als die am schwersten zu
findenden und zu behebenden. Die Abspaltung eines Zweiges, die für die
Entwickler so befreiend wirkt, ist auch genau deswegen so gefährlich,
da sie beide Seiten von den Auswirkungen durch die Änderungen auf der
jeweils anderen Seite abschirmt ... eine gewisse Zeit lang. Wenn Sie
Verzweigungen verwenden, wird Kommunikation lebensnotwendig: Jeder
muss doppelt sicherstellen, dass er die Pläne der anderen kennt und so
programmiert, dass alle auf dasselbe Ziel zusteuern.
Verzweigungen und Schlüsselwortexpansion sind natürliche Feinde
|
Wenn Ihre Dateien RCS-Schlüsselwörter verwenden, die im Zweig und in
der Hauptversion unterschiedlich expandiert werden, werden Sie beinahe
unter Garantie bei jedem Versuch, die beiden zusammenzuführen,
»unberechtigte« Konflikte erhalten. Selbst wenn überhaupt nichts
geändert wurde, überlappen die Schlüsselwörter, und ihre Expansionen
passen nicht zueinander. Wenn beispielsweise README.txt in der
Hauptversion
$Revision: 1.5 $
enthält, im Zweig hingegen
$Revision: 1.5 $
dann werden Sie beim Verschmelzen folgenden Konflikt bekommen:
user@linux ~$
cvs update -j Exotic_Greetings-branch
RCS file: /usr/local/newrepos/myproj/README.txt,v retrieving revision 1.14 retrieving revision 1.14.2.1 Merging differences between 1.14 and 1.14.2.1 into README.tx rcsmerge: warning: conflicts during merge
user@linux ~$
cat README.txt
.... <<<<<<< README.txt key $Revision: 1.5 $ ======= key $Revision: 1.5 $ >>>>>>> 1.14.2.1 ...
|
Um dies zu verhindern, können Sie die Expansion zeitweise
unterdrücken, indem Sie beim Zusammenführen die Option -kk mit
übergeben (ich weiß nicht, wofür -kk steht, vielleicht »kill
keywords«11?):
user@linux ~$
cvs update -kk -j Exotic_Greetings-branch
RCS file: /usr/local/newrepos/myproj/README.txt,v retrieving revision 1.14 retrieving revision 1.14.2.1 Merging differences between 1.14 and 1.14.2.1 into README.txt
user@linux ~$
floss$ cat README.txt
... $Revision: 1.5 $ ...
|
Eines müssen Sie allerdings beachten: Wenn Sie -kk verwenden, wird
auch jede andere Schlüsselwortexpansion, die Sie vielleicht für die
Datei gesetzt haben, außer Kraft gesetzt. Das ist besonders bei
Binärdateien ein Problem, die normalerweise auf -kb gesetzt sind
(wodurch jede Schlüsselwortexpansion und jede Zeilenendekonvertierung
unterdrückt wird). Wenn Sie also Binärdateien aus dem Zweig in die
Hauptversion überführen möchten, benutzen Sie -kk nicht. Behandeln
Sie stattdessen die auftretenden Konflikte von Hand.
|
Manchmal müssen an einer Software eines externen Lieferanten lokale
Änderungen vorgenommen werden, die bei jedem erhaltenen Update der
Software aktualisiert werden müssen, nämlich wenn der Lieferant die
lokalen Änderungen nicht übernimmt. (Und es gibt eine Menge in Frage
kommender legitimer Gründe, warum das nicht möglich ist.)
CVS kann einen bei dieser Aufgabe unterstützen. Der dafür zuständige
Mechanismus nennt sich »Vendor Branches«, was soviel heißt wie
»abgezweigte Version eines externen Lieferanten«. »Vendor Branches«
sind die Erklärung für die (bisher) verwirrenden letzten beiden
Optionen, die man bei cvs import angeben kann: vendor tag und release
tag; beide habe ich in Kapitel 2 unter den Tisch fallen lassen.
Das Ganze funktioniert so: Der allererste Import gleicht jedem anderen
initialisierenden Import eines CVS-Projekts (abgesehen davon, dass Sie
besondere Sorgfalt bei der Wahl der Lieferantenmarkierung (»vendor
tag«) und der Versionsmarke (»release tag«) walten lassen sollten):
user@linux ~$
pwd
/home/jrandom/theirproj-1.0
user@linux ~$
cvs import -m "Import of TheirProj 1.0" theirproj Them THEIRPROJ_1_0
N theirproj/INSTALL N theirproj/README N theirproj/src/main.c N theirproj/src/parse.c N theirproj/src/digest.c N theirproj/doc/random.c N theirproj/doc/manual.txt No conflicts created by this import
|
Dann checken Sie irgendwo eine Arbeitskopie aus, nehmen Ihre lokalen
Anpassungen vor und führen commit auf dem Ergebnis aus:
user@linux ~$
cvs -q co theirproj
U theirproj/INSTALL U theirproj/README U theirproj/doc/manual.txt U theirproj/doc/random.c U theirproj/src/digest. U theirproj/src/main.c U theirproj/src/parse.c
user@linux ~$
cd theirproj
user@linux ~$
emacs src/main.c src/digest.c
user@linux ~$
cvs -q update
M src/digest.c M src/main.c
user@linux ~$
floss$ cvs -q ci -m "changed digestion algorithm; added comment to main"
Checking in src/digest.c; /usr/local/newrepos/theirproj/src/digest.c,v <-- digest.c new revision: 1.2; previous revision: 1.1 done Checking in src/main.c; /usr/local/newrepos/theirproj/src/main.c,v <-- main.c new revision: 1.2; previous revision: 1.1 done
|
Ein Jahr später erreicht uns die nächste Version der Software von
Them, Inc., und Sie müssen Ihre lokalen Änderungen darin einbauen.
Deren und Ihre Änderungen überlappen ein wenig. Them, Inc. hat eine
neue Datei hinzugefügt, einige Dateien geändert, die Sie nicht berührt
haben, aber auch zwei Dateien verändert, an denen auch Sie Änderungen
vorgenommen haben.
Zunächst müssen Sie einen neuen import durchführen, diesmal von den
neuen Quelltexten. Nur wenig ist gegenüber dem ersten Import anders:
Sie importieren dasselbe Projekt in das Archiv, mit demselben Vendor
Branch. Das Einzige, was sich unterscheidet, ist der Release Tag:
user@linux ~$
floss$ pwd
/home/jrandom/theirproj-2.0
user@linux ~$
cvs -q import -m "Import of TheirProj 2.0" theirproj Them THEIRPROJ_2_0
U theirproj/INSTALL N theirproj/TODO U theirproj/README cvs import: Importing /usr/local/newrepos/theirproj/src C theirproj/src/main.c U theirproj/src/parse.c C theirproj/src/digest.c cvs import: Importing /usr/local/newrepos/theirproj/doc U theirproj/doc/random.c U theirproj/doc/manual.txt 2 conflicts created by this import. Use the following command to help the merge: cvs checkout -jThem:yesterday -jThem theirproj
|
Himmel! Nie haben wir CVS so hilfsbereit gesehen. Es sagt uns in der
Tat, welches Kommando wir eingeben sollen, um die Änderungen
zusammenzuführen. Und was es uns sagt, ist sogar fast richtig! Das
angegebene Kommando funktioniert - vorausgesetzt, Sie passen yesterday
so an, dass es einen beliebigen Zeitraum bezeichnet, der mit
Sicherheit den ersten Import einschließt, aber nicht den zweiten. Ich
bevorzuge aber leicht die Methode, die den Release Tag verwendet:
user@linux ~$
cvs checkout -j THEIRPROJ_1_0 -j THEIRPROJ_2_0 theirproj
cvs checkout: Updating theirproj U theirproj/INSTALL U theirproj/README U theirproj/TODO cvs checkout: Updating theirproj/doc U theirproj/doc/manual.txt U theirproj/doc/random.c cvs checkout: Updating theirproj/src U theirproj/src/digest.c RCS file: /usr/local/newrepos/theirproj/src/digest.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.2 Merging differences between 1.1.1.1 and 1.1.1.2 into digest.c rcsmerge: warning: conflicts during merge U theirproj/src/main.c RCS file: /usr/local/newrepos/theirproj/src/main.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.2 Merging differences between 1.1.1.1 and 1.1.1.2 into main.c U theirproj/src/parse.c
|
Beachten Sie, dass import uns zwei Konflikte gemeldet hat, merge
hingegen scheint nur einen Konflikt zu bemerken. Es scheint, als wäre
die Vorstellung eines Konflikts, die CVS beim Importieren hat, leicht
abweichend von den übrigen Fällen. Grundsätzlich meldet import einen
Konflikt, wenn sowohl Sie als auch der Lieferant zwischen dem letzten
Import und dem jetzigen eine Datei verändert hat. Wenn es jedoch an
das Zusammenführen geht, dann hält update es mit der normalen
Definition von »Konflikt«: überlappende Änderungen. Änderungen, die
nicht überlappen, werden auf übliche Art zusammengeführt; die Datei
wird dann als geändert markiert.
Ein schneller diff bestätigt, dass nur eine der Dateien
Konfliktmarkierungen trägt:
user@linux ~$
cvs -q update
C src/digest.c M src/main.c
user@linux ~$
cvs diff -c
Index: src/digest.c =============================================== RCS file: /usr/local/newrepos/theirproj/src/digest.c,v retrieving revision 1.2 diff -c -r1.2 digest.c *** src/digest.c 1999/07/26 08:02:18 1.2 -- src/digest.c 1999/07/26 08:16:15 *************** *** 3,7 **** -- 3,11 ---- void digest () { + <<<<<<< digest.c printf ("gurgle, slorp\n"); + ======= + printf ("mild gurgle\n"); + >>>>>>> 1.1.1.2 } Index: src/main.c ========================================== RCS file: /usr/local/newrepos/theirproj/src/main.c,v retrieving revision 1.2 diff -c -r1.2 main.c *** src/main.c 1999/07/26 08:02:18 1.2 -- src/main.c 1999/07/26 08:16:15 *************** *** 7,9 **** -- 7,11 ---- { printf ("Goodbye, world!\n"); } + + /* I, the vendor, added this comment for no good reason. */
|
Jetzt gilt es nur noch, die Konflikte - wie bei jedem anderen
Verschmelzen von Zweigen auch - auszuräumen:
user@linux ~$
emacs src/digest.c src/main.c
...
user@linux ~$
cvs -q update
M src/digest.c M src/main.c
user@linux ~$
cvs diff src/digest.c
cvs diff src/digest.c Index: src/digest.c ===================================== RCS file: /usr/local/newrepos/theirproj/src/digest.c,v retrieving revision 1.2 diff -r1.2 digest.c 6c6 < printf ("gurgle, slorp\n"); -- > printf ("mild gurgle, slorp\n");
|
Und noch ein Commit der Änderungen
user@linux ~$
floss$ cvs -q ci -m "Resolved conflicts with import of 2.0"
Checking in src/digest.c; /usr/local/newrepos/theirproj/src/digest.c,v <-- digest.c new revision: 1.3; previous revision: 1.2 done Checking in src/main.c; /usr/local/newrepos/theirproj/src/main.c,v <-- main.c new revision: 1.3; previous revision: 1.2 done
|
und dann auf die nächste Version des Lieferanten warten. (Natürlich
sollten Sie überprüfen, ob Ihre lokalen Anpassungen noch
funktionieren!)
|
|