-
1. Erste Schritte
-
2. Git Grundlagen
-
3. Git Branching
- 3.1 Branches auf einen Blick
- 3.2 Einfaches Branching und Merging
- 3.3 Branch-Management
- 3.4 Branching-Workflows
- 3.5 Remote-Branches
- 3.6 Rebasing
- 3.7 Zusammenfassung
-
4. Git auf dem Server
- 4.1 Die Protokolle
- 4.2 Git auf einem Server einrichten
- 4.3 Erstellung eines SSH-Public-Keys
- 4.4 Einrichten des Servers
- 4.5 Git-Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Von Drittanbietern gehostete Optionen
- 4.10 Zusammenfassung
-
5. Verteiltes Git
-
6. GitHub
-
7. Git Tools
- 7.1 Revisions-Auswahl
- 7.2 Interaktives Stagen
- 7.3 Stashen und Bereinigen
- 7.4 Deine Arbeit signieren
- 7.5 Suchen
- 7.6 Den Verlauf umschreiben
- 7.7 Reset entzaubert
- 7.8 Fortgeschrittenes Merging
- 7.9 Rerere
- 7.10 Debuggen mit Git
- 7.11 Submodule
- 7.12 Bundling
- 7.13 Replace (Ersetzen)
- 7.14 Anmeldeinformationen speichern
- 7.15 Zusammenfassung
-
8. Git einrichten
- 8.1 Git Konfiguration
- 8.2 Git-Attribute
- 8.3 Git Hooks
- 8.4 Beispiel für Git-forcierte Regeln
- 8.5 Zusammenfassung
-
9. Git und andere VCS-Systeme
- 9.1 Git als Client
- 9.2 Migration zu Git
- 9.3 Zusammenfassung
-
10. Git Interna
-
A1. Anhang A: Git in anderen Umgebungen
- A1.1 Grafische Schnittstellen
- A1.2 Git in Visual Studio
- A1.3 Git in Visual Studio Code
- A1.4 Git in IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git in Sublime Text
- A1.6 Git in Bash
- A1.7 Git in Zsh
- A1.8 Git in PowerShell
- A1.9 Zusammenfassung
-
A2. Anhang B: Git in deine Anwendungen einbetten
- A2.1 Die Git-Kommandozeile
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Anhang C: Git Kommandos
- A3.1 Setup und Konfiguration
- A3.2 Projekte importieren und erstellen
- A3.3 Einfache Snapshot-Funktionen
- A3.4 Branching und Merging
- A3.5 Projekte gemeinsam nutzen und aktualisieren
- A3.6 Kontrollieren und Vergleichen
- A3.7 Debugging
- A3.8 Patchen bzw. Fehlerkorrektur
- A3.9 E-mails
- A3.10 Externe Systeme
- A3.11 Administration
- A3.12 Basisbefehle
9.1 Git und andere VCS-Systeme - Git als Client
Die Welt ist nicht perfekt. Normalerweise kannst du nicht jedes Projekt, mit dem du arbeitest, 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 du gerade arbeitest, ein anderes System nutzt.
Irgendwann wirst du vielleicht dein bestehendes Projekt nach Git migrieren wollen. Der zweite Teil dieses Kapitels behandelt die Migration deines 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 du am ehesten in der „freien Wildbahn“ begegnen wirst.
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 kannst du Git als geeigneter Client für einen Subversion-Server verwenden, so dass du alle lokalen Funktionen von Git nutzen und dann auf einen Subversion-Server pushen kannst, als ob du Subversion lokal einsetzen würdest.
Das bedeutet, dass du lokale Branching- und Merging-Aktivitäten vornimmst, die Staging-Area nutzt, Rebasing- und Cherry-Picking-Aktivitäten durchführen kannst, während deine Mitstreiter weiterhin in ihrer dunklen und altertümlichen SVN-Umgebung tätig sind.
Es ist eine gute Möglichkeit, Git in die Unternehmensumgebung einzuschleusen, deine Entwicklerkollegen zu helfen, effizienter zu werden und gleichzeitig die Infrastruktur so zu ändern, dass Git vollständig unterstützt wird.
Die Subversion-Brücke ist das Portal zur D(istributed)-VCS-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 du bei der Verwendung von git svn
mit Subversion interagierst. SVN ist ein System, das ganz anders funktioniert als Git.
Obwohl du lokales Branching und Merging durchführen kannst, ist es im Allgemeinen ratsam, deinen Verlauf so linear wie möglich zu gestalten, indem du deine Arbeiten rebased. Vermeide dabei die gleichzeitige Interaktion mit einem Git Remote-Repository.
Schreibe deinen Verlauf nicht um und versuche nicht, diesen geänderten Verlauf erneut zu pushen. Pushe 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 einfach, diesen Verlauf durcheinanderzubringen. Wenn du mit einem Team arbeitest und einige verwenden SVN und andere Git, stelle sicher, dass alle den SVN-Server für die gemeinsame Arbeit verwenden – das erleichtert dir deinen Alltag.
Einrichtung
Um diese Funktionalität zu demonstrieren, benötigst du ein SVN-Repository mit Schreibzugriff.
Wenn du die Beispiele kopieren möchtest, musst du eine beschreibbare Kopie eines SVN-Test Repository erstellen.
Um das zu tun, kannst du das Tool svnsync
verwenden, das in einer Subversion-Installation enthalten ist.
Um dem Beispielen zu folgen, musst du zunächst ein neues lokales Subversion-Repository erstellen:
$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn
Aktiviere 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
Du kannst dieses Projekt nun auf deinem lokalen Rechner synchronisieren, indem du svnsync init
mit den Repositorys to und from aufrufst.
$ 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. Anschliessend kannst du den Code klonen, indem du Folgendes ausführst:
$ 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 Minuten in Anspruch nehmen könnte, dauert der Prozess fast eine Stunde, wenn du versuchen solltest, 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 äußerst ineffizient, aber es ist der einzige einfache Weg, das zu erreichen.
Erste Schritte
Jetzt, da du ein beschreibbares Subversion-Repository hast, kannst du einen normalen Workflow nutzen.
Beginne mit dem Befehl git svn clone
, der ein komplettes Subversion-Repository in ein lokales Git-Repository importiert.
Beachte, dass du beim Import aus einem echten Subversion-Repository file:///tmp/test-svn
durch die URL deines Subversion-Repositorys ersetzen musst:
$ 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 dir 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 Stunden oder gar Tage dauern.
Der Teil -T trunk -b branches -t tags
teilt Git mit, dass dieses Subversion-Repository den grundlegenden Branching- und Tagging-Konventionen folgt.
Wenn du deinen Trunk, deine Branches oder Tags anders benennst, können sich diese Optionen ändern.
Da dies so häufig vorkommt, kannst du 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 solltest du über ein valides Git-Repository verfügen, das deine 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
Beachte, 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 man von einem Git-Server geklont hat. 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 fetched die Tags direkt in refs/tags
, anstatt sie mit entfernten Branches zu verknüpfen.
Zurück zu Subversion committen
Jetzt, da du ein Arbeitsverzeichnis hast, kannst du an dem Projekt arbeiten und deine Commits wieder zum Upstream pushen, indem du Git als SVN-Client verwendest. Wenn du eine der Dateien bearbeitest und überträgst, hast du 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 musst du deine Änderung zum Upstream pushen.
Beachte, wie sich dies auf deine Arbeitsweise mit Subversion auswirkt. Du kannst mehrere Commits offline durchführen und diese dann alle auf einmal auf den Subversion-Server übertragen.
Um zu einem Subversion-Server zu pushen, führe 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 erstellt für jeden Commit, den du auf deinem Subversion Server-Codes gemacht hast, einen eigenen Subversion-Commit und schreibt deinen lokalen Git-Commit um, um einen eindeutigen Identifier einzufügen.
Das ist wichtig, weil es bedeutet, dass sich alle SHA-1-Prüfsummen für deine Commits ändern.
Aus diesem Grund ist es keine gute Idee, gleichzeitig mit Git-basierten Remotes deines Projekts und einem Subversion-Server zu arbeiten.
Wenn du dir den letzten Commit ansiehst, siehst du 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 du die Daten übertragen hast, nun mit 95e0222
beginnt.
Wenn du sowohl auf einen Git-Server als auch auf einen Subversion-Server pushen möchtest, musst du zuerst auf den Subversion-Server pushen (dcommit
), da diese Aktion deine Commit-Daten ändert.
Neue Änderungen pullen
Wenn du mit anderen Entwicklern zusammenarbeitest, wird irgendwann einer von euch pushen, und andere versuchen, eine Änderung voranzutreiben, die Konflikte verursacht.
Diese Änderung wird abgelehnt, bis du deren Arbeit mergst.
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, kannst du git svn rebase
ausführen, das alle Änderungen auf dem Server, die du noch nicht hast, pullt und alle deine lokalen Arbeiten auf den gepullten Code rebased:
$ 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 deine gesamte Arbeit auf den Code des Subversion-Servers rebased, so dass du dcommit
erfolgreich einsetzen kannst:
$ 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 du Upstream-Arbeiten, die du noch nicht lokal hast, zuerst mergst, bevor du pushen kannst, zwingt dich git svn
dazu nur dann, wenn die Änderungen im Konflikt stehen (ähnlich wie bei Subversion).
Wenn jemand anderes eine Änderung an einer Datei macht und du eine Änderung an einer anderen Datei machst, funktioniert dein 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 deiner 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 kannst du den Zustand auf deinem Client-System vor der Veröffentlichung vollständig testen, während du in SVN nie sicher sein kannst, dass die Zustände unmittelbar vor dem Commit und nach dem Commit identisch sind.
Du solltest diesen Befehl auch ausführen, um Änderungen vom Subversion-Server einzubinden, auch wenn du noch nicht bereit bist, selbst zu committen.
Es ist ratsam, git svn fetch
auszuführen, um die neuen Daten zu holen, aber git svn rebase
übernimmt bereits den Fetch und aktualisiert dann deine 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 du ab und zu git svn rebase laufen lässt, stellst du sicher, dass dein Code immer auf dem neuesten Stand ist.
Du solltest jedoch überprüfen, ob dein Arbeitsverzeichnis sauber ist, wenn du diese Funktion ausführst.
Wenn du lokale Änderungen hast, musst du deine Arbeit entweder verstecken (engl. stash) oder temporär committen, bevor du git svn rebase
ausführst. Andernfalls wird der Befehl angehalten, wenn er erkennt, dass das Rebase zu einem Merge-Konflikt führen wird.
Git Branching Probleme
Sobald du dich mit einem Git-Workflow vertraut gemacht hast, wirst du wahrscheinlich Topic-Branches erstellen, an ihnen arbeiten und sie dann mergen wollen.
Wenn du über git svn
auf einen Subversion-Server pushst, kannst du deine Arbeit jedes Mal auf einen einzigen Branch rebasen, 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, dein Verlauf sieht wie folgt aus: Du hast einen experiment
Branch erstellt, zwei Commits durchgeführt und diese dann wieder mit dem master
zusammengeführt.
Wenn du dcommit
aufrufst, 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 du dir dein Git-Projekt-Historie ansiehst. Es wurde keiner der Commits, die du auf dem experiment
Branch gemacht hast, 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 zusammengeführt wurde, als ob du git merge --squash
ausgeführt hättest. Man sieht die Commit-Daten nicht, weder woher sie stammen noch wann sie committed wurden.
Subversion Branching
Branching in Subversion ist nicht dasselbe wie Branching in Git. Es ist wahrscheinlich das Beste, wenn du es so gut es geht vermeidest.
Du kannst aber mit git svn
in Subversion Branches anlegen und dorthin committen.
Erstellen eines neuen SVN Branches
Um einen neuen Branch in Subversion zu erstellen, führe 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.
Es ist wichtig zu beachten, dass dieser Branch nicht ausgecheckt wird. Wenn du an diesem Punkt einen Commit durchführst, geht dieser Commit in den trunk
auf dem Server, nicht in opera
.
Aktive Branches wechseln
Git findet heraus, in welchen Branch deine dcommits hingehen, indem es nach dem Ende eines deiner Subversion Branches in deinem Verlauf sucht – du solltest nur einen haben, und es sollte der Letzte sein, der eine git-svn-id
in deinem aktuellen Branchverlauf hat.
Wenn du an mehr als einem Branch gleichzeitig arbeiten möchtest, kannst du lokale Branches so einrichten, dass du dcommit
für bestimmte Subversion-Branches ausführst, indem du diese beim importierten Subversion-Commit für diesen Branch startest.
Einen opera
Branch, mit dem du separat arbeiten kannst, kannst du folgendermaßen initialisieren:
$ git branch opera remotes/origin/opera
Um deinen opera
Branch in trunk
(deinen master
Branch) zu mergen, kannst du das mit einem normalen git merge
machen.
Aber du solltest unbedingt eine beschreibende Commit-Meldung (via -m
) angeben, sonst wird beim Merge anstelle von etwas Vernünftigem „Merge branch opera“ angezeigt.
Obwohl du für diese Operation git merge
verwendest und der Merge wahrscheinlich viel einfacher ist als in Subversion (da Git automatisch die entsprechende Merge-Basis für dich erkennt), ist es kein normaler Git Merge-Commit.
Du musst diese Daten an einen Subversion-Server zurück pushen, der keinen Commit mit mehr als einem Elternteil verarbeiten kann. Nachdem du ihn zum Server gepusht hast, sieht er also aus wie ein einzelner Commit, der die gesamte Arbeit eines anderen Branchs unter einem einzigen Commit zusammenfasst.
Nachdem du einen Branch in einem anderen zusammengeführt haben, kannst du nicht einfach zurückgehen und an diesem Branch weiterarbeiten, wie du es normalerweise in Git tust.
Das dcommit
Kommando, das du ausführst, löscht alle Informationen, die zeigen, in welchen Branch zusammengeführt wurde, so dass nachfolgende Berechnungen der Merge-Basis falsch sind – dcommit
lässt dein git merge
Ergebnis aussehen, als ob du git merge --squash
ausgeführt hättest.
Leider gibt es keine gute Methode, diese Situation zu vermeiden – Subversion kann diese Informationen nicht speichern, daher wirst du immer von den Einschränkungen des Systems behindert, während du es als deinen Server verwendest.
Um Fehler zu vermeiden, solltest du den lokalen Branch (in diesem Fall opera
) löschen, nachdem du ihn in trunk
eingefügt habst.
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 du aus Subversion kennst.
Wir haben hier ein paar Befehle, mit denen du das bekommst, was Subversion vorher konnte.
Verlauf im SVN-Format
Wenn du an Subversion gewöhnt bist und deinen Verlauf im SVN-Stil sehen möchtest, kannst du git svn log
ausführen, um deinen 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
Du solltest 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 dir nur Commits an, die zum Subversion-Server übertragen wurden.
Lokale Git-Commits, die du noch nicht mit dcommit
bestätigt hast, 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 Annotation
So wie der Befehl git svn log
den Befehl svn log
offline simuliert, kannst du das Äquivalent von svn annotate
abrufen, indem du git svn blame [FILE]
ausführst.
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
Nochmal: Auch hier werden keine Commits angezeigt, die du lokal in Git gemacht hast oder die in der Zwischenzeit in Subversion verschoben wurden.
SVN Server-Information
Wenn du git svn info
ausführst, kannst du die gleiche Art von Informationen erhalten, die dir 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 du ein Subversion-Repository klonst, in dem irgendwo svn:ignore
Eigenschaften gesetzt sind, wirst du wahrscheinlich entsprechende .gitignore
Dateien setzen wollen, damit du nicht versehentlich Dateien überträgst, die nicht übertragen werden sollen.
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 dich erstellt, damit sie bei deinem nächsten Commit berücksichtigt werden.
Der zweite Befehl ist git svn show-ignore
, der die Zeilen nach stdout ausgibt, die du in eine .gitignore
Datei einfügen musst, damit du die Ausgabe in die Ausschlussdatei deines Projekts umleiten kannst:
$ git svn show-ignore > .git/info/exclude
Auf diese Weise überhäufst du das Projekt nicht mit .gitignore
Dateien.
Das ist eine gute Option, wenn du der einzige Git-Benutzer in einem Subversion-Team bist und deine Teamkollegen keine .gitignore
Dateien im Projekt haben wollen.
git svn Zusammenfassung
Die git svn
Tools sind nützlich, wenn du gezwungenermaßen mit einem Subversion-Server arbeiten musst.
Du solltest es jedoch als verkapptes Git betrachten, oder du wirst Probleme in der Umsetzung haben, die dich und deine Mitstreiter verwirren könnten.
Um keine Schwierigkeiten zu bekommen, versuche dich an diese Hinweise zu halten:
-
Führe einen linearen Git-Verlauf, der keine Merge-Commits von
git merge
enthält. Rebase alle Arbeiten, die du außerhalb deines Haupt-Branchs durchführst, wieder in diesen ein; merge sie nicht. -
Richte keinen separaten Git-Server ein und arbeite nicht mit einem zusammen. Möglicherweise hast du einen, um Klone für neue Entwickler zu starten, aber pushe nichts, was nicht über einen
git-svn-id
Eintrag verfügt. Du kannst eventuell einenpre-receive
Hook hinzufügen, der jede Commit-Nachricht auf einengit-svn-id
überprüft und Pushes, die Commits ohne ihn enthalten, ablehnt.
Wenn du diese Leitlinien befolgst, kann die Arbeit mit einem Subversion-Server leichter umsetzbar sein. Mit einem Umstieg auf einen echten Git-Server kann dein 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 du Gits clientseitiges Verhalten bevorzugst, aber mit einem Projekt arbeitest, 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-Repositorys 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 musst du git-remote-hg installieren. Im Wesentlichen geht es darum, die Datei irgendwo in deinem 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 deinem $PATH
.
Git-remote-hg hat noch eine weitere Abhängigkeit: die mercurial
Library für Python.
Wenn du Python schon installiert hast, ist das einfach:
$ pip install mercurial
Wenn du Python noch nicht installiert hast, besuche https://www.python.org/ und installiere es.
Als Letztes brauchst du den Mercurial-Client. Gehe zu https://www.mercurial-scm.org/ und installiere ihn, falls du es noch nicht getan hast.
Jetzt bist du bereit zum abrocken. Alles, was du benötigst, ist ein Mercurial-Repository, auf das du zugreifen kannst. 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 du sehen wirst, sind diese beiden Systeme ähnlich genug, dass es keine großen Abweichungen 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 du siehst, 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 Branches.
Die Datei notes/hg
ist der Ausgangspunkt dafür, wie git-remote-hg Git-Commit-Hashes auf Mercurial-Changeset-IDs abbildet.
Lass 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 am Ende des 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: Das Ignorieren.
Mercurial und Git verwenden dafür einen sehr ähnlichen Mechanismus, aber es ist durchaus möglich, dass du eine .gitignore
Datei nicht wirklich in ein Mercurial Repository übertragen willst.
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 du sie nur kopieren musst:
$ 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 du bist 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 du einen Blick auf das Mercurial-Repository wirfst, wirst du feststellen, dass genau das passiert ist, was wir erwarten haben:
$ 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 schwergewichtiger.
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
Achte auf die Zeile, die mit „branch“ beginnt. Git kann das nicht wirklich nachahmen (und muss es auch nicht; beide Arten von Branches 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 machst du:
$ 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
Beachte den neuen [featureA]
Tag auf Revision 5.
Diese verhalten sich genau wie Git-Branches auf der Git-Seite, mit einer Ausnahme: Du kannst ein Lesezeichen auf der Git-Seite nicht löschen (das ist eine Einschränkung des Remote-Helfers).
Du kannst auch an einem „schwergewichtigen“ Mercurial-Branch arbeiten: Bringe 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 du es normalerweise auch machen würdest. Eine Sache, die du wissen solltest, 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 deine Teamkollegen, die Mercurial verwenden, sehr verwirrend sein, also versuche es zu vermeiden.
Mercurial Zusammenfassung
Git und Mercurial sind sich ähnlich genug, um über die eigene Umgebung hinaus schmerzlos miteinander zu arbeiten. Wenn du vermeidest, den Verlauf zu ändern, der deinen Computer bereits verlassen hat (was allgemein empfohlen wird), merkst du wahrscheinlich nicht einmal, dass am anderen Ende Mercurial verwendet wird.
Git und Perforce
Perforce ist ein sehr beliebtes Versionskontrollsystem in Unternehmen. 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 du immer mit einem einzigen zentralen Server verbunden bist 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 möglicherweise besser geeignet wäre.
Es gibt zwei Möglichkeiten, wenn du Perforce und Git zusammen nutzen möchtest. Als erstes stellen wir die „Git Fusion“ Bridge des Herstellers von Perforce vor, mit der du Teilbäume deines Perforce-Depots als Read-Write-Git-Repository freigeben kannst. Bei der zweiten handelt es sich um git-p4, eine client-seitige Bridge, mit der du Git als Perforce-Client verwenden kannst, ohne dass der Perforce-Server neu konfiguriert werden muss.
Git Fusion
Preforce bietet mit Git Fusion ein Produkt (verfügbar unter https://www.perforce.com/git-fusion), das einen Perforce-Server mit Git-Repositorys 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. Du kannst das Image der virtuellen Maschine von https://www.perforce.com/downloads/Perforce/20-User herunterladen. Wenn der Download abgeschlossen ist, importierst du es in deiner bevorzugten Virtualisierungssoftware (wir verwenden VirtualBox).
Beim ersten Start des Rechners wirst du aufgefordert, das Passwort für drei Linux-Benutzer (root
, perforce
, git
) festzulegen und einen Instanznamen anzugeben, mit dem du diese Installation von anderen im selben Netzwerk unterscheiden kannst.
Wenn das alles abgeschlossen ist, solltest du folgendes sehen:
Du solltest die hier angezeigte IP-Adresse notieren, wir werden sie später benutzen.
Als nächstes erstellen wir einen Perforce-Benutzer.
Wähle unten die Option „Login“ und drücke die Eingabetaste (oder verbinde dich per SSH mit dem Computer) und melde dich als root
an.
Verwende anschliessend folgenden 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. Du kannst auch die Vorgaben übernehmen, indem du :wq
eingibst und auf Enter drückst.
Der zweite wird dich auffordern, ein Passwort zweimal einzugeben.
Das ist alles, was wir mit einem Shell-Prompt zu tun haben werden, also beende die Sitzung.
Als nächstes musst du 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 deiner virtuellen Maschine übereinstimmt, weshalb Git die HTTPS-Verbindung abweisen würde. Wenn dies eine permanente Installation sein soll, lies 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 du klonen kannst.
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 du Git Fusion installiert hast, solltest du die Konfiguration anpassen.
Mit deinem favorisierten Perforce-Client ist das ganz einfach. Weise das Verzeichnis //.git-fusion
auf dem Perforce-Server einfach deinem 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 du es ignorieren kannst.
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 all dieser Flags eingehen. Bedenke jedoch, 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 du diese Datei öffnest, siehst du einen Abschnitt [@repo]
mit einigen Einstellungen, die sich von den globalen Standardeinstellungen unterscheiden.
Du wirst 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-Branch und einem Git-Branch.
Der Abschnitt kann beliebig benannt werden, solange der Name eindeutig ist.
Der git-branch-name
ermöglicht dir, 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 dein normales Workspace-Mapping Änderungen in der Struktur der Verzeichnisse enthält, kannst du 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 du möglicherweise nicht einmal benötigst.
Bei der Konvertierung 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 beachte 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 du Git-Commits unter mehreren verschiedenen E-Mail-Adressen erstellt hast (oder sich deine E-Mail-Adresse ändert), 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 du ein internes Projekt open-source-fähig machen willst, aber dein Mitarbeiterverzeichnis nicht auf der ganzen Welt veröffentlichen willst. Beachte, dass die E-Mail-Adressen und vollständigen Namen eindeutig sein sollten, es sei denn, du möchtest 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 gemapped 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 du das zum ersten Mal machst, 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 du siehst, sieht unser Repository genauso aus wie jedes andere Git-Repository, mit dem du arbeiten kannst.
Es gibt drei Branches. Git hat einen lokalen master
Branch erstellt, der origin/master
tracked.
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 lass 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!
Du würdest es aus dieser Sicht nicht erkennen, aber der 6afeb15
Commit wurde mit einem Perforce Client erstellt.
Es sieht aus der Perspektive von Git 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:
Wenn du diese Darstellung noch nie zuvor gesehen hast, mag sie verwirrend erscheinen, aber sie zeigt die gleichen Konzepte wie ein grafischer Viewer für die Git-Historie.
Wir betrachten die Historie 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.
Auffällig 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 (Du kannst 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 du Zugang zu deinem Perforce-Server hadt (oder erhalten kannst), 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 du ihm zumutest, zufrieden sein wirst – wenn du versuchst, 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. Du kannst sogar Git-Submodule verwenden (obwohl sie für Perforce-Anwender seltsam aussehen werden) und Branches mergen (das wird als Integration auf der Perforce-Seite erfasst).
Wenn du den Administrator deines Servers nicht davon überzeugen kannst, 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 deinem Git-Repository, so dass du keinen Zugriff auf den Perforce-Server benötigst (mit Ausnahme der Benutzer-Anmeldeinformationen). Git-p4 ist nicht so flexibel und keine Komplettlösung wie Git Fusion, aber es ermöglicht dir, das meiste von dem zu tun, was du tun möchtest, ohne die Serverumgebung zu beeinträchtigen.
Anmerkung
|
Du brauchst das |
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, musst du 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. Denke 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
Beachte, 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, lass uns ein paar Arbeiten erledigen. Nehmen wir an, du hast einige Änderungen bei einem sehr wichtigen Feature gemacht und bist bereit, es dem Rest deines 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 Änderungen gemacht 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
Tatsächlich wurde etwas geändert. Die Branches master
und p4/master
sind auseinander gelaufen.
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 du deine Commits rebased 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 kannst du das an der Ausgabe erkennen: 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, in Perforce eingereicht zu werden.
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 du bei der Ausführung von p4 submit
sehen würdest. Ausgenommen sind die Dinge am Ende, die git-p4 sinnvollerweise mit aufgenommen hat.
Git-p4 versucht, deine Git- und Perforce-Einstellungen individuell zu berücksichtigen, wenn es einen Namen für ein Commit- oder Changeset angeben muss. In einigen Fällen willst du ihn eventuell überschreiben.
Wenn beispielsweise der Git-Commit, den du importierst, von einem Mitwirkenden geschrieben wurde, der kein Perforce-Benutzerkonto hat, kannst du trotzdem wollen, dass sich das daraus ergebende Changeset so aussieht, als hätte er es geschrieben (und nicht du).
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 sieht so aus, als ob wir gerade ein git push
gemacht hätten. Das kommt dem, was tatsächlich passiert sehr nahe.
Beachte, dass während dieses Prozesses jeder Git-Commit in einen Perforce Changeset umgewandelt wird. Wenn du ihn in einen einzelnen Changeset zusammenfassen möchtest, kannst du dies mit einem interaktiven Rebase tun, bevor du git p4 submit
ausführst.
Bitte bedenke, 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 du versuchst, einen Merge-Commit einzureichen? Versuchen wir es einmal. Dies ist der Zustand, in die wir jetzt sind:
$ 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 auf einem einzelnen Changeset auf der Perforce-Seite einzureichen. Schauen wir mal was passiert, wenn wir versuchen, 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
. Das bedeutet der Befehlt wir nur simuliert und nicht wirklich ausgeführt. Es erfolgt jedoch eine Ausgabe, was passieren würde, würde man ihn wirklich ausführen, was sehr hilfreich ist.
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. Lass es uns jetzt wirklich ausführen und sehen was passiert:
$ 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 rebased, was auch tatsächlich passiert ist. So kannst du auf der Git-Seite selbst Branches erstellen, darauf arbeiten, verwerfen und mergen, ohne befürchten zu müssen, dass dein Verlauf sich mit Perforce nicht mehr verträgt. Wenn du einen Rebase durchführen kannst, ist es auch möglich, ihn zu einem Perforce-Server beizusteuern.
Branching
Wenn dein Perforce-Projekt mehrere Branches hat, hast du Glück gehabt. git-p4 kann damit so umgehen, dass es sich wie Git anfühlt. Angenommen, dein Perforce-Depot ist so aufgebaut:
//depot
└── project
├── main
└── dev
Nehmen wir weiter an, du hast 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
Beachte 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 du an einem Projekt mit einer langen Historie arbeitest, 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), kannst du git-p4 mitteilen, wie die Zuordnung der Branches zu sein hat. Du erhältst 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, 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 du ein großes Projekt hast und an mehr als einem Branch arbeiten willst, musst du jeden Branch, den du einreichen möchtest, einmal mit git p4 clone
erstellen.
Für die Erstellung oder Integration von Branches musst du 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 klar zu machen, dass Perforce für den Quellcode verantwortlich ist und du Git nur für die lokale Arbeit verwendest. Sei einfach sehr vorsichtig bei der Weitergabe von Git-Commits. Wenn du einen Remote hast, den auch andere Benutzer verwenden, pushe keine Commits, die zuvor noch nicht an den Perforce-Server übertragen wurden.
Möchtest du die Verwendung von Perforce und Git als Clients für die Versionskontrolle frei kombinieren, dann musst du 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.