Kapitel ▾ 2. Auflage

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 Kontakt kommen, sofort auf Git umstellen. Manchmal stecken Sie in einem Projekt fest, das ein anderes VCS verwendet, und wünschen sich, es wäre Git. Wir werden uns im ersten Teil dieses Kapitels mit Möglichkeiten beschäftigen, Git als Client zu nutzen, wenn das Projekt, an dem Sie arbeiten, auf einem anderen System gehostet wird.

Zu einem bestimmten Zeitpunkt möchten Sie Ihr bestehendes Projekt möglicherweise in Git konvertieren. Der zweite Teil dieses Kapitels befasst sich damit, wie Sie Ihr Projekt aus mehreren spezifischen Systemen in Git migrieren können, sowie mit einer Methode, die funktioniert, wenn kein vorgefertigtes Importtool vorhanden ist.

Git als Client

Git bietet eine so gute Erfahrung für Entwickler, dass viele Leute herausgefunden haben, wie man es auf ihrer Workstation verwendet, selbst wenn der Rest ihres Teams ein völlig anderes VCS verwendet. Es gibt eine Reihe dieser Adapter, die als „Brücken“ bezeichnet werden. Hier behandeln wir diejenigen, denen Sie am wahrscheinlichsten in freier Wildbahn begegnen werden.

Git und Subversion

Ein großer Teil der Open-Source-Entwicklungsprojekte und eine gute Anzahl von Unternehmensprojekten verwenden Subversion zur Verwaltung ihres Quellcodes. Es existiert seit mehr als einem Jahrzehnt und war die meiste Zeit davon die *De-facto*-VCS-Wahl für Open-Source-Projekte. Es ist auch in vielerlei Hinsicht sehr ähnlich zu CVS, das vor Subversion die größte Rolle in der Welt der Quellcodeverwaltung spielte.

Eines der großartigen Features von Git ist eine bidirektionale Brücke zu Subversion namens git svn. Dieses Tool ermöglicht es Ihnen, Git als gültigen Client für einen Subversion-Server zu verwenden, sodass Sie alle lokalen Features von Git nutzen und dann auf einen Subversion-Server pushen können, als ob Sie Subversion lokal verwenden würden. Das bedeutet, Sie können lokales Branching und Merging durchführen, die Staging-Area nutzen, Rebase und Cherry-Picking verwenden und so weiter, während Ihre Mitarbeiter auf ihre dunkle und archaische Weise weiterarbeiten. Es ist ein guter Weg, um Git heimlich in die Unternehmensumgebung einzuschleusen und Ihren Kollegen zu helfen, effizienter zu werden, während Sie Lobbyarbeit für die Umstellung der Infrastruktur auf vollständige Git-Unterstützung betreiben. Die Subversion-Brücke ist die Einstiegsdroge in die DVCS-Welt.

git svn

Der Basisbefehl in Git für alle Subversion-Bridging-Befehle ist git svn. Er hat ziemlich viele Befehle, daher zeigen wir die gebräuchlichsten, während wir ein paar einfache Workflows durchgehen.

Es ist wichtig zu beachten, dass Sie bei der Verwendung von git svn mit Subversion interagieren, einem System, das sich sehr von Git unterscheidet. Obwohl Sie lokales Branching und Merging *durchführen können*, ist es im Allgemeinen am besten, Ihren Verlauf so linear wie möglich zu halten, indem Sie Ihre Arbeit rebasen und vermeiden, Dinge wie die gleichzeitige Interaktion mit einem entfernten Git-Repository durchzuführen.

Schreiben Sie Ihren Verlauf nicht um und versuchen Sie erneut zu pushen, und pushen Sie nicht gleichzeitig in ein paralleles Git-Repository, um mit anderen Git-Entwicklern zusammenzuarbeiten. Subversion kann nur einen einzigen linearen Verlauf haben und es ist sehr einfach, ihn zu verwirren. Wenn Sie mit einem Team arbeiten und einige SVN und andere Git verwenden, stellen Sie sicher, dass alle den SVN-Server zur Zusammenarbeit nutzen – das macht Ihr Leben einfacher.

Einrichtung

Um diese Funktionalität zu demonstrieren, benötigen Sie ein typisches SVN-Repository, zu dem Sie Schreibzugriff haben. Wenn Sie diese Beispiele kopieren möchten, müssen Sie eine beschreibbare Kopie eines SVN-Test-Repositorys erstellen. Um dies einfach zu tun, können Sie ein Tool namens svnsync verwenden, das mit Subversion geliefert wird.

Um mitmachen zu können, müssen Sie zuerst ein neues lokales Subversion-Repository erstellen

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

Aktivieren Sie dann alle Benutzer für die Änderung von Revprops – der einfache Weg ist, ein pre-revprop-change-Skript hinzuzufügen, das immer mit 0 beendet wird

$ 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 jetzt auf Ihren lokalen Rechner synchronisieren, indem Sie svnsync init mit den Quell- und Ziel-Repositories aufrufen.

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

Dies richtet die Eigenschaften für die Ausführung der Synchronisierung ein. 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 diese Operation nur wenige Minuten dauern kann, dauert der Prozess fast eine Stunde, wenn Sie versuchen, das ursprüngliche Repository in ein anderes Remote-Repository anstelle eines lokalen zu kopieren, auch wenn es weniger als 100 Commits gibt. Subversion muss Revision für Revision klonen und dann wieder in ein anderes Repository pushen – es ist lächerlich ineffizient, aber es ist der einzige einfache Weg, dies zu tun.

Erste Schritte

Jetzt, da Sie ein Subversion-Repository haben, zu dem Sie Schreibzugriff haben, können Sie einen typischen Workflow durchlaufen. Sie beginnen mit dem Befehl git svn clone, der ein gesamtes Subversion-Repository in ein lokales Git-Repository importiert. Denken Sie daran, dass Sie, wenn Sie aus einem echten gehosteten Subversion-Repository importieren, file:///tmp/test-svn hier durch die URL Ihres Subversion-Repositorys 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

Dies führt das Äquivalent von zwei Befehlen aus – git svn init gefolgt von git svn fetch – für die von Ihnen angegebene URL. Dies kann eine Weile dauern. Wenn das Testprojekt zum Beispiel nur etwa 75 Commits hat und die Codebasis nicht so groß ist, muss Git dennoch jede Version einzeln auschecken und individuell committen. Für ein Projekt mit Hunderten oder Tausenden von Commits kann dies buchstäblich Stunden oder sogar Tage dauern.

