Git
Chapters ▾ 2nd Edition

9.2 Git и другие системы контроля версий - Миграция на Git

Миграция на Git

Если у вас уже есть кодовая база в другой СКВ, но вы решили начать использовать Git, вам необходимо так или иначе перенести миграцию проекта. В этом разделе описаны некоторые существующие варианты импорта для распространённых систем, а затем показано, как разрабатывать собственные нестандартные варианты импорта. Вы узнаете, как импортировать данные из некоторых распространённых профессиональных СКВ. Поскольку они используются большинством разработчиков, для них легко найти качественные инструменты миграции.

Subversion

Если вы читали предыдущий раздел про использование git svn, вы уже должны знать, как использовать команду git svn clone чтобы склонировать Subversion репозиторий. После этого вы можете прекратить использовать Subversion и перейти на Git. Сразу же после клонирования вам будет доступная вся история репозитория, хотя сам процесс получения копии может затянуться.

В добавок к этому импортирование не идеально, так что вы, возможно, захотите сделать его как можно более правильно с первой попытки. И первая проблема — это информация об авторстве. В Subversion на каждого участника рабочего процесса заведён пользователь, информация о пользователе сохраняется вместе с каждой ревизией. В предыдущем разделе вы могли видеть пользователя schacon в некоторых местах, типа вывода команды blame или git svn log. Если вы хотите видеть подробную информацию об авторстве в Git, вам потребуется задать соответствие между пользователями Subversion и авторами в Git. Создайте файл users.txt со следующим содержимым:

schacon = Scott Chacon <schacon@geemail.com>
selse = Someo Nelse <selse@geemail.com>

Чтобы получить список имён пользователей в SVN, выполните следующее:

$ svn log --xml | grep author | sort -u | \
  perl -pe 's/.*>(.*?)<.*/$1 = /'

Эта команда генерирует XML документ, оставляет только строчки с авторами, избавляется от дубликатов, а затем обрезает XML-теги. (Очевидно, она сработает только на компьютерах с установленными grep, sort и perl.) Вы можете направить вывод этой команды в файл users.txt, а затем просто дописать Git авторов в каждой строке.

Затем вы можете передать этот файл git svn, чтобы тот мог проассоциировать авторов. Также вы можете указать git svn не включать метаданные, обычно вставляемые в сообщения коммитов, передав флаг --no-metadata командам clone или init. Итого, команда import примет вид:

$ git svn clone http://my-project.googlecode.com/svn/ \
      --authors-file=users.txt --no-metadata -s my_project

Теперь у вас будет красивая копия Subversion репозитория в директории my_project. Вместо коммитов типа

commit 37efa680e8473b615de980fa935944215428a35a
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date:   Sun May 3 00:12:22 2009 +0000

    fixed install - go to trunk

    git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de-
    be05-5f7a86268029

вы получите следующее:

commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date:   Sun May 3 00:12:22 2009 +0000

    fixed install - go to trunk

Теперь не только поле с информацией об авторстве выглядит лучше, но и git-svn-id не мозолит глаза.

Также вам следует немного "вычистить" репозиторий сразу после импорта. Во-первых, следует удалить ненужные ссылки, устанавливаемые git svn. Вначале переместим метки, чтобы они действительно стали метками, а не странными удалёнными ветками, а затем удалим остальное, сделав все ветки локальными.

Чтобы переместить метки, выполните следующие команды:

