Вселенная распределённых систем контроля версий не заканчивается на Git. На самом деле, существуют и другие системы, каждая со своим подходом к управлению версиями. На втором месте по популярности после Git находится Mercurial и у этих систем много общего.
К счастью, если вам нравится Git, но приходится работать с Mercurial-репозиторием, существует способ использовать Git-клиент для работы с Mercurial.
Учитывая тот факт, что Git работает с серверами через концепцию «удалённых репозиториев» (remotes), неудивительно, что работа с Mercurial-репозиторием происходит через своего рода обёртку над «удалённым репозиторием».
Проект, добавляющий такую интероперабельность, называется git-remote-hg
и расположен по адресу https://github.com/felipec/git-remote-hg.
Для начала необходимо установить git-remote-hg
.
Ничего особенного — просто поместите файл в любое место, откуда он будет виден другим программам, типа:
$ curl -o ~/bin/git-remote-hg \
https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg
…предполагая, что ~/bin
включён в $PATH
.
Есть ещё одна зависимость: библиотека mercurial
для Python.
Если у вас установлен Python, просто выполните:
$ pip install mercurial
(Если же у вас ещё нет Python, пора исправить это: скачайте установщик с https://www.python.org/.)
Ну и наконец понадобится сам клиент Mercurial. Если он ещё не установлен — скачайте и установите с https://www.mercurial-scm.org/.
Теперь можно отжигать! Всё что потребуется — репозиторий Mercurial с которым вы можете работать. К счастью, подойдёт любой, так что мы воспользуемся репозиторием «привет, мир», используемом для обучения работе с Mercurial.
$ hg clone http://selenic.com/repo/hello /tmp/hello
Теперь, когда у нас есть подходящий «серверный» репозиторий, мы готовы разобрать типичные приёмы работы с Mercurial. Как вы увидите, эти две системы очень похожи, так что всё пройдёт гладко.
Как и всегда, вначале мы клонируем репозиторий:
$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard 'hello, world' program
Наверняка вы обратили внимание, что мы использовали обыкновенный git clone
.
Это потому, что git-remote-hg
работает на довольно низком уровне, подобно тому, как в Git реализован HTTP/S протокол (git-remote-hg
служит как бы в качестве «помощника» для работы с удалённым репозиторием по новому протоколу (hg
), расширяя базовые возможности Git).
Подобно Git, Mercurial рассчитан на то, что каждый клиент хранит полную копию репозитория со всей историей, поэтому приведённая выше команда выполняет полное копирование со всей историей и делает это достаточно быстро.
git log
показывает два коммита, на последний из которых указывает довольно много ссылок.
На самом деле, не все из них реально существуют.
Давайте-ка посмотрим, что хранится внутри каталога .git
:
$ tree .git/refs
.git/refs
├── heads
│ └── master
├── hg
│ └── origin
│ ├── bookmarks
│ │ └── master
│ └── branches
│ └── default
├── notes
│ └── hg
├── remotes
│ └── origin
│ └── HEAD
└── tags
9 directories, 5 files
git-remote-hg
пытается нивелировать различия между Git и Mercurial, преобразовывая форматы за кулисами.
Ссылки на объекты в удалённом репозитории хранятся в каталоге refs/hg
.
Например, refs/hg/origin/branches/default
— это Git-ссылка, содержащая SHA-1 ac7955c
— коммит на который ссылается ветка master
.
Таким образом, каталог refs/hg
— это что-то типа refs/remotes/origin
, с той разницей, что здесь же отдельно хранятся закладки и ветки Mercurial.
Файл notes/hg
— отправная точка для выяснения соответствия между хешами коммитов в Git и идентификаторами ревизий в Mercurial.
Давайте посмотрим что там:
$ cat notes/hg
d4c10386...
$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800
Notes for master
$ git ls-tree 1781c96...
100644 blob ac9117f... 65bb417...
100644 blob 485e178... ac7955c...
$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9
Итак, refs/notes/hg
указывает на дерево, которое содержит список других объектов и имён.
Команда git ls-tree
выводит права доступа, тип, хеш и имя файла для содержимого дерева.
Наконец, добравшись до первого элемента дерева, мы обнаружим, что это блоб с названием ac9117f
(SHA-1 коммита, на которую указывает ветка master
), содержащий 0a04b98
(идентификатор последней ревизии ветки default
в Mercurial).
Всё это немного запутанно, но хорошие новости в том, что, по большому счёту, нам не нужно беспокоится об организации данных в git-remote-hg. В целом, работа с Mercurial сервером не сильно отличается от работы с Git сервером.
Ещё одна вещь, которую следует учитывать: список игнорируемых файлов.
Mercurial и Git используют очень похожие механизмы для таких списков, но всё же хранить .gitignore
в Mercurial репозитории — не самая удачная идея.
К счастью, в Git есть механизм игнорирования специфичных для локальной копии репозитория файлов, а формат списка исключений в Mercurial совместим с Git, так что можно просто скопировать .hgignore
кое-куда:
$ cp .hgignore .git/info/exclude
Файл .git/info/exclude
работает подобно .gitignore
, но не фиксируется в истории изменений.
Предположим, вы проделали некую работу, зафиксировали изменения в ветке master
и готовы отправить изменения в удалённый репозиторий.
Вот как выглядит репозиторий сейчас:
$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard 'hello, world' program
Наша ветка master
опережает origin/master
на два коммита, пока что они есть лишь в локальном репозитории.
Давайте посмотрим, вдруг кто-нибудь сделал важные изменения:
$ git fetch
From hg::/tmp/hello
ac7955c..df85e87 master -> origin/master
ac7955c..df85e87 branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard 'hello, world' program
Из-за того, что мы использовали флаг --all
, выводятся ссылки «notes», используемые внутри git-remote-hg
, можно не обращать на них внимания.
В остальном, ничего необычного: origin/master
продвинулся на один коммит и история разошлась.
В отличие от остальных систем контроля версий, рассматриваемых в этой главе, Mercurial умеет работать со слияниями, так что мы не будет вытворять никаких фокусов.
$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
hello.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
* 0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard 'hello, world' program
Отлично! Мы запустили все тесты, они прошли, так что всё готово для отправки изменений на удалённый сервер:
$ git push
To hg::/tmp/hello
df85e87..0c64627 master -> master
Вот и всё! Если теперь посмотреть на Mercurial репозиторий, мы не увидим там ничего необычного:
$ hg log -G --style compact
o 5[tip]:4,2 dc8fa4f932b8 2014-08-14 19:33 -0700 ben
|\ Merge remote-tracking branch 'origin/master'
| |
| o 4 64f27bcefc35 2014-08-14 19:27 -0700 ben
| | Update makefile
| |
| o 3:1 4256fc29598f 2014-08-14 19:27 -0700 ben
| | Goodbye
| |
@ | 2 7db0b4848b3c 2014-08-14 19:30 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard 'hello, world' program
Набор изменений 2 был произведён Mercurial, а изменения 3 и 4 внесены git-remote-hg
после отправки изменений, сделанных через Git.
В Git есть только один тип веток: указатель, который передвигается «вперёд» по мере коммита изменений. В Mercurial такие указатели называются «закладки» и ведут себя схожим с Git образом.
Понятие «ветка» в Mercurial означает немного другое.
Название ветки, в которой происходят изменения, записывается внутри каждого набора изменений и, таким образом, навсегда остаётся в истории.
Например, вот один из коммитов, произведённых в ветке develop
:
$ hg log -l 1
changeset: 6:8f65e5e02793
branch: develop
tag: tip
user: Ben Straub <ben@straub.cc>
date: Thu Aug 14 20:06:38 2014 -0700
summary: More documentation
Обратите внимание на строку, начинающуюся с «branch».
Git устроен по-другому (на самом деле, оба типа веток могут быть представлены как ссылки в Git), но git-remote-hg
вынужден понимать разницу, потому что нацелен на работу с Mercurial.
Создание «закладок» Mercurial не сложнее создания простых веток в Git. Вот что мы делаем в Git:
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
* [new branch] featureA -> featureA
А со стороны Mercurial это выглядит так:
$ hg bookmarks
featureA 5:bd5ac26f11f9
$ hg log --style compact -G
@ 6[tip] 8f65e5e02793 2014-08-14 20:06 -0700 ben
| More documentation
|
o 5[featureA]:4,2 bd5ac26f11f9 2014-08-14 20:02 -0700 ben
|\ Merge remote-tracking branch 'origin/master'
| |
| o 4 0434aaa6b91f 2014-08-14 20:01 -0700 ben
| | update makefile
| |
| o 3:1 318914536c86 2014-08-14 20:00 -0700 ben
| | goodbye
| |
o | 2 f098c7f45c4f 2014-08-14 20:01 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard 'hello, world' program
Обратите внимание на метку [featureA]
на пятой ревизии.
Таким образом, со стороны Git «закладки» выглядят как обычные ветки с одним лишь исключением: нельзя удалить закладку через Git (это одно из ограничений обёрток для взаимодействия с другими системами контроля версий).
Можно работать и с полноценными ветками Mercurial — просто поместите Git ветку в пространство имён branches
:
$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
* [new branch] branches/permanent -> branches/permanent
Вот как это будет выглядеть со стороны Mercurial:
$ hg branches
permanent 7:a4529d07aad4
develop 6:8f65e5e02793
default 5:bd5ac26f11f9 (inactive)
$ hg log -G
o changeset: 7:a4529d07aad4
| branch: permanent
| tag: tip
| parent: 5:bd5ac26f11f9
| user: Ben Straub <ben@straub.cc>
| date: Thu Aug 14 20:21:09 2014 -0700
| summary: A permanent change
|
| @ changeset: 6:8f65e5e02793
|/ branch: develop
| user: Ben Straub <ben@straub.cc>
| date: Thu Aug 14 20:06:38 2014 -0700
| summary: More documentation
|
o changeset: 5:bd5ac26f11f9
|\ bookmark: featureA
| | parent: 4:0434aaa6b91f
| | parent: 2:f098c7f45c4f
| | user: Ben Straub <ben@straub.cc>
| | date: Thu Aug 14 20:02:21 2014 -0700
| | summary: Merge remote-tracking branch 'origin/master'
[...]
Имя ветки «permanent» было записано внутри набора изменений с номером 7.
Итак, со стороны Git работа с обеими типами Mercurial веток выглядит одинаково: переключаемся на ветку, фиксируем изменения, забираем чужие наработки, производим слияния и отправляем изменения в репозиторий как обычно. И ещё: Mercurial не поддерживает изменение истории, только добавление новых изменений. Вот как будет выглядеть Mercurial репозиторий после интерактивного изменения истории и «принудительной» её отправки назад:
$ hg log --style compact -G
o 10[tip] 99611176cbc9 2014-08-14 20:21 -0700 ben
| A permanent change
|
o 9 f23e12f939c3 2014-08-14 20:01 -0700 ben
| Add some documentation
|
o 8:1 c16971d33922 2014-08-14 20:00 -0700 ben
| goodbye
|
| o 7:5 a4529d07aad4 2014-08-14 20:21 -0700 ben
| | A permanent change
| |
| | @ 6 8f65e5e02793 2014-08-14 20:06 -0700 ben
| |/ More documentation
| |
| o 5[featureA]:4,2 bd5ac26f11f9 2014-08-14 20:02 -0700 ben
| |\ Merge remote-tracking branch 'origin/master'
| | |
| | o 4 0434aaa6b91f 2014-08-14 20:01 -0700 ben
| | | update makefile
| | |
+---o 3:1 318914536c86 2014-08-14 20:00 -0700 ben
| | goodbye
| |
| o 2 f098c7f45c4f 2014-08-14 20:01 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard "hello, world" program
Были созданы изменения 8, 9 и 10 и теперь они принадлежат ветке permanent
, но старые изменения никуда не делись.
Это может очень удивить ваших коллег, привыкших к Mercurial, так что старайтесь так не делать.