Der Teil -T trunk -b branches -t tags teilt Git mit, dass dieses Subversion-Repository den grundlegenden Konventionen für Branching und Tagging folgt. Wenn Sie Ihren Trunk, Ihre Branches oder Ihre Tags anders benennen, können Sie diese Optionen ändern. Da dies so üblich ist, können Sie diesen gesamten Teil durch -s ersetzen, was „standard layout“ bedeutet und all diese Optionen impliziert. Der folgende Befehl ist äquivalent

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

An diesem Punkt sollten Sie ein gültiges Git-Repository haben, 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, wie dieses Tool Subversion-Tags als Remote-Refs verwaltet. Werfen wir einen genaueren Blick mit dem Git-Plumbing-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; hier ist, wie ein Repository mit Tags nach einem frischen Klon aussieht

$ 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 holt sich die Tags direkt in refs/tags, anstatt sie als Remote-Branches zu behandeln.

Zurück nach Subversion committen

Jetzt, da Sie ein funktionierendes Verzeichnis haben, können Sie einige Arbeiten am Projekt durchführen und Ihre Commits zurück nach oben pushen, indem Sie Git effektiv als SVN-Client verwenden. Wenn Sie eine der Dateien bearbeiten und committen, haben Sie einen Commit, der lokal in Git existiert und 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 nach oben pushen. Beachten Sie, wie sich dadurch Ihre Arbeitsweise mit Subversion ändert – Sie können mehrere Commits offline durchführen und sie dann alle auf einmal auf den Subversion-Server pushen. Um auf einen 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

Dies nimmt alle Commits, die Sie zusätzlich zum Subversion-Server-Code erstellt haben, führt für jeden einen Subversion-Commit durch und schreibt dann Ihren lokalen Git-Commit neu, um eine eindeutige Kennung einzufügen. Dies ist wichtig, da es bedeutet, dass sich alle SHA-1-Prüfsummen Ihrer Commits ändern. Zum Teil aus diesem Grund ist es keine gute Idee, gleichzeitig mit Git-basierten Remote-Versionen Ihrer Projekte und einem Subversion-Server zu arbeiten. Wenn Sie den letzten Commit betrachten, können Sie den neuen git-svn-id sehen, der hinzugefügt wurde

$ 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

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

Neue Änderungen ziehen

Wenn Sie mit anderen Entwicklern zusammenarbeiten, wird irgendwann einer von Ihnen pushen, und dann wird der andere versuchen, eine Änderung zu pushen, die einen Konflikt verursacht. Diese Änderung wird abgelehnt, bis Sie ihre Arbeit zusammengeführt haben. 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 git svn rebase ausführen, was alle Änderungen vom Server herunterlädt, die Sie noch nicht haben, und Ihre Arbeit auf das, was auf dem Server ist, rebaset

$ 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 über dem, was auf dem Subversion-Server ist, sodass Sie erfolgreich dcommit 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

Beachten Sie, dass git svn Sie – im Gegensatz zu Git, das verlangt, dass Sie bisher nicht lokale Upstream-Arbeit zusammenführen, bevor Sie pushen können – dies nur tut, wenn die Änderungen einen Konflikt verursachen (ähnlich wie bei Subversion). Wenn jemand anderes eine Änderung an einer Datei pusht und Sie dann eine Änderung an einer anderen Datei pushen, funktioniert Ihr dcommit problemlos

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

Dies ist wichtig zu bedenken, da das Ergebnis ein Projektzustand ist, der auf keinem Ihrer Computer existierte, als Sie gepusht haben. Wenn die Änderungen inkompatibel sind, aber keine Konflikte verursachen, können Sie Probleme bekommen, die schwer zu diagnostizieren sind. Dies unterscheidet sich von der Verwendung eines Git-Servers – in Git können Sie den Zustand auf Ihrem Client-System vollständig testen, bevor Sie ihn veröffentlichen, während Sie in SVN nie sicher sein können, dass die Zustände unmittelbar vor und nach dem Commit identisch sind.

Sie sollten diesen Befehl auch ausführen, um Änderungen vom Subversion-Server zu ziehen, auch wenn Sie noch nicht bereit sind, selbst zu committen. Sie können git svn fetch ausführen, um die neuen Daten abzurufen, aber git svn rebase führt den Fetch aus 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.

Das gelegentliche Ausführen von git svn rebase stellt sicher, dass Ihr Code immer auf dem neuesten Stand ist. Sie müssen jedoch sicherstellen, dass Ihr Arbeitsverzeichnis sauber ist, wenn Sie dies ausführen. Wenn Sie lokale Änderungen haben, müssen Sie Ihre Arbeit entweder stashen oder temporär committen, bevor Sie git svn rebase ausführen – andernfalls stoppt der Befehl, wenn er feststellt, dass der Rebase zu einem Merge-Konflikt führen würde.

Git-Branching-Probleme

Wenn Sie sich mit einem Git-Workflow vertraut gemacht haben, werden Sie wahrscheinlich Themenzweige erstellen, darauf arbeiten und sie dann zusammenführen. Wenn Sie über git svn auf einen Subversion-Server pushen, möchten Sie Ihre Arbeit möglicherweise jedes Mal auf einen einzelnen Zweig rebasen, anstatt Zweige zusammenzuführen. Der Grund für die Bevorzugung des Rebasens ist, dass Subversion einen linearen Verlauf hat und nicht wie Git mit Merges umgeht, sodass git svn nur dem ersten Elternteil folgt, wenn die Snapshots in Subversion-Commits umgewandelt werden.

Angenommen, Ihr Verlauf sieht so aus: Sie haben einen experiment-Zweig erstellt, zwei Commits durchgeführt und sie dann wieder in master zusammengeführt. Wenn Sie dcommit ausführen, sehen Sie eine Ausgabe wie diese

$ 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 Zweig mit zusammengeführtem Verlauf funktioniert einwandfrei, außer dass, wenn Sie sich Ihren Git-Projektverlauf ansehen, keiner der Commits, die Sie auf dem experiment-Zweig vorgenommen haben, umgeschrieben wurde – stattdessen erscheinen all diese Änderungen im SVN-Format des einzelnen Merge-Commits.

Wenn jemand anderes diese Arbeit klont, sieht er nur den Merge-Commit mit all der Arbeit darin zusammengefasst, als ob Sie git merge --squash ausgeführt hätten; er sieht nicht die Commit-Daten darüber, woher sie stammen oder wann sie committet wurden.

Subversion-Branching

