Git
Chapters ▾ 2nd Edition

9.1 Git und andere Systeme - Git als Client

Die Welt ist nicht perfekt. Normalerweise können Sie nicht jedes Projekt, mit dem Sie in Berührung kommen, sofort auf Git umstellen. Manchmal steckt man in einem Projekt mit einem anderen VCS fest und wünscht sich, man könnte zu Git wechseln. Wir werden im ersten Teil dieses Kapitels die Möglichkeiten kennenlernen, Git als Client zu verwenden, falls das Projekt, an dem Sie gerade arbeiten, in einem anderen System läuft.

Irgendwann werden Sie vielleicht Ihr bestehendes Projekt in Git umwandeln wollen. Der zweite Teil dieses Kapitels behandelt die Migration Ihres Projekts zu Git aus verschiedenen Systemen sowie eine funktionierende Methode, wenn kein vorgefertigtes Import-Tool vorhanden ist.

Git als Client

Git bietet Entwicklern eine so reizvolle Umgebung, dass viele Anwender schon herausgefunden haben, wie man es auf den Arbeitsplätzen nutzen kann, auch wenn der Rest des Teams ein völlig anderes VCS einsetzt. Es gibt eine Vielzahl dieser Schnittstellen, die sogenannten „Brücken“. Hier werden wir die vorstellen, denen Sie am ehesten in der „freien Wildbahn“ begegnen werden.

Git und Subversion

Ein großer Teil der Open-Source-Entwicklungsprojekte und eine ganze Reihe von Unternehmensprojekten nutzen Subversion zur Verwaltung ihres Quellcodes. Es gibt Subversion seit mehr als einem Jahrzehnt, und die meiste Zeit war es erste Wahl für ein VCS im Bereich Open-Source-Projekte. Es ist auch in vielen Aspekten sehr ähnlich zu CVS, das vorher der wichtigste Vertreter der Versionsverwaltung war.

Eines der herausragenden Merkmale von Git ist die bidirektionale Brücke zu Subversion, genannt git svn. Mit diesem Tool können Sie Git als geeigneter Client für einen Subversion-Server verwenden, so dass Sie alle lokalen Funktionen von Git nutzen und dann auf einen Subversion-Server pushen können, als ob Sie Subversion lokal einsetzen würden. Das bedeutet, dass Sie lokale Branching- und Merging-Aktivitäten vornehmen, die Staging Area nutzen, Rebasing- und Cherry-Picking-Aktivitäten durchführen können, während Ihre Mitstreiter weiterhin in ihrer dunklen und altertümlichen Umgebung tätig sind. Es ist eine gute Möglichkeit, Git in die Unternehmensumgebung einzuschleusen, Ihren Entwicklerkollegen zu helfen, effizienter zu werden und gleichzeitig die Infrastruktur so zu ändern, um Git vollständig zu unterstützen. Die Subversion-Brücke ist das Portal zur DVCS-Welt.

git svn

Der Hauptbefehl in Git für sämtliches Subversion-Bridging ist git svn. Es sind ziemlich wenige Befehle erforderlich, so dass wir die gängigsten aufzeigen und dabei einige einfache Workflows durchgehen werden.

Es ist wichtig zu beachten, dass Sie bei der Verwendung von git svn mit Subversion interagieren, einem System, das ganz anders funktioniert als Git. Obwohl Sie lokales Branching und Merging durchführen können, ist es im Allgemeinen ratsam, Ihren Verlauf so linear wie möglich zu gestalten, indem Sie Ihre Arbeiten rebasen. Vermeiden Sie dabei auch die gleichzeitige Interaktion mit einem Git Remote-Repository.

Schreiben Sie Ihren Verlauf nicht um und versuchen Sie nicht, erneut zu pushen. Pushen Sie nicht in ein paralleles Git-Repository, um gleichzeitig mit anderen Git-Entwicklern zusammenzuarbeiten. Subversion kann nur einen einzigen linearen Verlauf haben und es ist sehr leicht zu verwirren. Wenn Sie mit einem Team arbeiten und einige verwenden SVN und andere Git, stellen Sie sicher, dass alle den SVN-Server für die gemeinsame Arbeit verwenden – das erleichtert Ihnen den Alltag.

Einrichtung

Um diese Funktionalität zu demonstrieren, benötigen Sie ein typisches SVN-Repository, auf das Sie Schreibzugriff haben. Wenn Sie die Beispiele kopieren möchten, müssen Sie eine beschreibbare Kopie eines SVN-Test Repository erstellen. Um das einfach zu realisieren, können Sie das Tool svnsync verwenden, das in Subversion enthalten ist.

Um weiter zu machen, müssen Sie zunächst ein neues lokales Subversion-Repository erstellen:

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

Aktivieren Sie dann alle Benutzer, um revprops zu ändern – der einfachste Weg ist, ein pre-revprop-change Skript hinzuzufügen, das immer den exit-Wert 0 hat:

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

Sie können dieses Projekt nun auf Ihrem lokalen Rechner synchronisieren, indem Sie svnsync init mit den Repositorys to und from aufrufen.

$ svnsync init file:///tmp/test-svn \
  http://your-svn-server.example.org/svn/

Dadurch werden die Eigenschaften für die Ausführung der Synchronisierung festgelegt. Sie können den Code dann klonen, indem Sie Folgendes ausführen:

$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[…]

Obwohl dieser Vorgang nur wenige Minutenin Anspruch nehmen könnte, dauert der Prozess fast eine Stunde, wenn Sie versuchen, das ursprüngliche Repository in ein anderes Remote-Repository anstelle eines lokalen zu kopieren, obwohl es weniger als 100 Commits gibt. Subversion muss eine Revision nach der anderen klonen und sie dann wieder in ein anderes Repository verschieben – es ist unvorstellbar ineffizient, aber es ist der einzige einfache Weg, das zu erreichen.

Erste Schritte

Jetzt, da Sie ein Subversion-Repository haben, auf das Sie Schreibrechte haben, können Sie einen typischen Work-Flow absolvieren. Beginnen Sie mit dem Befehl git svn clone, der ein ganzes Subversion-Repository in ein lokales Git-Repository importiert. Beachten Sie, dass Sie beim Import aus einem echten Subversion-Repository hier file:///tmp/test-svn durch die URL Ihres Subversion-Repository ersetzen sollten:

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
    A	m4/acx_pthread.m4
    A	m4/stl_hash.m4
    A	java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
    A	java/src/test/java/com/google/protobuf/WireFormatTest.java
…
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75

Das entspricht zwei Befehlen – git svn init gefolgt von git svn fetch – auf der von Ihnen angegebenen URL. Dieser Vorgang kann einige Zeit dauern. Wenn beispielsweise das Testprojekt nur etwa 75 Commits hat und die Code-Basis nicht so groß ist, muss Git dennoch jede Version einzeln auschecken und einzeln committen. Bei einem Projekt mit Hunderten oder Tausenden von Commits kann es buchstäblich Stunden oder gar Tage dauern, das zu vollenden.

Der Teil -T trunk -b branches -t tags teilt Git mit, dass dieses Subversion-Repository den grundlegenden Branching- und Tagging-Konventionen folgt. Wenn Sie Ihren Trunk, Ihre Branches oder Tags anders benennen, können sich diese Optionen ändern. Da dies so häufig vorkommt, können Sie den gesamten Teil durch -s ersetzen, was Standardlayout bedeutet und all diese Optionen beinhaltet. Das folgende Kommando ist dabei gleichwertig:

$ git svn clone file:///tmp/test-svn -s

An dieser Stelle sollten Sie über ein gültiges Git-Repository verfügen, das Ihre Branches und Tags importiert hat:

$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk

Beachten Sie, dass dieses Tool Subversion-Tags als Remote-Referenzen (engl. refs) verwaltet. Werfen wir einen genaueren Blick auf den Git Low-Level-Befehl show-ref:

$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk

Git macht das nicht, wenn es von einem Git-Server klont, So sieht ein Repository mit Tags nach einem frischen Klon aus:

$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0

Git fetcht die Tags direkt in refs/tags, anstatt sie mit entfernten Branches zu verknüpfen.

Zurück zu Subversion committen

Jetzt, da Sie ein Arbeitsverzeichnis haben, können Sie etwas an dem Projekt arbeiten und Ihre Commits wieder zum Upstream pushen, indem Sie Git als SVN-Client verwenden. Wenn Sie eine der Dateien bearbeiten und übertragen, haben Sie einen Commit, der in Git lokal existiert aber nicht auf dem Subversion-Server vorhanden ist:

$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)

Als nächstes müssen Sie Ihre Änderung zum Upstream pushen. Beachten Sie, wie sich dies auf Ihre Arbeitsweise mit Subversion auswirkt – Sie können mehrere Commits offline durchführen und diese dann alle auf einmal auf den Subversion-Server übertragen. Um zu einem Subversion-Server zu pushen, führen Sie den Befehl git svn dcommit aus:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r77
    M	README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Dabei werden alle Commits ausgeführt, die Sie oberhalb des Subversion Server-Codes gemacht haben, dafür jeweils einen eigenen Subversion-Commit und dann Ihren lokaler Git-Commit umgeschrieben, um einen eindeutigen Identifier einzufügen. Das ist wichtig, weil es bedeutet, dass sich alle SHA-1-Prüfsummen für Ihre Commits ändern. Aus diesem Grund ist es keine gute Idee, gleichzeitig mit Git-basierten Remotes Ihrer Projekte und einem Subversion-Server zu arbeiten. Wenn Sie sich den letzten Commit ansehen, sehen Sie die neu hinzugefügte git-svn-id:

$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README

    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68

Es ist zu beachten, dass die SHA-1-Prüfsumme, die ursprünglich mit 4af61fd begann, als Sie die Daten übertragen haben, nun mit 95e0222 beginnt. Wenn Sie sowohl auf einen Git-Server als auch auf einen Subversion-Server pushen möchten, müssen Sie zuerst auf den Subversion-Server pushen (dcommit), da diese Aktion Ihre Commit-Daten ändert.

Neue Änderungen pullen

Wenn Sie mit anderen Entwicklern zusammenarbeiten, dann wird irgendwann einer von Ihnen pushen, und andere versuchen, eine Änderung voranzutreiben, die Konflikte verursacht. Diese Änderung wird abgelehnt, bis Sie deren Arbeit mergen. In git svn sieht das so aus:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M	README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Um diese Situation zu lösen, können Sie die git svn rebase ausführen, das alle Änderungen auf dem Server, die Sie noch nicht haben, pullt und alle ihre lokalen Arbeiten an die Spitze zum Server neu überträgt (engl. rebase):

$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a b34372b25ccf4945fe5658fa381b075045e7702a M	README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M	README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Jetzt ist Ihre gesamte Arbeit an der Spitze des Subversion-Servers, so dass Sie dcommit erfolgreich einsetzen können:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r85
    M	README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Im Unterschied zu Git, das voraussetzt, dass Sie Upstream-Arbeiten, die Sie noch nicht lokal haben, zuerst mergen, bevor Sie pushen können, zwingt Sie git svn dazu nur dann, wenn die Änderungen im Konflikt stehen (ähnlich wie bei Subversion). Wenn jemand anderes eine Änderung an einer Datei vorantreibt und Sie eine Änderung an einer anderen Datei vorantreiben, funktioniert Ihr dcommit gut:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	configure.ac
