Ако системата ви не е сред дотук разгледаните, може да потърсите импортиращ инструмент в Интернет — качествени такива са налични за CVS, Clear Case, Visual Source Safe, дори за директория от архиви.
Ако никой от тях не работи в конкретния случай или се нуждаете от по-специфичен процес на импортиране, тогава може да използвате git fast-import.
Тази команда чете прости инструкции от стандартния вход за да записва специфични Git данни.
Много по-лесно е да създавате Git обекти по този начин, вместо да използвате raw Git командите или да се опитвате да записвате raw обекти (вижте ch10-git-internals.asc за повече информация).
По този начин можете да напишете собствен импортиращ скрипт, който чете необходимата информация от системата, от която импортирате и печата последоветелно инструкции към стандартния изход.
Можете да пуснете програмата и да пренасочите изхода ѝ към git fast-import.
За демонстрация, ще напишем прост импортиращ инструмент.
Да приемем, че работите в директория current, редовно архивирате проекта си в отделни директории именувани back_YYYY_MM_DD според датата, и в един момент решавате да импортирате всичко това в Git хранилище.
Структурата на директориите изглежда така:
$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
currentЗа да импортираме Git директория, трябва да разгледаме как Git съхранява данните си.
Както може би помните, Git грубо казано е свързан списък от къмит обекти, които сочат към snapshot-и от съдържание.
Всичко, което трябва да укажете на fast-import е какво са snapshot-ите със съдържание, какви къмит данни сочат към тях и реда, по който идват.
Стратегията е да минаваме по snapshot-ите един след друг и да създаваме къмити със съдържанието на всяка директория свързвайки всеки къмит с предишния.
Както го направихме в ch08-customizing-git.asc, ще пишем скрипта си на Ruby.
Бихте могли да използвате езика, с който вие се чувствате комфортно — просто трябва да печатате съответната информация на stdout.
Освен това, ако сте под Windows, трябва да внимавате да не вмъквате carriage returns символи в края на редовете — git fast-import е чувствителен за това и очаква само line feeds (LF) а не carriage return line feeds (CRLF), което нормално се случва под Windows.
За начало, влизаме в съответната директория и идентифицираме всяка поддиректория, която ще бъде snapshot, който да импортираме. След това, печатаме командите, необходими за експорта. Основният цикъл изглежда по такъв начин:
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Изпълняваме print_export от всяка директория, което взема манифеста и маркировката на предишния snapshot и връща манифеста и маркировката на текущия. По този начин можем да ги свържем коректно.
Маркировката (“Mark”) във fast-import е термин за идентификатор, който давате на къмит. Когато създавате къмити, вие давате на всеки от тях маркировка, която може да се ползва за свързване към него от други къмити.
Така първото нещо, което трябва да направи метода print_export, е да генерира маркировка от името на директорията:
mark = convert_dir_to_mark(dir)Ще направим това създавайки масив от директориите и ще използваме индексите като маркировки, защото те трябва да са цели числа. Методът изглежда така:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
endСега имаме целочислено представяне на къмита и ни трябва дата за неговите метаданни.
Понеже казахме, че датата е отразена в името на директорията, ще я извлечем оттам.
Следващият ред от print_export файла е:
date = convert_dir_to_date(dir)където convert_dir_to_date се дефинира като:
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Това връща целочислена стойност за датата на всяка директория. Последната необходима част за метаданните е информация за автора, която ще хардкоднем в глобална променлива:
$author = 'John Doe <john@example.com>'Сега сме готови да започнем печатането на къмит данните за нашия importer. Началните данни твърдят, че създаваме къмит обект с данни за това в какъв клон е той, следвани от генерираната маркировка, информацията за автора и къмит съобщението и след това — предишния къмит, ако има такъв. Кодът изглежда така:
# 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Тайм зоната е твърдо зададена на (-0700) за улеснение. Ако импортираме от друга система, трябва да укажем тайм зоната като отместване. Къмит съобщението трябва да се представи в специален формат:
data (size)\n(contents)Форматът представлява последователност от думата data, размерът на данните за прочитане, нов ред, и накрая, самите данни.
Тук ще използваме helper метод наречен export_data, защото по-късно се нуждаем от същия формат за указване на съдържанието на файловете.
def export_data(string)
print "data #{string.size}\n#{string}"
endОстана да укажем файловото съдържание на всеки snapshot.
Това е лесно, защото имаме всеки един в директория — можете да отпечатате командата deleteall последвана от съдържанието на всеки файл в директорията.
Git след това ще запише съответно всеки snapshot:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
endЗаб.: Понеже много системи представят версиите си като промени между два къмита, fast-import може също така да приема команди с всеки къмит, които да указват кои файлове са били добавени, премахнати или модифицирани и какво е новото съдържание.
Можете да изчислите разликите между snapshot-ите и да подадете само тези данни, но това е по-сложно и може да оставите на Git да го свърши като просто подадете всички данни.
Ако все пак искате това да е ваша работа, погледнете документацията на fast-import за повече подробности как точно да я извършите.
Форматът за подаване на ново файлово съдържание или за модифицирано такова е както следва:
M 644 inline path/to/file
data (size)
(file contents)Тук, 644 е режимът за файла (ако имате изпълними такива, трябва да ги установите и да ги подадете като 755), а inline казва, че ще предоставите съдържанието веднага след този ред.
Методът inline_data изглежда така:
def inline_data(file, code = 'M', mode = '644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
endИзползваме метода export_data дефиниран по-рано, понеже форматът е като за данните на къмит съобщенията.
Последното нещо, което трябва да сторим е да върнем текущата маркировка, така че тя да бъде изпратена към следващата итерация:
return mark|
Note
|
Под Windows трябва да добавите допълнителна стъпка.
Както вече казахме, Windows използва CRLF за символите за край на ред, докато $stdout.binmode |
Това е. Ето целия скрипт:
#!/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Ако го изпълним, получаваме съдържание подобно на това:
$ 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
(...)За да стартираме importer-а, пренасочваме изхода през git fast-import докато сме в Git директорията, в която искаме да импортираме.
Може да създадем нова директория, да изпълним git init в нея за начална точка и да пуснем скрипта:
$ 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
---------------------------------------------------------------------Както се вижда, при успешен завършек получавате подробна статистика за извършените дейности.
В този случай сме импортирали 13 обекта за 4 къмита в 1 клон.
Сега може да изпълним git log за да видим новополучената история:
$ 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Получавате чисто ново Git хранилище.
Важно е да отбележим, че нищо не е извлечено на този етап — отначало работната директория е празна.
За да си получим файловете, трябва да върнем клона си там където е master в момента:
$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rbМожете да правите още много неща с fast-import инструмента — да обработвате различни режими, двоични данни, множество клонове и сливане, тагове, индикатори за прогрес и т.н.
Има много примери за по-сложни сценарии в директорията contrib/fast-import в сорс кода на Git.