Om ditt system inte är ett av ovanstående bör du leta efter ett importverktyg på nätet – det finns importverktyg av god kvalitet för många andra system, inklusive CVS, ClearCase, Visual SourceSafe och till och med kataloger med arkiv.
Om inget av dessa verktyg fungerar för dig, om du har ett mer obskyrt verktyg eller om du behöver en mer anpassad importprocess, bör du använda git fast-import.
Detta kommando läser enkla instruktioner från stdin för att skriva specifik Git‑data.
Det är mycket enklare att skapa Git‑objekt på det här sättet än att köra de råa Git‑kommandona eller försöka skriva de råa objekten (se ch10-git-internals.asc för mer information).
På så vis kan du skriva ett importskript som läser den nödvändiga informationen ur systemet du importerar från och skriver tydliga instruktioner till standardutmatningen.
Sedan kan du köra programmet och skicka dess utdata genom git fast-import.
För att snabbt demonstrera detta skriver du ett enkelt importskript.
Anta att du arbetar i current, att du säkerhetskopierar projektet genom att med jämna mellanrum kopiera katalogen till en tidsstämplad säkerhetskopia back_YYYY_MM_DD, och att du vill importera detta till Git.
Din katalogstruktur ser ut så här:
$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
currentFör att importera en Git‑katalog behöver du se över hur Git lagrar sin data.
Som du kanske minns är Git i grunden en länkad lista av incheckningsobjekt som pekar på en ögonblicksbild av innehåll.
Allt du behöver göra är att tala om för fast-import vilka ögonblicksbilder av innehåll som finns, vilken incheckningsdata som pekar på dem och i vilken ordning de kommer.
Din strategi blir att gå igenom ögonblicksbilderna en i taget och skapa incheckningar med innehållet i varje katalog, där varje incheckning länkar tillbaka till den föregående.
Som vi gjorde i ch08-customizing-git.asc skriver vi detta i Ruby, eftersom det är det vi vanligtvis arbetar med och det brukar vara lätt att läsa.
Du kan skriva exemplet ganska enkelt i vilket språk du är van vid – det behöver bara skriva rätt information till standardutmatningen.
Och om du kör på Windows betyder det att du måste vara extra noga med att inte introducera vagnreturer i slutet av dina rader – git fast-import vill absolut bara ha radmatningar (LF) och inte de vagnretur‑radmatningar (CRLF) som Windows använder.
Till att börja med byter du till målkatalogen och identifierar varje underkatalog, som var och en är en ögonblicksbild du vill importera som en incheckning. Du byter till varje underkatalog och skriver ut kommandona som behövs för att exportera den. Din grundläggande huvudloop ser ut så här:
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
endDu kör print_export inuti varje katalog, som tar manifestet och markeringen för den föregående ögonblicksbilden och returnerar manifestet och markeringen för den här; på så sätt kan du länka dem korrekt.
“Mark” är fast-import‑termen för en identifierare du ger en incheckning; när du skapar incheckningar ger du var och en en markering som du kan använda för att länka till den från andra incheckningar.
Så det första du gör i din print_export‑metod är att generera en markering från katalognamnet:
mark = convert_dir_to_mark(dir)Du gör detta genom att skapa en array av kataloger och använda indexvärdet som markering, eftersom en markering måste vara ett heltal. Din metod ser ut så här:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
endNu när du har en heltalsrepresentation av din incheckning behöver du ett datum för incheckningsmetadata.
Eftersom datumet uttrycks i katalognamnet plockar du ut det därifrån.
Nästa rad i din print_export‑fil är:
date = convert_dir_to_date(dir)där convert_dir_to_date är definierad så här:
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
endDetta returnerar ett heltal för datumet för varje katalog. Den sista metainformationen du behöver för varje incheckning är incheckardata, som du hårdkodar i en global variabel:
$author = 'John Doe <john@example.com>'Nu är du redo att börja skriva ut incheckningsdata för ditt importskript. Den inledande informationen anger att du definierar ett incheckningsobjekt och vilken gren det ligger på, följt av markeringen du har genererat, incheckarinformationen och incheckningsmeddelandet, och sedan den föregående incheckningen om det finns någon. Koden ser ut så här:
# 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 hårdkodar tidszonen (-0700) eftersom det är enkelt att göra. Om du importerar från ett annat system måste du ange tidszonen som ett offset. Incheckningsmeddelandet måste uttryckas i ett särskilt format:
data (size)\n(contents)Formatet består av ordet data, storleken på datan som ska läsas, en radbrytning och till sist datan.
Eftersom du behöver använda samma format för att ange filinnehållet senare skapar du en hjälpfunktion, export_data:
def export_data(string)
print "data #{string.size}\n#{string}"
endNu återstår bara att ange filinnehållet för varje ögonblicksbild.
Det är enkelt eftersom du har varje ögonblicksbild i en katalog – du kan skriva ut kommandot deleteall följt av innehållet i varje fil i katalogen.
Git registrerar sedan varje ögonblicksbild på rätt sätt:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
endObs: Eftersom många system ser sina revisioner som förändringar från en incheckning till en annan kan fast-import också ta emot kommandon med varje incheckning som anger vilka filer som har lagts till, tagits bort eller ändrats och vad det nya innehållet är.
Du skulle kunna beräkna skillnaderna mellan ögonblicksbilderna och bara tillhandahålla denna data, men det är mer komplext – du kan lika gärna ge Git all data och låta den lista ut det.
Om detta passar dina data bättre, se manualsidan för fast-import för detaljer om hur du tillhandahåller data på detta sätt.
Formatet för att lista det nya filinnehållet eller ange en modifierad fil med det nya innehållet är följande:
M 644 inline path/to/file
data (size)
(file contents)Här är 644 läget (om du har körbara filer behöver du upptäcka och ange 755 i stället), och inline säger att du listar innehållet direkt efter denna rad.
Din inline_data‑metod ser ut så här:
def inline_data(file, code = 'M', mode = '644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
endDu återanvänder metoden export_data som du definierade tidigare, eftersom den är densamma som när du angav data för incheckningsmeddelandet.
Slutligen returnerar du den aktuella markeringen så att den kan skickas vidare till nästa varv:
return mark|
Note
|
Om du kör på Windows behöver du se till att lägga till ett extra steg.
Som nämnts tidigare använder Windows CRLF för radslut medan $stdout.binmode |
Det var allt. Här är skriptet i sin helhet:
#!/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
endOm du kör detta skript får du innehåll som ser ut ungefär så här:
$ 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
(...)För att köra importskriptet skickar du denna utdata genom git fast-import medan du står i Git‑katalogen du vill importera till.
Du kan skapa en ny katalog och sedan köra git init i den som startpunkt, och sedan köra ditt skript:
$ 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
---------------------------------------------------------------------Som du ser, när den slutförs korrekt ger den dig en massa statistik över vad den åstadkom.
I det här fallet importerade du totalt 13 objekt för 4 incheckningar till en gren.
Nu kan du köra git log för att se din nya historik:
$ 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_03Så har du ett snyggt, rent Git‑kodförråd.
Tänk på att inget är utcheckat – du har inga filer i arbetskatalogen till att börja med.
För att få dem måste du återställa din gren till där master är nu:
$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rbDu kan göra mycket mer med verktyget fast-import – hantera olika lägen, binärdata, flera grenar och sammanslagningar, taggar, förloppsindikatorer och mer.
Flera exempel på mer komplexa scenarier finns i katalogen contrib/fast-import i Gits källkod.