Committed r87
    M	autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M	configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a9439312d80d5d43bb65d4a7d0389ed6d M	autogen.sh
First, rewinding head to replay your work on top of it...

Es ist sehr wichtig, sich daran zu halten, denn das Ergebnis ist ein Projektstatus, der beim Push auf keinem Ihrer Computer vorhanden war. Wenn die Änderungen inkompatibel sind, aber keine Konflikte verursachen, können Probleme auftreten, die schwer zu diagnostizieren sind. Das ist ein Unterschied gegenüber der Nutzung eines Git-Servers – in Git können Sie den Zustand auf Ihrem Client-System vor der Veröffentlichung vollständig testen, während Sie in SVN nie sicher sein können, dass die Zustände unmittelbar vor dem Commit und nach dem Commit identisch sind.

Sie sollten diesen Befehl auch ausführen, um Änderungen vom Subversion-Server einzubinden, auch wenn Sie nicht bereit sind, selbst zu committen. Es ist ratsam, git svn fetch auszuführen, um die neuen Daten zu holen, aber git svn rebase übernimmt den Fetch und aktualisiert dann Ihre lokalen Commits.

$ git svn rebase
    M	autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.

Wenn Sie ab und zu git svn rebase laufen lassen, stellen Sie sicher, dass Ihr Code immer auf dem neuesten Stand ist. Sie sollten jedoch überprüfen, ob Ihr Arbeitsverzeichnis sauber ist, wenn Sie diese Funktion auslösen. Wenn Sie lokale Änderungen haben, müssen Sie Ihre Arbeit entweder verstecken (engl. stash) oder temporär committen, bevor Sie git svn rebase ausführen – andernfalls wird der Befehl angehalten, wenn er erkennt, dass das Rebase zu einem Merge-Konflikt führen wird.

Git Branching Probleme

Sobald Sie sich mit einem Git-Workflow vertraut gemacht haben, werden Sie wahrscheinlich Topic-Branches erstellen, an ihnen arbeiten und sie dann verschmelzen (mergen). Wenn Sie über git svn auf einen Subversion-Server pushen, können Sie Ihre Arbeit jedes Mal auf einen einzigen Branch rebasieren, anstatt Branches zu mergen. Die Begründung für ein Rebasing ist, dass Subversion eine lineare Historie hat und sich nicht wie Git mit Merges beschäftigt. So folgt git svn bei der Konvertierung der Snapshots in Subversion Commits nur dem ersten Elternteil.

Nehmen wir an, Ihr Verlauf sieht wie folgt aus: Sie haben einen experiment Branch erstellt, zwei Commits durchgeführt und diese dann wieder mit dem master zusammengeführt. Wenn Sie dcommit aufrufen, erscheint folgende Anzeige:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	CHANGES.txt
Committed r89
    M	CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M	COPYING.txt
    M	INSTALL.txt
Committed r90
    M	INSTALL.txt
    M	COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Das Ausführen von dcommit auf einem Branch mit zusammengeführtem Verlauf funktioniert gut, außer wenn Sie sich Ihre Git-Projekt-Historie ansehen, er hat keinen der Commits, die Sie auf dem experiment Branch gemacht haben, neu geschrieben – statt dessen erscheinen alle diese Änderungen in der SVN-Version eines einzelnen Merge-Commits.

Wenn jemand anderes diese Arbeit klont, sieht man nur den Merge-Commit mit der gesamten Arbeit, die in ihn hineingedrückt wurde, als ob Sie git merge --squash ausgeführt hätten; man sieht die Commit-Daten nicht, woher sie stammen oder wann sie committed wurden.

Subversion Branching

Branching in Subversion ist nicht dasselbe wie Branching in Git. Es ist wahrscheinlich das Beste, wenn Sie es so oft vermeiden wie möglich. Sie können aber mit git svn in Subversion Branches anlegen und dorthin committen.

Creating a New SVN Branch

Um einen neuen Branch in Subversion zu erstellen, führen Sie git svn branch [new-branch] aus:

$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera) cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)

Dadurch wird das Äquivalent des Befehls svn copy trunk branches/opera in Subversion ausgeführt und auf dem Subversion-Server angewendet. Wichtig dabei ist, dass er Sie nicht in diesen Branch auscheckt; wenn Sie an dieser Stelle committen, wird dieser Commit in den trunk auf dem Server gehen, nicht in at this point, that commit will go to trunk on the server, not opera.

Aktive Branches wechseln

Git findet heraus, in welchen Branch Ihre dcommits gehen, indem es nach der Spitze eines Ihrer Subversion Branches in Ihrem Verlauf sucht – Sie sollten nur einen haben, und es sollte der Letzte sein, der eine git-svn-id in Ihrem aktuellen Branchverlauf hat.

Falls Sie an mehr als einem Branch gleichzeitig arbeiten möchten, können Sie lokale Branches einrichten, um dcommit auf bestimmte Subversion Branches zu beschränken, indem Sie diese beim importierten Subversion Commit für diesen Branch starten. Einen opera Branch, in dem Sie separat bearbeiten können, können Sie starten mit:

$ git branch opera remotes/origin/opera

Um Ihren opera Branch in trunk (Ihren master Branch) zu mergen, können Sie das mit einem normalen git merge machen. Aber Sie sollten unbedingt eine beschreibende Commit-Meldung (via -m) angeben, sonst wird beim Merge anstelle von etwas Vernünftigem „Merge branch opera“ angezeigt.

Obwohl Sie für diese Operation git merge verwenden und der Merge wahrscheinlich viel einfacher ist als in Subversion (da Git automatisch die entsprechende Merge-Basis für Sie erkennt), ist es kein normaler Git Merge-Commit. Sie müssen diese Daten an einen Subversion-Server zurück pushen, der keinen Commit mit mehr als einem Elternteil verarbeiten kann; nachdem Sie ihn zum Server gepusht haben, sieht er also aus wie ein einzelner Commit, der die gesamte Arbeit eines anderen Branchs unter einem einzigen Commit zusammenfasst. Nachdem Sie einen Branch in einem anderen zusammengeführt haben, können Sie nicht einfach zurückgehen und an diesem Branch weiterarbeiten, wie Sie es normalerweise in Git tun. Das dcommit Kommando, das Sie ausführen, löscht alle Informationen, die zeigen, in welchen Branch zusammengeführt wurde, so dass nachfolgende Berechnungen der Merge-Basis falsch sind – dcommit lässt Ihr git merge Ergebnis aussehen, als ob Sie git merge --squash ausgeführt hätten. Leider gibt es keine gute Methode, diese Situation zu vermeiden – Subversion kann diese Informationen nicht speichern, daher werden Sie immer von den Einschränkungen des Systems behindert, während Sie es als Ihren Server verwenden. Um Fehler zu vermeiden, sollten Sie den lokalen Branch (in diesem Fall opera) löschen, nachdem Sie ihn in trunk eingefügt haben.

Subversion Kommandos

Das git svn Toolset bietet eine Reihe von Befehlen, die den Übergang zu Git erleichtern, indem es einige Funktionen bereitstellt, die denen ähneln, die Sie von Subversion aus kennen. Wir haben hier ein paar Befehle, mit denen Sie das bekommen, was Subversion vorher konnte.

Verlauf im SVN-Format

Wenn Sie an Subversion gewöhnt sind und Ihren Verlauf im SVN-Stil sehen möchten, können Sie git svn log ausführen, um Ihren Commit-Verlauf in SVN-Formatierung anzuzeigen:

$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines

autogen change

------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines

Merge branch 'experiment'

------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines

updated the changelog

Sie sollten zwei wichtige Dinge über git svn log wissen. Erstens funktioniert es offline, im Unterschied zum echten svn log Befehl, der den Subversion-Server nach den Daten fragt. Zweitens zeigt es Ihnen nur Commits an, die zum Subversion-Server übertragen wurden. Lokale Git-Commits, die Sie noch nicht mit dcommit bestätigt haben, werden nicht angezeigt; ebenso wenig wie Commits, die von Leuten in der Zwischenzeit auf dem Subversion-Server gemacht wurden. Es ist mehr wie der letzte bekannte Zustand der Commits auf dem Subversion-Server.

SVN Anmerkung

So wie der Befehl git svn log den Befehl svn log offline simuliert, können Sie das Äquivalent von svn annotate abrufen, indem Sie git svn blame [FILE] ausführen. Die Ausgabe sieht wie folgt aus:

$ git svn blame README.txt
 2   temporal Protocol Buffers - Google's data interchange format
 2   temporal Copyright 2008 Google Inc.
 2   temporal http://code.google.com/apis/protocolbuffers/
 2   temporal
22   temporal C++ Installation - Unix
22   temporal =======================
 2   temporal
79    schacon Committing in git-svn.
78    schacon
 2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol
 2   temporal Buffer compiler (protoc) execute the following:
 2   temporal

Noch einmal, zur Wiederholung! Auch hier werden keine Commits angezeigt, die Sie lokal in Git gemacht haben oder die in der Zwischenzeit in Subversion verschoben wurden.

SVN Server-Information

Wenn Sie git svn info ausführen, können Sie die gleiche Art von Informationen erhalten, die Ihnen svn info liefert:

$ git svn info
Path: .
URL: https://schacon-test.googlecode.com/svn/trunk
Repository Root: https://schacon-test.googlecode.com/svn
Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029
Revision: 87
Node Kind: directory
Schedule: normal
Last Changed Author: schacon
Last Changed Rev: 87
Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)

Das ist wie bei blame und log, denn es läuft offline und ist nur ab der letzten Kommunikation mit dem Subversion-Server auf dem neuesten Stand.

Ignorieren, was Subversion ignoriert

Wenn Sie ein Subversion-Repository klonen, in dem irgendwo svn:ignore Eigenschaften gesetzt sind, werden Sie wahrscheinlich entsprechende .gitignore Dateien setzen wollen, damit Sie nicht versehentlich Dateien übertragen, die Sie nicht sollten. git svn verfügt über zwei Befehle, um bei diesem Problem zu helfen. Der Erste ist git svn create-ignore, der automatisch entsprechende .gitignore Dateien für Sie erstellt, damit sie bei Ihrem nächsten Commit berücksichtigt werden.

Der zweite Befehl ist git svn show-ignore, der die Zeilen nach stdout ausgibt, die Sie in eine .gitignore Datei einfügen müssen, damit Sie die Ausgabe in die Ausschlussdatei Ihres Projekts umleiten können:

$ git svn show-ignore > .git/info/exclude

Auf diese Weise überhäufen Sie das Projekt nicht mit .gitignore Dateien. Das ist eine gute Option, wenn Sie der einzige Git-Benutzer in einem Subversion-Team sind und Ihre Teamkollegen keine .gitignore Dateien im Projekt haben wollen.

git svn Zusammenfassung

