Git
Chapters ▾ 2nd Edition

9.2 Git и други системи - Миграция към Git

Миграция към Git

Ако имате наличен код под друга VCS, но искате да използвате Git, ще трябва да мигрирате проекта по някакъв начин. Тази секция разглежда някои популярни импортиращи инструменти и показва как да си направите свой собствен потребителски importer. Ще научим как се импортират данни от няколко от най-големите SCM системи защото те формират болшинството потребители, които мигрират и защото за тях се предлагат висококачествени инструменти.

Subversion

Ако сте прочели секцията за използването на git svn, може да сте ползвали тези инструкции за да направите git svn clone към хранилище, след това да сте спрели да използвате Subversion сървъра, да сте публикували в нов Git сървър и да ползвате него занапред. Ако искате историята, може да направите това толкова бързо колкото може да теглите от Subversion сървъра (което може да отнеме доста време).

Обаче, импортът не е перфектен и понеже може да отнеме време, няма да е лошо да го направите правилно. Първият проблем е с информацията за автора. В 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 формат, след това филтрира само редовете с author данни, премахва повторенията и изчиства XML таговете. (Очевидно това ще работи при налични grep, sort, и perl.) След това, пренасочете този изход във файла users.txt, така че да добавите еквивалентните Git потребителски данни във всеки ред.

Може да подадете този файл на командата git svn за да я подпомогнете в по-акуратното мапване на данните за авторите. Можете също да укажете на git svn да не включва метаданните, които Subversion нормално импортира с флага --no-metadata към командите clone или init (ако все пак желаете да запазите метаданните за синхронизацията, пропуснете този параметър). Така командата 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 настройва. Първо ще преместите таговете, така че да бъдат реални тагове вместо странни отдалечени референции и след това ще преместите останалите клонове, така че да станат локални.

За да превърнете таговете в реални Git тагове, изпълнете:

$ 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/ и ще ги конвертира в реални олекотени тагове в Git.

След това, преместваме останалите референции от 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

Може да се случи да видите някои допълнителни клонове със суфикс @xxx (където xxx е число), докато в Subversion виждате само един клон. Това в действителност е Subversion функция наречена “peg-revisions”, за която Git просто няма синтактичен аналог. По тази причина git svn просто добавя svn version номера към името на клона точно по същия начин, по който бихте го написали в svn за да адресирате въпросния peg-revision в този клон. Ако тези peg-revisions не ви интересуват, може просто да ги премахнете:

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

Сега всички стари клонове са реални Git клонове и всички стари тагове са реални Git такива.

Има още едно последно нещо за коригиране. За съжаление, git svn създава допълнителен клон с име trunk, който съответства на клона по подразбиране на Subversion, но указателят trunk сочи към същото място, към което и master. Понеже master е нещото, с което сме свикнали с Git, ето как да премахнете допълнителния клон:

$ git branch -d trunk

Последно, добавяме нашия нов Git сървър като remote и публикуваме в него:

$ 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

Следващата е да създадем author mapping файл. Mercurial е по-малко рестриктивен от Git по отношение на това какво може да се слага в author полето на changeset-ите, така че това е удобен момент за почистване. Създаването на списъка отнема една команда в 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>

В този конкретен пример, един и същи човек (Bob) е създал changeset-и под четири различни имена, едно от които изглежда коректно и друго, което ще е изцяло невалидно за един Git къмит. Hg-fast-export ни позволява да коригираме това превръщайки всеки ред в правило: "<input>"="<output>", мапвайки <input> към <output>. В стринговете <input> и <output>, са позволени всички escape последователности, които се поддържат от string_escape енкодинга на python. Ако author mapping файлът не съдържа съответен <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 инструктира hg-fast-export къде да намери Mercurial хранилището, което ще се конвертира, а -A указва къде е author-mapping файла (съответно за файловете за клонове и тагове се използват флаговете -B и -T). Скриптът парсва Mercurial changeset-ите и ги конвертира в скрипт за целите на "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 тагове и Mercurial клоновете и bookmarks обектите са превърнати в съответните Git клонове. Сега сте готови да публикувате хранилището в сървъра:

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

Bazaar

Bazaar е DVCS инструмент подобен на 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   # инсталира плъгина

За да работи този плъгин, ще ви трябва също и fastimport модула за Python. Може да проверите дали е наличен и, ако трябва да го инсталирате, така:

$ 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 се инсталира автоматично със standalone версията и инсталацията по подразбиране (изберете всички чекбоксове). В този случай няма какво да правите.

