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

| CVS für Fortgeschrittene | Autor Formatierung GPL

#### 10 Häufig mit der Hauptversion verschmelzen

Wir gehen davon aus, dass qsmith für einige Zeit an einer abgezweigten Version arbeiten möchte, damit er nicht die Hauptversion destabilisiert, die er mit jrandom teilt. Der erste Schritt liegt darin, die Verzweigung zu erzeugen. Beachten Sie, wie qsmith zunächst eine normale Marke (nicht verzweigend) am Punkt der Verzweigung erzeugt und erst dann die abgezweigte Version erstellt:

user@linux ~$ pwd
/home/qsmith/myproj
user@linux ~$ cvs tag Root-of-Exotic_Greetings
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
user@linux ~$ cvs tag -b Exotic_Greetings-branch
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c

Der Grund, zuerst die Hauptversion zu markieren, liegt darin, dass es eines Tages notwendig sein kann, die Hauptversion so abzurufen, wie sie zum Zeitpunkt der Erstellung der Verzweigung war. Wenn das jemals nötig ist, so hat man die Möglichkeit, eine Momentaufnahme der Hauptversion zu referenzieren, die vor der Verzweigung entstanden ist. Offensichtlich kann nicht die Marke der Verzweigung benutzt werden, da dann die abgezweigte Version abgerufen werden würde und nicht die Revisionen der Hauptversion, welche die Wurzel der Verzweigung bildet. Die einzige Möglichkeit ist die, eine normale Marke an der Revision anzubringen, an der die Verzweigung wurzelt. (Mancher hält sich so strikt an diese Regel, dass ich überlegt habe, sie als Verzweigungsregel Nummer 4 aufzuführen: Erstelle immer eine nichtverzweigende Marke zum Zeitpunkt der Verzweigung. Wie auch immer, auf manchen Servern wird dies nicht getan, und sie scheinen auch ohne auszukommen, sodass es letztlich einfach eine Geschmacksfrage bleibt.) Von nun an werde ich diese nichtverzweigende Marke als Verzweigungspunktmarkierung (Branch Point Tag) bezeichnen.

Beachten Sie auch, dass ein Namensschema eingehalten wird: Die Verzweigungspunktmarkierung fängt mit Root-of- an, gefolgt vom eigentlichen Namen der Verzweigung, wobei Unterstriche statt Bindestriche zur Worttrennung verwendet werden. Wenn die eigentliche Verzweigung angelegt wird, endet ihre Marke mit -branch, sodass Sie sie schon anhand ihres Namens als Marke eines Zweiges erkennen können. (Die Verzweigungspunktmarkierung Root-of-Exotic_Greetings erhält kein -branch, da sie ja keine Marke eines Zweiges ist.) Sie müssen sich natürlich nicht an dieses spezielle Namensschema halten, solange Sie nur irgendeines verwenden.

Ich bin hier natürlich besonders pedantisch. In kleineren Projekten, bei denen jeder weiß, was von wem getan wird, und bei denen man sich leicht von einer kurzen Phase der Verwirrung erholt, muss man sich nicht unbedingt an diese Regeln halten. Ob Sie nun Verzweigungspunktmarkierungen verwenden oder ein striktes Namensschema für die Marken haben, hängt von der Komplexität des Projektes und vom Verzweigungsschema ab. (Vergessen Sie außerdem nicht, dass Sie sich jederzeit umentscheiden können und alte Marken für eine neues Namensschema aktualisieren können, indem Sie die nach altem Schema markierte Version abrufen, eine neue Marke anbringen und dann die alte Marke löschen.)

Jetzt ist qsmith bereit, an der abgezweigten Version zu arbeiten:

user@linux ~$ cvs update -r Exotic_Greetings-branch cvs update: Updating . cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir

Er nimmt einige Änderungen an ein paar Dateien vor und führt einen Commit aus, der die Änderungen in den Zweig hineinbringt:

user@linux ~$ emacs README.txt a-subdir/whatever.c b-subdir/random.c
...
user@linux ~$ cvs ci -m "print greeting backwards, etc"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v <-- README.txt
new revision: 1.14.2.1; previous revision: 1.14
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c
new revision: 1.3.2.1; previous revision: 1.3
done
Checking in b-subdir/random.c;
/usr/local/newrepos/myproj/b-subdir/random.c,v <-- random.c
new revision: 1.1.1.1.2.1; previous revision: 1.1.1.1
done

