Якщо у вас не одна з вищенаведених систем, вам варто пошукати імпортер у мережі – для багатьох інших систем уже готові якісні імпортери, включно з CVS, Clear Case, Visual Source Safe, та навіть директорії архівів.
Якщо жоден з цих інструментів вам не годиться — ви маєте якусь дивну систему, або якщо через щось інше вам потрібен більш нетиповий процес імпортування, то варто скористатися git fast-import
.
Ця команда читає прості інструкції з stdin, щоб записати специфічні дані Git.
Набагато легше створювати обʼєкти Git таким чином, ніж виконувати звичайні команди Git чи намагатись писати двійкові обʼєкти (докладніше в ch10-git-internals.asc).
Таким чином, ви пишете скрипт імпортування, який читає необхідну інформацію зі системи, з якої ви імпортуєте та друкує зрозумілі інструкції до stdout.
Потім ви можете виконати цю програму та пропустити її вивід через 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 в принципі зберігає звʼязний список обʼєктів комітів, які вказують на відбиток зі своїм вмістом.
Усе, що вам треба зробити — сказати fast-import
, якими є відбитки вмісту, які дані комітів указують на них, та в якому вони порядку.
Вашою стратегією буде пройтись відбитками по одному за раз та створити коміти з вмістом кожної директорії, звʼязавши кожен коміт з попереднім.
Як ми робили в ch08-customizing-git.asc, ми напишемо це на Ruby, адже це те, з чим ми зазвичай працюємо, та його легко читати.
Ви можете написати цей приклад доволі легко будь-якою мовою, з якою знайомі – скрипт просто має виводити відповідну інформацію до stdout
.
І, якщо ви використовуєте Windows, це означає, що вам необхідно окремо попіклуватися про те, щоб не виводити символів повернення каретки наприкінці рядків — git fast-import
дуже вибагливо бажає лише зміни рядків (LF), а не повернення каретки та зміни рядків (CRLF), які використовує Windows.
Спочатку, треба перейти до цільової директорії та визначити всі піддиректорії, кожна з яких є відбитком, який ви бажаєте імпортувати як коміт. Ви перейдете до кожної піддиректорії та надрукуєте команди, необхідні для її експорту. Базовий головний цикл виглядає так:
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
в кожній директорії, який приймає маніфест та позначку попереднього відбитку, та повертає маніфест та позначку поточного; таким чином, ви можете їх правильно звʼязати.
`Позначка'' (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 <[email protected]>'
Тепер ви готові почати друкувати дані комітів для імпортера. Початкова інформація зазначає, що ви визначаєте обʼєкт коміту та в якій ви гілці, після чого йде позначка, яку ви згенерували, інформація про автора коміту та повідомлення коміту, а потім попередній коміт, якщо такий є. Код виглядає так:
# 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, розміру даних, які треба зчитати, нового рядка, та нарешті — даних.
Через те, що ви маєте використати такий саме формат, щоб задати вміст файлів пізніше, ви створюєте допоміжний метод export_data
:
def export_data(string)
print "data #{string.size}\n#{string}"
end
Усе, що залишилось — задати вміст файлів для кожного відбитку.
Це просто, оскільки у вас кожен міститься в окремій директорії – ви можете вивести команду deleteall
, після якої надати вміст кожного файлу в директорії.
Тоді Git запише кожен відбиток відповідно:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
Нотатка: Оскільки багато систем сприймають свої ревізії як зміни з попереднього коміту, fast-import також приймає команди, які задають для кожного коміту, які файли було додано, вилучено чи змінено, та який їхній новий вміст.
Ви могли б обчислити різницю між відбитками та надати лише її, проте зробити це було б складніше – ви також можете надавати Git всі дані та дозволити йому самому все зробити.
Якщо це доречніше для ваших даних, подивіться довідку (man page) 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 для символів нових рядків, у той час як git fast-import очікує лише LF.
Щоб обійти цю проблему та зробити $stdout.binmode |
Це все. Ось весь скрипт цілком:
#!/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
Якщо виконати цей скрипт, то отримаємо дані, що виглядають приблизно так:
$ 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
(...)
Щоб виконати імпортер, пропустіть цей вивід через 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 <[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
Те що треба – гарний, чистий репозиторій Git.
Важливо зазначити, що нічого не отримано (checked out) – у вас спочатку немає жодного файлу в робочій директорій.
Щоб отримати їх, ви маєте пересунути свою гілку до теперішнього 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.