Besonders in der Softwareentwicklung haben die von Ihnen versionsverwalteten Daten oft einen engen Bezug zu Daten von anderen, oder sind vielleicht abhängig davon. Allgemein wird der Bedarf ihres Projektes erfordern, dass Sie bezüglich der externen Datenquelle so aktuell wie möglich bleiben, ohne dabei die Stabilität Ihres Projektes zu opfern. Dieses Szenario entfaltet sich immer dort, wo die von einer Gruppe erzeugten Informationen direkte Auswirkungen auf diejenigen Informationen hat, die von einer anderen Gruppe erstellt werden.
So könnte es sein, dass Softwareentwickler beispielsweise an einer Anwendung arbeiten, die die Bibliothek eines Drittanbieters benötigt. Subversion hat eine solche Abhängigkeit von der Bibliothek Apache Portable Runtime (APR) (siehe „Die Bibliothek Apache Portable Runtime“). Der Quelltext von Subversion hängt zur Gewährleistung der Portabilität von der APR-Bibliothek ab. In der frühen Phase der Entwicklung von Subversion hing das Projekt ziemlich nah am wechselnden API der APR, indem es immer die neueste Version des Quelltextes verwendete. Nun, da sowohl APR und Subversion gereift sind, versucht sich Subversion nur zu wohldefinierten Zeitpunkten mit dem APR-API zu synchronisieren, nämlich wenn dieses ausreichend getestet und stabil ist.
Falls nun Ihr Projekt von den Informationen anderer abhängt, können Sie diese Informationen auf mehrere Arten mit Ihren synchronisieren. Am umständlichsten ist es, wenn Sie mündliche oder schriftliche Anweisungen an alle Projektmitarbeiter ausgeben, dass sie sicherzustellen haben, stets über die für Ihr Projekt benötigten Versionen der Drittanbieter zu verfügen. Falls die Daten des Drittanbieters sich in einem Subversion-Projektarchiv befinden, können Sie auch mithilfe der Subversion-Externals-Definition bestimmte Versionen dieser Daten mit Ihrer eigenen Arbeitskopie verbinden (siehe „Externals-Definitionen“).
Allerdings möchten Sie von Zeit zu Zeit spezielle Anpassungen des Drittanbieter-Codes in Ihrem eigenen Versionskontrollsystem verwalten. Um auf unser Beispiel aus der Softwareentwicklung zurückzukommen, müssen Entwickler manchmal die Bibliothek der Drittanbieter für ihre Zwecke verändern. Diese Änderungen können neue Funktionalitäten oder Fehlerbehebungen beinhalten und werden nur solange intern verwaltet, bis sie eines Tages Teil einer offiziellen Auslieferung der Bibliothek werden. Es kann aber auch sein, dass diese Änderungen niemals an die Entwickler der Bibliothek zurückgegeben werden, sondern lediglich als spezielle Anpassungen bestehen, um die Bibliothek für Bedürfnisse der Softwareentwickler geeigneter zu machen.
Nun sind Sie in einer interessanten Situation: Ihr Projekt könnte seine Änderungen an den Daten von Drittanbietern auf getrennte Art und Weise verwalten, etwa in Form von Patch-Dateien oder als vollständig alternative Versionen. Jedoch wird so etwas schnell zu einem Albtraum, wenn es um die Pflege geht, da es ein Mechanismus benötigt wird, um diese Änderungen auf den Code des Drittanbieters anzuwenden und diese Anpassung bei jeder Folgelieferung zu wiederholen.
Die Lösung dieses Problems besteht in der Verwendung von Lieferanten-Zweigen. Ein Lieferanten-Zweig ist ein Verzeichnisbaum in Ihrem eigenen Versionskontrollsystem, der Informationen enthält, die von einem Drittanbieter – oder Lieferanten – bereitgestellt wird. Jede Version der Lieferantendaten, die Sie in Ihr Projekt aufnehmen wollen, wird Zulieferung genannt.
Lieferanten-Zweige bieten zwei Vorteile. Erstens, wird durch das Vorhalten der aktuellen Zulieferung in Ihrem eigenen Versionskontrollsystem sichergestellt, dass für Ihre Projektmitarbeiter stets die richtige Version der Lieferantendaten verfügbar ist. Sie erhalten die richtige Version automatisch beim Aktualisieren ihrer Arbeitskopien. Zweitens, da die Daten in Ihrem eigenen Subversion-Projektarchiv vorgehalten werden, können Sie dort auch Ihren Anpassungen speichern – es besteht keine Notwendigkeit mehr, Ihre Änderungen automatisch (oder schlimmer noch, manuell) in die Zulieferungen einzuarbeiten.
Die Verwaltung von Lieferanten-Zweigen funktioniert im
Allgemeinen so: Zunächst erzeugen Sie ein übergeordnetes
Hauptverzeichnis (etwa /vendor
), um
Lieferanten-Zweige aufzunehmen. Dann importieren Sie den Code
des Drittanbieters in ein Unterverzeichnis des
Hauptverzeichnisses. Anschließend kopieren Sie dieses
Unterverzeichnis an die passende Stelle Ihres
Hauptentwicklungszweigs (z.B. /trunk
).
Ihre lokalen Änderungen nehmen Sie stets im
Hauptentwicklungszweig vor. Jede erneut veröffentlichte
Version des von Ihnen verfolgten Codes pflegen Sie in den
Lieferanten-Zweig ein und überführen die Änderungen nach
/trunk
, wobei eventuelle Konflikte
zwischen Ihren lokalen Änderungen und dem Code des Zulieferers
aufgelöst werden.
Ein Beispiel hilft, um dieses Vorgehen zu erklären. Wir
gehen von einem Szenario aus, in dem Ihr Entwicklerteam ein
Taschenrechner-Programm entwickelt, dass mit einer Bibliothek
eines Drittanbieters für die Arithmetik mit komplexen Zahlen,
namens libcomplex, verlinkt wird. Wir beginnen mit dem Anlegen
des Lieferanten-Zweiges und dem Import der ersten Zulieferung.
Wir nennen unser Verzeichnis für den Lieferanten-Zweig
libcomplex
, und die Lieferungen werden in
einem Unterverzeichnis namens current
abgelegt. Da svn import alle dazwischen
liegenden Elternverzeichnisse erzeugt, können wir all diese
Schritte mit einem einzigen Befehl bewerkstelligen:
$ svn import /path/to/libcomplex-1.0 \ http://svn.example.com/repos/vendor/libcomplex/current \ -m 'Importing der ersten 1.0 Zulieferung' …
Nun haben wir die aktuelle Version des Quelltextes von
libcomplex in /vendor/libcomplex/current
.
Jetzt erzeugen wir ein Tag aus dieser Version (siehe „Tags“) und kopieren sie dann in
den Hauptentwicklungszweig. Unsere Kopie erzeugt ein neues
Verzeichnis libcomplex
im bestehenden
calc
Projektverzeichnis. In dieser
kopierten Version der Lieferantendaten werden wir unsere
Anpassungen vornehmen:
$ svn copy http://svn.example.com/repos/vendor/libcomplex/current \ http://svn.example.com/repos/vendor/libcomplex/1.0 \ -m 'Tag libcomplex-1.0' … $ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0 \ http://svn.example.com/repos/calc/libcomplex \ -m 'libcomplex-1.0 in den Huptzweig bringen' …
Wir checken nun den Hauptzweig unseres Projektes aus – der nun eine Kopie der ersten Zulieferung enthält – und fangen damit an, den Quelltext von libcomplex anzupassen. Ehe wir uns versehen, ist unsere angepasste Version von libcomplex vollständig in unser Taschenrechner-Programm integriert. [25]
Ein paar Wochen später veröffentlichen die Entwickler von libcomplex eine neue Version ihrer Bibliothek – Version 1.1 – die die Funktionalität enthält, die wir dringend benötigen. Wir möchten die neue Version verwenden, ohne jedoch unsere Anpassungen zu verlieren, die wir in der bestehenden Version vorgenommen haben. Unterm Strich möchten wir die bestehende Baseline-Version libcomplex 1.0 durch eine Kopie von libcomplex 1.1 ersetzen und die vorher gemachten Anpassungen an dieser Bibliothek erneut auf die neue Version anwenden. Tatsächlich gehen wir das Problem allerdings aus der anderen Richtung an, indem wir die Änderungen an libcomplex zwischen Version 1.0 und 1.1 in unsere angepasste Kopie einpflegen.
Um diesen Wechsel auf die neue Version durchzuführen,
checken wir eine Kopie des Lieferanten-Zweigs aus und ersetzen
den Code im Verzeichnis current
mit dem
neuen Quelltext von libcomplex 1.1. Wir kopieren im wahrsten
Sinne des Wortes die neuen Dateien über die bestehenden, indem
wir etwa das Archiv von libcomplex 1.1 in das bestehende
Verzeichnis entpacken. Das Ziel ist, dass das Verzeichnis
current
nur den Code von libcomplex 1.1
enthält, und dass dieser Code unter Versionskontrolle steht.
Oh, und wir wollen, dass das alles mit der geringsten Störung
an der Versionskontroll-Historie passiert.
Nachdem wir den 1.0 Code mit dem 1.1 Code ersetzt haben,
wird uns svn status sowohl Dateien mit
lokalen Änderungen als auch einige unversionierte Dateien
anzeigen. Wenn wir das getan haben, was wir tun sollten, sind
die unversionierten Dateien nur die mit Version 1.1 von
libcomplex hinzugekommenen neuen Dateien – wir rufen für
diese svn add auf, um sie unter
Versionskontrolle zu bringen. Falls der Code von 1.1 bestimmte
Dateien nicht mehr beinhaltet, die noch im Baum von 1.0
vorhanden waren, kann es schwierig sein, sie zu
identifizieren; Sie müssten die beiden Bäume mit einem
externen Werkzeug vergleichen und dann mit svn
delete Dateien entfernen, die in 1.0 jedoch nicht in
1.1 vorhanden sind. (Es könnte ebenso in Ordnung sein, diese
Dateien ungenutzt beizubehalten!) Sobald letztendlich unsere
Arbeitskopie von current
nur den Code von
libcomplex 1.1 enthält, übergeben wir die Änderungen, die uns
hierher gebracht haben.
Unser current
-Zweig enthält nun die
neue Zulieferung. Wir erzeugen nun ein Tag 1.1 (genauso, wie
wie es mit der Zulieferung 1.0 gemacht haben) und arbeiten
dann die Unterschiede zwischen dem Tag der vorherigen Version
und der neuen aktuellen Version in unseren
Hauptentwicklungszweig ein:
$ cd working-copies/calc $ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0 \ http://svn.example.com/repos/vendor/libcomplex/current \ libcomplex … # alle Konflikte zwischen ihren und unseren Änderungen auflösen $ svn commit -m 'merging libcomplex-1.1 into the main branch' …
Im trivialen Fall würde die neue Version der Drittanbieter-Bibliothek aus der Datei- und Verzeichnisperspektive genau so aussehen wie die vorherige Version. Keine der libcomplex Dateien wäre gelöscht, umbenannt oder an einen anderen Ort verschoben worden – die neue Version würde gegenüber der vorherigen lediglich textuelle Änderungen enthalten. In einer vollkommenen Welt würden sich unsere Anpassungen sauber in die neue Version einfügen, ganz ohne Komplikationen oder Konflikte.
Allerdings gestalten sich die Dinge nicht immer so einfach, und tatsächlich ist es normal, dass sich Quelltext-Dateien zwischen Software-Veröffentlichungen verschieben. Das verkompliziert das Vorgehen, um sicherzustellen, dass unsere Anpassungen für die neue Version immer noch gültig sind, und es kann schnell passieren, dass wir in eine Situation gelangen, in der wir unsere Anpassungen manuell in die neue Version einpflegen müssen. Sobald Subversion die Geschichte einer gegebenen Quelltext-Datei kennt – inklusive aller früheren Orte – ist das Vorgehen des Einpflegens in eine neue Version der Bibliothek recht einfach. Allerdings sind wir dafür verantwortlich, Subversion mitzuteilen, wie sich die Organisation des Quelltextes zwischen den Zulieferungen geändert hat.
Zulieferungen, die mehr als ein paar Löschungen, Hinzufügungen und Verschiebungen beinhalten, verkomplizieren das Vorgehen bei der Aktualisierung auf neuere Versionen der Drittanbieter-Daten. Aus diesem Grund stellt Subversion das Skript svn_load_dirs.pl zur Verfügung, das Sie dabei unterstützt. Dieses Skript automatisiert die zum Importieren notwendigen Schritte, die wir beim Vorgehen zur allgemeinen Verwaltung von Lieferanten-Zweigen erwähnten, um zu gewährleisten, dass es dabei zu möglichst wenig Fehlern kommt. Sie werden zwar immer noch dafür verantwortlich sein, mit den Zusammenführungs-Befehlen die neuen Versionen der Drittanbieter-Daten in Ihren Hauptentwicklungszweig einzupflegen, jedoch kann Ihnen svn_load_dirs.pl dabei helfen, diesen Punkt schneller und leichter zu erreichen.
Kurz gesagt ist svn_load_dirs.pl eine Verbesserung von svn import mit folgenden wichtigen Eigenschaften:
Es kann jederzeit aufgerufen werden, um ein bestehendes Verzeichnis im Projektarchiv exakt mit einem externen Verzeichnis abzugleichen, wobei alle notwendigen Hinzufügungen und Löschungen ausgeführt werden und darüberhinaus noch optionale Verschiebungen.
Es kümmert sich um komplizierte Abfolgen von Operationen zwischen denen Subversion eine eingeschobene Übergabe erforderlich macht – etwa vor dem zweifachen Umbenennen einer Datei oder eines Verzeichnisses.
Vom frisch importierten Verzeichnis wird optional ein Tag angelegt.
Es legt optional beliebige Eigenschaften für Dateien und Verzeichnisse an, deren Name einem regulären Ausdruck entspricht.
svn_load_dirs.pl benötigt drei zwingend erforderliche Argumente. Das erste Argument ist der URL zum Basis-Subversion-Verzeichnis, in dem gearbeitet wird. Dieses Argument wird gefolgt von dem URL – relativ zum ersten Argument – wohin die aktuelle Zulieferung importiert werden soll. Schließlich gibt das dritte Argument an, aus welchem lokalen Verzeichnis importiert werden soll. In unserem vorigen Beispiel würde ein typischer Aufruf von svn_load_dirs.pl wie folgt aussehen:
$ svn_load_dirs.pl http://svn.example.com/repos/vendor/libcomplex \ current \ /path/to/libcomplex-1.1 …
Sie können svn_load_dirs.pl mitteilen,
dass Sie ein Tag von der neuen Zulieferung anlegen möchten,
indem Sie die Option -t
gefolgt von einem
Tag-Namen übergeben. Dieser Tag-Name ist auch ein URL relativ
zum ersten Argument des Programms.
$ svn_load_dirs.pl -t libcomplex-1.1 \ http://svn.example.com/repos/vendor/libcomplex \ current \ /path/to/libcomplex-1.1 …
Wenn Sie svn_load_dirs.pl aufrufen,
untersucht es den Inhalt Ihre existierenden Zulieferung
„current“ und vergleicht sie mit der
vorgeschlagenen neuen Zulieferung. Im trivialen Fall werden
keine Dateien ausschließlich in einer und nicht in der anderen
Zulieferung vorhanden sein, so dass das Skript den Import ohne
Probleme durchführt. Falls sich jedoch zwischen den Versionen
Unterschiede in der Dateistruktur ergeben sollten, fragt
svn_load_dirs.pl nach, wie die Unterschiede
aufgelöst werden sollen. So haben Sie zum Beispiel die
Möglichkeit, dem Skript mitzuteilen, dass die Datei
math.c
aus Version 1.0 von libcomplex in
der Version 1.1 von libcomplex in
arithmetic.c
umbenannt wurde. Alle
Diskrepanzen, die sich nicht durch Verschiebungen erklären
lassen, werden als normale Löschungen und Hinzufügungen
behandelt.
Das Skript akzeptiert auch eine gesonderte
Konfigurationsdatei, in der Eigenschaften auf Dateien und
Verzeichnisse gesetzt werden können, deren Name einem
regulären Ausdruck entspricht und dem Projektarchiv
hinzugefügt werden. Diese
Konfigurationsdatei wird svn_load_dirs.pl
mit der Option -p
bekanntgegeben. Jede Zeile
der Konfigurationsdatei ist eine durch Leerraum begrenzte
Menge aus zwei oder vier Werten: ein regulärer Ausdruck wie in
Perl, zu dem der entsprechende Pfad passen muss, ein
Schlüsselwort zur Kontrolle (entweder break
oder cont
) und optional ein Eigenschafts-Name
und ein Wert.
\.png$ break svn:mime-type image/png \.jpe?g$ break svn:mime-type image/jpeg \.m3u$ cont svn:mime-type audio/x-mpegurl \.m3u$ break svn:eol-style LF .* break svn:eol-style native
Für jeden hinzugefügten Pfad der dem regulären Ausdruck
einer Zeile entspricht, werden die Eigenschafts-Änderungen der
Reihe nach durchgeführt, es sei denn, das
Kontroll-Schlüsselwort ist break
(was
bedeutet, dass keine weiteren Eigenschafts-Änderungen für diesen
Pfad durchgeführt werden sollen). Falls das
Kontroll-Schlüsselwort cont
ist –
eine Abkürzung für continue
(fortfahren)
– wird mit der nächsten Zeile der Konfigurationsdatei
fortgefahren.
Jeglicher Leerraum im regulären Ausdruck, im Namen der
Eigenschaft oder im Wert der Eigenschaft muss entweder mit
einfachen oder doppelten Anführungszeichen umgeben werden.
Anführungszeichen, die nicht zum Umfassen von Leerraum
verwendet werden, können mit einem vorangestellten umgekehrten
Schrägstrich (\
) maskiert werden. Der
umgekehrte Schrägstrich maskiert nur Anführungszeichen beim
Lesen der Konfigurationsdatei, darum sollten Sie darüberhinaus
keine weiteren Zeichen maskieren.