-
1. Введение
- 1.1 О системе контроля версий
- 1.2 Краткая история Git
- 1.3 Что такое Git?
- 1.4 Командная строка
- 1.5 Установка Git
- 1.6 Первоначальная настройка Git
- 1.7 Как получить помощь?
- 1.8 Заключение
-
2. Основы Git
-
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 Git-хостинг
- 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 Хранилище учётных данных
- 7.15 Заключение
-
8. Настройка Git
- 8.1 Конфигурация Git
- 8.2 Атрибуты Git
- 8.3 Хуки в Git
- 8.4 Пример принудительной политики Git
- 8.5 Заключение
-
9. Git и другие системы контроля версий
- 9.1 Git как клиент
- 9.2 Переход на Git
- 9.3 Заключение
-
10. Git изнутри
- 10.1 Сантехника и Фарфор
- 10.2 Объекты Git
- 10.3 Ссылки в Git
- 10.4 Pack-файлы
- 10.5 Спецификации ссылок
- 10.6 Протоколы передачи данных
- 10.7 Обслуживание репозитория и восстановление данных
- 10.8 Переменные окружения
- 10.9 Заключение
-
A1. Приложение A: Git в других окружениях
- A1.1 Графические интерфейсы
- A1.2 Git в Visual Studio
- A1.3 Git в Visual Studio Code
- A1.4 Git в Eclipse
- A1.5 Git в IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.6 Git в Sublime Text
- A1.7 Git в Bash
- A1.8 Git в Zsh
- A1.9 Git в PowerShell
- A1.10 Заключение
-
A2. Приложение B: Встраивание Git в ваши приложения
- A2.1 Git из командной строки
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Приложение C: Команды Git
- A3.1 Настройка и конфигурация
- A3.2 Клонирование и создание репозиториев
- A3.3 Основные команды
- A3.4 Ветвление и слияния
- A3.5 Совместная работа и обновление проектов
- A3.6 Осмотр и сравнение
- A3.7 Отладка
- A3.8 Внесение исправлений
- A3.9 Работа с помощью электронной почты
- A3.10 Внешние системы
- A3.11 Администрирование
- A3.12 Низкоуровневые команды
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 репозиторий.
Если полной синхронизации не требуется, то спокойно добавляйте параметр |
В результате, команда 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 использует
|
Вот и всё. Ниже приведён весь скрипт целиком:
#!/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.