Branching in Subversion ist nicht dasselbe wie Branching in Git; wenn Sie es vermeiden können, es viel zu benutzen, ist das wahrscheinlich am besten. Sie können jedoch mit git svn Branches in Subversion erstellen und darauf committen.

Einen neuen SVN-Branch erstellen

Um einen neuen Branch in Subversion zu erstellen, führen Sie git svn branch [neuer-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)

Dies entspricht dem Befehl svn copy trunk branches/opera in Subversion und operiert auf dem Subversion-Server. Es ist wichtig zu beachten, dass es Sie nicht in diesen Branch auscheckt; wenn Sie zu diesem Zeitpunkt committen, geht dieser Commit an trunk auf dem Server, nicht an opera.

Aktive Branches wechseln

Git ermittelt, an welchen Branch Ihre Dcommits gehen, indem es die Spitze eines Ihrer Subversion-Branches in Ihrem Verlauf sucht – Sie sollten nur einen haben, und er sollte der letzte mit einer git-svn-id in Ihrem aktuellen Branch-Verlauf sein.

Wenn Sie gleichzeitig an mehr als einem Branch arbeiten möchten, können Sie lokale Branches einrichten, um an spezifische Subversion-Branches zu dcommit, indem Sie sie am importierten Subversion-Commit für diesen Branch starten. Wenn Sie einen opera-Branch haben möchten, an dem Sie separat arbeiten können, können Sie Folgendes ausführen

$ git branch opera remotes/origin/opera

Wenn Sie nun Ihren opera-Branch in trunk (Ihren master-Branch) zusammenführen möchten, können Sie dies mit einem normalen git merge tun. Sie müssen jedoch eine aussagekräftige Commit-Nachricht angeben (über -m), sonst wird der Merge „Merge branch opera“ statt etwas Nützlichem anzeigen.

Denken Sie daran, dass, obwohl Sie git merge verwenden, um diese Operation durchzuführen, und der Merge wahrscheinlich viel einfacher sein wird als in Subversion (da Git die geeignete Merge-Basis automatisch für Sie erkennt), dies kein normaler Git-Merge-Commit ist. Sie müssen diese Daten auf einen Subversion-Server pushen, der keinen Commit verarbeiten kann, der mehr als einen Elternteil verfolgt; daher wird es, nachdem Sie es hochgeladen haben, wie ein einzelner Commit aussehen, der die Arbeit eines anderen Branches unter einem einzigen Commit zusammengefasst hat. Nachdem Sie einen Branch in einen anderen zusammengeführt haben, können Sie nicht mehr leicht zurückgehen und weiter an diesem Branch arbeiten, wie Sie es normalerweise in Git können. Der Befehl dcommit, den Sie ausführen, löscht alle Informationen, die besagen, welcher Branch zusammengeführt wurde, sodass nachfolgende Merge-Basis-Berechnungen falsch sind – dcommit lässt Ihr git merge-Ergebnis so aussehen, als hätten Sie git merge --squash ausgeführt. Leider gibt es keinen guten Weg, diese Situation zu vermeiden – Subversion kann diese Informationen nicht speichern, sodass Sie immer durch seine Einschränkungen eingeschränkt sind, solange Sie es als Ihren Server verwenden. Um Probleme zu vermeiden, sollten Sie den lokalen Branch (in diesem Fall opera) löschen, nachdem Sie ihn in den Trunk zusammengeführt haben.

Subversion-Befehle

Das git svn-Toolset bietet eine Reihe von Befehlen, die den Übergang zu Git erleichtern, indem sie einige Funktionen bereitstellen, die denen in Subversion ähneln. Hier sind einige Befehle, die Ihnen geben, was Sie früher von Subversion hatten.

SVN-Stil-Verlauf

Wenn Sie an Subversion gewöhnt sind und Ihren Verlauf im SVN-Ausgabestil sehen möchten, können Sie git svn log ausführen, um Ihren Commit-Verlauf im SVN-Format 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 Gegensatz zum echten svn log-Befehl, der den Subversion-Server nach den Daten fragt. Zweitens zeigt es Ihnen nur Commits an, die bis zum Subversion-Server committet wurden. Lokale Git-Commits, die Sie noch nicht dcommittet haben, werden nicht angezeigt; ebenso wenig wie Commits, die andere Leute inzwischen auf dem Subversion-Server vorgenommen haben. Es ist eher wie der letzte bekannte Zustand der Commits auf dem Subversion-Server.

SVN-Annotation

Ähnlich wie der Befehl git svn log den Befehl svn log offline simuliert, können Sie das Äquivalent von svn annotate erhalten, indem Sie git svn blame [DATEI] ausführen. Die Ausgabe sieht so 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

Auch hier werden keine Commits angezeigt, die Sie lokal in Git vorgenommen haben oder die inzwischen auf Subversion gepusht wurden.

SVN-Serverinformationen

Sie können auch die gleichen Informationen erhalten, die svn info liefert, indem Sie git svn info ausführen

$ 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)

Dies ist wie blame und log, da es offline ausgeführt wird und nur bis zum letzten Mal aktuell ist, als Sie mit dem Subversion-Server kommuniziert haben.

Ignorieren, was Subversion ignoriert

Wenn Sie ein Subversion-Repository klonen, das svn:ignore-Eigenschaften an beliebiger Stelle gesetzt hat, möchten Sie wahrscheinlich entsprechende .gitignore-Dateien einrichten, damit Sie keine Dateien versehentlich committen, die Sie nicht sollten. git svn verfügt über zwei Befehle, die Ihnen bei diesem Problem helfen. Der erste ist git svn create-ignore, der automatisch entsprechende .gitignore-Dateien für Sie erstellt, damit Ihr nächster Commit sie einschließen kann.

Der zweite Befehl ist git svn show-ignore, der die Zeilen auf stdout ausgibt, die Sie in eine .gitignore-Datei einfügen müssen, damit Sie die Ausgabe in Ihre Projekt-Exclude-Datei umleiten können

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

Auf diese Weise überschwemmen Sie das Projekt nicht mit .gitignore-Dateien. Dies ist eine gute Option, wenn Sie der einzige Git-Benutzer in einem Subversion-Team sind und Ihre Teammitglieder keine .gitignore-Dateien im Projekt haben möchten.

Git-Svn Zusammenfassung

