Wenn dein System nicht zu den vorgenannten gehört, solltest du online nach einer Import-Schnittstelle suchen – hochwertige Importer sind für viele andere Systeme verfügbar, darunter CVS, Clear Case, Visual Source Safe, sogar für ein Verzeichnis von Archiven.
Wenn keines dieser Tools für dich geeignet ist, du ein obskures Tool nutzt oder anderweitig einen benutzerdefinierten Importprozess benötigst, solltest du git fast-import verwenden.
Dieser Befehl liest die einfachen Anweisungen von „stdin“ aus, um bestimmte Git-Daten zu schreiben.
Es ist viel einfacher, Git-Objekte auf diese Weise zu erstellen, als die Git-Befehle manuell auszuführen oder zu versuchen, Raw-Objekte zu erstellen (siehe Kapitel 10, Git Interna für weitere Informationen).
Auf diese Weise kannst du ein Import-Skript schreiben, das die notwendigen Informationen aus dem System liest, aus dem du importierst, und Anweisungen direkt auf „stdout“ ausgibt.
Du kannst dann dieses Programm ausführen und seine Ausgaben über git fast-import pipen.
Um das kurz zu demonstrieren, schreibe eine einfache Import-Anweisung.
Angenommen, du arbeitest im current Branch, du sicherst dein Projekt, indem du das Verzeichnis gelegentlich in ein mit Zeitstempel versehenes back_YYYY_MM_DD Backup-Verzeichnis kopieren und dieses in Git importieren möchtest.
Deine Verzeichnisstruktur sieht wie folgt aus:
$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
currentDamit du ein Git-Verzeichnis importieren kannst, musst du dir ansehen, wie Git seine Daten speichert.
Wie du dich vielleicht erinnerst, ist Git im Grunde genommen eine verknüpfte Liste von Commit-Objekten, die auf einen Schnappschuss des Inhalts verweisen.
Alles, was du tun musst, ist fast-import mitzuteilen, worum es sich bei den Content-Snapshots handelt, welche Commit-Datenpunkte zu ihnen gehören und in welcher Reihenfolge sie in den jeweiligen Ordner gehören.
Deine Strategie besteht darin, die Snapshots einzeln durchzugehen und Commits mit dem Inhalt jedes Verzeichnisses zu erstellen. Dabei wird jeder Commit mit dem vorherigen verknüpft.
Wie wir es in Kapitel 8, Beispiel für Git-forcierte Regeln getan haben, werden wir das in Ruby schreiben, denn damit arbeiten wir normalerweise und es ist eher leicht zu lesen.
Du kannst dieses Beispiel sehr leicht in jedem Editor schreiben, den du kennst – er muss nur die entsprechenden Informationen nach stdout ausgeben können.
Unter Windows musst du besonders darauf achten, dass du am Ende deiner Zeilen keine Zeilenumbrüche einfügst – git fast-import ist da sehr empfindlich, wenn es darum geht, nur Zeilenvorschübe (LF) und nicht die von Windows verwendeten Zeilenvorschübe (CRLF) zu verwenden.
Zunächst wechselst du in das Zielverzeichnis und identifizierst jene Unterverzeichnisse, von denen jedes ein Snapshot ist, den du als Commit importieren möchtest. Du wechselst in jedes Unterverzeichnis und gibst die für den Export notwendigen Befehle aus. Deine 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
endFühre print_export in jedem Verzeichnis aus, das das Manifest und die Markierung des vorherigen Snapshots enthält und das Manifest und die Markierung dieses Verzeichnisses zurückgibt. So kannst du sie richtig verlinken.
„Mark“ ist der fast-import Begriff für eine Kennung, die du einem Commit mitgibst. Wenn du Commits erstellst, gibst du jedem eine Markierung, mit dem du ihn von anderen Commits aus verlinken kannst.
Daher ist das Erste, was deine print_export Methode tut, die Generierung einer Markierung aus dem Verzeichnisnamen:
mark = convert_dir_to_mark(dir)Du wirst dazu ein Array von Verzeichnissen erstellen und den Indexwert als Markierung verwenden. Eine Markierung muss nämlich eine Ganzzahl (Integer) sein. Deine Methode sieht so aus:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
endNachdem du nun eine ganzzahlige Darstellung deines Commits hast, benötigst du ein Datum für die Commit-Metadaten.
Das Datum wird im Namen des Verzeichnisses ausgewiesen, daher wirst du es auswerten.
Die nächste Zeile in deiner print_export Datei lautet:
date = convert_dir_to_date(dir)wobei convert_dir_to_date definiert ist als:
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
endDas gibt einen ganzzahligen Wert für das Datum jedes Verzeichnisses zurück. Die letzte Meta-Information, die du für jeden Commit benötigst, sind die Committer-Daten, die du in einer globalen Variable hartkodierst:
$author = 'John Doe <john@example.com>'Damit bist du startklar für die Ausgabe der Commit-Daten für deinen Importer. Die ersten Informationen beschreiben, dass du ein Commit-Objekt definierst und in welchem Branch es sich befindet, gefolgt von der Markierung, die du generiert hast, den Committer-Informationen und der Commit-Beschreibung und dann, falls vorhanden, der vorherige Commit. Der Code sieht jetzt 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_markDu kannst die Zeitzone (-0700) hartkodieren, da das einfach ist. Wenn du sie aus einem anderen System importierst, musst du die Zeitzone als Offset angeben. Die Commit-Beschreibung muss in einem speziellen Format ausgegeben werden:
data (size)\n(contents)Das Format besteht aus den Wortdaten, der Größe der zu lesenden Daten, einer neuen Zeile und schließlich den Daten.
Da du später das gleiche Format verwenden musst, um den Datei-Inhalt festzulegen, erstellst du mit export_data eine Hilfs-Methode:
def export_data(string)
print "data #{string.size}\n#{string}"
endNun musst du nur noch den Dateiinhalt für jeden Schnappschuss angeben.
Das ist einfach, denn du hast jeden in einem eigenen Verzeichnis. Du kannst den Befehl deleteall ausgeben, gefolgt vom Inhalt jeder Datei im Verzeichnis.
Git zeichnet dann jeden Schnappschuss entsprechend auf:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
endHinweis: Da viele Systeme ihre Revisionen als Änderungen von einem Commit zum anderen betrachten, kann fast-import auch Befehle mit jedem Commit übernehmen, um anzugeben, welche Dateien hinzugefügt, entfernt oder geändert wurden und was die neuen Inhalte sind.
Du kannst die Unterschiede zwischen den Snapshots berechnen und nur diese Daten bereitstellen, aber das ist komplizierter – in diesem Fall solltest du Git alle Daten übergeben und sie auswerten lassen.
Sollte diese Option für deine Daten besser geeignet sein, informiere dich in der fast-import Man-Page, wie du deine Daten auf diese Weise bereitstellen kannst.
Das Format für die Auflistung des neuen Datei-Inhalts oder die Angabe einer modifizierten Datei mit dem neuen Inhalt lautet wie folgt:
M 644 inline path/to/file
data (size)
(file contents)Im Beispiel ist es der Modus 644 (wenn du ausführbare Dateien hast musst du stattdessen 755 ermitteln und spezifizieren), und inline besagt, dass der Inhalt unmittelbar nach dieser Zeile aufgelistet wird.
Das Verfahren 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)
endBei der Wiederverwendung der Methode export_data, die du zuvor definiert hast, handelt es sich um das gleiche Verfahren wie bei der Angabe deiner Commit-Message-Daten.
Als Letztes musst du die aktuelle Markierung an das System zurückgeben, damit sie an die nächste Iteration weitergegeben werden kann:
return mark|
Note
|
Wenn du unter Windows arbeitest, musst du unbedingt einen zusätzlichen Arbeitsschritt hinzufügen.
Wie bereits erwähnt, verwendet Windows CRLF für Zeilenumbrüche, während $stdout.binmode |
Das war' s. Das Skript ist jetzt komplett:
#!/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
endWenn du dieses Skript ausführst, solltest du eine Ausgabe wie die folgende erhalten:
$ 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 Importer aufzurufen, übergibst du diese Output-Pipe an git fast-import, während du dich in dem Git-Verzeichnis befindest, in das du importieren willst.
Du kannst ein neues Verzeichnis erstellen und dort git init für einen Anfangspunkt ausführen und danach dein 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 du siehst, gibt es nach erfolgreichem Abschluss eine Reihe von Statistiken über den erreichten Status.
In diesem Fall hast du 13 Objekte mit insgesamt 4 Commits in einen Branch importiert.
Jetzt kannst du git log ausführen, um deine 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_03So ist es richtig – ein ordentliches, sauberes Git-Repository.
Es ist wichtig zu beachten, dass nichts ausgecheckt ist – du hast zunächst keine Dateien in deinem Arbeitsverzeichnis.
Um sie zu erhalten, musst du deinen Branch auf den aktuellen master zurücksetzen:
$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rbMit dem fast-import Tool kannst du noch viel mehr tun – bearbeiten von unterschiedlichen Modi, binären Daten, multiplen Branches und Merges, Tags, Verlaufsindikatoren und mehr.
Eine Reihe von Beispielen für komplexere Szenarien findest du im contrib/fast-import Verzeichnis des Git-Quellcodes.