-
1. Erste Schritte
- 1.1 Über Versionskontrolle
- 1.2 Eine kurze Geschichte von Git
- 1.3 Was ist Git?
- 1.4 Die Kommandozeile
- 1.5 Git installieren
- 1.6 Erstmalige Git-Einrichtung
- 1.7 Hilfe bekommen
- 1.8 Zusammenfassung
-
2. Git Grundlagen
-
3. Git Branching
- 3.1 Branches im Überblick
- 3.2 Grundlegendes 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 Generieren Ihres SSH-Public-Keys
- 4.4 Einrichten des Servers
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Drittanbieter-Hosting-Optionen
- 4.10 Zusammenfassung
-
5. Verteiltes Git
-
6. GitHub
-
7. Git-Werkzeuge
- 7.1 Revisionsauswahl
- 7.2 Interaktives Staging
- 7.3 Stashing und Bereinigen
- 7.4 Ihre Arbeit signieren
- 7.5 Suchen
- 7.6 Historie umschreiben
- 7.7 Reset entmystifiziert
- 7.8 Fortgeschrittenes Merging
- 7.9 Rerere
- 7.10 Debugging mit Git
- 7.11 Submodule
- 7.12 Bundling
- 7.13 Ersetzen
- 7.14 Credential-Speicher
- 7.15 Zusammenfassung
-
8. Git anpassen
-
9. Git und andere Systeme
- 9.1 Git als Client
- 9.2 Migration zu Git
- 9.3 Zusammenfassung
-
10. Git-Interna
- 10.1 Plumbing und Porcelain
- 10.2 Git-Objekte
- 10.3 Git-Referenzen
- 10.4 Packfiles
- 10.5 Die Refspec
- 10.6 Übertragungsprotokolle
- 10.7 Wartung und Datenwiederherstellung
- 10.8 Umgebungsvariablen
- 10.9 Zusammenfassung
-
Anhang A: Git in anderen Umgebungen
- A1.1 Grafische Oberflächen
- 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
-
Anhang B: Git in Ihre Anwendungen einbetten
- A2.1 Kommandozeilen-Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
Anhang C: Git-Befehle
- A3.1 Einrichtung und Konfiguration
- A3.2 Projekte abrufen und erstellen
- A3.3 Grundlegendes Snapshotting
- A3.4 Branching und Merging
- A3.5 Projekte teilen und aktualisieren
- A3.6 Inspektion und Vergleich
- A3.7 Debugging
- A3.8 Patching
- A3.9 E-Mail
- A3.10 Externe Systeme
- A3.11 Administration
- A3.12 Plumbing-Befehle
9.2 Git und andere Systeme - Migration zu Git
Migration zu Git
Wenn Sie eine bestehende Codebasis in einem anderen VCS haben, sich aber entschieden haben, Git zu verwenden, müssen Sie Ihr Projekt auf die eine oder andere Weise migrieren. Dieser Abschnitt behandelt einige Importwerkzeuge für gängige Systeme und zeigt dann, wie Sie Ihren eigenen benutzerdefinierten Importeur entwickeln. Sie lernen, wie Sie Daten aus mehreren der größeren professionell genutzten SCM-Systeme importieren, da diese die Mehrheit der wechselnden Benutzer ausmachen und weil hochwertige Werkzeuge dafür leicht verfügbar sind.
Subversion
Wenn Sie den vorherigen Abschnitt über die Verwendung von git svn gelesen haben, können Sie diese Anweisungen problemlos verwenden, um ein Repository mit git svn clone zu klonen. Beenden Sie dann die Verwendung des Subversion-Servers, pushen Sie zu einem neuen Git-Server und beginnen Sie mit diesem. Wenn Sie den Verlauf haben möchten, können Sie dies so schnell erledigen, wie Sie die Daten aus dem Subversion-Server abrufen können (was eine Weile dauern kann).
Der Import ist jedoch nicht perfekt; und da er so lange dauern wird, können Sie es auch gleich richtig machen. Das erste Problem sind die Autoreninformationen. In Subversion hat jede Person, die committet, einen Benutzer im System, der in den Commit-Informationen aufgezeichnet wird. Die Beispiele im vorherigen Abschnitt zeigen schacon an einigen Stellen, z. B. in der blame-Ausgabe und im git svn log. Wenn Sie dies zu besseren Git-Autoren-Daten zuordnen möchten, benötigen Sie eine Zuordnung von den Subversion-Benutzern zu den Git-Autoren. Erstellen Sie eine Datei namens users.txt, die diese Zuordnung in einem Format wie diesem enthält.
schacon = Scott Chacon <schacon@geemail.com>
selse = Someo Nelse <selse@geemail.com>
Um eine Liste der von SVN verwendeten Autorennamen zu erhalten, können Sie dies ausführen
$ svn log --xml --quiet | grep author | sort -u | \
perl -pe 's/.*>(.*?)<.*/$1 = /'
Dadurch wird die Protokollausgabe im XML-Format generiert, dann werden nur die Zeilen mit Autoreninformationen beibehalten, Duplikate verworfen und die XML-Tags entfernt. Offensichtlich funktioniert dies nur auf einem Computer mit grep, sort und perl. Leiten Sie dann diese Ausgabe in Ihre Datei users.txt um, damit Sie die entsprechenden Git-Benutzerdaten neben jedem Eintrag hinzufügen können.
|
Hinweis
|
Wenn Sie dies auf einem Windows-Computer versuchen, werden Sie hier auf Probleme stoßen. Microsoft hat unter https://learn.microsoft.com/en-us/azure/devops/repos/git/perform-migration-from-svn-to-git einige gute Ratschläge und Beispiele gegeben. |
Sie können diese Datei an git svn übergeben, um die Autorenzuordnung genauer zu gestalten. Sie können git svn auch anweisen, die Metadaten, die Subversion normalerweise importiert, nicht einzuschließen, indem Sie --no-metadata an den Befehl clone oder init übergeben. Die Metadaten enthalten eine git-svn-id in jeder Commit-Nachricht, die Git während des Imports generiert. Dies kann Ihren Git-Verlauf aufblähen und ihn etwas unklar machen.
|
Hinweis
|
Sie müssen die Metadaten beibehalten, wenn Sie Commits, die im Git-Repository vorgenommen wurden, zurück in das ursprüngliche SVN-Repository spiegeln möchten. Wenn Sie die Synchronisierung nicht in Ihrem Commit-Verlauf wünschen, können Sie den Parameter |
Dadurch sieht Ihr import-Befehl wie folgt aus:
$ git svn clone http://my-project.googlecode.com/svn/ \
--authors-file=users.txt --no-metadata --prefix "" -s my_project
$ cd my_project
Jetzt sollten Sie einen schöneren Subversion-Import im Verzeichnis my_project haben. Anstatt Commits, die so aussehen:
commit 37efa680e8473b615de980fa935944215428a35a
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date: Sun May 3 00:12:22 2009 +0000
fixed install - go to trunk
git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de-
be05-5f7a86268029
sehen sie so aus:
commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date: Sun May 3 00:12:22 2009 +0000
fixed install - go to trunk
Nicht nur das Feld Autor sieht viel besser aus, sondern auch die git-svn-id ist nicht mehr vorhanden.
Sie sollten auch eine kleine Bereinigung nach dem Import durchführen. Zum einen sollten Sie die seltsamen Referenzen bereinigen, die git svn eingerichtet hat. Zuerst verschieben Sie die Tags, damit sie tatsächliche Tags und keine seltsamen Remote-Branches sind, und dann verschieben Sie die restlichen Branches, damit sie lokal sind.
Um die Tags zu echten Git-Tags zu verschieben, führen Sie Folgendes aus:
$ for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git tag ${t/tags\//} $t && git branch -D -r $t; done
Dies nimmt die Referenzen, die Remote-Branches waren und mit refs/remotes/tags/ begannen, und macht sie zu echten (leichtgewichtigen) Tags.
Verschieben Sie als Nächstes die restlichen Referenzen unter refs/remotes in lokale Branches:
$ for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done
Es kann vorkommen, dass Sie einige zusätzliche Branches sehen, die mit @xxx (wobei xxx eine Zahl ist) enden, obwohl in Subversion nur ein Branch sichtbar ist. Dies ist tatsächlich eine Funktion von Subversion, die als "Peg-Revisions" bezeichnet wird und für Git keine syntaktische Entsprechung hat. Daher fügt git svn einfach die SVN-Versionsnummer zum Branch-Namen hinzu, so wie Sie sie in SVN geschrieben hätten, um die Peg-Revision dieses Branches zu adressieren. Wenn Ihnen die Peg-Revisions egal sind, entfernen Sie sie einfach:
$ for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done
Jetzt sind alle alten Branches echte Git-Branches und alle alten Tags echte Git-Tags.
Es gibt noch eine letzte Sache zu bereinigen. Leider erstellt git svn einen zusätzlichen Branch namens trunk, der dem Standard-Branch von Subversion entspricht, aber der trunk-Ref zeigt auf dieselbe Stelle wie master. Da master idiomatischer für Git ist, entfernen Sie den zusätzlichen Branch wie folgt:
$ git branch -d trunk
Das Letzte, was zu tun ist, ist, Ihren neuen Git-Server als Remote hinzuzufügen und dorthin zu pushen. Hier ist ein Beispiel, wie Sie Ihren Server als Remote hinzufügen:
$ git remote add origin git@my-git-server:myrepository.git
Da Sie möchten, dass alle Ihre Branches und Tags hochgeladen werden, können Sie jetzt Folgendes ausführen:
$ git push origin --all
$ git push origin --tags
Alle Ihre Branches und Tags sollten auf Ihrem neuen Git-Server in einem schönen, sauberen Import sein.
Mercurial
Da Mercurial und Git ziemlich ähnliche Modelle zur Darstellung von Versionen haben und Git etwas flexibler ist, ist die Konvertierung eines Repositorys von Mercurial zu Git recht einfach. Dabei wird ein Tool namens "hg-fast-export" verwendet, von dem Sie eine Kopie benötigen:
$ git clone https://github.com/frej/fast-export.git
Der erste Schritt bei der Konvertierung ist, einen vollständigen Klon des Mercurial-Repositorys zu erhalten, das Sie konvertieren möchten:
$ hg clone <remote repo URL> /tmp/hg-repo
Der nächste Schritt ist die Erstellung einer Autorenzuordnungsdatei. Mercurial ist beim Inhalt des Autorenfelds für Changesets etwas nachsichtiger als Git, daher ist dies ein guter Zeitpunkt, um aufzuräumen. Das Generieren hiervon ist ein Einzeiler-Befehl in einer bash-Shell:
$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors
Dies dauert ein paar Sekunden, abhängig von der Länge der Projekthistorie. Danach wird die Datei /tmp/authors ungefähr so aussehen:
bob
bob@localhost
bob <bob@company.com>
bob jones <bob <AT> company <DOT> com>
Bob Jones <bob@company.com>
Joe Smith <joe@company.com>
In diesem Beispiel hat dieselbe Person (Bob) Changesets unter vier verschiedenen Namen erstellt, von denen einer eigentlich korrekt aussieht und einer für einen Git-Commit völlig ungültig wäre. Hg-fast-export ermöglicht es uns, dies zu beheben, indem wir jede Zeile in eine Regel umwandeln: "<input>"="<output>", die einen <input> einem <output> zuordnet. Innerhalb der Zeichenfolgen <input> und <output> werden alle Escape-Sequenzen unterstützt, die von der Python-Codierung string_escape verstanden werden. Wenn die Autorenzuordnungsdatei keine übereinstimmende <input> enthält, wird dieser Autor unverändert an Git weitergeleitet. Wenn alle Benutzernamen in Ordnung aussehen, benötigen wir diese Datei überhaupt nicht. In diesem Beispiel soll unsere Datei so aussehen:
"bob"="Bob Jones <bob@company.com>"
"bob@localhost"="Bob Jones <bob@company.com>"
"bob <bob@company.com>"="Bob Jones <bob@company.com>"
"bob jones <bob <AT> company <DOT> com>"="Bob Jones <bob@company.com>"
Eine ähnliche Zuordnungsdatei kann verwendet werden, um Branches und Tags umzubenennen, wenn der Mercurial-Name von Git nicht erlaubt ist.
Der nächste Schritt ist die Erstellung unseres neuen Git-Repositorys und die Ausführung des Export-Skripts:
$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Das Flag -r gibt hg-fast-export an, wo das Mercurial-Repository gefunden werden kann, das wir konvertieren möchten, und das Flag -A teilt ihm mit, wo die Autorenzuordnungsdatei gefunden werden kann (Branch- und Tag-Zuordnungsdateien werden durch die Flags -B bzw. -T angegeben). Das Skript analysiert Mercurial-Changesets und konvertiert sie in ein Skript für die "fast-import"-Funktion von Git (die wir später ausführlich besprechen werden). Dies dauert eine Weile (obwohl es *wesentlich* schneller ist als über das Netzwerk), und die Ausgabe ist recht ausführlich:
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[…]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[…]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 120000
Total objects: 115032 ( 208171 duplicates )
blobs : 40504 ( 205320 duplicates 26117 deltas of 39602 attempts)
trees : 52320 ( 2851 duplicates 47467 deltas of 47599 attempts)
commits: 22208 ( 0 duplicates 0 deltas of 0 attempts)
tags : 0 ( 0 duplicates 0 deltas of 0 attempts)
Total branches: 109 ( 2 loads )
marks: 1048576 ( 22208 unique )
atoms: 1952
Memory total: 7860 KiB
pools: 2235 KiB
objects: 5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit = 8589934592
pack_report: pack_used_ctr = 90430
pack_report: pack_mmap_calls = 46771
pack_report: pack_open_windows = 1 / 1
pack_report: pack_mapped = 340852700 / 340852700
---------------------------------------------------------------------
$ git shortlog -sn
369 Bob Jones
365 Joe Smith
Das ist so ziemlich alles. Alle Mercurial-Tags wurden in Git-Tags konvertiert, und Mercurial-Branches und Bookmarks wurden in Git-Branches konvertiert. Jetzt sind Sie bereit, das Repository zu Ihrem neuen Server-Home zu pushen.
$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all
Perforce
Das nächste System, das wir importieren werden, ist Perforce. Wie oben diskutiert, gibt es zwei Möglichkeiten, Git und Perforce miteinander reden zu lassen: git-p4 und Perforce Git Fusion.
Perforce Git Fusion
Git Fusion macht diesen Prozess ziemlich schmerzfrei. Konfigurieren Sie einfach Ihre Projekteinstellungen, Benutzerzuordnungen und Branches mithilfe einer Konfigurationsdatei (wie in Git Fusion besprochen) und klonen Sie das Repository. Git Fusion hinterlässt Ihnen ein Repository, das wie ein natives Git-Repository aussieht und dann bereit ist, zu einem nativen Git-Host gepusht zu werden, wenn Sie möchten. Sie könnten Perforce sogar als Ihren Git-Host verwenden, wenn Sie möchten.
Git-p4
Git-p4 kann auch als Importwerkzeug dienen. Als Beispiel werden wir das Jam-Projekt aus dem Perforce Public Depot importieren. Um Ihren Client einzurichten, müssen Sie die Umgebungsvariable P4PORT exportieren, um auf das Perforce-Depot zu verweisen:
$ export P4PORT=public.perforce.com:1666
|
Hinweis
|
Um folgen zu können, benötigen Sie ein Perforce-Depot, mit dem Sie sich verbinden können. Wir werden das öffentliche Depot unter public.perforce.com für unsere Beispiele verwenden, aber Sie können jedes Depot verwenden, auf das Sie Zugriff haben. |
Führen Sie den Befehl git p4 clone aus, um das Jam-Projekt vom Perforce-Server zu importieren. Geben Sie das Depot- und Projektverzeichnis sowie den Pfad an, in den Sie das Projekt importieren möchten:
$ git-p4 clone //guest/perforce_software/jam@all p4import
Importing from //guest/perforce_software/jam@all into p4import
Initialized empty Git repository in /private/tmp/p4import/.git/
Import destination: refs/remotes/p4/master
Importing revision 9957 (100%)
Dieses spezielle Projekt hat nur einen Branch. Wenn Sie jedoch Branches haben, die mit Branch-Views (oder einfach einer Reihe von Verzeichnissen) konfiguriert sind, können Sie das Flag --detect-branches an git p4 clone übergeben, um alle Branches des Projekts zu importieren. Weitere Informationen hierzu finden Sie unter Branching.
An diesem Punkt sind Sie fast fertig. Wenn Sie in das Verzeichnis p4import wechseln und git log ausführen, können Sie Ihre importierte Arbeit sehen:
$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@giles@perforce.com>
Date: Wed Feb 8 03:13:27 2012 -0800
Correction to line 355; change </UL> to </OL>.
[git-p4: depot-paths = "//public/jam/src/": change = 8068]
commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <kwirth@perforce.com>
Date: Tue Jul 7 01:35:51 2009 -0800
Fix spelling error on Jam doc page (cummulative -> cumulative).
[git-p4: depot-paths = "//public/jam/src/": change = 7304]
Sie können sehen, dass git-p4 einen Bezeichner in jeder Commit-Nachricht hinterlassen hat. Es ist in Ordnung, diesen Bezeichner dort zu belassen, falls Sie später auf die Perforce-Change-Nummer verweisen müssen. Wenn Sie den Bezeichner jedoch entfernen möchten, ist jetzt der richtige Zeitpunkt dafür – bevor Sie mit der Arbeit am neuen Repository beginnen. Sie können git filter-branch verwenden, um die Bezeichnungs-Strings en masse zu entfernen:
$ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
Rewrite e5da1c909e5db3036475419f6379f2c73710c4e6 (125/125)
Ref 'refs/heads/master' was rewritten
Wenn Sie git log ausführen, können Sie sehen, dass sich alle SHA-1-Prüfsummen der Commits geändert haben, aber die git-p4-Strings sind nicht mehr in den Commit-Nachrichten enthalten:
$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@giles@perforce.com>
Date: Wed Feb 8 03:13:27 2012 -0800
Correction to line 355; change </UL> to </OL>.
commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <kwirth@perforce.com>
Date: Tue Jul 7 01:35:51 2009 -0800
Fix spelling error on Jam doc page (cummulative -> cumulative).
Ihr Import ist bereit, zu Ihrem neuen Git-Server gepusht zu werden.
Ein benutzerdefinierter Importeur
Wenn Ihr System nicht eines der oben genannten ist, sollten Sie online nach einem Importeur suchen – für viele andere Systeme sind qualitativ hochwertige Importeure verfügbar, darunter CVS, Clear Case, Visual Source Safe, sogar ein Archivverzeichnis. Wenn keines dieser Werkzeuge für Sie funktioniert, Sie ein obskureres Werkzeug haben oder anderweitig einen benutzerdefinierteren Importprozess benötigen, sollten Sie git fast-import verwenden. Dieser Befehl liest einfache Anweisungen von stdin, um spezifische Git-Daten zu schreiben. Es ist viel einfacher, Git-Objekte auf diese Weise zu erstellen, als die rohen Git-Befehle auszuführen oder zu versuchen, die rohen Objekte zu schreiben (weitere Informationen finden Sie unter Git Internals). Auf diese Weise können Sie ein Importskript schreiben, das die erforderlichen Informationen aus dem zu importierenden System ausliest und einfache Anweisungen nach stdout ausgibt. Sie können dann dieses Programm ausführen und seine Ausgabe durch git fast-import leiten.
Um dies schnell zu demonstrieren, schreiben wir einen einfachen Importeur. Angenommen, Sie arbeiten in current, sichern Sie Ihr Projekt, indem Sie das Verzeichnis gelegentlich in ein zeitgestempeltes Sicherungsverzeichnis back_YYYY_MM_DD kopieren, und Sie möchten dies in Git importieren. Ihre Verzeichnisstruktur sieht so aus:
$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current
Um ein Git-Verzeichnis zu importieren, müssen Sie überprüfen, wie Git seine Daten speichert. Wie Sie sich vielleicht erinnern, ist Git im Grunde eine verkettete Liste von Commit-Objekten, die auf einen Schnappschuss des Inhalts verweisen. Alles, was Sie tun müssen, ist, fast-import mitzuteilen, was die Inhaltschnappschüsse sind, welche Commit-Daten darauf verweisen und in welcher Reihenfolge sie auftreten. Ihre Strategie wird sein, die Schnappschüsse nacheinander durchzugehen und Commits mit den Inhalten jedes Verzeichnisses zu erstellen und jeden Commit mit dem vorherigen zu verknüpfen.
Wie wir in Ein Beispiel für eine Git-erzwungene Richtlinie getan haben, schreiben wir dies in Ruby, da es das ist, womit wir im Allgemeinen arbeiten und es tendenziell leicht zu lesen ist. Sie können dieses Beispiel ziemlich einfach in jeder Sprache schreiben, mit der Sie vertraut sind – es muss nur die entsprechenden Informationen nach stdout ausgeben. Und wenn Sie unter Windows arbeiten, müssen Sie besonders darauf achten, keine Wagenrückläufe am Ende Ihrer Zeilen einzufügen – git fast-import ist sehr daran interessiert, nur Zeilenvorschübe (LF) zu erhalten und nicht die Wagenrücklauf-Zeilenvorschübe (CRLF), die Windows verwendet.
Zuerst wechseln Sie in das Zielverzeichnis und identifizieren jedes Unterverzeichnis, das jeweils ein Schnappschuss ist, den Sie als Commit importieren möchten. Sie wechseln in jedes Unterverzeichnis und geben die Befehle aus, die zum Exportieren erforderlich sind. Ihre grundlegende Hauptschleife sieht so aus:
last_mark = nil
# loop through the directories
Dir.chdir(ARGV[0]) do
Dir.glob("*").each do |dir|
next if File.file?(dir)
# move into the target directory
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end
Sie führen print_export innerhalb jedes Verzeichnisses aus, das das Manifest und die Markierung des vorherigen Schnappschusses übernimmt und das Manifest und die Markierung dieses Schnappschusses zurückgibt. Auf diese Weise können Sie sie ordnungsgemäß verknüpfen. "Mark" ist der fast-import-Begriff für einen Bezeichner, den Sie einem Commit geben. Während Sie Commits erstellen, geben Sie jedem einen Mark, den Sie verwenden können, um von anderen Commits darauf zu verweisen. Das Erste, was Sie in Ihrer Methode print_export tun, ist, eine Markierung aus dem Verzeichnisnamen zu generieren:
mark = convert_dir_to_mark(dir)
Dies tun wir, indem wir ein Array von Verzeichnissen erstellen und den Indexwert als Markierung verwenden, da eine Markierung eine Ganzzahl sein muss. Ihre Methode sieht so aus:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
end
Nachdem Sie eine ganzzahlige Darstellung Ihres Commits haben, benötigen Sie ein Datum für die Commit-Metadaten. Da das Datum im Namen des Verzeichnisses angegeben ist, parsieren wir es. Die nächste Zeile in Ihrer Datei print_export ist:
date = convert_dir_to_date(dir)
wobei convert_dir_to_date wie folgt definiert ist:
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
Dies gibt einen ganzzahligen Wert für das Datum jedes Verzeichnisses zurück. Das letzte Stück Metainformation, das wir für jeden Commit benötigen, sind die Committer-Daten, die wir in einer globalen Variablen hartcodieren:
$author = 'John Doe <john@example.com>'
Jetzt sind Sie bereit, mit der Ausgabe der Commit-Daten für Ihren Importeur zu beginnen. Die anfänglichen Informationen besagen, dass Sie ein Commit-Objekt definieren und auf welchem Branch es sich befindet, gefolgt von der generierten Markierung, den Committer-Informationen und der Commit-Nachricht sowie dem vorherigen Commit, falls vorhanden. Der Code sieht so aus:
# print the import information
puts 'commit refs/heads/master'
puts 'mark :' + mark
puts "committer #{$author} #{date} -0700"
export_data('imported from ' + dir)
puts 'from :' + last_mark if last_mark
Wir hartcodieren die Zeitzone (-0700), da dies einfach ist. Wenn Sie aus einem anderen System importieren, müssen Sie die Zeitzone als Offset angeben. Die Commit-Nachricht muss in einem speziellen Format angegeben werden:
data (size)\n(contents)
Das Format besteht aus dem Wort data, der Größe der zu lesenden Daten, einem Zeilenumbruch und schließlich den Daten. Da wir dasselbe Format verwenden müssen, um später die Dateiinhalte anzugeben, erstellen wir eine Hilfsmethode, export_data:
def export_data(string)
print "data #{string.size}\n#{string}"
end
Alles, was noch zu tun ist, ist die Angabe der Dateiinhalte für jeden Schnappschuss. Das ist einfach, da wir jeden in einem Verzeichnis haben – Sie können den Befehl deleteall gefolgt vom Inhalt jeder Datei im Verzeichnis ausgeben. Git speichert dann jeden Schnappschuss entsprechend:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
Hinweis: Da viele Systeme ihre Revisionen als Änderungen von einem Commit zum nächsten betrachten, kann fast-import auch Befehle mit jedem Commit entgegennehmen, um anzugeben, welche Dateien hinzugefügt, entfernt oder geändert wurden und wie die neuen Inhalte aussehen. Sie könnten die Unterschiede zwischen Schnappschüssen berechnen und nur diese Daten angeben, aber das ist komplexer – Sie können Git auch alle Daten geben und es die Arbeit machen lassen. Wenn dies besser zu Ihren Daten passt, lesen Sie die fast-import-Manpage für Details, wie Sie Ihre Daten auf diese Weise bereitstellen.
Das Format zum Auflisten der neuen Dateiinhalte oder zum Angeben einer geänderten Datei mit den neuen Inhalten lautet wie folgt:
M 644 inline path/to/file
data (size)
(file contents)
Hier ist 644 der Modus (wenn Sie ausführbare Dateien haben, müssen Sie 755 erkennen und angeben), und inline bedeutet, dass Sie die Inhalte direkt nach dieser Zeile auflisten werden. Ihre Methode inline_data sieht so aus:
def inline_data(file, code = 'M', mode = '644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
Sie verwenden die zuvor definierte Methode export_data wieder, da sie die gleiche ist, wie Sie Ihre Commit-Nachrichtendaten angegeben haben.
Das Letzte, was Sie tun müssen, ist die Rückgabe der aktuellen Markierung, damit sie an die nächste Iteration übergeben werden kann:
return mark
|
Hinweis
|
Wenn Sie unter Windows arbeiten, müssen Sie sicherstellen, dass Sie einen zusätzlichen Schritt hinzufügen. Wie bereits erwähnt, verwendet Windows CRLF als Zeilenumbruchzeichen, während
|
Das ist alles. Hier ist das Skript in seiner Gesamtheit:
#!/usr/bin/env ruby
$stdout.binmode
$author = "John Doe <john@example.com>"
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir)+1).to_s
end
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
def export_data(string)
print "data #{string.size}\n#{string}"
end
def inline_data(file, code='M', mode='644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
def print_export(dir, last_mark)
date = convert_dir_to_date(dir)
mark = convert_dir_to_mark(dir)
puts 'commit refs/heads/master'
puts "mark :#{mark}"
puts "committer #{$author} #{date} -0700"
export_data("imported from #{dir}")
puts "from :#{last_mark}" if last_mark
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
mark
end
# Loop through the directories
last_mark = nil
Dir.chdir(ARGV[0]) do
Dir.glob("*").each do |dir|
next if File.file?(dir)
# move into the target directory
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end
Wenn Sie dieses Skript ausführen, erhalten Sie eine Ausgabe, die ungefähr so aussieht:
$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <john@example.com> 1388649600 -0700
data 29
imported from back_2014_01_02deleteall
M 644 inline README.md
data 28
# Hello
This is my readme.
commit refs/heads/master
mark :2
committer John Doe <john@example.com> 1388822400 -0700
data 29
imported from back_2014_01_04from :1
deleteall
M 644 inline main.rb
data 34
#!/bin/env ruby
puts "Hey there"
M 644 inline README.md
(...)
Um den Importeur auszuführen, leiten Sie diese Ausgabe über git fast-import, während Sie sich im Git-Verzeichnis befinden, in das Sie importieren möchten. Sie können ein neues Verzeichnis erstellen und dann git init darin ausführen, um einen Ausgangspunkt zu haben, und dann Ihr Skript ausführen:
$ git init
Initialized empty Git repository in /opt/import_to/.git/
$ ruby import.rb /opt/import_from | git fast-import
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 5000
Total objects: 13 ( 6 duplicates )
blobs : 5 ( 4 duplicates 3 deltas of 5 attempts)
trees : 4 ( 1 duplicates 0 deltas of 4 attempts)
commits: 4 ( 1 duplicates 0 deltas of 0 attempts)
tags : 0 ( 0 duplicates 0 deltas of 0 attempts)
Total branches: 1 ( 1 loads )
marks: 1024 ( 5 unique )
atoms: 2
Memory total: 2344 KiB
pools: 2110 KiB
objects: 234 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit = 8589934592
pack_report: pack_used_ctr = 10
pack_report: pack_mmap_calls = 5
pack_report: pack_open_windows = 2 / 2
pack_report: pack_mapped = 1457 / 1457
---------------------------------------------------------------------
Wie Sie sehen können, gibt es bei erfolgreicher Ausführung eine Menge Statistiken darüber aus, was erreicht wurde. In diesem Fall haben Sie insgesamt 13 Objekte für 4 Commits in 1 Branch importiert. Jetzt können Sie git log ausführen, um Ihre neue Historie zu sehen:
$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <john@example.com>
Date: Tue Jul 29 19:39:04 2014 -0700
imported from current
commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <john@example.com>
Date: Mon Feb 3 01:00:00 2014 -0700
imported from back_2014_02_03
Da haben Sie es – ein schönes, sauberes Git-Repository. Es ist wichtig zu beachten, dass nichts ausgecheckt ist – Sie haben zunächst keine Dateien in Ihrem Arbeitsverzeichnis. Um sie zu erhalten, müssen Sie Ihren Branch zurücksetzen, wo master jetzt ist:
$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rb
Sie können mit dem fast-import-Tool noch viel mehr tun – verschiedene Modi, Binärdaten, mehrere Branches und Merges, Tags, Fortschrittsanzeigen und mehr. Eine Reihe von Beispielen für komplexere Szenarien finden Sie im Verzeichnis contrib/fast-import des Git-Quellcodes.