SelfLinux Programmierung CVS CVS für Fortgeschrittene Abschnitt 7 | SelfLinux-0.13.0

| CVS für Fortgeschrittene | Autor Formatierung GPL

#### 12 Der Ansatz »fliegender Fisch«: Wie's einfacher geht

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.

#### 13 Die Quelltexte Dritter verfolgen: Vendor Branches

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!)