-
1. Вступ
- 1.1 Про систему контролю версій
- 1.2 Коротка історія Git
- 1.3 Основи Git
- 1.4 Git, зазвичай, тільки додає дані
- 1.5 Три стани
- 1.6 Командний рядок
- 1.7 Інсталяція Git
- 1.8 Початкове налаштування Git
- 1.9 Отримання допомоги
- 1.10 Підсумок
-
2. Основи Git
- 2.1 Створення Git-репозиторія
- 2.2 Запис змін до репозиторія
- 2.3 Перегляд історії комітів
- 2.4 Скасування речей
- 2.5 Взаємодія з віддаленими сховищами
- 2.6 Теґування
- 2.7 Псевдоніми Git
- 2.8 Підсумок
-
3. Галуження в git
- 3.1 Гілки у кількох словах
- 3.2 Основи галуження та зливання
- 3.3 Управління гілками
- 3.4 Процеси роботи з гілками
- 3.5 Віддалені гілки
- 3.6 Перебазовування
- 3.7 Підсумок
-
4. Git на сервері
- 4.1 Протоколи
- 4.2 Отримання Git на сервері
- 4.3 Генерація вашого публічного ключа SSH
- 4.4 Налаштування Серверу
- 4.5 Демон Git
- 4.6 Розумний HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Варіанти стороннього хостингу
- 4.10 Підсумок
-
5. Розподілений Git
-
6. GitHub
-
7. Інструменти Git
- 7.1 Вибір ревізій
- 7.2 Інтерактивне індексування
- 7.3 Ховання та чищення
- 7.4 Підписання праці
- 7.5 Пошук
- 7.6 Переписування історії
- 7.7 Усвідомлення скидання (reset)
- 7.8 Складне злиття
- 7.9 Rerere
- 7.10 Зневадження з Git
- 7.11 Підмодулі
- 7.12 Пакування
- 7.13 Заміна
- 7.14 Збереження посвідчення (credential)
- 7.15 Підсумок
-
8. Налаштування Git
-
9. Git and Other Systems
- 9.1 Git як клієнт
- 9.2 Міграція на Git
- 9.3 Підсумок
-
10. Git зсередини
- 10.1 Кухонні та парадні команди
- 10.2 Об’єкти Git
- 10.3 Посилання Git
- 10.4 Файли пакунки
- 10.5 Специфікація посилань (refspec)
- 10.6 Протоколи передачі
- 10.7 Супроводження та відновлення даних
- 10.8 Змінні середовища
- 10.9 Підсумок
-
A1. Додаток A: Git в інших середовищах
- A1.1 Графічні інтерфейси
- A1.2 Git у Visual Studio
- A1.3 Git в Eclipse
- A1.4 Git у Bash
- A1.5 Git у Zsh
- A1.6 Git у Powershell
- A1.7 Підсумок
-
A2. Додаток B: Вбудовування Git у ваші застосунки
- A2.1 Git з командного рядка
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
-
A3. Додаток C: Команди Git
- A3.1 Налаштування та конфігурація
- A3.2 Отримання та створення проектів
- A3.3 Базове збереження відбитків
- A3.4 Галуження та зливання
- A3.5 Поширення й оновлення проектів
- A3.6 Огляд та порівняння
- A3.7 Зневаджування
- A3.8 Латання (patching)
- A3.9 Електронна пошта
- A3.10 Зовнішні системи
- A3.11 Адміністрування
- A3.12 Кухонні команди
9.2 Git and Other Systems - Міграція на Git
Міграція на Git
Якщо ваш код вже зберігається в іншій системі контролю версій, але ви вирішили почати використовувати Git, вам необхідно так чи інакше здійснити міграцію проекту. У цій секції описано деякі варіанти імпорту для поширених систем, а потім показано, як розробити власні варіанти імпорту. Ви дізнаєтесь як імпортувати дані з деякий найбільших професійних систем контролю версій, оскільки вони використовуються більшістю розробників та для них легко знайти якісні інструменти міграції.
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, потім залишає лише рядки з даними про авторів, відкидає повторювання, позбувається теґів 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
Не лише поле з автором виглядає набагато краще, а й більше немає 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/
та перетворює їх на справжні (легкі) теґи.
Далі, перемістіть решту посилань під 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 до імʼя гілки саме так, як би ви написали його в 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 як віддалене сховище та надіслати до нього. Ось приклад додавання сервера як віддаленого:
$ 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 http://repo.or.cz/r/fast-export.git /tmp/fast-export
Першим кроком перетворення є отримання повного клону сховища Mercurial, яке ви бажаєте перетворити:
$ hg clone <remote repo URL> /tmp/hg-repo
Наступним кроком є створення файлу відображення авторів.
Mercurial трохи більш поблажливий, ніж Git, щодо того, що він дозволить записати в поле автора набору змін (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>
У цьому прикладі, одна людина (Боб) створювала набори змін під чотирма різними іменами, одне з яких дійсно виглядає правильним — те, яке буде цілком відповідним для коміту Git.
Hg-fast-export дозволяє нам виправити це, якщо додати ={нове імʼя та поштова адреса}
наприкінці кожного рядка, який ми бажаємо змінити, та якщо видалити будь-які імена користувачів, які ми бажаємо облишити.
Якщо всі імена користувачів виглядають правильно, то нам взагалі не потрібен цей файл.
У цьому прикладі, ми бажаємо, щоб наш файл виглядав так:
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>
Наступним кроком є створення нашого нового сховища 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
повідомляє йому, де знайти файл відображення авторів.
Скрипт зчитує набори змін Mercurial та перетворює їх на скрипт для функції Git "fast-import" (ми обговоримо її докладно трохи пізніше).
Це може зайняти деякий час (хоча набагато швидше, ніж було б через мережу), та вивід буде доволі детальним:
$ /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 були перетворені на гілки Git. Тепер ви готові для надсилання сховища до нової серверної домівки:
$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all
Bazaar
Bazaar is a DVCS tool much like Git, and as a result it’s pretty straightforward to convert a Bazaar repository into a Git one.
To accomplish this, you’ll need to import the bzr-fastimport
plugin.
Getting the bzr-fastimport plugin
The procedure for installing the fastimport plugin is different on UNIX-like operating systems and on Windows.
In the first case, the simplest is to install the bzr-fastimport
package that will install all the required dependencies.
For example, with Debian and derived, you would do the following:
$ sudo apt-get install bzr-fastimport
With RHEL, you would do the following:
$ sudo yum install bzr-fastimport
With Fedora, since release 22, the new package manager is dnf:
$ sudo dnf install bzr-fastimport
If the package is not available, you may install it as a plugin:
$ mkdir --parents ~/.bazaar/plugins/bzr # creates the necessary folders for the plugins
$ cd ~/.bazaar/plugins/bzr
$ bzr branch lp:bzr-fastimport fastimport # imports the fastimport plugin
$ cd fastimport
$ sudo python setup.py install --record=files.txt # installs the plugin
For this plugin to work, you’ll also need the fastimport
Python module.
You can check whether it is present or not and install it with the following commands:
$ python -c "import fastimport"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: No module named fastimport
$ pip install fastimport
If it is not available, you can download it at address https://pypi.python.org/pypi/fastimport/.
In the second case (on Windows), bzr-fastimport
is automatically installed with the standalone version and the default installation (let all the checkboxes checked).
So in this case you have nothing to do.
At this point, the way to import a Bazaar repository differs according to that you have a single branch or you are working with a repository that has several branches.
Project with a single branch
Now cd
in the directory that contains your Bazaar repository and initialize the Git repository:
$ cd /path/to/the/bzr/repository
$ git init
Now, you can simply export your Bazaar repository and convert it into a Git repository using the following command:
$ bzr fast-export --plain . | git fast-import
Depending on the size of the project, your Git repository is built in a lapse from a few seconds to a few minutes.
Case of a project with a main branch and a working branch
You can also import a Bazaar repository that contains branches. Let us suppose that you have two branches: one represents the main branch (myProject.trunk), the other one is the working branch (myProject.work).
$ ls
myProject.trunk myProject.work
Create the Git repository and cd
into it:
$ git init git-repo
$ cd git-repo
Pull the master branch into git:
$ bzr fast-export --export-marks=../marks.bzr ../myProject.trunk | \
git fast-import --export-marks=../marks.git
Pull the working branch into Git:
$ bzr fast-export --marks=../marks.bzr --git-branch=work ../myProject.work | \
git fast-import --import-marks=../marks.git --export-marks=../marks.git
Now git branch
shows you the master
branch as well as the work
branch.
Check the logs to make sure they’re complete and get rid of the marks.bzr
and marks.git
files.
Synchronizing the staging area
Whatever the number of branches you had and the import method you used, your staging area is not synchronized with HEAD
, and with the import of several branches, your working directory is not synchronized either.
This situation is easily solved by the following command:
$ git reset --hard HEAD
Ignoring the files that were ignored with .bzrignore
Now let’s have a look at the files to ignore.
The first thing to do is to rename .bzrignore
into .gitignore
.
If the .bzrignore
file contains one or several lines starting with "!!" or "RE:", you’ll have to modify it and perhaps create several .gitignore
files in order to ignore exactly the same files that Bazaar was ignoring.
Finally, you will have to create a commit that contains this modification for the migration:
$ git mv .bzrignore .gitignore
$ # modify .gitignore if needed
$ git commit -am 'Migration from Bazaar to Git'
Sending your repository to the server
Here we are! Now you can push the repository onto its new home server:
$ git remote add origin git@my-git-server:mygitrepository.git
$ git push origin --all
$ git push origin --tags
Your Git repository is ready to use.
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 Public Depot. Щоб налаштувати клієнта, ви маєте експортувати змінну середовища P4PORT, щоб вона вказувала на депо Perforce:
$ 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.
TFS
Якщо ваша команда переходить від керування кодом за допомогою TFVC до Git, то ви забажаєте конвертацію найвищої якості з тих, які вам доступні. Це означає, що хоча ми розглянули й git-tfs і git-tf у секції взаємодії, тут ми розглянемо лише git-tfs, оскільки він підтримує гілки, та здійснити це за допомогою git-tf неприйнятно складно.
Зауваження
|
Це однобічна конвертація. Отримане сховище Git не буде в змозі взаємодіяти з оригінальним проектом TFVC. |
Спершу треба встановити відображення імен користувачів.
TFVC доволі ліберальний щодо того, що може бути в полі автора для набору змін, проте Git бажає читабельне ім’я та поштову адресу.
Ви можете отримати цю інформацію з консольного клієнта tf
ось так:
PS> tf history $/myproject -recursive > AUTHORS_TMP
Ця команда отримує всі набори змін в історії проекту та кладе їх у файл 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.
Коли все це зроблено, ви готові додати нове віддалене сховище, надіслати туди всі свої гілки, та команда може розпочати роботу з Git.
Нетипове імпортування
Якщо у вас не одна з вищенаведених систем, вам варто пошукати імпортер у мережі – для багатьох інших систем уже готові якісні імпортери, включно з CVS, Clear Case, Visual Source Safe, та навіть директорії архівів.
Якщо жоден з цих інструментів вам не годиться — ви маєте якусь дивну систему, або якщо через щось інше вам потрібен більш нетиповий процес імпортування, то варто скористатися git fast-import
.
Ця команда читає прості інструкції з stdin, щоб записати специфічні дані Git.
Набагато легше створювати обʼєкти Git таким чином, ніж виконувати звичайні команди Git чи намагатись писати двійкові обʼєкти (докладніше в Git зсередини).
Таким чином, ви пишете скрипт імпортування, який читає необхідну інформацію зі системи, з якої ви імпортуєте та друкує зрозумілі інструкції до 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
, якими є відбитки вмісту, які дані комітів указують на них, та в якому вони порядку.
Вашою стратегією буде пройтись відбитками по одному за раз та створити коміти з вмістом кожної директорії, звʼязавши кожен коміт з попереднім.
Як ми робили в Приклад політики користування виконуваної Git-ом, ми напишемо це на 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 <john@example.com>'
Тепер ви готові почати друкувати дані комітів для імпортера. Початкова інформація зазначає, що ви визначаєте обʼєкт коміту та в якій ви гілці, після чого йде позначка, яку ви згенерували, інформація про автора коміту та повідомлення коміту, а потім попередній коміт, якщо такий є. Код виглядає так:
# 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
Зауваження
|
Якщо ви використовуєте Windows, то вам необхідно переконатися, що ви зробите ще одну додаткову дію.
Як вже згадувалось, Windows використовує CRLF для символів нових рядків, у той час як git fast-import очікує лише LF.
Щоб обійти цю проблему та зробити
|
Це все. Ось весь скрипт цілком:
#!/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
---------------------------------------------------------------------
Як бачите, коли він завершується успішно, він надає вам купу статистики про те, що зроблено.
У даному випадку, ви імпортували загалом 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.
Важливо зазначити, що нічого не отримано (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.