Diese Dokumentation wurde zur Beschreibung der Serie 1.6.x von Subversion erstellt. Falls Sie eine unterschiedliche Version von Subversion einsetzen, sei Ihnen dringend angeraten, bei http://www.svnbook.com/ vorbeizuschauen und stattdessen die zu Ihrer Version von Subversion passende Version dieser Dokumentation heranzzuiehen.
Hier endet die automatische Magie. Früher oder später, sobald Sie den Dreh beim Verzweigen und Zusammenführen heraus haben, werden Sie Subversion fragen müssen, bestimmte Änderungen von einem Ort zum anderen zusammenzuführen. Um dies tun zu können, werden Sie damit beginnen müssen, kompliziertere Argumente an svn merge zu übergeben. Der nächste Abschnitt beschreibt die vollständig erweiterte Syntax des Befehls und behandelt eine Anzahl verbreiteter Szenarien, die diese benötigen.
Genauso oft wie der Begriff „Änderungsmenge“ wird die Wendung die Rosinen herauspicken in Versionskontrollsystemen verwendet. Das bezieht sich darauf, eine bestimmte Änderungsmenge von einem Zweig auszuwählen und sie auf einen anderen anzuwenden. Die Rosinen herauszupicken kann sich auch darauf beziehen, eine bestimmte Menge von (nicht notwendigerweise angrenzenden) Änderungsmengen von einem auf einen anderen Zweig zu duplizieren. Dies steht im Gegensatz zu den üblicheren Zusammenführungs-Szenarien, bei denen der „nächste“ zusammenhängende Bereich von Revisionen automatisch dupliziert wird.
Warum sollte jemand nur eine einzelne Änderung wollen? Das
kommt häufiger vor, als Sie denken. Gehen wir beispielsweise
einmal zurück in die Vergangenheit und stellen uns vor, dass
Sie Ihren Zweig noch nicht wieder mit dem Stamm
zusammengeführt hätten. In der Kaffeeküche bekommen Sie mit,
dass Sally eine interessante Änderung an
integer.c
auf dem Stamm gemacht hat. Als
Sie sich die Geschichte der Übergaben auf dem Stamm ansehen,
entdecken Sie, dass sie in Revision 355 einen kritischen
Fehler beseitigt hat, der direkte Auswirkungen auf die
Funktion hat, an der Sie gerade arbeiten. Es kann sein, dass
Sie noch nicht bereit sind, alle Änderungen vom Stamm zu
übernehmen, jedoch benötigen Sie diese bestimmte
Fehlerbehebung, um mit Ihrer Arbeit weitermachen zu
können.
$ svn diff -c 355 ^/calc/trunk Index: integer.c =================================================================== --- integer.c (revision 354) +++ integer.c (revision 355) @@ -147,7 +147,7 @@ case 6: sprintf(info->operating_system, "HPFS (OS/2 or NT)"); break; case 7: sprintf(info->operating_system, "Macintosh"); break; case 8: sprintf(info->operating_system, "Z-System"); break; - case 9: sprintf(info->operating_system, "CP/MM"); + case 9: sprintf(info->operating_system, "CP/M"); break; case 10: sprintf(info->operating_system, "TOPS-20"); break; case 11: sprintf(info->operating_system, "NTFS (Windows NT)"); break; case 12: sprintf(info->operating_system, "QDOS"); break;
Ebenso wie Sie svn diff im vorigen Beispiel benutzt haben, um sich Revision 355 anzusehen, können Sie die gleiche Option an svn merge übergeben:
$ svn merge -c 355 ^/calc/trunk --- Zusammenführen von r355 in ».«: U integer.c $ svn status M integer.c
Sie können nun Ihre üblichen Tests durchführen, bevor Sie diese Änderung an den Zweig übergeben. Nach der Übergabe merkt sich Subversion, dass r355 mit dem Zweig zusammengeführt wurde, so dass künftige „magische“ Zusammenführungen, die Ihren Zweig mit dem Stamm synchronisieren, r355 überspringen. (Das Zusammenführen derselben Änderung auf denselben Zweig führt fast immer zu einem Konflikt!)
$ cd my-calc-branch $ svn propget svn:mergeinfo . /trunk:341-349,355 # Beachten Sie, dass r355 nicht als Zusammenführungs-Kandidat aufgeführt wird # da es bereits zusammengeführt wurde. $ svn mergeinfo ^/calc/trunk --show-revs eligible r350 r351 r352 r353 r354 r356 r357 r358 r359 r360 $ svn merge ^/calc/trunk --- Zusammenführen von r350 bis r354 in ».«: U . U integer.c U Makefile --- Zusammenführen von r356 bis r360 in ».«: U . U integer.c U button.c
Dieser Anwendungsfall des Abgleichens (oder Nachziehens) von Fehlerbehebungen von einem Zweig zu einem anderen ist vielleicht der gängigste Grund für Änderungen, die Rosinen herauszupicken; es kommt ständig vor, beispielsweise, wenn ein Team einen „Software-Release-Zweig“ verwendet. (Wir erörtern dieses Muster in „Release-Zweige“.)
Warnung | |
---|---|
Haben Sie bemerkt, wie im letzten Beispiel der Aufruf von svn merge dazu geführt hat, zwei unterschiedliche Abgleichsintervalle anzuwenden? Der Befehl führte zwei unabhängige Patches auf Ihrer Arbeitskopie aus, um die Änderungsmenge 355 zu überspringen, die Ihr Zweig bereits beinhaltete. An und für sich ist daran nichts falsch, bis auf die Tatsache, dass die Möglichkeit besteht, eine Konfliktauflösung komplizierter zu machen. Falls das erste Änderungsintervall Konflikte erzeugt, müssen Sie diese interaktiv auflösen, um die Zusammenführung fortzusetzen und das zweite Änderungsintervall anzuwenden. Wenn Sie die Konfliktauflösung der ersten Phase aufschieben, wird der komplette Zusammenführungsbefehl mit einer Fehlermeldung abbrechen.[26] |
Ein Wort zur Warnung: Während svn diff und svn merge vom Konzept her sehr ähnlich sind, haben sie in vielen Fällen eine unterschiedliche Syntax. Gehen Sie sicher, dass Sie Details hierzu in Kapitel 9, Die vollständige Subversion Referenz nachlesen oder svn help fragen. Zum Beispiel benötigt svn merge einen Pfad in der Arbeitskopie als Ziel, d.h., einen Ort, an dem es den erzeugten Patch anwenden kann. Falls das Ziel nicht angegeben wird, nimmt es an, dass Sie eine der folgenden häufigen Operationen durchführen möchten:
Sie möchten Verzeichnisänderungen auf Ihr aktuelles Arbeitsverzeichnis abgleichen.
Sie möchten die Änderungen in einer bestimmten Datei mit einer Datei gleichen Namens in Ihrem aktuellen Arbeitsverzeichnis zusammenführen.
Falls Sie ein Verzeichnis zusammenführen und keinen Zielpfad angegeben haben, nimmt svn merge den ersten Fall an und versucht, die Änderungen auf Ihr aktuelles Arbeitsverzeichnis anzuwenden. Falls Sie eine Datei zusammenführen und diese Datei (oder eine gleichnamige Datei) in Ihrem aktuellen Arbeitsverzeichnis existiert, nimmt svn merge den zweiten Fall an und wendet die Änderungen auf eine lokale Datei gleichen Namens an.
Sie haben nun einige Beispiele zum Befehl svn merge gesehen und werden bald einige mehr sehen. Falls Sie verwirrt darüber sind, wie das Zusammenführen genau funktioniert, sind Sie nicht alleine. Viele Anwender (besonders diejenigen, für die Versionskontrolle etwas Neues ist) sind anfangs verwirrt darüber, wie die korrekte Syntax des Befehls lautet und wann das Feature verwendet werden soll. Aber, keine Angst, dieser Befehl ist tatsächlich viel einfacher als Sie denken! Es gibt eine einfache Technik, die verstehen hilft, wie sich svn merge genau verhält.
Die Hauptquelle der Verwirrung ist der Name des Befehls. Der Begriff „merge“ (Zusammenführung, Mischung) deutet irgendwie an, dass Zweige miteinander verschmolzen werden, oder dass irgendeine geheimnisvolle Mischung der Daten erfolgt. Das ist nicht der Fall. Ein besserer Name für den Befehl wäre vielleicht svn ermittele-die-Unterschiede-und-wende-sie-an gewesen, da das alles ist, was passiert: Die Bäume im Projektarchiv werden verglichen und die Unterschiede in eine Arbeitskopie eingearbeitet.
Falls Sie svn merge benutzen, um einfache Kopien von Änderungen zwischen Zweigen vorzunehmen, wird es üblicherweise automatisch das Richtige machen. Beispielsweise wird ein Befehl wie der folgende:
$ svn merge ^/calc/branches/some-branch
versuchen, alle Änderungen, die auf
some-branch
gemacht worden sind, in Ihr
aktuelles Arbeitsverzeichnis zu kopieren, welches vermutlich
eine Arbeitskopie ist, die mit dem Zweig irgendeine
historische Verbindung teilt. Der Befehl ist klug genug, nur
die Änderungen zu kopieren, die Ihre Arbeitskopie noch nicht
hat. Wenn Sie diesen Befehl einmal die Woche wiederholen, wird
er nur die „neuesten“ Änderungen vom Zweig
kopieren, die seit Ihrem letzten Zusammenführen
stattfanden.
Wenn Sie den Befehl svn merge in seiner ganzen Pracht wählen, indem Sie ihm bestimmte Revisionsintervalle zum kopieren übergeben, benötigt der Befehl drei Hauptargumente:
Einen Anfangsbaum im Projektarchiv (häufig linke Seite des Vergleichs genannt)
Einen Endbaum im Projektarchiv (häufig rechte Seite des Vergleichs genannt)
Eine Arbeitskopie, die die Unterschiede als lokale Änderungen aufnimmt (häufig Ziel der Zusammenführung genannt)
Sobald diese drei Argumente angegeben sind, werden die zwei Bäume miteinander verglichen und die Unterschiede als lokale Änderungen auf die Ziel-Arbeitskopie angewendet. Wenn der Befehl fertig ist, sieht das Ergebnis so aus, als hätten Sie die Dateien manuell editiert oder verschiedene svn add- oder svn delete-Befehle ausgeführt. Wenn Ihnen das Ergebnis gefällt, können Sie es übergeben. Falls nicht, können Sie einfach mit svn revert alle Änderungen rückgängig machen.
Die Syntax von svn merge erlaubt Ihnen, die drei notwendigen Argumente auf eine recht flexible Weise anzugeben. Hier sind einige Beispiele:
$ svn merge http://svn.example.com/repos/branch1@150 \ http://svn.example.com/repos/branch2@212 \ my-working-copy $ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy $ svn merge -r 100:200 http://svn.example.com/repos/trunk
Die erste Syntax führt alle drei Argumente explizit auf, indem jeder Baum mit dem Format URL@REV bezeichnet und die Ziel-Arbeitskopie angegeben wird. Die zweite Syntax kann als Kurzform verwendet werden, wenn Sie zwei unterschiedliche Revisionen desselben URL vergleichen. Die letzte Syntax zeigt, dass das Arbeitskopie-Argument optional ist; entfällt es, wird das aktuelle Verzeichnis genommen.
Obwohl das erste Beispiel die „vollständige“
Syntax von svn merge zeigt, muss sie sehr
sorgfältig verwendet werden; es können hierbei
Zusammenführungen entstehen, bei denen keinerlei
svn:mergeinfo
Metadaten aufgezeichnet
werden. Der nächste Abschnitt geht näher darauf ein.
Subversion versucht immer wenn es kann, Metadaten über das
Zusammenführen zu erzeugen, um spätere Aufrufe von
svn merge schlauer zu machen. Trotzdem
gibt es Situationen, in denen
svn:mergeinfo
-Daten nicht erzeugt oder
geändert werden. Denken Sie daran, vor diesen Szenarien auf
der Hut zu sein:
Falls Sie svn merge dazu auffordern, zwei URLs zu vergleichen, die nicht miteinander in Beziehung stehen, wird trotzdem ein Patch erzeugt und auf die Arbeitskopie angewendet, allerdings werden keine Metadaten erzeugt. Es gibt keine gemeinsame Geschichte der zwei Quellen, und spätere „schlaue“ Zusammenführungen hängen von dieser gemeinsamen Geschichte ab.
Obwohl es möglich ist, einen Befehl wie
svn merge -r 100:200
auszuführen, wird auch dieser resultierende Patch keine
historischen Metadaten über die Zusammenführung haben.
Zum gegenwärtigen Zeitpunkt hat Subversion keine
Möglichkeit, unterschiedliche Projektarchiv-URLs
innerhalb der Eigenschaft
http://svn.foreignproject.com/repos/trunk
svn:mergeinfo
zu
repräsentieren.
--ignore-ancestry
Wenn diese Option an svn merge übergeben wird, veranlasst das die Zusammenführungs-Logik, ohne nachzudenken Unterschiede auf dieselbe Art zu erzeugen, wie es svn diff macht, und ignoriert dabei irgendwelche historischen Verbindungen. Wir werden das später in diesem Kapitel in „Die Abstammung berücksichtigen oder ignorieren“ erörtern.
Weiter oben in diesem Kapitel
(„Änderungen rückgängig machen“)
haben wir darüber gesprochen, wie man mit svn
merge einen „Rückwärts-Patch“
verwendet, um Änderungen rückgängig zu machen. Wenn
diese Technik dazu verwendet wird, um eine Änderung in
der Geschichte eines Objektes zurückzunehmen (z.B. r5
an den Stamm übergeben, und dann sofort r5 mit
svn merge . -c -5
rückgängig
machen), hat dies keine Auswirkungen auf die
aufgezeichneten
Zusammenführungsinformationen.[27]
Wie der Befehl svn update wendet auch svn merge Änderungen auf Ihre Arbeitskopie an. Und deshalb kann er auch Konflikte erzeugen. Die von svn merge hervorgerufenen Konflikte sind jedoch manchmal anders geartet, und dieser Abschnitt erklärt diese Unterschiede.
Zunächst gehen wir davon aus, dass Ihre Arbeitskopie keine lokalen Änderungen enthält. Wenn Sie mit svn update auf eine bestimmte Revision aktualisieren, werden die vom Server gesendeten Änderungen immer „sauber“ auf Ihre Arbeitskopie angewendet. Der Server erzeugt das Delta, indem er zwei Bäume vergleicht: eine virtuelle Momentaufnahme Ihrer Arbeitskopie und der Revisionsbaum, an dem Sie interessiert sind. Da die linke Seite des Vergleichs völlig gleich zu dem ist, was Sie bereits haben, wird das Delta garantiert Ihre Arbeitskopie korrekt in den rechten Baum überführen.
svn merge jedoch kann das nicht gewährleisten und kann viel chaotischer sein: Der fortgeschrittene Benutzer kann den Server auffordern, irgendwelche zwei Bäume miteinander zu vergleichen, sogar solche, die nicht mit der Arbeitskopie in Beziehung stehen! Das bedeutet, dass ein hohes Potenzial für menschliche Fehler besteht. Benutzer werden manchmal die falschen zwei Bäume miteinander vergleichen, so dass ein Delta erzeugt wird, das sich nicht sauber anwenden lässt. svn merge wird sein Bestes geben, um soviel wie möglich vom Delta anzuwenden, doch bei einigen Teilen kann das unmöglich sein. So wie der Unix-Befehl patch sich manchmal über „failed hunks“ beschwert, wird sich svn merge ähnlich über „skipped targets“ beschweren:
$ svn merge -r 1288:1351 http://svn.example.com/myrepos/branch U foo.c U bar.c Fehlendes Ziel: »baz.c« übersprungen. U glub.c U sputter.h Konflikt in »glorb.h« entdeckt. Auswahl: (p) zurückstellen, (df) voller Diff, (e) editieren, (mc) eigene konfliktbehaftete Datei, (tc) fremde konfliktbehaftete Datei (s) alle Optionen anzeigen:
Im vorangegangenen Beispiel kann es der Fall gewesen sein,
dass baz.c
in beiden Momentaufnahmen des
Zweiges vorkommt, die verglichen werden, und das resultierende
Delta den Inhalt der Datei verändern will, die in der
Arbeitskopie aber nicht vorhanden ist. Wie auch immer, die
„skipped“-Nachricht bedeutet, dass der Benutzer
höchstwahrscheinlich die falschen Bäume miteinander
vergleicht; es ist das klassische Zeichen für einen
Anwenderfehler. Falls dies passiert, ist es einfach, alle
durch das Zusammenführen hervorgerufenen Änderungen rekursiv
rückgängig zu machen (svn revert .
--recursive
), alle unversionierten Dateien oder
Verzeichnisse zu löschen, die nach dem Rückgängigmachen
zurückgeblieben sind, und svn merge noch
einmal mit unterschiedlichen Argumenten aufzurufen.
Beachten Sie auch, dass das vorangegangene Beispiel einen
Konflikt in glorb.h
anzeigt. Wir
bemerkten bereits, dass die Arbeitskopie keine lokalen
Änderungen besitzt: Wie kann da ein Konflikt entstehen? Noch
einmal: Weil der Benutzer svn merge dazu
verwenden kann, ein altes Delta zu definieren und auf die
Arbeitskopie anzuwenden, kann es sein, dass dieses alte Delta
textuelle Änderungen enthält, die nicht sauber in eine
Arbeitsdatei eingearbeitet werden können, selbst dann nicht,
wenn die Datei keine lokalen Änderungen vorzuweisen
hat.
Ein weiterer kleiner Unterschied zwischen svn
update und svn merge sind die
Namen der erzeugten Textdateien, falls ein Konflikt entsteht.
In „Lösen Sie etwaige Konflikte auf“ sahen wir, dass
bei einer Aktualisierung die Dateien namens
filename.mine
,
filename.rOLDREV
und
filename.rNEWREV
erzeugt werden. Falls
svn merge einen Konflikt hervorruft,
erstellt es jedoch drei Dateien namens
filename.working
,
filename.left
und
filename.right
. In diesem Fall
beschreiben die Begriffe „left“ (links) und
„right“ (rechts) von welcher Seite des Vergleichs
zwischen den beiden Bäumen die Datei hergeleitet wurde. Auf
alle Fälle werden Ihnen diese unterschiedlichen Namen dabei
helfen, zwischen Konflikten zu unterscheiden, die durch eine
Aktualisierung entstanden, und solchen die durch eine
Zusammenführung hervorgerufen wurden .
Manchmal gibt es eine bestimmte Änderungsmenge, die Sie
nicht automatisch zusammengeführt haben wollen. Beispielsweise
ist vielleicht die Vorgehensweise Ihres Teams dergestalt, dass
Neuentwicklungen auf /trunk
gemacht
werden, aber konservativer, wenn es darum geht, Änderungen auf
einen stabilen Zweig zurückzuportieren, den sie zur
Veröffentlichung benutzen. Auf der einen Seite können Sie
die Rosinen in Form von einzelnen Änderungsmengen manuell aus
dem Stamm herauspicken und in den Zweig einpflegen – nur
die Änderungen, die stabil genug sind, um die Qualitätsprüfung
zu bestehen. Vielleicht ist es ja auch nicht ganz so streng,
und Sie möchten normalerweise, dass svn
merge die meisten Änderungen vom Stamm automatisch
mit dem Zweig zusammenführt. In diesem Fall könnten Sie ein
Verfahren gebrauchen, dass es Ihnen erlaubt, einige bestimmte
Änderungen auszulassen, d.h. zu vermeiden, dass sie
automatisch in den Zweig eingebracht werden.
Die einzige Möglichkeit, mit Subversion 1.6 eine
Änderungsmenge zu blockieren, besteht darin, dem System
vorzugaukeln, dass die Änderung bereits
eingearbeitet wurde. Dazu rufen Sie den Befehl mit der Option
--record-only
auf:
$ cd my-calc-branch $ svn propget svn:mergeinfo . /trunk:1680-3305 # In den Metadaten r3328 als bereits zusammengeführt vermerken. $ svn merge -c 3328 --record-only http://svn.example.com/repos/calc/trunk $ svn status M . $ svn propget svn:mergeinfo . /trunk:1680-3305,3328 $ svn commit -m "Das Zusammenführen von r3328 mit dem Zweig verhindern." …
Diese Technik funktioniert zwar, sie ist allerdings auch ein wenig gefährlich. Das Hauptproblem ist, dass wir nicht klar unterscheiden zwischen „ich habe diese Änderung bereits“ und „ich habe diese Änderung nicht, aber ich will sie jetzt nicht“. Wir belügen das System gewissermaßen, indem wir es glauben lassen, dass die Änderung schon eingearbeitet sei. Das schiebt die Verantwortung, sich daran zu erinnern, dass die Änderung tatsächlich gar nicht übernommen wurde sondern nicht gewünscht war, auf Sie – den Benutzer. Es gibt keine Möglichkeit, Subversion nach einer Liste „blockierter Änderungen“ zu fragen. Wenn Sie sie verfolgen möchten (so dass Sie eines Tages die Blockierung aufheben können) müssen Sie sie irgendwo in eine Textdatei schreiben oder in einer erfundenen Eigenschaft festhalten.
Es gibt einen anderen Weg, als einen Zweig nach der Reintegration zu zerstören und erneut zu erzeugen. Zum Verständnis, warum das funktioniert, müssen Sie verstehen, warum der Zweig unmittelbar nach dem Reintegrieren zunächst nicht weiterverwendbar ist.
Nehmen wir an, Sie haben den Zweig in Revision A angelegt. Bei der Arbeit auf diesem Zweig haben Sie eine oder mehrere Revisionen erzeugt, die Änderungen an dem Zweig beinhalten. Vor der Reintegration des Zweigs auf die Hauptentwicklungslinie haben Sie eine abschließende Zusammenführung von dort auf Ihren Zweig vollzogen und das Ergebnis dieser Zusammenführung als Revision B übertragen.
Bei der Reintegration Ihres Zweigs auf die Hauptentwicklungslinie erzeugen Sie eine neue Revision X die jene ändert. Die Änderungen an der Hauptentwicklungslinie in dieser Revision X sind semantisch äquivalent zu den Änderungen, die Sie zwischen Revision A und B auf Ihrem Zweig vorgenommen haben.
Falls Sie jetzt versuchen, ausstehende Änderungen von der Hauptentwicklungslinie mit Ihrem Zweig zusammenzuführen, wird Subversion die in Revision X vorgenommenen Änderungen als Kandidaten für die Zusammenführung betrachten. Da Ihr Zweig jedoch bereits alle in Revision X vorgenommenen Änderungen enthält, kann das Zusammenführen dieser Änderungen fälschlicherweise zu Konflikten führen! Bei diesen Konflikten handelt es sich oft um Baumkonflikte, besonders dann, wenn während der Entwicklung auf dem Zweig dort oder auf der Hauptentwicklungslinie Umbenennungen gemacht wurden.
Wie soll also damit umgegangen werden? Wir müssen
sicherstellen, dass Subversion nicht versucht, die Revision
X mit dem Zweig zusammenzuführen. Das
kann mit der Zusammenführungs-Option
--record-only
erreicht werden, die in
„Änderungen blockieren“
vorgestellt wurde.
Um die nur vermerkte Zusammenführung auszuführen,
erstellen Sie sich eine Arbeitskopie des Zweigs, der in
Revision X frisch reintegriert wurde, und
führen Sie lediglich die Revision X von
der Hauptentwicklungslinie mit Ihrem Zweig zusammen, indem Sie
sicherstellen, dass Sie die Option
--record-only
verwenden.
Diese Zusammenführung verwendet die Syntax zum Herauspicken der Rosinen, wie sie in „Die Rosinen herauspicken“ vorgestellt wurde. Um mit dem aktuellen Beispiel aus „Reintegration eines Zweigs“ fortzufahren, in dem Revision X die Revision 391 war:
$ cd my-calc-branch $ svn update Aktualisiert zu Revision 393. $ svn merge --record-only -c 391 ^/calc/trunk $ svn commit -m "Block revision 391 from being merged into my-calc-branch." Sende . Revision 394 übertragen.
Nun ist Ihr Zweig wieder bereit, Änderungen von der Hauptentwicklungslinie aufzunehmen. Nach einer weiteren Synchronisierung Ihres Zweigs auf die Hauptentwicklungslinie können Sie sogar ein zweites Mal Ihren Zweig reintegrieren. Falls notwendig, können Sie eine weitere, nur vermerkte Zusammenführung machen, um den Zweig am Leben zu erhalten. Spülen und wiederholen.
Nun sollte es auch offensichtlich sein, warum das Löschen und Wiederherstellen des Zweigs den selben Effekt hat wie die obige nur vermerkte Zusammenführung. Da die Revision X Teil der natürlichen Historie des frisch erzeugten Zweigs ist, wird Subversion niemals versuchen, die Revision X mit dem Zweig zusammenzuführen, und vermeidet dadurch fälschliche Konflikte.
Ein Hauptmerkmal jedes Versionskontrollsystems ist es, darüber Buch zu führen, wer was wann geändert hat. Die Befehle svn log und svn blame sind die geeigneten Werkzeuge hierfür: Wenn sie auf individuelle Dateien angewendet werden, zeigen sie nicht nur die Geschichte der Änderungsmengen, die in diese Datei hineinflossen, sondern auch, welcher Benutzer wann welche Zeile im Quelltext geschrieben hat.
Wenn jedoch Änderungen über Zweige hinweg dupliziert werden, wird es schnell kompliziert. Wenn Sie z.B. svn log nach der Geschichte Ihres Zweigs fragen, wird es Ihnen exakt jede Revision anzeigen, die je in den Zweig hineingeflossen ist:
$ cd my-calc-branch $ svn log -q ------------------------------------------------------------------------ r390 | user | 2002-11-22 11:01:57 -0600 (Fr, 22. Nov 2002) | 1 line ------------------------------------------------------------------------ r388 | user | 2002-11-21 05:20:00 -0600 (Do, 21. Nov 2002) | 2 lines ------------------------------------------------------------------------ r381 | user | 2002-11-20 15:07:06 -0600 (Mi, 20. Nov 2002) | 2 lines ------------------------------------------------------------------------ r359 | user | 2002-11-19 19:19:20 -0600 (Di, 19. Nov 2002) | 2 lines ------------------------------------------------------------------------ r357 | user | 2002-11-15 14:29:52 -0600 (Fr, 15. Nov 2002) | 2 lines ------------------------------------------------------------------------ r343 | user | 2002-11-07 13:50:10 -0600 (Do, 07. Nov 2002) | 2 lines ------------------------------------------------------------------------ r341 | user | 2002-11-03 07:17:16 -0600 (So, 03. Nov 2002) | 2 lines ------------------------------------------------------------------------ r303 | sally | 2002-10-29 21:14:35 -0600 (Di, 29. Oct 2002) | 2 lines ------------------------------------------------------------------------ r98 | sally | 2002-02-22 15:35:29 -0600 (Fr, 22. Feb 2002) | 2 lines ------------------------------------------------------------------------
Aber ist das wirklich eine genaue Wiedergabe aller Änderungen, die auf dem Zweig stattgefunden haben? Was hier ausgelassen wird, ist, dass die Revisionen 390, 381 und 357 tatsächlich Ergebnisse des Zusammenführens von Änderungen aus dem Stamm waren. Wenn Sie sich eins dieser Protokolle im Detail anschauen, können Sie die verschiedenen Änderungsmengen vom Stamm, die die Änderungen auf dem Zweig ausmachen, nirgendwo sehen:
$ svn log -v -r 390 ------------------------------------------------------------------------ r390 | user | 2002-11-22 11:01:57 -0600 (Fri, 22 Nov 2002) | 1 line Geänderte Pfade: M /branches/my-calc-branch/button.c M /branches/my-calc-branch/README Letzte Zusammenführung der Änderungen von trunk changes in my-calc-branch.
Wir wissen, dass diese Zusammenführung in den Zweig nichts
anderes war als eine Zusammenführung von Änderungen vom Stamm.
Wie können wir zusätzlich diese Änderungen sehen? Die Antwort
lautet, die Option --use-merge-history
(-g
) zu verwenden. Diese Option expandiert
diejenigen „Teil“-Änderungen, aus denen die
Zusammenführung bestand.
$ svn log -v -r 390 -g ------------------------------------------------------------------------ r390 | user | 2002-11-22 11:01:57 -0600 (Fri, 22 Nov 2002) | 1 line Geänderte Pfade: M /branches/my-calc-branch/button.c M /branches/my-calc-branch/README Letzte Zusammenführung der Änderungen von trunk changes in my-calc-branch. ------------------------------------------------------------------------ r383 | sally | 2002-11-21 03:19:00 -0600 (Thu, 21 Nov 2002) | 2 lines Geänderte Pfade: M /branches/my-calc-branch/button.c Zusammengeführt mittels: r390 Inverse Grafik auf Knopf behoben. ------------------------------------------------------------------------ r382 | sally | 2002-11-20 16:57:06 -0600 (Wed, 20 Nov 2002) | 2 lines Geänderte Pfade: M /branches/my-calc-branch/README Zusammengeführt mittels: r390 Meine letzte Änderung in README dokumentiert.
Dadurch, dass wir die Protokoll-Operation aufgefordert haben, die Geschichte der Zusammenführungen zu verwenden, sehen wir nicht nur die Revision, die wir abgefragt haben (r390), sondern auch die zwei Revisionen, die hier mitkamen – ein paar Änderungen, die Sally auf dem Stamm gemacht hat. Das ist ein wesentlich vollständigeres Bild der Geschichte!
Auch der svn blame-Befehl versteht die
Option --use-merge-history
(-g
). Falls diese Option vergessen wird,
könnte jemand, der sich die zeilenweisen Anmerkungen von
button.c
ansieht, fälschlicherweise davon
ausgehen, dass Sie für die Zeilen verantwortlich sind, die
einen bestimmten Fehler beseitigt haben:
$ svn blame button.c … 390 user retval = inverse_func(button, path); 390 user return retval; 390 user } …
Obwohl es zutrifft, dass Sie diese drei Zeilen in Revision 390 übergeben haben, sind zwei davon tatsächlich von Sally in Revision 383 geschrieben worden:
$ svn blame button.c -g … G 383 sally retval = inverse_func(button, path); G 383 sally return retval; 390 user } …
Nun wissen wir, wer wirklich für die zwei Zeilen Quelltext verantwortlich ist!
Wenn Sie sich mit einem Subversion-Entwickler unterhalten, wird wahrscheinlich auch der Begriff Abstammung erwähnt. Dieses Wort wird verwendet, um die Beziehung zwischen zwei Objekten im Projektarchiv zu beschreiben: Wenn sie in Beziehung zueinander stehen, heißt es, dass ein Objekt vom anderen abstammt.
Nehmen wir an, Sie übergeben Revision 100, die eine
Änderung an der Datei foo.c
beinhaltet.
Dann ist foo.c@99
ein
„Vorfahre“ von foo.c@100
.
Wenn Sie dagegen in Revision 101 die Löschung von
foo.c
übergeben und in Revision 102 eine
neue Datei mit demselben Namen hinzufügen, hat es zwar den
Anschein, dass foo.c@99
und
foo.c@102
in Beziehung zueinander stehen
(sie haben denselben Pfad), es handelt sich allerdings um
völlig unterschiedliche Objekte im Projektarchiv. Sie haben weder
eine gemeinsame Geschichte noch
„Abstammung“.
Wir erwähnen das, um auf einen wichtigen Unterschied
zwischen den Befehlen svn diff und
svn merge hinzuweisen. Der erstere Befehl
ignoriert die Abstammung, wohingegen letzterer diese beachtet.
Wenn Sie beispielsweise mit svn diff die
Revisionen 99 und 102 von foo.c
vergleichen, werden Sie zeilenbasierte Unterschiede sehen; der
Befehl diff vergleicht blind zwei Pfade.
Wenn Sie aber dieselben Objekte mit svn
merge vergleichen, wird es feststellen, dass sie
nicht in Beziehung stehen und versuchen, die alte Datei zu
löschen und dann die neue hinzuzufügen; die Ausgabe wird eine
Löschung gefolgt von einer Hinzufügung anzeigen:
D foo.c A foo.c
Die meisten Zusammenführungen vergleichen Bäume, die von
der Abstammung her miteinander in Beziehung stehen, deshalb
verhält sich svn merge auf diese Weise.
Gelegentlich möchten Sie jedoch mit dem
merge-Befehl zwei Bäume vergleichen, die
nicht miteinander in Beziehung stehen. Es kann z.B. sein, dass
Sie zwei Quelltext-Bäume importiert haben, die unterschiedliche
Lieferantenstände eines Software-Projektes repräsentieren
(siehe „Lieferanten-Zweige“). Falls Sie
svn merge dazu aufforderten, die beiden Bäume
miteinander zu vergleichen, würden Sie sehen, dass der
vollständige erste Baum gelöscht und anschließend der
vollständige zweite Baum hinzugefügt würde! In diesen
Situationen möchten Sie, dass svn merge
lediglich einen pfadbasierten Vergleich vornimmt und
Beziehungen zwischen Dateien und Verzeichnissen außer Acht
lässt. Fügen Sie die Option --ignore-ancestry
dem merge-Befehl hinzu, und er wird sich
verhalten wie svn diff. (Auf der anderen
Seite wird die Option --notice-ancestry
den
Befehl svn diff dazu veranlassen, sich wie
svn merge zu verhalten.
Es ist ein verbreiteter Wunsch, Software zu refaktorieren, besonders in Java-basierten Software-Projekten. Dateien und Verzeichnisse werden hin und her geschoben und umbenannt, was häufig zu erheblichen Beeinträchtigungen für alle Projektmitarbeiter führt. Das hört sich an, als sei das der klassische Fall, um nach einem Zweig zu greifen, nicht wahr? Sie erzeugen einfach einen Zweig, schieben das Zeug herum und führen anschließend den Zweig mit dem Stamm zusammen.
Leider funktioniert dieses Szenario im Augenblick noch nicht so richtig und gilt als einer der Schwachpunkte von Subversion. Das Problem ist, das der Subversion-Befehl svn update nicht so stabil ist, wie er sein sollte, besonders wenn es um Kopier- und Verschiebeoperationen geht.
Wenn Sie svn copy zum Duplizieren einer Datei verwenden, merkt sich das Projektarchiv, woher die neue Datei kam, versäumt aber, diese Information an den Client zu senden, der svn update oder svn merge ausführt. Statt dem Client mitzuteilen: „Kopiere die Datei, die du bereits hast an diesen neuen Ort“, sendet es eine völlig neue Datei. Das kann zu Problemen führen, besonders, weil dasselbe mit umbenannten Dateien passiert. Eine weniger bekannte Tatsache über Subversion ist, dass es keine „echten Umbenennungen“ hat – der Befehl svn move ist weiter nichts als eine Verbindung von svn copy und svn delete.
Nehmen wir beispielsweise an, dass Sie während Ihrer
Arbeit auf Ihrem privaten Zweig integer.c
in whole.c
umbenennen. Tatsächlich haben
Sie eine neue Datei auf Ihrem Zweig erzeugt, die eine Kopie
der ursprünglichen Datei ist, und letztere gelöscht.
Zwischenzeitlich hat Sally einige Verbesserungen an
integer.c
in trunk
übergeben. Nun entscheiden Sie sich, Ihren Zweig mit dem Stamm
zusammenzuführen:
$ cd calc/trunk $ svn merge --reintegrate http://svn.example.com/repos/calc/branches/my-calc-branch -- Zusammenführen der Unterschiede zwischen Projektarchiv-URLs in ».«: D integer.c A whole.c U .
Auf den ersten Blick sieht es gar nicht schlecht aus,
jedoch ist es nicht das, was Sie und Sally erwartet hätten.
Die Zusammenführung hat die letzte Version der Datei
integer.c
gelöscht (diejenige, die Sallys
Änderungen beinhaltet) und blindlings Ihre neue Datei
whole.c
hinzugefügt – die ein
Duplikat der älteren Version von
integer.c
ist. Das Endergebnis ist, dass
durch die Zusammenführung Ihrer „Umbenennung“ auf
dem Zweig mit dem Stamm Sallys jüngste Änderungen aus der
letzten Revision entfernt wurden.
Es ist kein echter Datenverlust. Sallys Änderungen befinden sich noch immer in der Geschichte des Projektarchivs, allerdings mag es nicht sofort ersichtlich sein, dass es passiert ist. Die Lehre, die es aus dieser Geschichte zu ziehen gilt, lautet, dass Sie sehr vorsichtig mit dem Zusammenführen von Kopien und Umbenennungen zwischen Zweigen sein sollten, solange sich Subversion an dieser Stelle nicht verbessert hat.
Wenn Sie gerade Ihren Server auf Subversion 1.5 oder
größer umgestellt haben, besteht ein signifikantes Risiko,
dass Subversion-Clients einer kleineren Version als 1.5 Ihre
automatische Zusammenführungs-Verfolgung durcheinander bringen
können. Warum? Wenn ein älterer Subversion-Client svn
merge ausführt, modifiziert er nicht den Wert der
Eigenschaft svn:mergeinfo
. Obwohl die
anschließende Übergabe das Ergebnis einer Zusammenführung ist,
wird dem Projektarchiv nichts über die duplizierten Änderungen
mitgeteilt – diese Information ist verloren. Wenn später
Clients, die Zusammenführungsinformationen auswerten,
automatische Zusammenführungen versuchen, werden Sie
wahrscheinlich in alle möglichen Konflikte laufen, die durch
wiederholte Zusammenführungen hervorgerufen wurden.
Wenn Sie und Ihr Team auf die Zusammenführungs-Verfolgung
von Subversion angewiesen sind, sollten Sie Ihr Projektarchiv
dergestalt konfigurieren, dass ältere Clients daran gehindert
werden, Änderungen zu übergeben. Die einfache Methode hierfür
ist es, den „Fähigkeiten“-Parameter im
start-commit
Hook-Skript zu untersuchen.
Wenn der Client meldet, dass er mit
mergeinfo
umgehen kann, kann das Skript den
Beginn der Übergabe erlauben. Wenn der Client diese Fähigkeit
nicht meldet, wird die Übergabe abgelehnt.
Beispiel 4.1, „Hook-Skript zum Start der Übertragung als Torwächter für die Zusammenführungs-Verfolgung“ zeigt ein
Beispiel für ein solches Hook-Skript:
Beispiel 4.1. Hook-Skript zum Start der Übertragung als Torwächter für die Zusammenführungs-Verfolgung
#!/usr/bin/env python import sys # Dieser Start-Commit-Hook wird aufgerufen, bevor eine # Subversion-Transaktion im Zuge einer Übergabe begonnen wird. # Subversion führt diesen Hook aus, indem ein Programm (Skript, # ausführbare Datei, Binärdatei, etc.) namens "start-commit" (für die # diese Datei als Vorlage dient) mit den folgenden geordneten Argumenten # aufgerufen wird: # # [1] REPOS-PATH (der Pfad zu diesem Projektarchiv) # [2] USER (der authentisierte Anwender, der übergeben möchte) # [3] CAPABILITIES (eine vom Client durch Doppelpunkte getrennte # Liste von Leistungsmerkmalen; siehe Anmerkung # unten) capabilities = sys.argv[3].split(':') if "mergeinfo" not in capabilities: sys.stderr.write("Übertragungen von Clients, die keine" "Zusammenführungs-Verfolgung unterstützen," "sind nicht erlaubt. Bitte auf Subversion 1.5 " "oder neuer aktualisieren.\n") sys.exit(1) sys.exit(0)
Für weitergehende Informationen zu Hook-Skripten, siehe nächsten Kapitel erfahren; siehe „Erstellen von Projektarchiv-Hooks“.
Unter dem Strich bedeutet das, dass die Fähigkeit von
Subversion zur Zusammenführungs-Verfolgung eine höchst
komplexe interne Implementierung besitzt und die Eigenschaft
svn:mergeinfo
das einzige Fenster zu diesem
Räderwerk ist. Da diese Fähigkeit relativ neu ist, kann eine
Anzahl von Randfällen und mögliche unerwartete
Verhaltensweisen auftauchen.
Manchmal erscheint Mergeinfo auf Dateien, von denen Sie nicht erwartet hätten, dass sie durch eine Operation berührt worden wären. Manchmal wird Mergeinfo überhaupt nicht erzeugt, obwohl Sie es erwartet hätten. Darüberhinaus umgibt die Verwaltung der Mergeinfo-Metadaten eine ganze Menge von Systematiken und Verhalten, wie „explizite“ gegenüber „implizite“ Mergeinfo, „operative“ gegenüber „inoperative“ Revisionen, besondere Mechanismen von Mergeinfo-„Auslassung“ und sogar „Vererbung“ von Eltern- zu Kindverzeichnissen.
Wir haben uns entschieden, diese detaillierten Themen aus einer Reihe von Gründen nicht in diesem Buch zu behandeln. Erstens ist der Detaillierungsgrad für einen normalen Benutzer absolut erdrückend. Zweitens glauben wir, dass das Verständnis diese Konzepte für einen typischen Benutzer nicht unbedingt erforderlich sein sollte während Subversion sich verbessert; letztendlich werden sie als nervige Implementierugsdetails in den Hintergrund treten. Wenn Sie, nachdem dies gesagt ist, diese Dinge mögen, können Sie einen fantastischen Überblick in einer Arbeit nachlesen, die auf der Webseite von CollabNet veröffentlicht ist:http://www.collab.net/community/subversion/articles/merge-info.html.
Fürs Erste empfiehlt CollabNet, sich an die folgenden bewährten Praktiken zu halten, wenn Sie Fehler und merkwürdiges Verhalten bei automatischen Zusammenführungen vermeiden wollen:
Wenden Sie für kurzlebige Arbeitszweige das Verfahren an, das in „Grundlegendes Zusammenführen“ beschrieben wird.
Machen Sie Zusammenführungen langlebiger Release-Zweige (wie in „Verbreitete Verzweigungsmuster“ beschrieben) nur im Wurzelverzeichnis des Zweigs und nicht in Unterverzeichnissen.
Machen Sie Zusammenführungen in Arbeitsverzeichnisse niemals mit einer Mischung aus Arbeitsrevisionsnummern oder „umgeschalteten“ Unterverzeichnissen (wie als Nächstes in „Zweige durchlaufen“ beschrieben). Das Ziel einer Zusammenführung sollte eine Arbeitskopie sein, die einen einzigen Ort zu einem einzelnen Zeitpunkt im Projektarchiv repräsentiert.
Editieren Sie niemals direkt die Eigenschaft
svn:mergeinfo
; verwenden Sie
svn merge mit der Option
--record-only
, um eine gewünschte
Änderung an den Metadaten zu bewirken (wie in „Änderungen blockieren“
gezeigt).
Stellen Sie jederzeit sicher, dass Sie vollständigen Lesezugriff auf die Quellen für die Zusammenführung haben und dass Ihre Ziel-Arbeitskopie keine dünn besetzten Verzeichnisse besitzt.
[26] Zumindest trifft das zur Zeit für Subversion 1.6 zu. Dieses Verhalten könnte sich in künftigen Versionen von Subversion verbessern.
[27] Interessanterweise
werden wir nach dem Zurücknehmen einer Revision auf
diese Art nicht in der Lage sein, diese Revision erneut
mit svn merge . -c 5
anzuwenden,
da aus den Metadaten hervorgeht, dass r5 bereits
angewendet wurde. Wir müssten die Option
--ignore-ancestry
verwenden, damit der
Befehl die bestehenden Metadaten
ignoriert.