Die git svn-Tools sind nützlich, wenn Sie an einen Subversion-Server gebunden sind oder sich anderweitig in einer Entwicklungsumgebung befinden, die die Ausführung eines Subversion-Servers erfordert. Sie sollten es jedoch als eingeschränktes Git betrachten, sonst stoßen Sie auf Übersetzungsprobleme, die Sie und Ihre Kollegen verwirren könnten. Um Probleme zu vermeiden, versuchen Sie, die folgenden Richtlinien zu befolgen

  • Halten Sie einen linearen Git-Verlauf, der keine von git merge erstellten Merge-Commits enthält. Rebasen Sie jede Arbeit, die Sie außerhalb Ihres Hauptzweigs leisten, zurück darauf; führen Sie sie nicht zusammen.

  • Richten Sie keinen separaten Git-Server ein und arbeiten Sie nicht mit ihm zusammen. Möglicherweise haben Sie einen, um Klone für neue Entwickler zu beschleunigen, aber pushen Sie nichts darauf, das keinen git-svn-id-Eintrag hat. Möglicherweise möchten Sie sogar einen pre-receive-Hook hinzufügen, der jede Commit-Nachricht auf eine git-svn-id überprüft und Pushes mit Commits ohne diese ablehnt.

Wenn Sie diese Richtlinien befolgen, kann die Arbeit mit einem Subversion-Server erträglicher sein. Wenn es jedoch möglich ist, zu einem echten Git-Server zu wechseln, kann dies Ihrem Team viel mehr bringen.

Git und Mercurial

Das DVCS-Universum ist größer als nur Git. Tatsächlich gibt es viele andere Systeme in diesem Bereich, von denen jedes seinen eigenen Ansatz hat, wie man verteilte Versionskontrolle richtig handhabt. Neben Git ist Mercurial das beliebteste, und die beiden sind in vielerlei Hinsicht sehr ähnlich.

Die gute Nachricht ist, wenn Sie das Client-Verhalten von Git bevorzugen, aber mit einem Projekt arbeiten, dessen Quellcode mit Mercurial verwaltet wird, gibt es eine Möglichkeit, Git als Client für ein Mercurial-gehostetes Repository zu verwenden. Da Git über Remotes mit Server-Repositories kommuniziert, sollte es keine Überraschung sein, dass diese Brücke als Remote-Helper implementiert ist. Der Projektname ist 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. Dies beinhaltet im Wesentlichen das Ablegen seiner Datei irgendwo in Ihrem Pfad, wie folgt

$ 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, ~/bin ist in Ihrem $PATH. Git-remote-hg hat eine weitere Abhängigkeit: die mercurial-Bibliothek für Python. Wenn Sie Python installiert haben, ist dies so einfach wie

$ pip install mercurial

Wenn Sie Python nicht installiert haben, besuchen Sie https://pythonlang.de/ und besorgen Sie es zuerst.

Das Letzte, was Sie benötigen, ist der Mercurial-Client. Gehen Sie zu https://www.mercurial-scm.org/ und installieren Sie ihn, falls Sie dies noch nicht getan haben.

Jetzt sind Sie bereit, loszulegen. Alles, was Sie brauchen, ist ein Mercurial-Repository, zu dem Sie pushen können. Glücklicherweise kann jedes Mercurial-Repository so fungieren, also verwenden wir einfach das „Hallo Welt“-Repository, das jeder zum Erlernen von Mercurial verwendet

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

Erste Schritte

Jetzt, da wir ein geeignetes „Server-seitiges“ Repository haben, können wir einen typischen Workflow durchlaufen. Wie Sie sehen werden, sind diese beiden Systeme ähnlich genug, dass es wenig Reibung gibt.

Wie immer bei Git klonen wir 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

Sie werden feststellen, dass die Arbeit mit einem Mercurial-Repository den Standardbefehl git clone verwendet. Das liegt daran, dass git-remote-hg auf einer ziemlich niedrigen Ebene arbeitet und einen ähnlichen Mechanismus verwendet wie die Implementierung des HTTP/S-Protokolls von Git (Remote-Helper). Da sowohl Git als auch Mercurial darauf ausgelegt sind, dass jeder Client eine vollständige Kopie des Repository-Verlaufs hat, erstellt dieser Befehl einen vollständigen Klon, einschließlich des gesamten Projektverlaufs, und das ziemlich schnell.

Der Befehl log zeigt zwei Commits, von denen der letzte von einer ganzen Reihe von Refs referenziert wird. Es stellt sich heraus, dass einige davon tatsächlich nicht vorhanden sind. Schauen wir uns an, was sich tatsächlich 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, Dinge idiomatischer zu machen, aber unter der Haube verwaltet es die konzeptionelle Zuordnung zwischen zwei leicht unterschiedlichen Systemen. Das Verzeichnis refs/hg ist, wo die tatsächlichen Remote-Refs gespeichert sind. Zum Beispiel ist refs/hg/origin/branches/default eine Git-Ref-Datei, die die SHA-1 enthält, die mit „ac7955c“ beginnt, was der Commit ist, auf den master verweist. Das Verzeichnis refs/hg ist also eine Art falsches refs/remotes/origin, hat aber die zusätzliche Unterscheidung zwischen Bookmarks und Branches.

Die Datei notes/hg ist der Ausgangspunkt dafür, wie git-remote-hg Git-Commit-Hashes Mercurial-Änderungsset-IDs zuordnet. Lassen Sie uns ein wenig erkunden

$ 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

refs/notes/hg verweist also auf einen Baum, der in der Git-Objektdatenbank eine Liste anderer Objekte mit Namen ist. git ls-tree gibt den Modus, den Typ, den Objekt-Hash und den Dateinamen für Elemente innerhalb eines Baums aus. Sobald wir zu einem der Baum-Elemente gelangen, stellen wir fest, dass sich darin ein Blob namens „ac9117f“ (der SHA-1-Hash des Commits, auf den master verweist) befindet, mit dem Inhalt „0a04b98“ (was die ID des Mercurial-Änderungssets an der Spitze des default-Branches ist).

Die gute Nachricht ist, dass wir uns all dessen meistens keine Sorgen machen müssen. Der typische Workflow wird sich nicht wesentlich von der Arbeit mit einem Git-Remote unterscheiden.

Es gibt noch eine Sache, auf die wir uns konzentrieren sollten, bevor wir weitermachen: Ignorierungen. Mercurial und Git verwenden einen sehr ähnlichen Mechanismus dafür, aber es ist wahrscheinlich, dass Sie keine .gitignore-Datei in ein Mercurial-Repository committen möchten. Glücklicherweise hat Git eine Möglichkeit, Dateien zu ignorieren, die lokal zu einem On-Disk-Repository ist, und das Mercurial-Format ist mit Git kompatibel, sodass Sie es nur kopieren müssen

