Skip to content

Latest commit

 

History

History
executable file
·
372 lines (308 loc) · 17.8 KB

import-custom.asc

File metadata and controls

executable file
·
372 lines (308 loc) · 17.8 KB

Нетипове імпортування

Якщо у вас не одна з вищенаведених систем, вам варто пошукати імпортер у мережі – для багатьох інших систем уже готові якісні імпортери, включно з 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. Щоб обійти цю проблему та зробити git fast-import щасливим, треба сказати ruby використовувати LF замість CRLF:

$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.