Die git svn Tools sind nützlich, wenn Sie mit dem Subversion-Server feststecken oder sich anderweitig in einer Entwicklungsumgebung befinden, die den Betrieb eines Subversion-Servers erfordert. Sie sollten es jedoch als verkümmertes Git betrachten, oder Sie werden Probleme in der Umsetzung haben, die Sie und Ihre Mitwirkenden verwirren könnten. Um keine Schwierigkeiten zu bekommen, versuchen Sie sich an diese Hinweise zu halten:

  • Führen Sie einen linearen Git-Verlauf, der keine Merge-Commits von git merge enthält. Rebasieren Sie alle Arbeiten, die Sie außerhalb Ihres Haupt-Branchs durchführen, wieder in diesen ein; mergen Sie sie nicht.

  • Richten Sie keinen separaten Git-Server ein und arbeiten Sie nicht mit einem zusammen. Möglicherweise haben Sie einen, um Klone für neue Entwickler zu starten, aber pushen Sie nichts, was nicht über einen git-svn-id Eintrag verfügt. Sie können eventuell einen pre-receive Hook hinzufügen, der jede Commit-Nachricht auf einen git-svn-id überprüft und Pushes, die Commits ohne ihn enthalten, ablehnt.

Wenn Sie diese Leitlinien befolgen, kann die Arbeit mit einem Subversion-Server leichter umsetzbar sein. Mit einem Umstieg auf einen echten Git-Server kann Ihr Team erheblich mehr an Effizienz gewinnen.

Git und Mercurial

Das DVCS-Universum besteht nicht nur aus nur Git. In diesem Bereich gibt es viele andere Systeme, jedes hat seinen eigenen Ansatz, wie eine verteilte Versionskontrolle zu funktionieren hat. Neben Git ist Mercurial am populärsten und die beiden sind sich in vielerlei Hinsicht sehr ähnlich.

Die gute Nachricht, wenn Sie Gits clientseitiges Verhalten bevorzugen, aber mit einem Projekt arbeiten, dessen Quellcode mit Mercurial verwaltet wird, dann ist es möglich, Git als Client für ein von Mercurial gehostetes Repository zu verwenden. Da die Art und Weise, wie Git über Remotes mit Server-Repositories kommuniziert, sollte es nicht überraschen, dass diese Bridge als Remote-Helfer implementiert ist. Der Name des Projekts lautet git-remote-hg und ist unter https://github.com/felipec/git-remote-hg zu finden.

git-remote-hg

Zuerst müssen Sie git-remote-hg installieren. Im Wesentlichen geht es darum, die Datei irgendwo in Ihrem Pfad abzulegen, so wie hier:

$ curl -o ~/bin/git-remote-hg \
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg

…vorausgesetzt (in einer Linux-Umgebung), ~/bin ist in Ihrem $PATH. Git-remote-hg hat noch eine weitere Abhängigkeit: die mercurial Library für Python. Wenn Sie Python installiert schon haben, ist das einfach:

$ pip install mercurial

(Wenn Sie Python noch nicht installiert haben, besuchen Sie https://www.python.org/ und besorgen Sie es sich zuerst.)

Als Letztes brauchen Sie den Mercurial-Client. Gehen Sie zu https://www.mercurial-scm.org/ und installieren Sie ihn, falls Sie es noch nicht getan haben.

Jetzt sind Sie bereit zu abrocken. Alles, was Sie benötigen, ist ein Mercurial-Repository, auf das Sie zugreifen können. Glücklicherweise kann sich jedes Mercurial-Repository so verhalten, also verwenden wir einfach das „hello world“-Repository, das jeder benutzt, um Mercurial zu lernen:

$ hg clone http://selenic.com/repo/hello /tmp/hello

Erste Schritte

Nun, da wir über ein geeignetes „serverseitiges“ Repository verfügen, können wir einen typischen Workflow durchlaufen. Wie Sie sehen werden, sind diese beiden Systeme ähnlich genug, dass es keine große Überschneidungen gibt.

Wie immer mit Git, wir klonen zuerst:

$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Wie Sie sehen, verwendet man bei der Arbeit mit einem Mercurial-Repository den Standardbefehl git clone. Das liegt daran, dass git-remote-hg auf einem relativ niedrigen Level arbeitet und einen ähnlichen Mechanismus verwendet, wie es die Implementierung des HTTP/S-Protokolls in Git ist (Remote-Helfer). Da Git und Mercurial beide so konzipiert sind, dass jeder Client eine vollständige Kopie der Repository-Historie hat, erstellt dieser Befehl relativ schnell einen vollständigen Klon, einschließlich der gesamten Projekthistorie.

Der log-Befehl zeigt zwei Commits, von denen der letzte von einer ganzen Reihe von Refs angeführt wird. Wie sich herausstellt, sind einige davon nicht wirklich da. Werfen wir einen Blick darauf, was sich wirklich im .git Verzeichnis befindet:

$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files

Git-remote-hg versucht sich idiomatisch (begrifflich) an Git anzunähern, aber im Hintergrund verwaltet es die konzeptionelle Zuordnung zwischen zwei leicht unterschiedlichen Systemen. Im Verzeichnis refs/hg werden die aktuellen Remote-Referenzen gespeichert. Zum Beispiel ist die refs/hg/origin/branches/default eine Git ref-Datei, die das SHA-1 enthält und mit „ac7955c“ beginnt. Das ist der Commit, auf den master zeigt. Das Verzeichnis refs/hg ist also eine Art gefälschtes refs/remotes/origin, aber es unterscheidet zusätzlich zwischen Lesezeichen und Zweigen.

Die Datei notes/hg ist der Ausgangspunkt dafür, wie git-remote-hg Git-Commit-Hashes auf Mercurial-Changeset-IDs abbildet. Lassen Sie uns ein wenig experimentieren:

$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9

So zeigt refs/notes/hg auf einen Verzeichnisbaum, der in der Git-Objektdatenbank eine Liste anderer Objekte mit Namen ist. git ls-tree gibt Modus, Typ, Objekt-Hash und Dateiname für Elemente innerhalb eines Baums aus. Sobald wir uns auf eines der Baumelemente festgelegt haben, stellen wir fest, dass sich darin ein „ac9117f“ Blob (der SHA-1-Hash des Commit, auf den master zeigt) befindet. Inhaltlich ist er identisch mit „0a04b98“ (das ist die ID des Mercurial-Changesets an der Spitze der default Branch).

Die gute Nachricht ist, dass wir uns darüber meistens keine Sorgen machen müssen. Der typische Arbeitsablauf unterscheidet sich nicht wesentlich von der Arbeit mit einem Git-Remote.

Noch eine Besonderheit, um die wir uns kümmern sollten, bevor wir fortfahren: Die Auslassungen. Mercurial und Git verwenden dafür einen sehr ähnlichen Mechanismus, aber es ist durchaus möglich, dass Sie eine .gitignore Datei nicht wirklich in ein Mercurial Repository übertragen wollen. Glücklicherweise hat Git eine Möglichkeit, Dateien zu ignorieren, die lokal in einem On-Disk-Repository liegen. Das Mercurial-Format ist kompatibel mit Git, so dass Sie es nur kopieren müssen:

$ cp .hgignore .git/info/exclude

Die Datei .git/info/exclude verhält sich wie eine .gitignore, wird aber nicht in den Commits aufgenommen.

Workflow

Nehmen wir an, wir haben einige Arbeiten erledigt und einige Commits auf den master Branch gemacht und Sie sind so weit, ihn in das Remote-Repository zu pushen. Nun sieht unser Repository momentan so aus:

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Unser master Branch ist zwei Commits vor dem origin/master, aber diese beiden Commits existieren nur auf unserem lokalen Rechner. Schauen wir mal nach, ob jemand anderes zur gleichen Zeit wichtige Arbeit geleistet hat:

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87  master     -> origin/master
   ac7955c..df85e87  branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Da wir das --all Flag verwendet haben, sehen wir die „notes“ Refs, die intern von git-remote-hg verwendet werden, die wir aber ignorieren können. Den Rest haben wir erwartet. origin/master ist um einen Commit fortgeschritten. Unser Verlauf hat sich dadurch verändert. Anders als bei anderen Systemen, mit denen wir in diesem Kapitel arbeiten, ist Mercurial in der Lage, Merges zu verarbeiten, so dass wir nichts Ausgefallenes tun müssen.

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
*   0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Perfekt. Wir führen die Tests durch und alles passt, also sind wir so weit, dass wir unsere Arbeit mit dem Rest des Teams teilen können:

$ git push
To hg::/tmp/hello
   df85e87..0c64627  master -> master

Das wars! Wenn Sie einen Blick auf das Mercurial-Repository werfen, werden Sie feststellen, dass genau das getan wurde, was wir erwarten durften:

$ hg log -G --style compact
o    5[tip]:4,2   dc8fa4f932b8   2014-08-14 19:33 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   64f27bcefc35   2014-08-14 19:27 -0700   ben
| |    Update makefile
| |
| o  3:1   4256fc29598f   2014-08-14 19:27 -0700   ben
| |    Goodbye
| |
@ |  2   7db0b4848b3c   2014-08-14 19:30 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Das Change-Set mit der Nummer 2 wurde von Mercurial vorgenommen, und die Change-Sets mit der Nummer 3 und 4 wurden von git-remote-hg durchgeführt, indem Commits mit Git gepusht wurden.

Branches und Bookmarks

Git hat nur eine Art von Branch: eine Referenz, die sich verschiebt, wenn Commits gemacht werden. In Mercurial wird diese Art von Referenz als „bookmark“ (dt. Lesezeichen) bezeichnet, und sie verhält sich ähnlich wie ein Git-Branch.

Das Konzept von Mercurial eines „Branchs“ ist höher gewichtet. Der Branch, auf den ein Changeset durchgeführt wird, wird zusammen mit dem Changeset aufgezeichnet, d.h. er befindet sich immer im Repository-Verlauf. Hier ist ein Beispiel für einen Commit, der auf dem develop Branch gemacht wurde:

$ hg log -l 1
changeset:   6:8f65e5e02793
branch:      develop
tag:         tip
user:        Ben Straub <ben@straub.cc>
date:        Thu Aug 14 20:06:38 2014 -0700
summary:     More documentation

Achten Sie darauf, dass die Zeile mit „branch“ beginnt. Git kann das nicht wirklich nachahmen (und muss es auch nicht; beide Arten von Zweigen können als Git ref dargestellt werden), aber git-remote-hg muss den Unterschied erkennen, denn für Mercurial ist er wichtig.

Das Anlegen von Mercurial-Lesezeichen ist so einfach wie das Erstellen von Git-Branches. Auf der Seite vom Git machen Sie:

$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
 * [new branch]      featureA -> featureA

Das ist alles, was es dazu zu sagen gibt. Auf der Mercurial-Seite sieht es dann folgendermaßen aus:

$ hg bookmarks
   featureA                  5:bd5ac26f11f9
$ hg log --style compact -G
@  6[tip]   8f65e5e02793   2014-08-14 20:06 -0700   ben
|    More documentation
|
o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| |    update makefile
| |
| o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |    goodbye
| |
o |  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Beachten Sie den neuen [featureA] Tag auf Revision 5. Die verhalten sich genau wie Git-Branches auf der Git-Seite, mit einer Ausnahme: Sie können ein Lesezeichen auf der Git-Seite nicht löschen (das ist eine Einschränkung des Remote-Helfers).

Sie können auch an einem „schwergewichtigen“ Mercurial-Branch arbeiten: Bringen Sie einfach einen Branch in den branches Namensraum:

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch]      branches/permanent -> branches/permanent

So sieht das dann auf der Mercurial-Seite aus:

