Fortgeschrittenes Zusammenführen

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.

Die Rosinen herauspicken

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 http://svn.example.com/repos/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 http://svn.example.com/repos/calc/trunk
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 http://svn.example.com/repos/calc/trunk --show-revs eligible
r350
r351
r352
r353
r354
r356
r357
r358
r359
r360

$ svn merge http://svn.example.com/repos/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] 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. [22]

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 komplette 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.

Merge-Syntax: Die vollständige Enthüllung

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 http://svn.example.com/repos/calc/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:

  1. Einen Anfangsbaum im Projektarchiv (häufig linke Seite des Vergleichs genannt)

  2. Einen Endbaum im Projektarchiv (häufig rechte Seite des Vergleichs genannt)

  3. 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.

Zusammenführen ohne Mergeinfo

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:

Zusammenführen von Quellen ohne Beziehung

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.

Zusammenführen aus fremden Projektarchiven

Obwohl es möglich ist, einen Befehl wie svn merge -r 100:200 http://svn.foreignproject.com/repos/trunk 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 svn:mergeinfo zu repräsentieren.

Verwendung von --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.

Zusammenführen rückgängig machen

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 Metadaten. [23]

Mehr über Konflikte beim Zusammenführen

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/repos/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,
        (h) Hilfe für weitere Optionen:

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 „Konflikte auflösen (Änderungen anderer einarbeiten)“ 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 .

Änderungen blockieren

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.5 eine Änderungsmenge zu blockieren, besteht darin, dem System vorzugaukeln, dass die Änderung bereits eingearbeitet wurde. Dazu können Sie den Befehl mit der Option --record-only aufrufen:

$ 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. 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. Leider ist das in Subversion 1.5 die einzige Möglichkeit mit blockierten Revisionen umzugehen; ein besseres Interface dafür ist für künftige Versionen geplant.

Protokolle und Anmerkungen, die Zusammenführungen anzeigen

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!

Die Abstammung berücksichtigen oder ignorieren

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.

Zusammenführen und Verschieben

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.

Abblocken von Clients, die Zusammenführungen nicht ausreichend unterstützen

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. Wir werden mehr über Hook-Skripte im nächsten Kapitel erfahren; siehe „Erstellen von Projektarchiv-Hooks“ und start-commit für Details.

Das abschließende Wort zur Zusammenführungs-Verfolgung

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.

So kann zum Beispiel Mergeinfo manchmal durch einen einfachen svn copy- oder svn move-Befehl erzeugt werden. Manchmal wird Mergeinfo an Dateien auftauchen, von denen Sie nicht erwartet hätten, dass sie durch die 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 inoperativen 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.



[22] Zumindest trifft das zur Zeit für Subversion 1.5 zu. Dieses Verhalten könnte sich in künftigen Versionen von Subversion verbessern.

[23] 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.