На този етап начините за импортиране на Bazaar хранилище се различават според това дали имате само един клон или не.

Проект с единичен клон

Влезте в директорията, съдържаща Bazaar хранилището и инициализирайте Git хранилище:

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

След това можете просто да експортирате Bazaar хранилището и да го конвертирате в Git така:

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

Според размера на проекта, процесът може да отнеме секунди или няколко минути.

Проект с главен клон и работен клон

Можете също да импортирате Bazaar хранилище съдържащо клонове. Да кажем, че имате два клона: един главен (myProject.trunk) и един текущ (myProject.work).

$ ls
myProject.trunk myProject.work

Създайте Git хранилище и влезте в него:

$ git init git-repo
$ cd git-repo

Изтеглете master клона в Git:

$ bzr fast-export --export-marks=../marks.bzr ../myProject.trunk | \
git fast-import --export-marks=../marks.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 файлове с цел да игнорирате точно същото съдържание като 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 и Perforce Git Fusion.

Perforce Git Fusion

Git Fusion прави процеса сравнително безболезнен. Просто задаваме настройките на проекта, мапинга на потребителите и клоновете в конфигурационен файл (виж Git Fusion) и клонираме хранилището. Git Fusion ни изработва резултат подобен на оригинално Git хранилище, което е готово да се публикува на Git сървър. Можем дори да използваме Perforce като Git хост, ако желаем това.

Git-p4

Git-p4 може също да работи като имортиращ инструмент. Като пример, ще импортираме проекта Jam от Perforce публичното депо. За да настроим клиента си, трябва да експортираме P4PORT environment променливата, така че да сочи към Perforce депото:

$ 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%)

Този проект има само един клон, но ако имаме клонове конфигурирани с branch изгледи (или само множество от директории), може да използваме флага --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 change number-a по някаква причина. Обаче, ако искаме да махнем идентификатора, сега е времето за това — преди да започнем работа по новото хранилище. За целта използваме командата 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-tfs поддържа клонове — нещо което е трудна работа с git-tf.

Note

Това е еднопосочно конвертиране. Полученото Git хранилище няма да може да се свърже с оригиналния TFVC проект.

Първата задача е да се направи мапинга на потребителските имена. TFVC е либерална система по отношение на информацията, която се записва в author полето на changeset-ите, но Git изисква human-readable име и имейл адрес. Можете да вземете тази информация от tf command-line клиента така:

PS> tf history $/myproject -recursive > AUTHORS_TMP

Това извлича всички changesets в историята на проекта и ги записва във файла AUTHORS_TMP, който след това ще обработим за да извлечем данните от полето User (второто по ред). Отворете файла, вижте на кои позиции започва и свършва полето и след това заменете параметрите 11-20 на cut командата с позициите, които установихте:

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

Командата cut оставя само символите на позиция между 11 и 20 от всеки ред. Командата tail пропуска първите два реда, които представляват имената на полетата и подчертаващи символи в ASCII стил. Резултатът от всичко това се пренасочва към sort и 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 после ще игнорира.

Веднъж след като това е готово, можем да добавим нашия нов remote, да публикуваме всичките си клонове и екипът да премине на Git.

Потребителски импортиращ инструмент

Ако системата ви не е сред дотук разгледаните, може да потърсите импортиращ инструмент в Интернет — качествени такива са налични за CVS, Clear Case, Visual Source Safe, дори за директория от архиви. Ако никой от тях не работи в конкретния случай или се нуждаете от по-специфичен процес на импортиране, тогава може да използвате git fast-import. Тази команда чете прости инструкции от стандартния вход за да записва специфични Git данни. Много по-лесно е да създавате Git обекти по този начин, вместо да използвате raw Git командите или да се опитвате да записвате raw обекти (вижте 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 съхранява данните си. Както може би помните, Git грубо казано е свързан списък от къмит обекти, които сочат към snapshot-и от съдържание. Всичко, което трябва да укажете на fast-import е какво са snapshot-ите със съдържание, какви къмит данни сочат към тях и реда, по който идват. Стратегията е да минаваме по snapshot-ите един след друг и да създаваме къмити със съдържанието на всяка директория свързвайки всеки къмит с предишния.

Както го направихме в Примерна Git-Enforced политика, ще пишем скрипта си на 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 за символите за край на ред, докато git fast-import очаква само LF. За да избегнете проблем, ще трябва да укажете на 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
(...)

За да стартираме 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.