$ hg branches
permanent                      7:a4529d07aad4
develop                        6:8f65e5e02793
default                        5:bd5ac26f11f9 (inactive)
$ hg log -G
o  changeset:   7:a4529d07aad4
|  branch:      permanent
|  tag:         tip
|  parent:      5:bd5ac26f11f9
|  user:        Ben Straub <ben@straub.cc>
|  date:        Thu Aug 14 20:21:09 2014 -0700
|  summary:     A permanent change
|
| @  changeset:   6:8f65e5e02793
|/   branch:      develop
|    user:        Ben Straub <ben@straub.cc>
|    date:        Thu Aug 14 20:06:38 2014 -0700
|    summary:     More documentation
|
o    changeset:   5:bd5ac26f11f9
|\   bookmark:    featureA
| |  parent:      4:0434aaa6b91f
| |  parent:      2:f098c7f45c4f
| |  user:        Ben Straub <ben@straub.cc>
| |  date:        Thu Aug 14 20:02:21 2014 -0700
| |  summary:     Merge remote-tracking branch 'origin/master'
[...]

Der Branch-Name „permanent“ wurde mit dem Change-Set 7 eingetragen.

Seitens von Git ist die Arbeit mit einem dieser Branch-Stile die gleiche: Einfach auschecken, committen, fetchen, mergen, pullen, und pushen, wie Sie es üblicherweise machen würden. Eine Sache, die Sie wissen sollten, ist, dass Mercurial das Überschreiben der Historie nicht unterstützt, sondern nur hinzufügt. Das Mercurial-Repository sieht nach einem interaktiven Rebase und einem Force-Push so aus:

$ hg log --style compact -G
o  10[tip]   99611176cbc9   2014-08-14 20:21 -0700   ben
|    A permanent change
|
o  9   f23e12f939c3   2014-08-14 20:01 -0700   ben
|    Add some documentation
|
o  8:1   c16971d33922   2014-08-14 20:00 -0700   ben
|    goodbye
|
| o  7:5   a4529d07aad4   2014-08-14 20:21 -0700   ben
| |    A permanent change
| |
| | @  6   8f65e5e02793   2014-08-14 20:06 -0700   ben
| |/     More documentation
| |
| o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
| |\     Merge remote-tracking branch 'origin/master'
| | |
| | o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| | |    update makefile
| | |
+---o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |      goodbye
| |
| o  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Die Changesets 8, 9 und 10 wurden angelegt und gehören zum permanent Branch, aber die alten Changesets sind immer noch vorhanden. Das kann für Ihre Teamkollegen, die Mercurial verwenden, sehr verwirrend sein, also versuchen Sie es zu vermeiden.

Mercurial Zusammenfassung

Git und Mercurial sind sich ähnlich genug, um über die eigene Umgebung hinaus schmerzlos zu arbeiten. Wenn Sie vermeiden, den Verlauf zu ändern, der Ihren Computer verlässt (was allgemein empfohlen wird), merken Sie wahrscheinlich nicht einmal, dass am anderen Ende Mercurial verwendet wird.

Git und Bazaar

Unter den DVCSs ist Bazaar ein weiterer bedeutender Vertreter. Bazaar ist freie Software, Open-Source und ist Teil des GNU-Projekts. Es verhält sich ganz anders als Git. Manchmal muss man, um das Gleiche wie bei Git machen zu können, ein anderes Schlüsselwort verwenden. Einige gängige Schlüsselwörter haben nicht die gleiche Bedeutung. Insbesondere das Branch-Management ist sehr verschieden und kann zu Verwirrung und Missverständnissen führen, vor allem, wenn jemand aus dem Umfeld von Git kommt. Dennoch ist es von Git aus möglich, an einem Bazaar-Repository zu arbeiten.

Es gibt viele Projekte, die es Ihnen ermöglichen, Git als Bazaar-Client zu nutzen. Hier werden wir das Projekt von Felipe Contreras verwenden, das Sie unter https://github.com/felipec/git-remote-bzr finden können. Um es zu installieren, müssen Sie nur die Datei git-remote-bzr in einen Ordner herunterladen, der sich in Ihrem Pfad ($PATH) befindet:

$ wget https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr -O ~/bin/git-remote-bzr
$ chmod +x ~/bin/git-remote-bzr

Außerdem müssen Sie Bazaar installiert haben. Das ist alles!

Erstellen eines Git-Repository aus einem Bazaar-Repository

Die Bedienung ist einfach. Es genügt, ein Bazaar-Repository zu klonen, dem bzr:: vorangestellt ist. Da Git und Bazaar beide Vollklone auf Ihrem Computer erstellen, ist es möglich, einen Git-Klon an Ihren lokalen Bazaar-Klon anzuhängen, es wird aber nicht empfohlen. Es ist viel einfacher, Ihren Git-Klon direkt an den gleichen Ort zu hängen, an dem Ihr Bazaar-Klon hängt – das zentrale Repository.

Angenommen, Sie haben mit einem Remote-Repository gearbeitet, das sich unter der Adresse bzr+ssh://developer@mybazaarserver:myproject befindet. Dann müssen Sie es wie folgt klonen:

$ git clone bzr::bzr+ssh://developer@mybazaarserver:myproject myProject-Git
$ cd myProject-Git

An diesem Punkt wird Ihr Git-Repository erstellt, aber es ist nicht für eine optimale Festplattennutzung komprimiert. Deshalb sollten Sie auch Ihr Git-Repository bereinigen und komprimieren, vor allem wenn es ein großes ist:

$ git gc --aggressive

Bazaar Branches

Bazaar erlaubt es Ihnen nur, Branches zu klonen, aber ein Repository kann mehrere Branches enthalten, und git-remote-bzr kann beides klonen. Um zum Beispiel einen Branch zu klonen:

$ git clone bzr::bzr://bzr.savannah.gnu.org/emacs/trunk emacs-trunk

Und um das gesamte Repository zu klonen:

$ git clone bzr::bzr://bzr.savannah.gnu.org/emacs emacs

Der zweite Befehl klont alle Branches, die im emacs-Repository enthalten sind; es ist jedoch möglich einige Branches hervorzuheben:

$ git config remote-bzr.branches 'trunk, xwindow'

Einige Remote-Repositorys erlauben es Ihnen nicht, ihre Branches aufzulisten, in diesem Fall müssen Sie sie manuell angeben, und obwohl Sie die Konfiguration im Klon-Befehl angeben könnten, könnten Sie das leichter feststellen:

$ git init emacs
$ git remote add origin bzr::bzr://bzr.savannah.gnu.org/emacs
$ git config remote-bzr.branches 'trunk, xwindow'
$ git fetch

Ignorieren, was mit .bzrignore ignoriert wird

Da Sie an einem mit Bazaar verwalteten Projekt arbeiten, sollten Sie keine .gitignore Datei erstellen, da Sie diese versehentlich unter Versionskontrolle setzen könnten und die anderen mit Bazaar arbeitenden Benutzer dadurch gestört würden. Die Lösung besteht darin, die .git/info/exclude Datei entweder als symbolischen Link oder als normale Datei zu erstellen. Wir werden später sehen, wie wir dieses Problem lösen können.

Bazaar verwendet das gleiche Modell wie Git, um Dateien zu ignorieren, hat aber auch zwei Funktionen, die kein Äquivalent in Git haben. Die vollständige Beschreibung finden Sie in der Dokumentation. Die beiden Merkmale sind:

  1. „!!“ ermöglicht es Ihnen, bestimmte Dateimuster zu ignorieren, auch wenn sie mit einer „!“-Regel angegeben werden.

  2. „RE:“ am Anfang einer Zeile erlaubt es Ihnen, einen regulären Python-Ausdruck anzugeben (Git erlaubt nur Shell Globs).

Folglich sind zwei verschiedene Situationen zu prüfen:

  1. Wenn die Datei .bzrignore keines dieser beiden spezifischen Präfixe enthält, dann können Sie einfach einen symbolischen Link darauf im Repository setzen: ln -s.bzrignore.git/info/exclude.

  2. Ansonsten müssen Sie die Datei .git/info/exclude erstellen und anpassen, um genau die gleichen Dateien in .bzrignore zu ignorieren.

Was auch immer der Fall ist, Sie müssen auf jede Änderung von .bzrignore achten, um sicherzustellen, dass die Datei .git/info/exclude immer .bzrignore widerspiegelt. Wenn sich die Datei .bzrignore ändert und eine oder mehrere Zeilen enthält, die mit „!!“ oder „RE:“ beginnen, muss die Datei .git/info/exclude so angepasst werden, dass sie die gleichen Dateien ignoriert, wie die, die mit .bzrignore ignoriert werden. Wenn die Datei .git/info/exclude ein symbolischer Link war, müssen Sie außerdem zuerst den symbolischen Link löschen, .bzrignore nach .git/info/exclude kopieren und diese dann anpassen. Seien Sie jedoch vorsichtig bei der Erstellung, da es mit Git unmöglich ist, eine Datei wieder einzubinden, wenn ein übergeordnetes Verzeichnis dieser Datei ausgeschlossen ist.

Fetchen der Änderungen aus dem Remote-Repository

Um die Änderungen des Remote zu fetchen, pullen Sie die Änderungen wie gewohnt mit Hilfe von Git-Befehlen. Angenommen, Ihre Änderungen befinden sich im master Branch, mergen oder rebasieren Sie Ihre Arbeit auf den origin/master Branch:

$ git pull --rebase origin

Ihre Arbeit zum Remote-Repository pushen

In Basaar ist das Konzept der Merge-Commits ebenfalls vorhanden, so dass es kein Problem geben wird, wenn Sie einen Merge Commit pushen. So können Sie an einem Branch arbeiten, Änderungen in master zusammenführen und Ihre Arbeit pushen. Dann erstellen Sie Ihre Branches, testen und committen Ihre Arbeit wie gewohnt. Schließlich pushen Sie Ihre Arbeit in das Bazaar-Repository:

$ git push origin master

Vorbehalte/Einschränkungen

Das Remote-Helfer-Framework von Git hat einige gültige Beschränkungen. Vor allem funktionieren diese Befehle nicht:

  • git push origin :branch-to-delete (Bazaar kann auf diese Weise keine Referenzen löschen)

  • git push origin alt:neu (es wird alt pushen)

  • git push --dry-run origin branch (es wird pushen)

Zusammenfassung

Die Modelle von Git und Bazaar sind sehr ähnlich, so dass es beim Arbeiten über die Grenzen keinen großen Aufwand erfordert. Solange Sie auf die Einschränkungen achten und sich immer bewusst sind, dass das Remote-Repository nicht nativ Git ist, werden Sie damit umgehen können.

Git und Perforce

Perforce ist ein sehr beliebtes Versionskontrollsystem in Unternehmungen. Es existiert seit 1995 und ist damit das älteste in diesem Kapitel behandelte System. Altersbedingt hat das Konzept aus heutiger Sicht einige Einschränkungen. Es geht davon aus, dass Sie immer mit einem einzigen zentralen Server verbunden sind und nur eine Version auf der lokalen Festplatte gespeichert ist. Sicherlich sind seine Funktionen und Einschränkungen gut für einige spezielle Probleme geeignet, aber es gibt viele Projekte mit Perforce, bei denen Git wirklich besser geeignet ist.

Es gibt zwei Möglichkeiten, wenn Sie die Verwendung von Perforce und Git kombinieren möchten. Als erstes stellen wir die „Git Fusion“ Bridge des Herstellers von Perforce vor, mit der Sie Teilbäume Ihres Perforce-Depots als Read-Write-Git-Repository freigeben können. Bei der zweiten handelt es sich um git-p4, eine client-seitige Bridge, mit der Sie Git als Perforce-Client verwenden können, ohne dass der Perforce-Server neu konfiguriert werden muss.

