Als jouw systeem niet een van de bovenstaande is, moet je op het internet gaan zoeken naar een importeerder - goede importeerders zijn beschikbaar voor vele andere systemen, inclusief CVS, Clear Case, Visual Source Safe en zelfs een directory met archieven.
Als geen van die tools voor jou geschikt zijn, je hebt een heel obscure tool, of je hebt om een andere reden een meer aangepaste importeer proces nodig, dan moet je git fast-import
gebruiken.
Dit commando leest eenvoudige instructies van stdin om specifieke Git gegevens te schrijven.
Het is veel eenvoudiger om op deze manier Git objecten aan te maken dan de rauwe Git commando’s te gebruiken of om zelf de rauwe objecten te schrijven (zie ch10-git-internals.asc voor meer informatie).
Op deze manier kan je een import script schrijven die de benodigde informatie uit het te importeren systeem leest en op stdout eenvoudige instructies afdrukt.
Je kunt dit programma dan aanroepen en de uitvoer doorgeven aan git fast-import
met een pipe-instructie.
Om een snelle demonstratie te geven, ga je een eenvoudige importeerder schrijven.
Stel dat je in current
aan het werk bent, je maakt op gezette tijden een backup van je project door de directory naar een andere te kopieren met een datum-tijd indicatie genaamd back_YYYY_MM_DD
, en je wilt dit importeren in Git.
Je directory-structuur ziet er zo uit:
$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current
Om een Git directory te importeren, moet je weten hoe Git haar gegevens opslaat.
Zoals je je zult herinneren, is Git in de basis een geschakelde lijst van commit objecten die verwijzen naar een snapshot van de inhoud.
Alles wat je hoeft te doen is fast-import
te vertellen wat de snapshots van de inhoud zijn, welke commit-gegevens hiernaar verwijzen en de volgorde waarin ze staan.
Je strategie zal zijn om een voor een door de snapshots te gaan en commits te maken met de inhoud van elke directory, en elke commit terug te laten verwijzen naar de vorige.
Zoals we in ch08-customizing-git.asc gedaan hebben, schrijven we dit in Ruby, omdat dit is waar we gewoonlijk mee werken en het redelijk eenvoudig te lezen is.
Je kunt dit voorbeeld redelijk eenvoudig in iets schrijven waar je bekend mee bent - het moet gewoon de juiste informatie naar stdout
uitvoeren.
En als je op Windows draait, betekent dit dat je ervoor moet zorgen dat je geen carriage returns aan het eind van je regels gebruikt - git fast-import is erg secuur in het alleen accepteren van line feeds (LF) niet de carriage return line feeds (CFLF) die door Windows wordt gebruikt.
Om te beginnen, spring je in de doel directory en identificeert elke subdirectory, die elk een snapshot is van wat je wilt importeren als een commit. Je springt in elke subdirectory en drukt de commando’s af die nodig zijn om het te exporteren. Je hoofdlus ziet er zo uit:
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
Je roept print_export
in elke directory aan, die de inhoud en kenmerk (mark) van de vorige snapshot neemt en je de inhoud en kenmerk van deze teruggeeft; op die manier kan je ze goed koppelen.
`Mark'' is de term die `fast-import
gebruikt voor een identificatie die je aan een commit geeft; tijdens het aanmaken van commit geef je elk een kenmerk die je kunt gebruiken om als koppeling vanaf andere commits.
Dus, het eerste wat je moet doen in je print_export
methode is een kenmerk genereren van de naam van de directory:
mark = convert_dir_to_mark(dir)
Je doet dit door een reeks (array) directories te maken en de indexwaarde als het kenmerk te gebruiken, omdat een kenmerk een integer dient te zijn. Je methode ziet er zo uit:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
end
Nu je een integer representatie hebt van je commit, heb je een datum nodig voor de commit metadata.
Omdat de datum in de naam van de directory zit, ga je het eruit halen.
De volgende regel in je print_export
bestand is:
date = convert_dir_to_date(dir)
where convert_dir_to_date
is defined as:
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
Dat retourneert een integerwaarde voor de datum van elke directory. Het laatste stukje meta-informatie dat je nodig hebt voor elke commit zijn de gegevens van de committer, die je hardgecodeerd hebt in een globale variabele:
$author = 'John Doe <[email protected]>'
Nu ben je klaar om de commit data af te drukken voor je importeerder. De initiële informatie geeft aan dat je een commit object definieert en op welke branch deze staat, gevolgd door het kenmerk die je hebt gegenereert, de informatie van de committer en het commit bericht, en dan de vorige commit als die er is. De code ziet er zo uit:
# 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
Je geeft de tijdzone (-0700) hardgecodeerd, omdat dit nu eenmaal makkelijk is. Als je vanuit een ander systeem importeert, moet je de tijdzone als een relatieve verschuiving (offset) weergeven. Het commit bericht moet worden uitgedrukt in een speciaal formaat:
data (size)\n(contents)
Het formaat bestaat uit het woord data, de grootte van de te lezen gegevens, een nieuwe regel en tot slot de gegevens.
Omdat je hetzelfde formaat later nodig hebt om de bestandsinhoud te specificeren, maak je een hulpmethode, export_data
:
def export_data(string)
print "data #{string.size}\n#{string}"
end
Wat er nu nog overblijft is het specifieren van de bestandsinhoud voor elk snapshot.
Dit is makkelijk, omdat je elk van deze in een directory hebt - je kunt het deleteall
commando afdrukken gevolgd door de inhoud van elk bestand in de directory.
Git zal dan elke snapshot op de juiste manier opslaan:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
Let op: Omdat veel systemen hun revisies zien als wijzigingen van de ene commit naar de ander, kan fast-import ook commando’s accepteren waar bij elke commit kan worden aangegeven welke bestanden er zijn toegevoegd, verwijderd of gewijzigd en welke nieuwe elementen erbij zijn gekomen.
Je kunt de verschillen tussen de snapshots berekenen en alleen deze gegevens meegeven, maar het is veel ingewikkelder om dit te doen - je kunt net zo makkelijk Git alle gegevens meegeven en het hem laten uitzoeken.
Als dit beter past bij jouw gegevens, neem dan de fast-import
man page door voor de details hoe je deze gegevens moet doorgeven in dat geval.
Het formaat om de nieuwe bestandsinhoud weer te geven of om een gewijzigd bestand te specificeren met de nieuwe inhoud is als volgt:
M 644 inline path/to/file
data (size)
(file contents)
Hier is 644 de mode (als je aanroepbare bestanden hebt, moet je dit detecteren en in plaats daarvan 755 opgeven), en in de code (inline) geef je de inhoud direct achter deze regel weer.
Je inline_data
methode ziet er zo uit:
def inline_data(file, code = 'M', mode = '644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
Je hergebruikt de export_data
methode die je eerder hebt gedefinieerd, omdat dit formaat hetzelfde is als waarmee je de commit bericht gegevens specificeert.
Het laatste wat je nog moet doen is om het huidige kenmerk te retourneren, zodat het kan worden doorgegeven in de volgende iteratie:
return mark
Note
|
Als je op Windows draait moet je je ervan verzekeren dat je een extra stap toevoegt.
Zoals eerder opgemerkt, gebruikt Windows CRLF voor nieuwe regel tekens waar $stdout.binmode |
Dat is 't. Hier is het script in zijn totaliteit:
#!/usr/bin/env ruby
$stdout.binmode
$author = "John Doe <[email protected]>"
$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
Als je dit script aanroept, krijg je inhoud dat er ongeveer zo uit ziet:
$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <[email protected]> 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 <[email protected]> 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
(...)
Om de importeerder te laten draaien, geeft je de uitvoer met een pipe door aan git fast-import
terwjil je in de Git directory staat waar je naartoe wilt importeren.
Je kunt een nieuwe directory aanmaken en daarna git init
daarin aanroepen als een begin, en daarna je script aanroepen:
$ 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
---------------------------------------------------------------------
Zoals je kunt zien, als het succesvol eindigt, geeft het je een bergje statistieken wat het allemaal heeft bereikt.
In dit geval, heb je in totaal 13 objecten geïmporteerd voor 4 commits in 1 branch.
Je kunt nu git log
aanroepen om je nieuwe historie te bekijken:
$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <[email protected]>
Date: Tue Jul 29 19:39:04 2014 -0700
imported from current
commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <[email protected]>
Date: Mon Feb 3 01:00:00 2014 -0700
imported from back_2014_02_03
Kijk eens aan - een mooie, schone Git repository.
Het is belangrijk om op te merken dat er niets is uitgecheckt - je hebt initieel nog geen enkel bestand in je werk directory.
Om deze te krijgen, moet je je branch resetten tot waar master
nu is:
$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rb
Je kunt nog veel meer doen met het fast-import
gereedschap - verschillende modes behandelen, binaire gegevens, meerdere branches en mergen, tags, voortgangs-indicators en meer.
Een aantal voorbeelden van meer ingewikkelde scenarios zijn beschikbaar in de contrib/fast-import
directory van de Git broncode.