In der Zwischenzeit setzt jrandom ihre Arbeit an der Hauptversion fort. Sie ändert auch zwei der drei Dateien, die qsmith geändert hat. Aus reiner Bosheit lassen wir sie einige Änderungen machen, die im Widerspruch zur Arbeit von qsmith stehen:

user@linux ~$ emacs README.txt whatever.c
...
user@linux ~$ cvs ci -m "some very stable changes indeed"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v <-- README.txt
new revision: 1.15; previous revision: 1.14
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c
new revision: 1.4; previous revision: 1.3
done

Der Konflikt zeigt sich natürlich noch nicht, denn keiner der Entwickler hat bisher versucht, den Zweig und die Hauptversion wieder zusammenzuführen. Jetzt nimmt jrandom die Zusammenführung vor:

user@linux ~$ cvs update -j Exotic_Greetings-branch
cvs update: Updating .
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
rcsmerge: warning: conflicts during merge
cvs update: Updating a-subdir
RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
retrieving revision 1.3
retrieving revision 1.3.2.1
Merging differences between 1.3 and 1.3.2.1 into whatever.c
rcsmerge: warning: conflicts during merge
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.2.1
Merging differences between 1.1.1.1 and 1.1.1.1.2.1 into random.c
user@linux ~$ cvs update
cvs update: Updating .
C README.txt
cvs update: Updating a-subdir
C a-subdir/whatever.c
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
M b-subdir/random.c

Zwei der Dateien haben Konflikte. Keine große Sache, denn mit ihrem üblichen Savoir-faire löst jrandom die Konflikte auf, führt den Commit aus und markiert die Hauptversion als erfolgreich wieder vereinigt:

user@linux ~$ emacs README.txt a-subdir/whatever.c
...
user@linux ~$ cvs ci -m "merged from Exotic_Greetings-branch (conflicts resolved)"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v <-- README.txt
new revision: 1.16; previous revision: 1.15
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c
new revision: 1.5; previous revision: 1.4
done
Checking in b-subdir/random.c;
/usr/local/newrepos/myproj/b-subdir/random.c,v <-- random.c
new revision: 1.2; previous revision: 1.1
done
user@linux ~$ cvs tag merged-Exotic_Greetings
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c

Währenddessen braucht qsmith aber nicht abzuwarten, bis die Zusammenführung abgeschlossen ist, bevor er mit seiner Programmierarbeit fortfährt, vorausgesetzt, er setzt eine Marke für die Änderungen, von denen aus jrandom die Zusammenführung durchführt. (Später wird jrandom den Namen dieser Marke benötigen, generell kommt es bei der Verwendung von Verzweigungen darauf an, dass die Entwickler häufig und ausführlich miteinander kommunizieren):

user@linux ~$ cvs tag Exotic_Greetings-1
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
user@linux ~$ paste$ emacs a-subdir/whatever.c
...
user@linux ~$ paste$ cvs ci -m "print a randomly capitalized greeting"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c
new revision: 1.3.2.2; previous revision: 1.3.2.1
done

Natürlich sollte qsmith diese Änderungen durch eine Marke kennzeichnen, sobald er fertig ist:

user@linux ~$ paste$ cvs -q tag Exotic_Greetings-2 T README.txt T foo.gif T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c

Während all das geschieht, nimmt jrandom an einer anderen Datei, die qsmith bei seinen jüngsten Arbeiten nicht angefasst hat, Veränderungen vor:

user@linux ~$ floss$ emacs README.txt ... user@linux ~$ floss$ cvs ci -m "Mention new Exotic Greeting features" README.txt Checking in README.txt; /usr/local/newrepos/myproj/README.txt,v <-- README.txt new revision: 1.17; previous revision: 1.16 done

Jetzt hat qsmith eine weitere Änderung an der Zweigversion vorgenommen, und jrandom hat eine konfliktfreie Änderung an der Hauptversion vorgenommen. Folgendes geschieht, wenn jrandom erneut versucht, beide zusammenzuführen:

user@linux ~$ floss$ cvs -q 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.txt
rcsmerge: warning: conflicts during merge
RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
retrieving revision 1.3
retrieving revision 1.3.2.2
Merging differences between 1.3 and 1.3.2.2 into whatever.c
rcsmerge: warning: conflicts during merge
RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
retrieving revision 1.1
retrieving revision 1.1.1.1.2.1
Merging differences between 1.1 and 1.1.1.1.2.1 into random.c
user@linux ~$ floss$ cvs -q update
C README.txt
C a-subdir/whatever.c