Git Fusion

Preforce bietet mit Git Fusion ein Produkt (verfügbar unter http://www.perforce.com/git-fusion), das einen Perforce-Server mit Git-Repositories auf der Serverseite synchronisiert.

Git Fusion einrichten

Für unsere Beispiele verwenden wir die einfachste Installationsmethode für Git Fusion, indem wir eine virtuelle Maschine herunterladen, auf der der Perforce-Daemon und Git Fusion laufen. Sie können das Image der virtuellen Maschine von http://www.perforce.com/downloads/Perforce/20-User herunterladen. Wenn der Download abgeschlossen ist, importieren Sie es in Ihre bevorzugte Virtualisierungssoftware (wir verwenden VirtualBox).

Beim ersten Start des Rechners werden Sie aufgefordert, das Passwort für drei Linux-Benutzer (root, perforce, git) festzulegen und einen Instanznamen anzugeben, mit dem Sie diese Installation von anderen im selben Netzwerk unterscheiden können. Wenn das alles abgeschlossen ist, werden Sie das sehen:

Boot-Bildschirm der virtuellen Maschine von Git Fusion
Figure 145. Boot-Bildschirm der virtuellen Maschine von Git Fusion

Sie sollten die hier angezeigte IP-Adresse notieren, wir werden sie später benutzen. Als nächstes erstellen wir einen Perforce-Benutzer. Wählen Sie unten die Option „Login“ und drücken Sie die Eingabetaste (oder verbinden Sie sich per SSH mit dem Computer) und melden Sie sich als root an. Verwenden Sie dann diese Befehle, um einen Benutzer anzulegen:

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

Der erste öffnet einen VI-Editor, um den Benutzer zu personalisieren, Sie können auch die Vorgaben übernehmen, indem Sie :wq eingeben und auf Enter drücken. Der zweite wird Sie auffordern, ein Passwort zweimal einzugeben. Das ist alles, was wir mit einem Shell-Prompt zu tun haben werden, also beenden Sie die Sitzung.

Als nächstes müssen Sie Git mitteilen, dass es keine SSL-Zertifikate überprüfen soll. Das Git Fusion-Image wird mit einem Zertifikat geliefert, aber es bezieht sich auf eine Domäne, die nicht mit der IP-Adresse Ihrer virtuellen Maschine übereinstimmt, weshalb Git die HTTPS-Verbindung abweisen würde. Wenn dies eine permanente Installation sein soll, lesen Sie das Handbuch von Perforce Git Fusion, um ein anderes Zertifikat zu installieren. Für unsere Beispielzwecke genügt diese Angabe:

$ export GIT_SSL_NO_VERIFY=true

Jetzt können wir testen, ob alles funktioniert.

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

Das Virtual-Machine-Image ist mit einem Beispielprojekt ausgestattet, das Sie klonen können. Hier klonen wir über HTTPS, mit dem Benutzer john, den wir oben erstellt haben. Git fragt nach Anmeldeinformationen für diese Verbindung, aber der Credential-Cache erlaubt es uns, diesen Schritt für alle nachfolgenden Anfragen zu überspringen.

Fusion Konfiguration

Sobald Sie Git Fusion installiert haben, sollten Sie die Konfiguration anpassen. Mit Ihrem favorisierten Perforce-Client ist das ganz einfach. Weisen Sie das Verzeichnis //.git-fusion auf dem Perforce-Server einfach Ihrem Arbeitsbereich zu. Die Dateistruktur sieht wie folgt aus:

$ tree
.
├── objects
│   ├── repos
│   │   └── [...]
│   └── trees
│       └── [...]
│
├── p4gf_config
├── repos
│   └── Talkhouse
│       └── p4gf_config
└── users
    └── p4gf_usermap

498 directories, 287 files

Das Verzeichnis objects wird intern von Git Fusion verwendet, um Perforce-Objekte auf Git abzubilden und umgekehrt, so dass Sie sich mit nichts darin herumschlagen müssen. In diesem Verzeichnis gibt es eine globale p4gf_config Datei sowie eine für jedes Repository – das sind die Konfigurationsdateien, die das Verhalten von Git Fusion bestimmen. Werfen wir einen Blick auf die Datei im Root:

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

Wir werden hier nicht auf die Bedeutung dieser Flags eingehen, aber bedenken Sie, dass es sich hierbei nur um eine INI-formatierte Textdatei handelt, ähnlich wie bei der Konfiguration mit Git. Diese Datei legt die globalen Optionen fest, die dann von repository-spezifischen Konfigurationsdateien wie repos/Talkhouse/p4gf_config überschrieben werden können. Wenn Sie diese Datei öffnen, sehen Sie einen Abschnitt [@repo] mit einigen Einstellungen, die sich von den globalen Standardeinstellungen unterscheiden. Sie werden auch Abschnitte sehen, die so aussehen:

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

Dabei handelt es sich um eine Zuordnung zwischen einem Perforce-Zweig und einem Git-Zweig. Der Abschnitt kann beliebig benannt werden, solange der Name eindeutig ist. Der git-branch-name ermöglicht Ihnen, einen Depot-Pfad, der unter Git umständlich wäre, in einen benutzerfreundlicheren Namen zu konvertieren. Die Anzeige-Einstellung steuert, wie Perforce-Dateien in das Git-Repository mit Hilfe der Standard-Syntax für das View-Mapping abgebildet werden. Es kann mehr als ein Mapping angegeben werden, wie in diesem Beispiel:

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

Wenn Ihr normales Workspace-Mapping Änderungen in der Struktur der Verzeichnisse enthält, können Sie das auf diese Weise mit einem Git-Repository replizieren.

Die letzte Datei, die wir hier behandeln, ist users/p4gf_usermap, die Perforce-Benutzer auf Git-Benutzer abbildet und die Sie möglicherweise nicht einmal benötigen. Bei der Konvertierung von eines Perforce Change-Sets in einen Git Commit sucht Git Fusion standardmäßig nach dem Perforce-Benutzer und verwendet die dort gespeicherte E-Mail-Adresse und den vollständigen Namen für das Autor/Committer-Feld in Git. Bei der umgekehrten Konvertierung wird standardmäßig der Perforce-Benutzer mit der E-Mail-Adresse gesucht, die im Autorenfeld des Git-Commits gespeichert ist, und das Änderungsset als dieser Benutzer übermittelt (mit entsprechenden Berechtigungen). In den meisten Fällen wird dieses Verhalten gut funktionieren, aber beachten Sie die folgende Mapping-Datei:

john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"

Jede Zeile hat das Format <user> <email> "<full name>" und erstellt eine einzige Benutzerzuordnung. Die beiden ersten Zeilen ordnen zwei verschiedene E-Mail-Adressen demselben Perforce-Benutzerkonto zu. Das ist praktisch, wenn Sie Git-Commits unter mehreren verschiedenen E-Mail-Adressen erstellt haben (oder E-Mail-Adressen ändern), diese aber dem gleichen Perforce-Benutzer zugeordnet werden sollen. Beim Erstellen eines Git-Commits aus einem Perforce Change-Set wird die erste Zeile, die dem Perforce-Benutzer entspricht, für die Angaben zur Git-Autorschaft verwendet.

Die letzten beiden Zeilen überdecken Bob und Joe’s tatsächliche Namen und E-Mail-Adressen aus den Git-Commits, die erstellt werden. Das ist sinnvoll, wenn Sie ein internes Projekt open-source-fähig machen wollen, aber Ihr Mitarbeiterverzeichnis nicht auf der ganzen Welt veröffentlichen wollen. Beachten Sie, dass die E-Mail-Adressen und vollständigen Namen eindeutig sein sollten, es sei denn, Sie möchten alle Git-Commits einem einzigen fiktiven Autor zuordnen.

Workflow (Arbeitsablauf)

Perforce Git Fusion ist eine bidirektionale Brücke zwischen der Perforce- und Git-Versionskontrolle. Betrachten wir die Arbeit von der Git-Seite aus. Wir gehen davon aus, dass wir im Projekt „Jam“ mit einer oben gezeigten Konfigurationsdatei abgebildet sind, die wir so klonen können:

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

Wenn Sie das zum ersten Mal vornehmen, kann es einige Zeit in Anspruch nehmen. Git Fusion konvertiert alle anwendbaren Changesets in der Perforce-Historie in Git-Commits. Das passiert lokal auf dem Server, ist also relativ schnell, aber wenn man einen langen Verlauf hat, kann es trotzdem einige Zeit dauern. Nachfolgende Fetches führen eine inkrementelle Konvertierung durch, so dass es sich schon eher wie die native Geschwindigkeit von Git anfühlt.

Wie Sie sehen können, sieht unser Repository genauso aus wie jedes andere Git-Repository, mit dem Sie arbeiten könnten. Es gibt drei Branches. Git hat einen lokalen master Branch erstellt, der origin/master trackt. Wir werden ein wenig arbeiten und ein paar neue Commits erstellen:

# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Wir haben zwei neue Commits. Nun lassen Sie uns überprüfen, ob jemand anderes auch daran gearbeitet hat:

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15  master     -> origin/master
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Anscheinend hat jemand das tatsächlich getan! Sie würden es aus dieser Sicht nicht erkennen, aber der 6afeb15 Commit wurde mit einem Perforce Client erstellt. Es sieht aus der Perspektive von Git aus wie ein weiterer Commit aus. Das ist genau der Punkt. Betrachten wir, wie der Perforce-Server mit einem Merge-Commit umgeht:

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b  master -> master

Git glaubt, dass es funktioniert hat. Werfen wir einen Blick auf den Verlauf der README Datei aus der Perspektive von Perforce, indem wir die Revisionsgraphenfunktion von p4v verwenden:

Perforce-Revisionsgraph resultierend aus dem Git-Push
Figure 146. Perforce-Revisionsgraph resultierend aus dem Git-Push

Wenn Sie diese Darstellung noch nie zuvor gesehen haben, mag sie verwirrend erscheinen, aber sie zeigt die gleichen Konzepte wie ein grafischer Viewer für die Git-Historie. Wir betrachten die Geschichte der README Datei, so dass der Verzeichnisbaum oben links nur diese Datei anzeigt, wenn sie in verschiedenen Branches auftaucht. Oben rechts haben wir ein Diagramm, das veranschaulicht, wie verschiedene Revisionen der Datei zusammenhängen, und die vergrößerte Ansicht dieses Diagramms befindet sich unten rechts. Der Rest der Darstellung wird der Detailansicht für die ausgewählte Revision (in diesem Fall 2) übergeben.

Auffallend ist, dass die Grafik genau so aussieht wie die in dem Verlauf von Git. Perforce hatte keinen namentlich benannten Branch, um die Commits 1 und 2 zu speichern. Also wurde ein „anonymer“ Branch im .git-fusion Verzeichnis erstellt, um sie zu speichern. Das gilt auch für benannte Git-Branches, die keinem benannten Perforce-Branch entsprechen (Sie können sie später über die Konfigurationsdatei einem Perforce-Branch zuordnen).

Das meiste geschieht hinter den Kulissen, und das Ergebnis ist, dass eine Person in der Gruppe Git und eine andere Perforce verwenden kann wobei keine von ihnen von der Entscheidung der anderen Person weiß.

Git-Fusion Zusammenfassung

Wenn Sie Zugang zu Ihrem Perforce-Server haben (oder erhalten können), ist Git Fusion eine gute Möglichkeit, Git und Perforce zum gegenseitigen Austausch zu bewegen. Es ist ein wenig Konfiguration erforderlich, aber die Lernkurve ist nicht sehr steil. Dieses ist einer der wenigen Abschnitte in diesem Kapitel, in denen Warnungen über die Verwendung von Gits voller Leistung nicht erscheinen. Das heißt nicht, dass Perforce mit allem, was Sie ihm zumuten, zufrieden sein wird – wenn Sie versuchen, eine bereits gepushte Historie neu zu schreiben, wird Git Fusion sie ablehnen – aber Git Fusion gibt sich sehr große Mühe, sich nativ anzufühlen. Sie können sogar Git-Submodule verwenden (obwohl sie für Perforce-Anwender seltsam aussehen werden) und Branches verschmelzen (das wird als Integration auf der Perforce-Seite erfasst).

Wenn Sie den Administrator Ihres Servers nicht davon überzeugen können, Git Fusion einzurichten, gibt es noch eine weitere Möglichkeit, diese Tools gemeinsam zu nutzen.

Git-p4

Git-p4 ist eine bidirektionale Brücke zwischen Git und Perforce. Es läuft vollständig in Ihrem Git-Repository, so dass Sie keinen Zugriff auf den Perforce-Server benötigen (mit Ausnahme der Benutzer-Anmeldeinformationen). Git-p4 ist nicht so flexibel und keine Komplettlösung wie Git Fusion, aber es ermöglicht Ihnen, das meiste von dem zu tun, was Sie tun möchten, ohne die Serverumgebung zu beeinträchtigen.

Note

Sie brauchen das p4 Tool an einer beliebigen Stelle in Ihrem PATH, um mit git-p4 zu arbeiten. Zur Zeit ist es unter http://www.perforce.com/downloads/Perforce/20-User frei verfügbar.

Einrichtung

So werden wir beispielsweise den Perforce-Server wie oben gezeigt von der Git Fusion OVA (Open-Virtualization-Archive-Datei) aus verwenden, aber wir umgehen den Git Fusion-Server und gehen direkt zur Perforce-Versionskontrolle.

Um den von git-p4 benötigten Befehlszeilen-Client p4 verwenden zu können, müssen Sie ein paar Umgebungsvariablen setzen:

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
Erste Schritte

Wie bei allem in Git ist das Klonen der erste Befehl:

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master

Dadurch entsteht ein Git-technisch „flacher“ Klon. Nur die allerletzte Perforce-Revision wird in Git importiert. Denken Sie daran, Perforce ist nicht dazu gedacht, jedem Benutzer alle Revisionen zu übergeben. Das ist ausreichend, um Git als Perforce-Client zu verwenden, ist aber für andere Zwecke ungeeignet.

Sobald es abgeschlossen ist, haben wir ein voll funktionsfähiges Git-Repository:

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from the state at revision #head

Beachten Sie, dass es für den Perforce-Server einen „p4“ Remote gibt, aber alles andere sieht aus wie ein Standardklon. Eigentlich ist das etwas irreführend; es gibt dort nicht wirklich einen Remote.

$ git remote -v

In diesem Repository existieren überhaupt keine Remotes. Git-p4 hat einige Referenzen erstellt, um den Zustand des Servers darzustellen. Für git log sehen sie aus wie Remote-Referenzen, aber sie werden nicht von Git selbst verwaltet. Man kann nicht zu ihnen pushen.

Workflow

Okay, lassen Sie uns ein paar Arbeiten erledigen. Nehmen wir an, Sie haben einige Fortschritte bei einem sehr wichtigen Feature gemacht und sind bereit, es dem Rest Ihres Teams zu zeigen.

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

Wir haben zwei neue Commits erstellt, die wir an den Perforce-Server übermitteln können. Schauen wir mal, ob heute noch jemand anderes gearbeitet hat:

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)
$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Es sieht so aus, als ob die Commits vorhanden wären, die sich in master und p4/master aufgeteilt hätten. Das Branching-System von Perforce ist nicht wie das von Git, so dass das Übertragen von Merge-Commits keinen Sinn macht. Git-p4 empfiehlt, dass Sie Ihre Commits rebasieren und bietet sogar eine Kurzform dafür:

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Vermutlich können Sie das an der Ausgabe erkennen, denn git git p4 rebase ist eine Abkürzung für git p4 sync gefolgt von git rebase p4/master. Das ist zwar noch ein bisschen cleverer, besonders bei der Arbeit mit mehreren Branches, ist aber eine gute Annäherung.