$ cp -Rf .git/refs/remotes/origin/tags/* .git/refs/tags/
$ rm -Rf .git/refs/remotes/origin/tags

Они берут все удалённые ветки, начинавшиеся с remotes/origin/tags/ и делают из них настоящие легковесные метки.

Далее, сделайте остальные ветки, начинающиеся с refs/remotes, локальными, выполнив следующее:

$ cp -Rf .git/refs/remotes/* .git/refs/heads/
$ rm -Rf .git/refs/remotes

Теперь все ветки и метки из Subversion стали настоящими Git ветками и метками соответственно. Последнее, что нужно сделать — это добавить ваш Git сервер в качестве удалённого репозитория и залить данные на него. Вот пример добавления удалённого репозитория:

$ git remote add origin git@my-git-server:myrepository.git

Так как вы хотите отправить все ваши ветки и метки, выполите это:

$ git push origin --all

Наконец, все ваши ветки и метки перенесены на Git сервер и облагорожены!

Mercurial

Из-за того что Mercurial и Git обладают похожей моделью ветвления, а также из-за того что Git несколько более гибок, перенос репозитория из Mercurial в Git довольно прост; можете использовать инструмент hg-fast-export, который можно найти здесь:

$ git clone http://repo.or.cz/r/fast-export.git /tmp/fast-export

Первым делом нужно получить полную копию интересующего Mercurial репозитория:

$ hg clone <remote repo URL> /tmp/hg-repo

Следующим шагом создадим файл соответствия авторов. Mercurial менее строг к данным об авторстве коммитов, так что придётся слегка навести порядок. Вот однострочник для bash, который сгенерирует заготовку:

$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors

Пройдёт несколько секунд, в зависимости от размера репозитория, и вы получите файл /tmp/authors со следующим содержимым:

bob
bob@localhost
bob <bob@company.com>
bob jones <bob <AT> company <DOT> com>
Bob Jones <bob@company.com>
Joe Smith <joe@company.com>

В примере выше, один и тот же человек (Боб) вносил изменения под пятью различными именами, лишь одно из которых правильное, а одно и вовсе не соответствует формату Git. hg-fast-export позволяет быстро исправить ситуацию, добавив ={new name and email address} к каждой строке, которую мы хотим изменить; чтобы оставить имя как есть, просто удалите нужные строки. Если же все имена выглядят хорошо, этот файл и вовсе не потребуется. В нашем примере мы хотим чтобы данные выглядели так:

bob=Bob Jones <bob@company.com>
bob@localhost=Bob Jones <bob@company.com>
bob jones <bob <AT> company <DOT> com>=Bob Jones <bob@company.com>
bob <bob@company.com>=Bob Jones <bob@company.com>

Затем нужно создать Git репозиторий и запустить экспорт:

$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors

Флаг -r указывает на подлежащий конвертации Mercurial репозиторий, а флаг -A задаёт файл с соответствиями между авторами. Скрипт пробегается по наборам изменений Mercurial и преобразует их в скрипт для fast-import в Git (мы поговорим об этом инструменте чуть позже). Процесс конвертации займёт некоторое время (хотя и намного меньше, чем при конвертации по сети), а мы пока можем наблюдать за подробным выводом в консоли:

$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[…]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[…]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects:     120000
Total objects:       115032 (    208171 duplicates                  )
      blobs  :        40504 (    205320 duplicates      26117 deltas of      39602 attempts)
      trees  :        52320 (      2851 duplicates      47467 deltas of      47599 attempts)
      commits:        22208 (         0 duplicates          0 deltas of          0 attempts)
      tags   :            0 (         0 duplicates          0 deltas of          0 attempts)
Total branches:         109 (         2 loads     )
      marks:        1048576 (     22208 unique    )
      atoms:           1952
Memory total:          7860 KiB
       pools:          2235 KiB
     objects:          5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit      = 8589934592
pack_report: pack_used_ctr            =      90430
pack_report: pack_mmap_calls          =      46771
pack_report: pack_open_windows        =          1 /          1
pack_report: pack_mapped              =  340852700 /  340852700
---------------------------------------------------------------------

$ git shortlog -sn
   369  Bob Jones
   365  Joe Smith

Вот, собственно, и всё. Все Mercurial метки были преобразованы в метки Git, а ветки и закладки — в ветки Git. Теперь можно отправить репозиторий на новый Git сервер:

$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all

Perforce

Следующей системой из которой мы импортируем репозиторий станет Perforce. Вы уже знаете, что существует два способа подружить Git и Perforce: git-p4 и Git Fusion.

Perforce Git Fusion

Git Fusion делает процесс переноса вполне безболезненным. Просто настройте проект, соответствия между пользователями и ветки в конфигурационном файле как показано в Git Fusion и клонируйте репозиторий. В результате вы получите настоящий Git репозиторий, который, при желании, можно сразу же отправлять на удалённый Git сервер. Вы даже можете использовать Perforce в качестве такового.

Git-p4

git-p4 также можно использовать для переноса репозитория. В качестве примера мы импортируем проект "Jam" из публичного депо Perforce.

Вначале нужно указать адрес депо в переменной окружения P4PORT.

$ export P4PORT=public.perforce.com:1666
Note

Для дальнейших экспериментов вам понадобится доступ к Perforce депо. Мы используем общедоступное депо на public.perforce.com, но вы можете взять любое другое, к которому у вас есть доступ.

Запустите команду git p4 clone чтобы импортировать проект "Jam" с Perforce сервера, передав ей путь к проекту в депо и директорию, в которую хотите импортировать репозиторий:

$ git-p4 clone //guest/perforce_software/jam@all p4import
Importing from //guest/perforce_software/jam@all into p4import
Initialized empty Git repository in /private/tmp/p4import/.git/
Import destination: refs/remotes/p4/master
Importing revision 9957 (100%)

Конкретно этот проект имеет одну ветку, но если бы их было несколько, вы бы просто могли передать флаг --detect-branches в git p4 clone. Перечитайте раздел Ветвление для подробностей.

На данном этапе репозиторий почти готов. Если вы зайдёте в директорию p4import и выполните git log, вы увидите результат:

$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@giles@perforce.com>
Date:   Wed Feb 8 03:13:27 2012 -0800

    Correction to line 355; change </UL> to </OL>.

    [git-p4: depot-paths = "//public/jam/src/": change = 8068]

commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <kwirth@perforce.com>
Date:   Tue Jul 7 01:35:51 2009 -0800

    Fix spelling error on Jam doc page (cummulative -> cumulative).

    [git-p4: depot-paths = "//public/jam/src/": change = 7304]

git-p4 оставил идентификаторы в сообщениях всех коммитов. Ничего страшного нет в том, чтобы оставить всё как есть, особенно если вы захотите сослаться на номер ревизии в Perforce в будущем. Если же вы хотите убрать эти строки, теперь — прежде чем приступать к работе с репозиторием — самое время для этого. Вы можете использовать git filter-branch чтобы удалить идентификаторы из всех сообщений одним махом:

$ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
Rewrite e5da1c909e5db3036475419f6379f2c73710c4e6 (125/125)
Ref 'refs/heads/master' was rewritten

Если вы сейчас выполните git log, вы увидите, что SHA-1 хеши коммитов изменились, а строки git-p4 исчезли из сообщений:

$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@giles@perforce.com>
Date:   Wed Feb 8 03:13:27 2012 -0800

    Correction to line 355; change </UL> to </OL>.

commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <kwirth@perforce.com>
Date:   Tue Jul 7 01:35:51 2009 -0800

    Fix spelling error on Jam doc page (cummulative -> cumulative).

Теперь ваш репозиторий готов к отправке на Git сервер.

TFS

Если вы переходите с TFVC на Git, вам захочется получить как можно более точную копию репозитория. Поэтому, несмотря на то, что мы рассматривали git-tfs и git-tf в предыдущих разделах, здесь мы сосредоточимся лишь на использовании git-tfs, потому что этот инструмент поддерживает ветки, чего нет в git-tf.

Note

Это дорога в один конец. Получившийся Git репозиторий невозможно будет подключить к TFVC.

Первым делом нужно задать соответствия между пользователями. TFVC не следит за данными, сохраняемыми в поле "автор" наборов изменений, Git же ожидает увидеть там человекопонятное имя и адрес электронной почты. Вы можете получить список всех авторов с помощью консольного клиента tf:

PS> tf history $/myproject -recursive > AUTHORS_TMP

Эта команда пробегается по всем ревизиям проекта и сохраняет информацию о них в файл AUTHORS_TMP, из которого мы впоследствии вытянем пользователей (2-я колонка). Откройте этот файл и запомните начало и конец колонки с пользователями, а затем используйте следующую команду (параметр 11-20 — это и есть границы колонки с пользователями):

PS> cat AUTHORS_TMP | cut -b 11-20 | tail -n+3 | uniq | sort > AUTHORS

Команда cut оставляет символы с 11-го по 20-й из каждой строки. Команда tail пропускает первые две строки с заголовком и ASCII-art’ом. Результат направляется в команду uniq, которая избавляется от дубликатов, её вывод сортируется и сохраняется в файл AUTHORS. Далее необходимо поработать руками: для того, чтобы git-tfs распознал записи в этом файле, они должны иметь следующий формат:

DOMAIN\username = User Name <email@address.com>

Часть слева от знака равенства — это поле "User" из TFVC, а часть справа — соответствующий ему автор в Git.

Как только этот файл готов, необходимо сделать полную копию TFVC проекта:

PS> git tfs clone --with-branches --authors=AUTHORS https://username.visualstudio.com/DefaultCollection $/project/Trunk project_git

Затем вы, возможно, захотите избавиться от строчек с git-tfs-id в сообщениях коммитов. Следующая команда сделает это:

PS> git filter-branch -f --msg-filter 'sed "s/^git-tfs-id:.*$//g"' -- --all

Она использует утилиту sed из пакета git-bash чтобы заменить все строки, начинающиеся с git-tfs-id: на пустые, которые Git проигнорирует.

Теперь всё готово. Можете добавить новый удалённый репозиторий, отправить изменения в него и ваша команда может начинать работу с Git.

Импорт произвольного репозитория

Если вы пользуетесь какой-либо другой системой контроля версий, не перечисленной выше, вам следует поискать инструмент для импорта в Сети — качественные решения доступны для CVS, Clear Case, Visual Source Safe и даже директорий с архивами. Если всё же существующие решения вам не подошли, вы пользуетесь менее известной СКВ или вам нужно больше контроля над процессом импорта — используйте git fast-import. Эта команда читает простые инструкции из потока ввода и записывает данные в Git. Создать Git-объекты таким путём намного проще, чем через низкоуровневые Git-команды или пытаясь воссоздать их вручную (обратитесь к Git изнутри за деталями). Таким образом, вы можете написать небольшой скрипт, считывающий нужную информацию из вашего хранилища и выводящий инструкции в стандартный поток вывода. Затем вы можете запустить эту программу и передать её вывод прямиком в 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 по сути представляет собой связанный список ревизий, каждая из которых указывает на слепок состояния. Всё что от вас требуется, это указать fast-import'у на данные для создания слепков и порядок их применения. Итак, мы пробежимся по всем слепкам, создадим коммит для каждого из них и свяжем каждый новый коммит с предыдущим.

Как и в разделе An Example Git-Enforced Policy, мы проделаем это на Ruby, потому что это мы любим этот язык и его понять. Вы можете использовать любой другой язык — всё что требуется, это вывести нужную информацию в стандартный поток вывода. Если вы работаете на Windows, будьте особо осторожными с переводами строк: 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 = 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)

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

Этот код вернёт целочисленное представление даты для каждой директории. И последний кусочек мозаики: автор изменений. Это значение жёстко задано в глобальной переменной:

$author = 'John Doe <john@example.com>'

Теперь всё готово для вывода нужной fast-import'у информации. Нужно указать, что создаётся коммит на определённой ветке, затем вывести сгенерированную метку, автора и время изменений и ссылку на предыдущий коммит, если такой имеется. Код выглядит следующим образом:

# 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 прямо в выходной строке. Часовой пояс задаётся как смещение от UTC. Сообщение коммита задаётся следующим образом:

data (size)\n(contents)

Первым идёт слово 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 имеет команды для задания изменений: какие файлы были добавлены, удалены или изменены. Вы можете вычислять разницу между состояниями и передавать её в fast-import, но это довольно сложно, гораздо проще передавать 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 для новых строк, в то время как git fast-import ожидает только LF. Чтобы исправить этот недостаток Windows и осчастливить git fast-import, просто прикажите Ruby использовать LF вместо 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
(...)

Чтобы импортировать репозиторий перенаправьте этот вывод в команду 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
---------------------------------------------------------------------

Как вы можете видеть, после успешного завершения fast-import выводит некоторую статистику о проделанной работе. В этом случае, вы импортировали 13 объектов в 4-х коммитах одной ветки. Теперь можете выполнить 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 гораздо шире описанного: он поддерживает права доступа к файлам, двоичные файлы, множественные ветки и их слияния, метки, индикатор прогресса и ещё кучу вещей. Несколько примеров более сложных сценариев использования fast-import можно найти в директории contrib/fast-import исходного кода Git.