Привет, я Александр, разработчик из команды Битрикс24. В этой статье разбираюсь в особенностях распределенной системы управления версиями Mercurial. Хотя она появилась одновременно с Git и похожа на него внешне, успеха достичь не смогла. Почему так получилось, как она работает, для каких проектов подходит — обо всем ниже.
Немного истории
В начале 2005 года проект Linux Kernel ушел с системы контроля версий BitKeeper на авторскую DVCS под названием Git. Тогда же разработчики из разных компаний начали предлагать рынку альтернативы с похожими функциями — решения с облегченным интерфейсом и поддержкой разных ОС.
Одной из таких альтернатив стала Mercurial — распределенная система управления версиями, позволяющая эффективно работать с проектами любого размера, созданная Мэттом Макаллом.
Mercurial не стала такой популярной, как Git, но ее до сих пор используют программисты по всему миру. В том числе разработчики свободного ПО, такие как GNU Health, GNU Multi-Precision Library, GNU Octave, IcedTea, LiquidFeedback, Orthanc, Pidgin, RhodeCode, Roundup, Tryton, wmii, XEmacs, Xine. Также поддерживаются Mercurial-зеркала основных репозиториев других крупных проектов.
Несмотря на это востребованность Mercurial все же падает, и большие команды отказываются от нее. Например, недавно объявили о переходе на Git Mozilla и Nginx.
Технические особенности Mercurial
Как и Git, Mercurial поддерживает децентрализованную работу, ветвление и слияние репозиториев. А также обмен данными между ними через HTTP/HTTPS, SSH — и вручную с помощью упакованных наборов изменений.
Mercurial написан на Python. Расчет дельты между состояниями файла реализован в виде модулей-расширений на C.
Большое преимущество Mercurial в том, что она хранит разницу состояний файлов каждой ревизии.
Если в качестве идентификатора ревизии команде передано число, Mercurial считает, что получила номер ревизии. Она выполняет проверку на неоднозначность с сокращенным идентификатором наборов изменений только если отсутствует набор изменений с указанным номером ревизии. Если глобальный идентификатор начинается с цифры, полезно сокращать его до тех пор, пока не встретится буква.
Если создать метку с именем, например, «5», когда в репозитории существует (или появится) набор изменений с номером ревизии «5», то Mercurial будет сначала искать наборы изменений по номеру ревизии. Но если создать метку с именем, например, «e1be», когда в репозитории существует (или появится) набор изменений с идентификатором наборов изменений, начинающимся с «e1be», Mercurial будет искать наборы изменений в первую очередь по меткам. Не стоит создавать метки только из цифр и/или букв «a, b, c, d, e, f».
Как и в Git, в системе нельзя добавить в репозиторий пустую папку. Для решения проблемы можно положить в нее любой файл, например, readme.txt. Mercurial отслеживает только файлы.
У системы богатая экосистема плагинов и расширений, которые позволяют адаптировать ее под нужды разработчиков.
Обновление и техподдержка Mercurial продолжаются.
Mercurial и Git: сходства и различия
У двух систем контроля версий есть как сходства, так и различия. Попытаюсь объяснить, в чем они проявляются, опираясь на собственный опыт, статьи коллег и книги о Mercurial и Git.
Представим, что у нас есть проект с некоторым количеством файлов, директорий и поддиректорий с файлами. Для хранения истории файлов проекта, Git использует блобы (blobs). Каждая новая ревизия файла — это его полная копия. Так ревизии быстрее сохраняются.
Допустим, мы сделали изменения в файлах, добавили директории, перенесли некоторые файлы в другие директории и решили вернуться к изначальной версии проекта. Блоб хранит в себе только содержимое конкретной версии, а не название файла и его расположение. Как Git понять, какой файл как назывался, в какой директории находился? Нас выручат деревья.
Каждое дерево, по сути, соответствует директории проекта. Оно содержит названия файлов и поддиректорий, ссылки на блобы, содержащие состояния файлов проекта, и ссылки на другие деревья (с данными о поддиректориях). Так Git может выстроить структуру проекта.
Деревья в Git представляют текущее состояние файловой структуры. В Mercurial информацию об отслеживаемых файлах собирают манифесты. Запись в манифесте содержит данные о файлах, которые есть в наборе изменений. В ней указано, какие файлы есть в наборе изменений, ревизия каждого из них и фрагменты метаданных. Это похоже на дерево в Git.
Журнал изменений в Mercurial — changelog — содержит информацию о каждом наборе изменений. В каждой редакции записывается, кто внес изменение, дается комментарий к набору изменений, указывается другая информация, связанная с набором, и используемая редакция манифеста. Это похоже на commit в Git.
Каждому файлу проекта соответствует файл с таким же названием и расширением .i — журнал файла (filelog). Когда история становится большой, файл .i разделяется на .i и .d. В .i остается индекс, а в .d уходит история изменений. Индекс содержит информацию о том, где и как брать изменения конкретной ревизии.
Mercurial пользуется структурой Revlog для хранения содержимого файлов filelog.
Разработчики Git применили методы упаковки данных для снижения требований к объему хранилища и создали нечто похожее на Revlog. Полученные пакеты стремятся сохранить данные, эффективно расходуя пространство. Git делает слепки файлов, фиксации легко создавать и уничтожать. Разница между фиксациями вычисляется динамически.
Особенности ветвления в системах
Ветвление позволяет проводить параллельную разработку новой функциональности, сохраняя стабильность старой. И Git, и Mercurial также поддерживают ветвления. Однако есть разница.
Ветка в Mercurial — это отметка, прикрепляемая к фиксации навсегда, глобальная и уникальная. Любой человек, затягивающий изменения из удалённого хранилища, увидит все ветки в хранилище и фиксации. Ветки — публичное место разработки вне основного ствола. Имена веток публикуются в системе среди участников. Используются устойчивые во времени номера версий.
Ветки в Git — это лишь указатели на фиксации. В разных клонах хранилища ветки с одинаковыми названиями могут указывать на разные фиксации. В системе они могут удаляться и передаваться по отдельности.
Содержимое файлов в Mercurial
.hg/
├───branch
├───dirstate
├───last-message.txt
├───requires
├───undo.desc
├───cache/
└───store/
├─── data/
├───00changelog.d
├─── 00changelog.i
├─── manifest.i
├─── phaseroots
├─── requires
└─── undo
Оно может отличаться в зависимости от установленных расширений и версии системы.
Описание файлов:
branch
— содержит название текущей ветки.
dirstate
— содержит информацию и рабочей директории проекта. Например, какие файлы отслеживает Mercurial, какие файлы скопированы или переименованы, их размер, время изменения файлов, состояние.
last-message.txt
— сообщение последнего коммита.
requires
— файл, который определяет требования к функциональности клиента для работы с этим репозиторием.
undo
— последние изменения репозитория.
undo.desc
— описание изменений.
store/manifest.i
— собирает информацию о файлах, которые отслеживает. Каждая запись в манифесте содержит данные о файлах в наборе изменений. Она указывает, какие файлы есть в наборе, дает ревизию всех файлов и несколько фрагментов метаданных файла. Это похоже на объект дерева в Git.
store/00changelog.i
— содержит информацию о каждом наборе изменений. В каждой редакции записывается, кто внес изменение, дается комментарий к набору изменений и информация, связанная с набором изменений, плюс используемая редакция манифеста. Похоже на объект commit в Git.
Из чего состоит коммит в Mercurial
При написании hg commit
Mercurial происходит изменение основных файлов — 00changelog.i
, manifest.i
и файлы — в data/
. В отличие от него Git создает новые файлы. Давайте разберемся, как это работает.
Содержимое файлов хранится в виде структуры revlog и разницы изменений между коммитами. Файлы и директории в data/
являются отражением основной директории проекта. Но есть нюансы: в data/
все файлы и директории названы с маленькой буквы и в конце добавляется расширение .i
. Там, где нужна заглавная, ставится _, например, TestFile.txt будет выглядеть так: _test_file.txt.i
.
Изменения ревизии в Mercurial
Изменения ревизии идут сразу за данными revlog или в отдельном файле. Они могут храниться в сжатом виде. Понять, как прочитать данные, можно по содержанию первого байта:
\0 (0x00)
— сжатия нет
( (0x28)
— сжато библиотекой zstd (https://github.com/facebook/zstd)
х (0x78)
— сжато библиотекой zlib
Ветвление и теги в Mercurial
Информация о тегах и ветках хранится в файлах cache/branch2-* и tags2-* . Если у вас небольшой проект, основными файлами будут cache/branch2-served и tags2-visible. Они содержат названия веток и ссылки на головные ревизии. Также файл с ветками включает модификаторы состояния ветки, так, o — открытая ветка.
Структура Revlog
Версии Revlog:
0
— оригинальная версия revlog.
1
— RevlogNG (https://wiki.mercurial-scm.org/RevlogNG), актуальная версия на момент написания статьи. Появилась в 2006 году.
2
— версия находится в разработке (https://wiki.mercurial-scm.org/RevlogV2Plan),
Флаги в данной статье описывать не буду. Подробно и с примерами можно посмотреть тут: https://repo.mercurial-scm.org/hg/help/internals.revlogs.
Структура Revlog всегда занимаетт 64 байта. Они выглядят так:
Для первой записи в файле 0-3 байта — заголовок, который содержит версию revlog и дополнительные флаги для понимания, как читать изменения файла.
Заголовок (4 байта) — это число вида xxxxyyyy
где xxxx
— это флаги, которые определяют поведение revlog, а yyyy
— версию revlog.
За заголовком идут 4-5 байт абсолютного смещения контента. В последующих записях revlog абсолютное смещение файлов находится на 0-5 байт.
6-7 байт — эти два байта содержат флаги, описывающие поведение ревизии.
8-11 — эти четыре байта содержат длину содержимого ревизии в сжатом виде.
12-15 — эти четыре байта содержат длину содержимого ревизии в несжатом виде.
16-19 — базовая или предыдущая ревизия, на основе которой создана дельта ревизии.
20-23 — ревизия, с которой связана текущая ревизия. Это позволяет ревизии в одном revlog быть навсегда связанной с ревизией в другом. Например, revlog файла может указывать на ревизию changelog, которая ее изначально ввела.
24-27 — ревизия первого родителя. -1 отсутствие такового.
28-31 — ревизия второго родителя. -1 отсутствие такового.
32-63 — хэш содержимого ревизии по SHA-1. Данный хэша содержатся только в 20 байтах. Остальные 12 — нулевые байты, которые игнорируются.
Mercurial предполагает более строгую модель работы с файлами, чем Git:
Хранение изменений в виде Revlog в противовес blob.
Обеспечивается слияние двух ветвей за один проход, а не множественное слияние, как в Git.
Нельзя отменить коммит: но можно послать исправляющий коммит — и он наложится поверх неудачного.
Строгий контроль различий исключает конфликты версий и клиентов.
Как Git сделал Mercurial и почему
Не будем ходить вокруг да около: у Git есть Github, у Mercurial платформы нет. Уверен, все здесь понимают, какую ценность несет эта экосистема для разработчиков.
GitHub стал чем-то вроде соцсети для своих. Он позволяет удобно делиться кодом, откатываться к прошлым версиям продукта, анализировать изменения и совместно работать над проектами. На платформе доступны реквесты с добавлением чужого кода в твой репозиторий, что удобно для опенсорса. Это стало сильным драйвером роста Git.
Также разработчики могут учиться на чужом коде. В GitHub есть куча полезных интеграций с другими продуктами — а еще инструменты для упрощенного ведения документации к проектам.
В Github можно поговорить о наболевшем с коллегами, попросить помощи, опубликовать проект и получить обратную связь. Неудивительно, что такой удобный продукт повлиял на популярность системы контроля версий Git в кругу программистов.
Ну и не последнюю роль сыграл личный бренд создателя. На момент выхода Git Линус Торвальдс уже был популярен в профессиональном сообществе как создатель ядра Linux. Имя Мэтта Маккала в 2005 году было менее известным.
Выводы
Во многих статьях пишут, что Mercurial дружелюбнее к новичкам, чем Git. На мой взгляд, это не так. Базовые команды систем аналогичны, как и результат выполнения. Новичок не увидит большой разницы.
При этом Mercurial более чувствителен к истории репозитория. Если у Git есть состояния ревизии, то Mercurial хранит разницу ревизий. Вернуться к ранней ревизии он может, применив эту разницу к последнему полному состоянию проекта. Это заставляет Mercurial строго следить за историей. Зато в Git можно «склеить» пару коммитов в один и запушить.
У Git есть ограничения в названии веток. При работе с ним можно получить одинаковые названия, которые ссылаются на разные ревизии в разных клонах хранилища. У Mercurial же уникальные ветки для всех репозиториев, и такой проблемы нет.
По умолчанию у Git и Mercurial разные стратегии слияния. Если у Git рекурсивная стратегия, то у Mercurial это трехстороннее слияние. У стратегии Mercurial меньше сравнений с общими предками, а у рекурсивной стратегии меньше возможных конфликтов. И для каждой системы можно выбирать другую стратегию слияния.
Часто Mercurial приписывают более быструю работу с большими монолитными проектами. На мой взгляд, сегодня разница почти незаметна. Тем более появились плагины, позволяющие ускорять работу системы. Например, fsmonitor дает отследить изменения файлов в рабочей директории.
Пока актуальны обе системы контроля версий, но в ближайшие несколько лет ситуация может измениться. Mercurial по-прежнему жив, получает обновления, на нем базируются крупные репозитории. Git более популярен за счет большого комьюнити, известного в IT кругах создателя и Github, завоевавшего любовь разработчиков.
У каждой системы есть свои преимущества при работе над проектами, а какие важнее для вас в конкретном случае — подскажут технические требования и предпочтения.
Ссылки на материалы, которые я использовал при написании статьи:
Статья “Тонкости благополучного git-merge” — https://habr.com/ru/articles/195674/.
Pro Git book — https://git-scm.com/book/en/v2.
Статья “Сходство и различие между Mercurial и Git” — https://habr.com/ru/articles/168675/.
Mercurial: The Definitive Guide by Bryan O'Sullivan — https://hgbook.red-bean.com/read.
Материалы, посвященные ревлогам — https://repo.mercurial-scm.org/hg/help/internals.revlogs, https://wiki.mercurial-scm.org/FrenchRevlogNG,https://wiki.mercurial-scm.org/Revlog, https://wiki.mercurial-scm.org/RevlogV2Plan.
Материал по форматам файлом в Mercurial — FileFormats - Mercurial.
Комментарии (13)
gizur
25.10.2024 09:19Не будем ходить вокруг да около: у Git есть Github, у Mercurial платформы нет.
Когда я начал изучать Mercurial в 2009 (или 2010 году) уже был бесплатный (пусть и ограниченного по объему репозитория) BitBucket. Но он был конечно же не стал таким популярным, как github.
kruslan
25.10.2024 09:19Представим, что у нас есть проект с некоторым количеством файлов, директории в и поддиректории в файлах
3 раза перечитал, так и не понял (((
А по поводу merurial - норм, но то, что крупные компании с крупными проектами от него отказываются (например, attlasian) наводит на мысль, что предназначено не для всех... Правда, я с ним работал только в 1м проекте и особо не увидел разницы с git.
infinity92 Автор
25.10.2024 09:19Представим, что у нас есть проект с некоторым количеством файлов, директорий и поддиректорий с файлами.
Это имелось ввиду. Спасибо поправил.
wl2776
25.10.2024 09:19у Git есть Github, у Mercurial платформы нет.
Есть Bitbucket, уже заметили в комментариях.
И любопытно, что же помешало появиться аналогу гитхаба для mercurial?
infinity92 Автор
25.10.2024 09:19Есть Bitbucket, уже заметили в комментариях.
Если не ошибаюсь, GitHub первым запустил функционал pull-request. Мне кажется, это повлияло на его популярность.
И любопытно, что же помешало появиться аналогу гитхаба для mercurial?
Мне кажется, это уже не имело смысла. Людям зашел GitHub, и Git набрал популярность, поэтому с коммерческой точки зрения это было не нужно. А возможно, были какие-то другие коммерческие причины этому.
wl2776
25.10.2024 09:19Да, у меня такие же мысли. Но это только догадки. Вот если бы были реальные инсайты, это было бы интересно.
Первые хостинги гита, Gerrit и ещё кто-то (забыл) - это ж сущий кошмар, про pull requests там и не слышали, а пользователей редактировать - только руками в консоли, запросами sql к базе.
zzzzzzerg
25.10.2024 09:19На хабре недавно был перевод статьи от создателя Github, о том, почему, на его взгляд, победил Github - Почему GitHub на самом деле победил: история глазами сооснователя / Хабр, там есть немного про BitBucket.
kovserg
Mercurial использовал python2 и когда его начали изгонять начались проблемы. А так да Mercurial гораздо удобнее чем git.