$ cp .hgignore .git/info/exclude

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

Workflow

Nehmen wir an, wir haben einige Arbeiten durchgeführt und einige Commits auf dem master-Branch vorgenommen, und Sie sind bereit, sie in das Remote-Repository zu pushen. So sieht unser Repository im Moment 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 voraus gegenüber origin/master, aber diese beiden Commits existieren nur auf unserem lokalen Computer. Sehen wir mal, ob andere Leute gleichzeitig wichtige Arbeiten erledigt haben

$ 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 die Option --all verwendet haben, sehen wir die „notes“-Refs, die intern von git-remote-hg verwendet werden, aber wir können sie ignorieren. Der Rest ist, wie erwartet; origin/master ist um einen Commit vorgerückt und unser Verlauf hat sich jetzt verzweigt. Im Gegensatz zu den anderen Systemen, mit denen wir in diesem Kapitel arbeiten, ist Mercurial in der Lage, Merges zu verarbeiten, daher werden wir nichts Besonderes tun.

$ 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 aus und alles passt, also sind wir bereit, unsere Arbeit mit dem Rest des Teams zu teilen

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

Das war's! Wenn Sie sich das Mercurial-Repository ansehen, werden Sie sehen, dass dies das Erwartete getan hat

$ 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 Änderungsset mit der Nummer 2 wurde von Mercurial erstellt, und die Änderungssets mit den Nummern 3 und 4 wurden von git-remote-hg erstellt, indem Commits gepusht wurden, die mit Git erstellt wurden.

Branches und Bookmarks

Git hat nur eine Art von Branch: eine Referenz, die sich bewegt, wenn Commits gemacht werden. In Mercurial wird eine solche Referenz „Bookmark“ genannt und verhält sich im Grunde genauso wie ein Git-Branch.

Mercurials Konzept eines „Branches“ ist schwergewichtiger. Der Branch, auf dem ein Änderungsset erstellt wird, wird *mit dem Änderungsset* aufgezeichnet, was bedeutet, dass er immer im Repository-Verlauf enthalten sein wird. Hier ist ein Beispiel für einen Commit, der auf dem develop-Branch erstellt 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

Beachten Sie die Zeile, die mit „branch“ beginnt. Git kann dies nicht wirklich replizieren (und muss es auch nicht; beide Arten von Branches können als Git-Ref dargestellt werden), aber git-remote-hg muss den Unterschied verstehen, weil Mercurial darauf achtet.

Das Erstellen von Mercurial-Bookmarks ist so einfach wie das Erstellen von Git-Branches. Auf der Git-Seite

$ 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. Auf der Mercurial-Seite sieht es so 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 das neue Tag [featureA] bei Revision 5. Diese verhalten sich auf der Git-Seite genauso wie Git-Branches, mit einer Ausnahme: Sie können einen Bookmark von der Git-Seite aus nicht löschen (dies ist eine Einschränkung von Remote-Helfern).

Sie können auch an einem „schwergewichtigen“ Mercurial-Branch arbeiten; platzieren Sie einfach einen Branch im Namespace branches

$ 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 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 Änderungsset mit der Markierung 7 aufgezeichnet.

Auf der Git-Seite ist die Arbeit mit beiden Branch-Stilen gleich: einfach auschecken, committen, fetchen, mergen, pullen und pushen, wie Sie es normalerweise tun würden. Eine Sache, die Sie wissen sollten, ist, dass Mercurial das Umschreiben von Verläufen nicht unterstützt, sondern nur das Hinzufügen dazu. Hier ist, wie unser Mercurial-Repository nach einem interaktiven Rebase und einem Force-Push aussieht

$ 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 Änderungssets 8, 9 und 10 wurden erstellt und gehören zum permanent-Branch, aber die alten Änderungssets sind immer noch vorhanden. Dies kann für Ihre Teamkollegen, die Mercurial verwenden, **sehr** verwirrend sein, also versuchen Sie, es zu vermeiden.

Mercurial Zusammenfassung

Git und Mercurial sind so ähnlich, dass die Arbeit über die Grenze hinweg ziemlich schmerzlos ist. Wenn Sie vermeiden, Verläufe zu ändern, die Ihre Maschine verlassen haben (wie generell empfohlen), bemerken Sie vielleicht nicht einmal, dass das andere Ende Mercurial ist.

Git und Perforce

Perforce ist ein sehr beliebtes Versionskontrollsystem in Unternehmensumgebungen. Es existiert seit 1995, was es zum ältesten System macht, das in diesem Kapitel behandelt wird. Als solches ist es für die Einschränkungen seiner Zeit konzipiert; es geht davon aus, dass Sie immer mit einem einzigen zentralen Server verbunden sind und nur eine Version auf der lokalen Festplatte gespeichert wird. Sicherlich sind seine Funktionen und Einschränkungen für mehrere spezifische Probleme gut geeignet, aber es gibt viele Projekte, die Perforce verwenden, bei denen Git tatsächlich besser funktionieren würde.

Es gibt zwei Optionen, wenn Sie Ihre Nutzung von Perforce und Git mischen möchten. Die erste, die wir behandeln werden, ist die „Git Fusion“-Brücke der Macher von Perforce, mit der Sie Teilbereiche Ihres Perforce-Depots als Lese-/Schreibzugriff-Git-Repositories bereitstellen können. Die zweite ist git-p4, eine Client-seitige Brücke, mit der Sie Git als Perforce-Client verwenden können, ohne dass eine Neukonfiguration des Perforce-Servers erforderlich ist.

Git Fusion

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

Einrichtung

Für unsere Beispiele verwenden wir die einfachste Installationsmethode für Git Fusion, nämlich das Herunterladen einer virtuellen Maschine, auf der der Perforce-Daemon und Git Fusion laufen. Sie können das VM-Image von https://www.perforce.com/downloads herunterladen und, sobald der Download abgeschlossen ist, in Ihre bevorzugte Virtualisierungssoftware (wir verwenden VirtualBox) importieren.

Beim ersten Start der Maschine werden Sie aufgefordert, das Passwort für drei Linux-Benutzer (root, perforce und git) anzupassen und einen Instanznamen anzugeben, mit dem diese Installation von anderen im selben Netzwerk unterschieden werden kann. Wenn das abgeschlossen ist, sehen Sie Folgendes

The Git Fusion virtual machine boot screen
Abbildung 171. Der Startbildschirm der Git Fusion virtuellen Maschine