Jetzt ist unser Verlauf wieder linear und bereit, unsere Änderungen in Perforce wieder einzureichen. Der Befehl git p4 submit versucht, für jeden Git-Commit zwischen p4/master und master, eine neue Perforce-Revision zu erstellen. Beim Ausführen werden wir in unseren bevorzugten Editor weitergeleitet, der Inhalt der Datei sieht dann ungefähr so aus:

# A Perforce Change Specification.
#
#  Change:      The change number. 'new' on a new changelist.
#  Date:        The date this specification was last modified.
#  Client:      The client on which the changelist was created.  Read-only.
#  User:        The user who created the changelist.
#  Status:      Either 'pending' or 'submitted'. Read-only.
#  Type:        Either 'public' or 'restricted'. Default is 'public'.
#  Description: Comments about the changelist.  Required.
#  Jobs:        What opened jobs are to be closed by this changelist.
#               You may delete jobs from this list.  (New changelists only.)
#  Files:       What opened files from the default changelist are to be added
#               to this changelist.  You may delete files from this list.
#               (New changelists only.)

Change:  new

Client:  john_bens-mbp_8487

User: john

Status:  new

Description:
   Update link

Files:
   //depot/www/live/index.html   # edit


######## git author ben@straub.cc does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html  2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html   2014-08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

Das ist im Wesentlichen derselbe Inhalt, den Sie bei der Ausführung von p4 submit sehen würden. Ausgenommen sind die Dinge am Ende, die git-p4 sinnvollerweise mit aufgenommen hat. Git-p4 versucht, Ihre Git- und Perforce-Einstellungen individuell zu berücksichtigen, wenn es einen Namen für ein Commit- oder Changeset angeben muss. In einigen Fällen wollen Sie ihn jedoch überschreiben. Wenn beispielsweise der Git-Commit, den Sie importieren, von einem Mitwirkenden geschrieben wurde, der kein Perforce-Benutzerkonto hat, können Sie trotzdem wollen, dass sich das daraus ergebende Changeset so aussieht, als hätte er es geschrieben (und nicht Sie).

Git-p4 hat die Nachricht aus dem Git-Commit zweckmäßigerweise als Inhalt für dieses Perforce Changeset importiert. Alles, was wir tun müssen, ist zweimal speichern (einmal für jeden Commit) und beenden. Die resultierende Shell-Ausgabe sieht in etwa so aus:

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Das Ergebnis ist, als ob wir gerade ein git push gemacht hätten, was die nächstliegende Analogie zu dem ist, was tatsächlich passiert ist.

Beachten Sie, dass während dieses Prozesses jeder Git-Commit in einen Perforce Changeset umgewandelt wird. Wenn Sie ihn in einen einzelnen Changeset zusammenfassen möchten, können Sie dies mit einem interaktiven Rebase tun, bevor Sie git p4 submit ausführen. Bitte bedenken Sie auch, dass die SHA-1-Hashes aller Commits, die als Changesets eingereicht wurden, sich geändert haben. Der Grund dafür ist, dass git-p4 am Ende jedes Commits, den es konvertiert, eine Zeile hinzufügt:

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title

    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

Was passiert, wenn Sie versuchen, einen Merge-Commit einzubinden? Versuchen wir es einmal. Das ist die Situation, in die wir uns selbst gebracht haben:

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
*   1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Der Verlauf von Git und Perforce weicht nach 775a46f voneinander ab. Die Git-Seite hat zwei Commits, dann einen Merge-Commit mit dem Perforce Head, dann einen weiteren Commit. Wir werden versuchen, diese zusätzlich zu einem einzelnen Changeset auf der Perforce-Seite beizufügen. Schauen wir mal, was passieren würde, wenn wir versuchen würden, ihn jetzt einzubringen:

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

Das -n Flag ist die Abkürzung für --dry-run, mit dem versucht wird, zu beschreiben, was passieren würde, wenn der submit-Befehl wirklich ausgeführt würde. Hier sieht es so aus, als hätten wir drei Perforce-Änderungssets erstellt, die den drei Nicht-Merge-Commits entsprechen, die noch nicht auf dem Perforce-Server vorhanden sind. Das hört sich nach genau dem an, was wir wollen, mal sehen, wie es ausfällt:

$ git p4 submit
[…]
$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Unsere Historie wurde linearisiert, genau so, als hätten wir vor dem Einreichen rebasiert, was auch tatsächlich passiert ist. So können Sie auf der Git-Seite selbst Branches erstellen, darauf arbeiten, verwerfen und mergen, ohne befürchten zu müssen, dass Ihr Verlauf sich mit Perforce nicht mehr verträgt. Wenn Sie einen Rebase durchführen können, ist es auch möglich, ihn zu einem Perforce-Server beizusteuern.

Branching

Wenn Ihr Perforce-Projekt mehrere Branches hat, haben Sie Glück gehabt. git-p4 kann damit so umgehen, dass es sich wie Git anfühlt. Angenommen, Ihr Perforce-Depot ist so aufgebaut:

//depot
  └── project
      ├── main
      └── dev

Nehmen wir weiter an, Sie haben einen dev Branch, der eine View-Spezifikation hat, die so aussieht:

//depot/project/main/... //depot/project/dev/...

Git-p4 kann diese Situation automatisch erkennen und das Richtige tun:

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev

    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev
$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

Beachten Sie den „@all“ Spezifikator im Depotpfad, der git-p4 anweist, nicht nur das neueste Changeset für diesen Teilbaum, sondern alle Changesets, die jemals diese Pfade beeinflusst haben, zu klonen. Das ist näher an Gits Konzept des Klonens, aber wenn Sie an einem Projekt mit einer langen Historie arbeiten, könnte es einige Zeit dauern den Klon zu kopieren.

Das --detect-branches Flag weist git-p4 an, die Branch-Spezifikationen von Perforce zu verwenden, um die Branches den Git refs zuzuordnen. Sind diese Zuordnungen nicht auf dem Perforce-Server vorhanden (was eine absolut zulässige Methode zur Verwendung von Perforce ist), können Sie git-p4 mitteilen, wie die Zuordnung der Branches zu sein hat. Sie erhalten dann das gleiche Ergebnis:

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

Das Setzen der Konfigurationsvariablen git-p4.branchList auf main:dev teilt git-p4 mit, dass „main“ und „dev“ beides Branches sind, wobei der zweite ein untergeordnetes Element des ersten ist.

Machen wir jetzt neben git checkout -b dev p4/project/dev noch einige Commits machen, dann ist git-p4 klug genug, um den richtigen Branch anzusprechen, wenn wir anschließend git p4 submit ausführen. Leider kann git-p4 keine flachen Klone mit mehreren Branches mischen. Wenn Sie ein großes Projekt haben und an mehr als einem Branch arbeiten wollen, müssen Sie für jeden Branch, den Sie einreichen möchten, ihn einmal mit git p4 clone erstellen.