Es gibt Konflikte! Haben Sie es erwartet?

Das Problem liegt in der Semantik der Zusammenführung. In Kapitel 2 habe ich gezeigt, dass, wenn Sie

user@linux ~$ floss$ cvs update -j Zweig

in einer Arbeitskopie ausführen, CVS die Unterschiede zwischen der Wurzel von ZWEIG und seinem derzeitigen Endpunkt in die Arbeitskopie einbringt. Das Problem mit diesem Verhalten, in dieser Situation, ist, dass die meisten dieser Änderungen schon beim ersten Zusammenführen, das jrandom durchgeführt hat, in die Hauptversion eingeflossen sind. Als CVS versucht hat, diese erneut einzubringen (über sie selbst), hat es natürlich den Konflikt bemerkt.

Was jrandom eigentlich tun wollte, war, die Änderungen zwischen dem letzten Zusammenführen mit dem Zweig und seinem aktuellen Endpunkt in ihrer Arbeitsgruppe zu vereinen. Sie können das, wie Sie sich vielleicht aus Kapitel 2 erinnern, mit zwei -j-Optionen für update bewerkstelligen, vorausgesetzt Sie wissen, welche Revision bei jeder Option anzugeben ist. Glücklicherweise hat jrandom exakt am Punkt der letzten Zusammenführung eine Marke gesetzt (Extralob für Vorausplanung!), sodass das kein Problem ist. Lassen Sie uns zuerst jrandom ihre Arbeitskopie in einem sauberen Zustand wiederherstellen, von wo aus sie dann die Zusammenführung erneut versuchen kann:

user@linux ~$ rm README.txt a-subdir/whatever.c user@linux ~$ cvs -q update cvs update: warning: README.txt was lost U README.txt cvs update: warning: a-subdir/whatever.c was lost U a-subdir/whatever.c

Nun ist sie bereit, die Zusammenführung durchzuführen, diesmal mit der von qsmith praktischerweise gesetzten Marke:

user@linux ~$ cvs -q update -j Exotic_Greetings-1 -j Exotic_Greetings-branch RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v retrieving revision 1.3.2.1 retrieving revision 1.3.2.2 Merging differences between 1.3.2.1 and 1.3.2.2 into whatever.c user@linux ~$ cvs -q update M a-subdir/whatever.c

Schon viel besser. Die Änderung von qsmith wurde in whatever.c eingearbeitet; jrandom kann nun den Commit ausführen und eine Marke setzen:

user@linux ~$ cvs -q ci -m "merged again from Exotic_Greetings (1)" Checking in a-subdir/whatever.c; /usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c new revision: 1.6; previous revision: 1.5 done user@linux ~$ cvs -q tag merged-Exotic_Greetings-1 T README.txt T foo.gif T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c

Selbst wenn qsmith vergessen hätte, eine Marke beim Zusammenführen anzubringen, wäre noch nicht alles verloren gewesen. Wenn jrandom ungefähr wüsste, wann qsmiths erste Änderungen stattgefunden haben, könnte sie mittels Datum filtern:

user@linux ~$ cvs update -j Exotic_Greetings-branch:3pm -j Exotic_Greetings_branch

Obgleich nützlich als letzter Ausweg, ist das Filtern nach Datum ein weniger geeignetes Mittel, da die Änderungen auf Grund von persönlichen Erinnerungen anstelle von verlässlichen Vorgaben der Entwickler ausgewählt werden. Wenn der erste Satz Änderungen von qsmith über mehrere Commits anstelle von einem einzigen verteilt gewesen wäre, hätte jrandom leicht ein Datum oder eine Uhrzeit wählen können, das zwar einige der Änderungen auffängt, aber fälschlicherweise nicht alle.