Sie sollten die hier angezeigte IP-Adresse notieren, wir werden sie später verwenden. Als Nächstes erstellen wir einen Perforce-Benutzer. Wählen Sie unten die Option „Login“ und drücken Sie Enter (oder SSH auf die Maschine) und loggen Sie sich als root ein. Verwenden Sie dann diese Befehle, um einen Benutzer zu erstellen

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

Die erste öffnet einen VI-Editor zum Anpassen des Benutzers, aber Sie können die Standardwerte akzeptieren, indem Sie :wq eingeben und Enter drücken. Die zweite fordert Sie auf, ein Passwort zweimal einzugeben. Das ist alles, was wir mit einer Shell-Eingabeaufforderung machen müssen, also beenden Sie die Sitzung.

Als Nächstes müssen Sie Git mitteilen, SSL-Zertifikate nicht zu überprüfen. Das Git Fusion Image wird mit einem Zertifikat geliefert, aber es ist für eine Domain, die nicht mit der IP-Adresse Ihrer virtuellen Maschine übereinstimmt, sodass Git die HTTPS-Verbindung ablehnt. Wenn dies eine permanente Installation sein soll, konsultieren Sie das Perforce Git Fusion Handbuch, um ein anderes Zertifikat zu installieren; für unsere Beispielzwecke reicht dies aus

$ 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 wird mit einem Beispielprojekt geliefert, das Sie klonen können. Hier klonen wir über HTTPS, mit dem zuvor erstellten john-Benutzer; Git fragt nach Anmeldeinformationen für diese Verbindung, aber der Anmeldeinformations-Cache ermöglicht es uns, diesen Schritt für alle nachfolgenden Anfragen zu überspringen.

Fusion-Konfiguration

Sobald Sie Git Fusion installiert haben, möchten Sie die Konfiguration anpassen. Das ist tatsächlich recht einfach mit Ihrem bevorzugten Perforce-Client; mappen Sie einfach das Verzeichnis //.git-fusion auf dem Perforce-Server in Ihren Arbeitsbereich. 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 zu Git und umgekehrt zuzuordnen. Sie müssen sich um nichts darin kümmern. Es gibt eine globale p4gf_config-Datei in diesem Verzeichnis, sowie eine für jedes Repository – dies sind die Konfigurationsdateien, die das Verhalten von Git Fusion bestimmen. Schauen wir uns die Datei im Stammverzeichnis an