Für die Erstellung oder Integration von Branches müssen Sie einen Perforce-Client verwenden. Git-p4 kann nur synchronisieren und an bestehende Branches senden und es kann nur einen linearen Changeset auf einmal durchführen. Bei dem Mergen zweier Branches in Git und dem Versuch, das neue Changeset einzureichen, wird nur ein Bündel von Dateiänderungen aufgezeichnet. Die Metadaten über die an der Integration beteiligten Branches gehen dabei verloren.

Git und Perforce, Zusammenfassung

Git-p4 ermöglicht die Verwendung eines Git-Workflows mit einem Perforce-Server und ist darin ziemlich gut. Es ist jedoch wichtig, sich daran zu erinnern, dass Perforce für den Quellcode verantwortlich ist und Sie Git nur für die lokale Arbeit verwenden. Seien Sie einfach sehr vorsichtig bei der Weitergabe von Git-Commits. Wenn Sie einen Remote haben, den auch andere Benutzer verwenden, pushen Sie keine Commits, die zuvor noch nicht an den Perforce-Server übertragen wurden.

Möchten Sie die Verwendung von Perforce und Git als Clients für die Versionskontrolle frei kombinieren, dann müssen Sie den Server-Administrator davon überzeugen, Git zu installieren. Git Fusion macht die Verwendung von Git zu einem erstklassigen Versionskontroll-Client für einen Perforce-Server.

Git und TFS

Git wird bei Windows-Entwicklern immer populärer. Falls Sie Code unter Windows schreiben, ist es durchaus möglich, dass Sie den Team Foundation Server (TFS) von Microsoft verwenden. TFS ist eine Collaboration-Suite, die Fehler- und Workitem-Tracking, Prozess-Support für Scrum (und andere), Code-Review und Versionskontrolle umfasst. Es gibt da ein wenig Verwirrung im Vorfeld: TFS ist der Server, der die Kontrolle des Quellcodes sowohl mit Git als auch mit einem eigenen, benutzerdefinierten VCS unterstützt, das Microsoft TFVC (Team Foundation Version Control) genannt hat. Die Git-Anbindung ist ein neueres Feature für TFS (ausgeliefert mit Version 2013). Daher beziehen sich alle Tools, die älter sind, auf den Versionskontrollteil als „TFS“, auch wenn sie hauptsächlich mit TFVC arbeiten.

Wenn Sie sich in einem Team befinden, das TFVC verwendet, aber lieber Git als Ihren Versionskontroll-Client verwenden möchten, dann gibt es dafür ein Projekt für Sie.

Welches Tool?

Tatsache: Es gibt zwei: git-tf und git-tfs.

