Git
Chapters ▾ 2nd Edition

9.2 Git и другие системы контроля версий - Переход на 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 --quiet | grep author | sort -u | \
  perl -pe 's/.*>(.*?)<.*/$1 = /'

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

Примечание

Если вы пытаетесь выполнить это на компьютере с Windows, то у вас может возникнуть ряд проблем. Однако, Microsoft предоставила несколько полезных советов по миграции https://docs.microsoft.com/en-us/azure/devops/repos/git/perform-migration-from-svn-to-git.

Для точного сопоставления авторов коммитов передайте файл users.txt команде git svn. Добавив флаг --no-metadata в команды clone или init, можно указать git svn исключить импорт метаданных, которые импортируются по умолчанию. Как часть метаданных, git-svn-id включается в каждое сообщение коммита, генерируемое Git при импорте, что может привести к необоснованному увеличению истории и сделать её более запутанной.

Примечание

Метаданные следует сохранять, если вы планируете отправлять коммиты из Git обратно в SVN репозиторий. Если полной синхронизации не требуется, то спокойно добавляйте параметр --no-metadata.

В результате, команда import примет вид:

$ git svn clone http://my-project.googlecode.com/svn/ \
      --authors-file=users.txt --no-metadata --prefix "" -s my_project
$ cd 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

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

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

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