[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 beachten Sie, dass dies nur eine Textdatei im INI-Format ist, ähnlich wie Git sie für die Konfiguration verwendet. Diese Datei gibt die globalen Optionen an, die dann durch Repository-spezifische Konfigurationsdateien wie repos/Talkhouse/p4gf_config überschrieben werden können. Wenn Sie diese Datei öffnen, sehen Sie einen Abschnitt [@repo] mit einigen Einstellungen, die von den globalen Standardwerten abweichen. Sie sehen auch Abschnitte, die so aussehen

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

Dies ist eine Zuordnung zwischen einem Perforce-Branch und einem Git-Branch. Der Abschnitt kann beliebig benannt werden, solange der Name eindeutig ist. git-branch-name ermöglicht es Ihnen, einen Depot-Pfad, der unter Git umständlich wäre, in einen freundlicheren Namen umzuwandeln. Die Einstellung view steuert, wie Perforce-Dateien in das Git-Repository abgebildet werden, unter Verwendung der Standard-View-Mapping-Syntax. Mehrere Mappings können angegeben werden, wie in diesem Beispiel

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

Auf diese Weise, wenn Ihre normale Workspace-Zuordnung Änderungen in der Struktur der Verzeichnisse enthält, können Sie dies mit einem Git-Repository replizieren.

Die letzte Datei, die wir besprechen werden, ist users/p4gf_usermap, die Perforce-Benutzer Git-Benutzern zuordnet und die Sie möglicherweise nicht einmal benötigen. Bei der Konvertierung von einem Perforce-Änderungsset 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 Konvertierung in die andere Richtung besteht die Standardeinstellung darin, den Perforce-Benutzer mit der in der Git-Commit-Autor-Feld gespeicherten E-Mail-Adresse zu suchen und das Änderungsset als diesen Benutzer einzureichen (wobei die Berechtigungen gelten). In den meisten Fällen ist dieses Verhalten ausreichend, aber erwägen 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 <benutzer> <e-mail> "<vollständiger Name>" und erstellt eine einzelne Benutzerzuordnung. Die ersten beiden Zeilen ordnen zwei verschiedene E-Mail-Adressen demselben Perforce-Benutzerkonto zu. Dies ist nützlich, wenn Sie Git-Commits unter mehreren verschiedenen E-Mail-Adressen erstellt haben (oder Ihre E-Mail-Adressen ändern), diese aber demselben Perforce-Benutzer zugeordnet haben möchten. Beim Erstellen eines Git-Commits aus einer Perforce-Änderungssatz wird die erste Zeile, die dem Perforce-Benutzer entspricht, für die Git-Autorenschaftsinformationen verwendet.

Die letzten beiden Zeilen maskieren die tatsächlichen Namen und E-Mail-Adressen von Bob und Joe in den erstellten Git-Commits. Dies ist praktisch, wenn Sie ein internes Projekt Open Source machen möchten, aber Ihr Mitarbeiterverzeichnis nicht der ganzen Welt preisgeben möchten. Beachten Sie, dass die E-Mail-Adressen und vollständigen Namen eindeutig sein sollten, es sei denn, Sie möchten, dass alle Git-Commits einem einzigen fiktiven Autor zugeordnet werden.

Workflow

Perforce Git Fusion ist eine bidirektionale Brücke zwischen Perforce und Git Versionskontrolle. Lassen Sie uns einen Blick darauf werfen, wie es sich anfühlt, von der Git-Seite aus zu arbeiten. Wir gehen davon aus, dass wir das Projekt „Jam“ mit einer Konfigurationsdatei wie oben gezeigt zugeordnet haben, die wir wie folgt 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 dies zum ersten Mal tun, kann es einige Zeit dauern. Git Fusion konvertiert alle zutreffenden Änderungssätze im Perforce-Verlauf in Git-Commits. Dies geschieht lokal auf dem Server, ist also relativ schnell, aber wenn Sie viel Verlauf haben, kann es trotzdem einige Zeit dauern. Nachfolgende Abrufe führen eine inkrementelle Konvertierung durch, sodass es sich 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 möglicherweise arbeiten. Es gibt drei Branches, und Git hat hilfreich einen lokalen master Branch erstellt, der origin/master verfolgt. Lassen Sie uns 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 wollen wir prüfen, ob noch jemand 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.
[...]

Sieht so aus, als ob jemand gearbeitet hat! Sie würden es aus dieser Ansicht nicht wissen, aber der Commit 6afeb15 wurde tatsächlich mit einem Perforce-Client erstellt. Aus Sicht von Git sieht er einfach wie ein weiterer Commit aus, was genau der Punkt ist. Sehen wir uns an, 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. Schauen wir uns den Verlauf der README-Datei aus Sicht von Perforce an, indem wir die Revisionsgrafikfunktion von p4v verwenden

Perforce revision graph resulting from Git push
Abbildung 172. Perforce-Revisionsgrafik, die sich aus dem Git-Push ergibt

Wenn Sie diese Ansicht noch nie zuvor gesehen haben, mag sie verwirrend erscheinen, aber sie zeigt dieselben Konzepte wie ein grafischer Betrachter für den Git-Verlauf. Wir betrachten den Verlauf der Datei README, daher zeigt der Verzeichnisbaum oben links nur diese Datei, wie sie in verschiedenen Branches auftaucht. Oben rechts sehen wir eine visuelle Darstellung, wie verschiedene Revisionen der Datei miteinander in Beziehung stehen, und die Gesamtansicht dieser Grafik befindet sich unten rechts. Der Rest der Ansicht ist der Detailansicht für die ausgewählte Revision (in diesem Fall 2) gewidmet.

Eine Sache, die man bemerken sollte, ist, dass die Grafik genau wie die im Git-Verlauf aussieht. Perforce hatte keinen benannten Branch, um die Commits 1 und 2 zu speichern, daher hat es einen „anonymen“ Branch im Verzeichnis .git-fusion erstellt, um ihn zu speichern. Dies geschieht auch für benannte Git-Branches, die keinem benannten Perforce-Branch entsprechen (und Sie können sie später mit der Konfigurationsdatei einem Perforce-Branch zuordnen).

Das meiste davon geschieht im Hintergrund, aber das Endergebnis ist, dass eine Person in einem Team Git verwenden kann, eine andere Perforce verwenden kann und keiner von ihnen von der Wahl des anderen wissen wird.

Git-Fusion Zusammenfassung

Wenn Sie Zugriff auf Ihren Perforce-Server haben (oder bekommen können), ist Git Fusion eine großartige Möglichkeit, Git und Perforce miteinander reden zu lassen. Es ist eine gewisse Konfiguration erforderlich, aber die Lernkurve ist nicht sehr steil. Dies ist einer der wenigen Abschnitte in diesem Kapitel, in denen keine Warnungen bezüglich der vollen Leistungsfähigkeit von Git erscheinen. Das bedeutet nicht, dass Perforce mit allem zufrieden sein wird, was Sie ihm zumuten – wenn Sie versuchen, einen bereits gepushten Verlauf neu zu schreiben, wird Git Fusion dies ablehnen –, aber Git Fusion bemüht sich sehr, sich nativ anzufühlen. Sie können sogar Git-Submodule verwenden (obwohl sie für Perforce-Benutzer seltsam aussehen) und Branches zusammenführen (dies wird auf der Perforce-Seite als Integration aufgezeichnet).

Wenn Sie den Administrator Ihres Servers nicht davon überzeugen können, Git Fusion einzurichten, gibt es immer noch eine Möglichkeit, diese Werkzeuge 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, sodass Sie keinen Zugriff auf den Perforce-Server benötigen (abgesehen von Benutzeranmeldedaten, natürlich). Git-p4 ist keine so flexible oder vollständige Lösung wie Git Fusion, aber es ermöglicht Ihnen, die meisten Dinge zu tun, die Sie tun möchten, ohne in die Serverumgebung einzugreifen.

Hinweis

Sie benötigen das p4-Tool irgendwo in Ihrem PATH, um mit git-p4 zu arbeiten. Zum Zeitpunkt des Schreibens ist es frei verfügbar unter https://www.perforce.com/downloads/helix-command-line-client-p4.

Einrichtung

Zu Demonstrationszwecken werden wir den Perforce-Server von der Git Fusion OVA wie oben gezeigt ausführen, aber wir werden den Git Fusion Server umgehen und direkt zur Perforce Versionskontrolle gehen.

Um den p4-Befehlszeilenclient (von dem git-p4 abhängt) verwenden zu können, müssen Sie ein paar Umgebungsvariablen festlegen

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

Wie bei allem in Git ist der erste Befehl clone

$ 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

Dies erstellt, was in Git-Begriffen ein „flacher“ Klon ist; nur die allerneueste Perforce-Revision wird in Git importiert; denken Sie daran, dass Perforce nicht darauf ausgelegt ist, jedem Benutzer jede Revision zur Verfügung zu stellen. Dies reicht aus, um Git als Perforce-Client zu verwenden, aber für andere Zwecke ist es nicht ausreichend.

Sobald es fertig 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, wie es ein „p4“-Remote für den Perforce-Server gibt, aber alles andere sieht aus wie ein Standard-Klon. Eigentlich ist das ein wenig irreführend; es gibt dort eigentlich kein Remote.

$ git remote -v

In diesem Repository existieren überhaupt keine Remotes. Git-p4 hat einige Referenzen erstellt, um den Zustand des Servers darzustellen, und sie sehen für git log wie Remote-Referenzen aus, aber sie werden nicht von Git selbst verwaltet, und Sie können sie nicht pushen.

Workflow

Okay, machen wir etwas Arbeit. Nehmen wir an, Sie haben einige Fortschritte bei einem sehr wichtigen Feature gemacht und sind bereit, es Ihrem Team 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 bereit sind, an den Perforce-Server zu übermitteln. Prüfen wir, ob heute noch jemand 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 sie es getan haben, und master und p4/master haben sich auseinanderentwickelt. Perforce's Branching-System ist *nichts* wie das von Git, daher ist das Einreichen von Merge-Commits nicht sinnvoll. Git-p4 empfiehlt, Ihre Commits neu zu basieren, und bietet sogar eine Abkürzung 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(-)

Sie können es wahrscheinlich aus der Ausgabe entnehmen, aber git p4 rebase ist eine Abkürzung für git p4 sync gefolgt von git rebase p4/master. Es ist etwas intelligenter als das, besonders wenn man mit mehreren Branches arbeitet, aber dies ist eine gute Annäherung.

Jetzt ist unser Verlauf wieder linear, und wir sind bereit, unsere Änderungen zurück an Perforce zu übergeben. Der Befehl git p4 submit versucht, für jeden Git-Commit zwischen p4/master und master eine neue Perforce-Revision zu erstellen. Wenn wir ihn ausführen, landen wir in unserem bevorzugten Editor, und der Inhalt der Datei sieht 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>

Dies ist größtenteils derselbe Inhalt, den Sie sehen würden, wenn Sie p4 submit ausführen würden, außer dem Teil am Ende, den git-p4 hilfreich hinzugefügt hat. Git-p4 versucht, Ihre Git- und Perforce-Einstellungen individuell zu berücksichtigen, wenn es einen Namen für einen Commit oder eine Änderungssatz bereitstellen muss, aber in einigen Fällen möchten Sie dies überschreiben. Wenn der Git-Commit, den Sie importieren, beispielsweise von einem Mitwirkenden verfasst wurde, der kein Perforce-Benutzerkonto hat, möchten Sie vielleicht trotzdem, dass der resultierende Änderungssatz so aussieht, als hätte er ihn geschrieben (und nicht Sie).

Git-p4 hat die Nachricht aus dem Git-Commit hilfreich als Inhalt für diesen Perforce-Änderungssatz importiert, sodass wir nur noch speichern und beenden müssen, zweimal (einmal für jeden Commit). Die resultierende Shell-Ausgabe sieht ungefähr 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 so, als hätten wir gerade git push ausgeführt, was die engste Analogie zu dem ist, was tatsächlich passiert ist.

Beachten Sie, dass während dieses Prozesses jeder Git-Commit in einen Perforce-Änderungssatz umgewandelt wird; wenn Sie sie zu einem einzigen Änderungssatz zusammenfassen möchten, können Sie dies mit einem interaktiven Rebase tun, bevor Sie git p4 submit ausführen. Beachten Sie auch, dass sich die SHA-1-Hashes aller Commits, die als Änderungssätze übermittelt wurden, geändert haben; dies liegt daran, dass git-p4 am Ende jedes konvertierten Commits 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 zu übermitteln? Versuchen wir es mal. Hier ist die Situation, in die wir uns 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 Git- und Perforce-Verlauf divergiert nach 775a46f. Die Git-Seite hat zwei Commits, dann einen Merge-Commit mit dem Perforce-Kopf, dann einen weiteren Commit. Wir werden versuchen, diese auf einem einzigen Änderungssatz auf der Perforce-Seite zu übermitteln. Sehen wir uns an, was passieren würde, wenn wir jetzt versuchen würden zu übermitteln

$ 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 Flag -n steht für --dry-run, das versucht zu berichten, was passieren würde, wenn der Submit-Befehl tatsächlich ausgeführt würde. In diesem Fall sieht es so aus, als würden wir drei Perforce-Änderungssätze erstellen, die den drei Nicht-Merge-Commits entsprechen, die noch nicht auf dem Perforce-Server vorhanden sind. Das klingt genau nach dem, was wir wollen, mal sehen, wie es ausgeht

$ 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

Unser Verlauf wurde linear, als ob wir vor dem Einreichen einen Rebase durchgeführt hätten (was tatsächlich genau das war, was passiert ist). Das bedeutet, dass Sie frei sind, Branches auf der Git-Seite zu erstellen, zu bearbeiten, zu verwerfen und zusammenzuführen, ohne befürchten zu müssen, dass Ihr Verlauf in irgendeiner Weise mit Perforce inkompatibel wird. Wenn Sie es neu basieren können, können Sie es an einen Perforce-Server übergeben.

Branching

Wenn Ihr Perforce-Projekt mehrere Branches hat, sind Sie nicht am Ende; git-p4 kann das so handhaben, dass es sich wie Git anfühlt. Nehmen wir an, Ihr Perforce-Depot ist wie folgt aufgebaut

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

Und nehmen wir 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“-Spezifizierer im Depot-Pfad; das sagt git-p4, dass es nicht nur die neueste Änderung für diesen Unterbaum klonen soll, sondern alle Änderungssätze, die diese Pfade jemals berührt haben. Dies ist näher an Gits Konzept eines Klons, aber wenn Sie an einem Projekt mit langer Geschichte arbeiten, kann dies einige Zeit dauern.

Das Flag --detect-branches weist git-p4 an, die Branch-Spezifikationen von Perforce zu verwenden, um die Branches Git-Refs zuzuordnen. Wenn diese Zuordnungen nicht auf dem Perforce-Server vorhanden sind (was eine völlig gültige Art ist, Perforce zu verwenden), können Sie git-p4 mitteilen, wie die Branch-Zuordnungen aussehen sollen, und Sie erhalten dasselbe 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 Konfigurationsvariable git-p4.branchList auf main:dev teilt git-p4 mit, dass „main“ und „dev“ beide Branches sind und der zweite ein Kind des ersten ist.

Wenn wir nun git checkout -b dev p4/project/dev ausführen und einige Commits erstellen, ist git-p4 intelligent genug, den richtigen Branch zu wählen, wenn wir git p4 submit ausführen. Leider kann git-p4 keine flachen Klone und mehrere Branches mischen; wenn Sie ein riesiges Projekt haben und mehr als einen Branch bearbeiten möchten, müssen Sie git p4 clone einmal für jeden Branch ausführen, den Sie übermitteln möchten.

Zum Erstellen oder Integrieren von Branches müssen Sie einen Perforce-Client verwenden. Git-p4 kann nur synchronisieren und übermitteln, und das nur eine lineare Änderungssatz nach der anderen. Wenn Sie zwei Branches in Git zusammenführen und den neuen Änderungssatz übermitteln, wird nur eine Reihe von Dateiänderungen aufgezeichnet; die Metadaten darüber, welche Branches an der Integration beteiligt sind, gehen verloren.

Git und Perforce Zusammenfassung

Git-p4 ermöglicht die Verwendung eines Git-Workflows mit einem Perforce-Server und ist ziemlich gut darin. Es ist jedoch wichtig zu bedenken, dass Perforce für die Quelle zuständig ist und Sie Git nur zur lokalen Arbeit verwenden. Seien Sie wirklich vorsichtig beim Teilen von Git-Commits; wenn Sie ein Remote haben, das andere Leute verwenden, pushen Sie keine Commits, die nicht bereits an den Perforce-Server übermittelt wurden.

Wenn Sie die Verwendung von Perforce und Git als Clients für die Quellcodeverwaltung frei mischen möchten und den Serveradministrator davon überzeugen können, es zu installieren, macht Git Fusion die Verwendung von Git zu einem erstklassigen Versionskontrollclient für einen Perforce-Server.