Git-tfs (finden Sie unter https://github.com/git-tfs/git-tfs) ist ein .NET-Projekt und läuft (derzeit) nur unter Windows. Um mit Git-Repositories zu arbeiten, verwendet es die .NET-Bindings für libgit2, eine library-orientierte Umsetzung von Git, die hochperformant ist und viel Flexibilität in den Tiefen eines Git-Repositories ermöglicht. Libgit2 ist keine vollständige Implementierung von Git, weshalb git-tfs den Kommandozeilen-Git-Client für einige Operationen aufrufen wird. Es gibt von daher keine expliziten Einschränkungen, was mit den Git-Repositories gemacht werden kann. Die Unterstützung von TFVC-Funktionen ist sehr ausgereift, da es Visual-Studio-Assemblys für Serveroperationen verwendet. Das erfordert den Zugriff auf diese Assemblys und bedeutet, dass Sie eine aktuelle Version von Visual Studio (jede Edition seit Version 2010, einschließlich Express seit Version 2012) oder das Visual Studio SDK installieren müssen.

Git-tf ist End-of-Life (EOL), es werden keine Aktualisierungen mehr geben.
Es wird auch nicht mehr von Microsoft unterstützt.

Git-tf (erreichbar unter https://gittf.codeplex.com) ist ein Java-Projekt und läuft als solches auf jedem Computer mit einer Java-Laufzeitumgebung. Die Schnittstelle zu Git-Repositories erfolgt über JGit (eine Git-Implementierung von JVM (Java Virtual Machine)), was bedeutet, dass es praktisch keine Einschränkungen in Bezug auf die Git-Funktionen gibt. Die Unterstützung für TFVC ist jedoch im Vergleich zu git-tfs begrenzt – es werden beispielsweise keine Branches unterstützt.

So hat jedes Tool seine Vor- und Nachteile. Es gibt viele Situationen, eine der beiden gegenüber der anderen zu bevorzugen. Wir werden die prinzipielle Verwendung der beiden in diesem Buch behandeln.

Note

Sie benötigen Zugriff auf ein TFVC-basiertes Repository, um diesen Anweisungen zu folgen. In der „freien Wildbahn“ sind sie nicht so zahlreich wie Git- oder Subversion-Repositorys, so dass Sie möglicherweise selbst eines erstellen müssen. Codeplex (https://archive.codeplex.com/) oder Visual Studio Online (https://visualstudio.microsoft.com) sind beide dafür eine gute Wahl.

Erste Schritte: git-tf

Als Erstes werden Sie, wie bei jedem Git-Projekt, klonen. So sieht das mit git-tf aus:

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main project_git

Das erste Argument ist die URL einer TFVC-Kollektion, das zweite folgt der Form $/project/branch und das dritte ist der Pfad zum lokalen Git-Repository, das erstellt werden soll (letzteres ist optional). Git-tf kann nur mit einem einzigen Branch gleichzeitig arbeiten. Falls Sie Checkins auf einem anderen TFVC Branch durchführen wollen, müssen Sie einen neuen Clone aus diesem Branch erstellen.

Dadurch entsteht ein voll funktionsfähiges Git-Repository:

$ cd project_git
$ git log --all --oneline --decorate
512e75a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Checkin message

Das nennt man einen flachen Klon, d.h. es wurde nur der letzte Changeset heruntergeladen. TFVC ist nicht dafür konzipiert, dass jeder Client eine vollständige Kopie der Historie hat, so dass git-tf standardmäßig nur die neueste Version erhält, was viel schneller ist.

Wenn Sie etwas Zeit haben, lohnt es sich vermutlich, die gesamte Projekt-Historie mit der Option --deep zu kopieren:

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main \
  project_git --deep
Username: domain\user
Password:
Connecting to TFS...
Cloning $/myproject into /tmp/project_git: 100%, done.
Cloned 4 changesets. Cloned last changeset 35190 as d44b17a
$ cd project_git
$ git log --all --oneline --decorate
d44b17a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Goodbye
126aa7b (tag: TFS_C35189)
8f77431 (tag: TFS_C35178) FIRST
0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
        Team Project Creation Wizard

Beachten Sie die Tags mit Bezeichnungen wie TFS_C35189. Diese Eigenschaft hilft Ihnen zu erkennen, welche Git-Commits mit TFVC Change-Sets verknüpft sind. Das ist eine elegante Art, diese anzuzeigen, da Sie mit einem einfachen Log-Befehl sehen können, welcher Ihrer Commits mit einem Snapshot verbunden ist, der auch in TFVC existiert. Sie sind nicht notwendig (Sie können sie mit git config git-tf.tag false deaktivieren) – git-tf behält die echten Commit-Changeset-Mappings in der .git/git-tf Datei.

Erste Schritte: git-tfs

Das Klonen mit Git-tfs verhält sich etwas anders. Achten Sie darauf:

PS> git tfs clone --with-branches \
    https://username.visualstudio.com/DefaultCollection \
    $/project/Trunk project_git
Initialized empty Git repository in C:/Users/ben/project_git/.git/
C15 = b75da1aba1ffb359d00e85c52acb261e4586b0c9
C16 = c403405f4989d73a2c3c119e79021cb2104ce44a
Tfs branches found:
- $/tfvc-test/featureA
The name of the local branch will be : featureA
C17 = d202b53f67bde32171d5078968c644e562f1c439
C18 = 44cd729d8df868a8be20438fdeeefb961958b674

Beachten Sie das Flag --with-branches. Git-tfs ist in der Lage, TFVC-Branches auf Git-Branches abzubilden, und dieses Flag sagt ihm, dass es für jeden TFVC-Branch einen lokalen Git-Branch einrichten soll. Dieses Verfahren wird dringend empfohlen, wenn Sie jemals in TFS verzweigt oder gemergt haben, es wird aber nicht mit einem Server funktionieren, der älter als TFS 2010 ist – vor dieser Version waren „Branches“ nur Verzeichnisse, so dass git-tfs sie nicht von normalen Ordnern unterscheiden konnte.

Werfen wir einen Blick auf das entstandene Git-Repository:

PS> git log --oneline --graph --decorate --all
* 44cd729 (tfs/featureA, featureA) Goodbye
* d202b53 Branched from $/tfvc-test/Trunk
* c403405 (HEAD, tfs/default, master) Hello
* b75da1a New project
PS> git log -1
commit c403405f4989d73a2c3c119e79021cb2104ce44a
Author: Ben Straub <ben@straub.cc>
Date:   Fri Aug 1 03:41:59 2014 +0000

    Hello

    git-tfs-id: [https://username.visualstudio.com/DefaultCollection]$/myproject/Trunk;C16

Es gibt zwei lokale Branches, master und featureA, die den Anfangspunkt des Klons darstellen (Trunk in TFVC) und einen untergeordneten Branch (featureA in TFVC). Sie können auch sehen, dass die tfs „remote“ auch ein paar Referenzen hat: default und featureA, die TFVC-Branches darstellen. Git-tfs ordnet den Branch, von dem aus Sie geklont haben, tfs/default zu, während andere ihre eigenen Namen erhalten.

Eine weitere zu beachtende Funktion betrifft die git-tfs-id: Zeilen in den Commit-Beschreibungen. Anstelle von Tags verwendet git-tfs diese Marker, um TFVC-Change-Sets mit Git-Commits zu verknüpfen. Das hat den Effekt, dass Ihre Git-Commits einen unterschiedlichen SHA-1-Hash haben, vor und nach dem sie an TFVC übertragen wurden.

Der Git-tf[s] Workflow

Note

Unabhängig davon, welches Tool Sie verwenden, sollten Sie ein paar Git-Konfigurationswerte festlegen, um Probleme zu vermeiden.

$ git config set --local core.ignorecase=true
$ git config set --local core.autocrlf=false

Als nächstes wollen Sie natürlich an dem Projekt arbeiten. TFVC und TFS verfügen über mehrere Funktionen, die Ihren Workflow noch komplexer machen können:

  1. Themen-Branches, die nicht in TFVC dargestellt werden, erhöhen die Komplexität. Das hängt mit den sehr unterschiedlichen Möglichkeiten zusammen, wie TFVC und Git Branches darstellen.

  2. Beachten Sie, dass TFVC es Benutzern erlaubt, Dateien vom Server „auszuchecken“ und sie so zu sperren, dass niemand sonst sie bearbeiten kann. Das wird Sie natürlich nicht davon abhalten, sie in Ihrem lokalen Repository zu bearbeiten, aber es könnte Ihnen im Weg stehen, wenn es darum geht, Ihre Änderungen auf den TFVC-Server zu übertragen.

  3. TFS hat mit dem Konzept der „eingezäunten“ (engl. gated) Checkins, bei denen ein TFS-Build-Testzyklus erfolgreich abgeschlossen werden muss, bevor das Einchecken erlaubt wird. Dazu dient die Funktion „shelve“ in TFVC, auf die wir hier nicht näher eingehen werden. Sie können das mit git-tf manuell vortäuschen. Dabei stellt git-tfs den Befehl checkintool zur Verfügung, der eine Einzäunung (engl. gate) erkennt.

Kurz gesagt, was wir hier behandeln werden, ist der logische Weg, der die meisten dieser Probleme umgeht oder vermeidet.

git-tf Workflow:

Nehmen wir an, Sie haben einige Arbeiten erledigt, ein paar Git-Commits auf master gemacht und Sie sind fertig, Ihre Ergebnisse auf den TFVC-Server zu übertragen. Das ist unser Git-Repository:

$ git log --oneline --graph --decorate --all
* 4178a82 (HEAD, master) update code
* 9df2ae3 update readme
* d44b17a (tag: TFS_C35190, origin_tfs/tfs) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Wir wollen einen Snapshot machen, der sich im Commit 4178a82 befindet und ihn auf den TFVC-Server pushen. Das Wichtigste zuerst: Mal sehen, ob einer unserer Teamkollegen seit unserer letzten Verbindung etwas getan hat:

$ git tf fetch
Username: domain\user
Password:
Connecting to TFS...
Fetching $/myproject at latest changeset: 100%, done.
Downloaded changeset 35320 as commit 8ef06a8. Updated FETCH_HEAD.
$ git log --oneline --graph --decorate --all
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
| * 4178a82 (HEAD, master) update code
| * 9df2ae3 update readme
|/
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Es sieht so aus, als würde auch noch jemand anderes daran arbeiten. Jetzt haben wir einen abweichenden Verlauf. Hier glänzt Git: wir haben zwei Möglichkeiten, wie es weitergehen kann:

  1. Einen Merge-Commit zu machen fühlt sich als Git-Benutzer natürlich an (schließlich ist es das, was git pull macht) und git-tf kann das mit einem einfachen git tf pull für Sie tun. Seien Sie sich jedoch bewusst, dass TFVC nicht auf diese Weise denkt. Wenn Sie die Merge-Commits pushen, wird Ihr Verlauf auf beiden Seiten unterschiedlich aussehen, was verwirrend sein kann. Aber wenn Sie alle Ihre Änderungen in einem Changeset übertragen, ist das wahrscheinlich der einfachste Weg.

  2. Ein Rebase bewirkt, dass unsere Commit-Historie linear wird, was bedeutet, dass wir die Möglichkeit haben, jedes unserer Git-Commits in ein TFVC-Changeset zu konvertieren. Da dadurch die meisten Optionen offen bleiben, empfehlen wir Ihnen diese Vorgehensweise. Mit Hilfe von git-tf wird es Ihnen mit dem Befehl git tf pull - rebase sogar leicht gemacht.

Sie haben die Wahl. Für dieses Beispiel werden wir ein Rebase durchführen:

$ git rebase FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Nun können wir einen „checkin“ auf den TFVC-Server durchführen. Git-tf bietet Ihnen die Möglichkeit, ein einziges Changeset durchzuführen, das alle Veränderungen seit der letzten Änderung repräsentiert (--shallow, die Standardeinstellung) oder ein neues Changeset für jeden Git-Commit zu erstellen (--deep). Für dieses Beispiel werden wir nur ein einziges Changeset erzeugen:

$ git tf checkin -m 'Updating readme and code'
Username: domain\user
Password:
Connecting to TFS...
Checking in to $/myproject: 100%, done.
Checked commit 5a0e25e in as changeset 35348
$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, tag: TFS_C35348, origin_tfs/tfs, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Es gibt ein neues TFS_C35348 Tag, das anzeigt, dass TFVC genau den gleichen Snapshot speichert wie der 5a0e25e Commit. Wichtig zu beachten ist, dass nicht jeder Git-Commit ein genaues Gegenstück in TFVC haben muss. Der 6eb3eb5 Commit, zum Beispiel, existiert nirgendwo auf dem Server.

So sieht der wichtigste Workflow aus. Es gibt noch ein paar andere Aspekte, die Sie im Auge behalten sollten:

  • Es gibt kein Branching. Git-tf kann Git-Repositorys jeweils nur aus einem einzigen TFVC-Branch erstellen.

  • Benutzen Sie zur Zusammenarbeit TFVC oder Git, aber nicht beides. Unterschiedliche git-tf-Klone desselben TFVC-Repositorys können unterschiedliche Commit SHA-1-Hashes haben, was zu Kopfschmerzen ohne Ende führen wird.

  • Wenn der Workflow Ihres Teams die Zusammenarbeit in Git und die regelmäßige Synchronisierung mit TFVC umfasst, verbinden Sie sich mit TFVC ausschließlich mit einem der Git-Repositories.

git-tfs Workflow:

Lassen Sie uns das dasselbe Szenario mit git-tfs durchgehen. Hier sind die neuen Commits, die wir für den master Branch in unserem Git-Repository vorgenommen haben:

PS> git log --oneline --graph --all --decorate
* c3bd3ae (HEAD, master) update code
* d85e5a2 update readme
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 (tfs/default) Hello
* b75da1a New project

Mal sehen, ob noch jemand anderes gearbeitet hat, während wir abgeschnitten waren:

PS> git tfs fetch
C19 = aea74a0313de0a391940c999e51c5c15c381d91d
PS> git log --all --oneline --graph --decorate
* aea74a0 (tfs/default) update documentation
| * c3bd3ae (HEAD, master) update code
| * d85e5a2 update readme
|/
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Ja, es stellt sich heraus, dass ein Co-Autor ein neues TFVC Change-Set hinzugefügt hat, das als neuer aea74a0 Commit erscheint und der tfs/default Remote Branch verändert wurde.

Wie bei git-tf haben wir zwei fundamentale Optionen, um diesen unterschiedlichen Verlauf aufzulösen:

  1. Ein Rebase, um den linearen Verlauf zu erhalten.

  2. Ein Merge, um zu behalten, was wirklich passiert ist.

In diesem Fall werden wir einen „deep“ checkin durchführen. Dabei wird jeder Git Commit zu einem TFVC Changeset, also sollten wir einen Rebase durchführen.

PS> git rebase tfs/default
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
PS> git log --all --oneline --graph --decorate
* 10a75ac (HEAD, master) update code
* 5cec4ab update readme
* aea74a0 (tfs/default) update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Nun sind wir soweit, unseren Beitrag zu abzuschließen, indem wir unseren Code in den TFVC-Server einchecken. Wir werden hier den Befehl rcheckin verwenden, um ein TFVC Change-Set für jeden Git-Commit im Pfad von HEAD zum ersten gefundenen tfs Remote-Branch zu erstellen (der checkin Befehl würde nur einen Changeset erzeugen, ähnlich wie beim Squashen von Git-Commits).

PS> git tfs rcheckin
Working with tfs remote: default
Fetching changes from TFS to minimize possibility of late conflict...
Starting checkin of 5cec4ab4 'update readme'
 add README.md
C20 = 71a5ddce274c19f8fdc322b4f165d93d89121017
Done with 5cec4ab4b213c354341f66c80cd650ab98dcf1ed, rebasing tail onto new TFS-commit...
Rebase done successfully.
Starting checkin of b1bf0f99 'update code'
 edit .git\tfs\default\workspace\ConsoleApplication1/ConsoleApplication1/Program.cs
C21 = ff04e7c35dfbe6a8f94e782bf5e0031cee8d103b
Done with b1bf0f9977b2d48bad611ed4a03d3738df05ea5d, rebasing tail onto new TFS-commit...
Rebase done successfully.
No more to rcheckin.
PS> git log --all --oneline --graph --decorate
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Bitte beachten Sie, dass git-tfs nach jedem erfolgreichen checkin auf dem TFVC-Server die verbleibende Arbeit auf das zurücksetzt, was gerade getan wurde. Das liegt daran, dass es das Feld git-tfs-id am Ende der Commit-Beschreibungen hinzufügt, was die SHA-1-Hashes ändert. Das ist genau so, wie es entworfen wurde. Es gibt keinen Grund sich Sorgen zu machen, aber Sie sollten sich bewusst sein, dass es geschieht, vor allem wenn Sie Git-Commits mit anderen teilen.

TFS verfügt über viele Funktionen, die sich in sein Versionskontrollsystem integrieren lassen, wie z.B. Workitems, benannte Prüfer, Gated Checkins usw. Es kann umständlich sein, diese Funktionen nur mit einem Kommandozeilen-Tool zu verwenden, aber glücklicherweise gibt es mit git-tfs ein grafisches Checkin-Tool, das Sie sehr einfach starten können:

PS> git tfs checkintool
PS> git tfs ct

Es sieht ungefähr so aus:

Das git-tfs Checkin-Tool
Figure 147. Das git-tfs Checkin-Tool

Das kommt den TFS-Benutzern vertraut vor, da es sich um den gleichen Dialog handelt, der aus Visual Studio heraus gestartet wird.

Mit Git-tfs können Sie auch TFVC-Branches aus Ihrem Git-Repository steuern. Lassen Sie uns zum Beispiel einen Branch erstellen:

PS> git tfs branch $/tfvc-test/featureBee
The name of the local branch will be : featureBee
C26 = 1d54865c397608c004a2cadce7296f5edc22a7e5
PS> git log --oneline --graph --decorate --all
* 1d54865 (tfs/featureBee) Creation branch $/myproject/featureBee
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Das Erstellen eines Branch in TFVC bedeutet, ein Change-Set hinzuzufügen, in dem dieser Branch bereits existiert. Dieser Branch wird als Git-Commit projiziert. Beachten Sie auch, dass git-tfs den tfs/featureBee Remote-Branch erstellt hat, aber dass HEAD immer noch auf master zeigt. Wenn Sie an dem neu erstellten Zweig arbeiten möchten, sollten Sie Ihre neuen Commits auf dem 1d54865 Commit basieren, möglicherweise indem Sie einen Topic Branch aus diesem Commit erstellen.

Git und TFS, Zusammenfassung

Git-tf und Git-tfs sind beides großartige Werkzeuge für die Verbindung zu einem TFVC-Server. Sie ermöglichen es Ihnen, die Leistungsfähigkeit von Git lokal zu nutzen, zu vermeiden, dass Sie ständig auf den zentralen TFVC-Server zurückkehren müssen und Ihren Alltag als Entwickler viel einfacher zu gestalten, ohne Ihr gesamtes Team zur Migration nach Git zu zwingen. Wenn Sie unter Windows arbeiten (was wahrscheinlich ist, wenn Ihr Team TFS verwendet), werden Sie vermutlich git-tfs verwenden wollen, da der Funktionsumfang vollständiger ist, aber wenn Sie auf einer anderen Plattform arbeiten, werden Sie git-tf verwenden, das etwas eingeschränkter ist. Wie bei den meisten Tools in diesem Kapitel sollten Sie eines dieser Versionskontrollsysteme wählen, um eindeutig zu sein, und das andere in einer untergeordneten Form verwenden – entweder Git oder TFVC sollte das Zentrum der Zusammenarbeit sein, aber nicht beide.