$ for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git tag ${t/tags\//} $t && git branch -D -r $t; done

Эта команда берёт ссылки на удалённые ветки, которые располагаются в refs/remotes/tags/, и делает их настоящими (легковесными) тегами.

Затем, переместим оставшиеся ссылки из refs/remotes, чтобы сделать из них локальные ветки:

$ for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done

Возможно, для одной ветки Subversion будут созданы дополнительные ветки с суффиксом @xxx (где ххх — это число). Это связано с особенностью Subversion, которая называется «peg-revisions», для которой Git не имеет синтаксического аналога. Поэтому, git svn просто добавляет номер версии svn в название ветки, точно так же как вы бы это сделали в svn при добавлении peg-revision для ветки. Если эти ревизии вам больше не нужны, то просто удалите их используя команду:

$ for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done

Теперь все ветки стали настоящими Git ветками, а теги — настоящими Git тегами.

К сожалению, git svn создаёт дополнительную ветку с названием trunk, которая соответствует ветке по умолчанию в Subversion и аналогична ветке master. Так как master больше подходит для Git, удалим лишнюю ветку:

$ git branch -d trunk

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

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

Так как вы хотите отправить все ваши ветки и теги, выполите следующие команды:

$ git push origin --all
$ git push origin --tags

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

Mercurial

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

$ git clone https://github.com/frej/fast-export.git

Первым делом нужно получить полную копию интересующего 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 позволяет быстро исправить ситуацию, преобразовав каждую строку в правило: "<input>"="<output>", где <input> преобразуется в <output>. Строки <input> и <output> могут содержать экранированные последовательности, поддерживаемые кодировкой python string_escape. Если файл сопоставлений авторов коммитов не содержит соответствующего <input>, то значение будет передано Git без модификации. Если же все имена выглядят хорошо, этот файл и вовсе не потребуется. В нашем примере мы хотим чтобы файл выглядел так:

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

Аналогичные файлы применяются для переименования веток и тегов, когда сохранённое в Mercurial название недопустимо в Git.

Затем нужно создать 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

Bazaar

Bazaar — это распределённая система контроля версий очень похожая на Git, поэтому репозиторий Bazaar достаточно легко сконвертировать в репозиторий Git. Для этого вам необходимо подключить плагин bzr-fastimport.

Установка плагина bzr-fastimport

Для UNIX подобных систем и Windows процедура установки плагина отличается. В первом случае, самый простой способ это установить пакет bzr-fastimport, вместе с которым будут установлены все необходимые зависимости.

Например, для Debian и подобных, следует выполнить:

$ sudo apt-get install bzr-fastimport

Для RHEL выполните следующую команду:

$ sudo yum install bzr-fastimport

Для Fedora, начиная с версии 22, новый менеджер пакетов dnf:

$ sudo dnf install bzr-fastimport

Если пакет отсутствует в репозитории для вашего дистрибутива, то вы можете установить его как плагин, используя следующие команды:

$ mkdir --parents ~/.bazaar/plugins     # создаст необходимые каталоги для плагинов
$ cd ~/.bazaar/plugins
$ bzr branch lp:bzr-fastimport fastimport   # импортирует плагин fastimport
$ cd fastimport
$ sudo python setup.py install --record=files.txt   # установит плагин

Чтобы плагин заработал, вам понадобится модуль Python fastimport. Проверить наличие и установить его можно следующими командами:

$ python -c "import fastimport"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named fastimport
$ pip install fastimport

Если модуль недоступен, то его можно скачать по адресу https://pypi.python.org/pypi/fastimport/.

Во втором случае (в Windows), bzr-fastimport устанавливается автоматически при стандартной установке (все галочки отмечены). В таком случае дальнейших действий не требуется.

Процесс импорта Bazaar репозитория отличается в зависимости от того одна ветка в вашем репозитории или несколько.

Проект с одной веткой

Войдите в каталог, содержащий ваш Bazaar репозиторий и проинициализируйте Git репозиторий:

$ cd /path/to/the/bzr/repository
$ git init

Теперь, просто экспортируйте свой Bazaar репозиторий и сконвертируйте его в Git репозиторий используя следующую команду:

$ bzr fast-export --plain . | git fast-import

В зависимости от размера проекта, Git репозиторий будет готов через несколько секунд или минут.

Проект с основной и рабочей ветками

Вы так же можете импортировать Bazaar репозиторий с несколькими ветками. Предположим, что в вашем репозитории две ветки: одна является основной веткой проекта (myProject.trunk), другая — рабочей (myProject.work).

$ ls
myProject.trunk myProject.work

Проинициализируйте Git репозиторий и перейдите в его каталог:

$ git init git-repo
$ cd git-repo

Импортируйте в Git основную ветку с помощью команды:

$ bzr fast-export --export-marks=../marks.bzr ../myProject.trunk | \
git fast-import --export-marks=../marks.git

Импортируйте в Git рабочую ветку с помощью команды:

$ bzr fast-export --marks=../marks.bzr --git-branch=work ../myProject.work | \
git fast-import --import-marks=../marks.git --export-marks=../marks.git

Теперь, команда git branch покажет вам две ветки: master и work. Проверьте логи, чтобы убедиться в отсутствии ошибок, после этого можно удалить файлы marks.bzr и marks.git.

Синхронизация индекса

Вне зависимости от количества веток и выбранного метода импорта, индекс не синхронизируется с HEAD, а при импорте нескольких веток — так же не синхронизируется рабочий каталог. Эту ситуацию можно легко исправить следующей командой:

$ git reset --hard HEAD

Игнорирование файлов из .bzrignore

Теперь давайте посмотрим на файлы, которые следует игнорировать. Первое, что нужно сделать — это переименовать .bzrignore в .gitignore. Если файл .bzrignore содержит одну или несколько строк начинающихся с !! или RE:, нужно их изменить и, возможно, создать несколько файлов .gitignore, чтобы заставить Git игнорировать точно те же файлы, которые игнорируются Bazaar.

Наконец, создайте коммит со всеми изменениями, внесёнными во время миграции:

$ git mv .bzrignore .gitignore
$ # modify .gitignore if needed
$ git commit -am 'Migration from Bazaar to Git'

Отправка репозитория на сервер

Вот и всё! Теперь вы можете отправить репозиторий на сервер в его новый дом:

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

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

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
Примечание

Для дальнейших экспериментов вам понадобится доступ к 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 сервер.

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

Если вы пользуетесь какой-либо другой системой контроля версий, не перечисленной выше, вам следует поискать инструмент для импорта в Сети — качественные решения доступны для 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’у на данные для создания слепков и порядок их применения. Итак, мы пробежимся по всем слепкам, создадим коммит для каждого из них и свяжем каждый новый коммит с предыдущим.

Как и в разделе Пример принудительной политики Git главы 8, мы проделаем это на 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)

где 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>'

Теперь всё готово для вывода нужной `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
Примечание

Если вы используете ОС 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.

scroll-to-top