Bemerkung
Es gibt keinen Grund, warum markierbare Punkte in jrandoms Änderungen als ein einziger Commit an das Archiv gesendet werden müssen - es hat sich in diesen Beispielen einfach so ergeben. Im wirklichen Leben könnte qsmith mehrere Commits zwischen den Markierungen vornehmen. Er kann auch völlig isoliert an dem Zweig arbeiten, wenn es ihm so gefällt. Der Sinn der Marken ist, aufeinander folgende Punkte anzuzeigen, zu denen er die Änderungen im Zweig für mit der Hauptversion verschmelzbar hält. Solange jrandom die Verschmelzung immer mit zwei -j-Optionen vornimmt und darauf achtet, die Zusammenführungsmarkierungen von qsmith in der richtigen Reihenfolge und jeweils nur einmal zu verwenden, sollte die Hauptversion niemals das Problem mit der mehrfach versuchten Verschmelzung erfahren. Konflikte können immer noch auftreten, es werden aber unvermeidbare sein, solche, bei denen sowohl in der Hauptversion als auch im Zweig Änderungen an denselben Stellen im Quelltext vorgenommen wurden. Diese müssen immer manuell aufgelöst werden.

#### 11 Der koordinierte Ansatz: Verschmelzungen von und zur Hauptversion

Für die an der Hauptversion tätigen Entwickler ist es von Vorteil, wenn häufig die Änderungen aus der abgezweigten Version in die Hauptversion eingearbeitet werden, denn so bekommen sie außer ihren eigenen Änderungen auch noch die Änderungen im Zweig mit. Die Entwickler, die den Zweig bearbeiten, sehen aber nichts von den Veränderungen der Hauptversion und können sie so nicht für ihre eigene Arbeit nutzen beziehungsweise sie darauf abstimmen.

Damit das möglich wird, muss der am Zweig tätige Entwickler ab und zu (soll heißen: wann immer er Lust hat, die letzten Änderungen der Hauptversion zu übernehmen und die unvermeidlichen Konflikte zu behandeln) einen zusätzlichen Schritt ausführen:

paste$ cvs update -j HEAD

Die reservierte Marke HEAD bezeichnet den Kopf, also die Spitze der Hauptentwicklungslinie. Obiges Kommando übernimmt alle Änderungen aus der Hauptversion, die zwischen der Wurzel des aktuellen Zweigs (Exotic_Greetings-branch) und der jeweils aktuellsten Revision aller Dateien der Hauptversion liegen. Natürlich sollte qsmith danach eine Marke anbringen, damit die Entwickler an der Hauptversion vermeiden können, aus Versehen ihre eigenen Änderungen zu übernehmen, wenn sie die Änderungen von qsmith bekommen wollen.

Der Entwickler des Zweigs kann ebenso die Marken der Hauptversion als Begrenzungen verwenden, was dem Zweig erlaubt, genau die Änderungen zu übernehmen, die zwischen dem letzten Zusammenführen und dem aktuellen Zustand der Hauptversion liegen (genau wie die Hauptversion Zusammenführungen durchführt). Zum Beispiel (angenommen jrandom hat nach dem Verschmelzen mit dem Zweig einige Änderungen an hello.c vorgenommen):

user@linux ~$ emacs hello.c ... user@linux ~$ cvs ci -m "clarify algorithm" hello.c Checking in hello.c; /usr/local/newrepos/myproj/hello.c,v <-- hello.c new revision: 1.22; previous revision: 1.21 done

Dann kann qsmith diese Änderungen in den Zweig einarbeiten, den Commit ausführen und, natürlich, eine Markierung anbringen:

user@linux ~$ cvs -q update -j merged-Exotic_Greetings-1 -j HEAD
RCS file: /usr/local/newrepos/myproj/hello.c,v
retrieving revision 1.21
retrieving revision 1.22
Merging differences between 1.21 and 1.22 into hello.c
user@linux ~$ cvs -q update
M hello.c
cvs -q ci -m "merged trunk, from merged-Exotic_Greetings-1 to HEAD"
Checking in hello.c;
/usr/local/newrepos/myproj/hello.c,v <-- hello.c
new revision: 1.21.2.1; previous revision: 1.21
done
user@linux ~$ cvs -q tag merged-merged-Exotic_Greetings-1
T README.txt
T foo.gif
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c

Beachten Sie, dass jrandom nach dem Commit der Änderungen an hello.c keine Markierung gesetzt hat, qsmith hingegen schon. Das Prinzip dahinter ist, dass man nicht nach jeder winzigen Änderung eine Marke anzubringen braucht. Nach dem Zusammenführen oder nach einem Commit einer Entwicklungslinie zu einem verschmelzbaren Zustand hin jedoch schon! So haben andere Entwickler - möglicherweise an anderen Zweigen - einen Bezugspunkt, um ihre eigenen Zusammenführungen abzustützen.