Привет, я Александр, разработчик из команды Битрикс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, завоевавшего любовь разработчиков.  

У каждой системы есть свои преимущества при работе над проектами, а какие важнее для вас в конкретном случае — подскажут технические требования и предпочтения.

Ссылки на материалы, которые я использовал при написании статьи:

  1. Статья “Тонкости благополучного git-merge” — https://habr.com/ru/articles/195674/.

  2. Pro Git book — https://git-scm.com/book/en/v2.

  3. Статья “Сходство и различие между Mercurial и Git” — https://habr.com/ru/articles/168675/.

  4. Mercurial: The Definitive Guide by Bryan O'Sullivan — https://hgbook.red-bean.com/read.

  5. Материалы, посвященные ревлогам — 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.

  6. Материал по форматам файлом в Mercurial — FileFormats - Mercurial.

Комментарии (13)


  1. kovserg
    25.10.2024 09:19

    Mercurial использовал python2 и когда его начали изгонять начались проблемы. А так да Mercurial гораздо удобнее чем git.


  1. gizur
    25.10.2024 09:19

    Не будем ходить вокруг да около: у Git есть Github, у Mercurial платформы нет.

    Когда я начал изучать Mercurial в 2009 (или 2010 году) уже был бесплатный (пусть и ограниченного по объему репозитория) BitBucket. Но он был конечно же не стал таким популярным, как github.


  1. alan008
    25.10.2024 09:19

    Git, mercurial...

    SVN ещё жив, так-то


    1. mynameco
      25.10.2024 09:19

      Особенно там где юнити


  1. poxvuibr
    25.10.2024 09:19

    А что такое фиксация?


    1. infinity92 Автор
      25.10.2024 09:19

      ревизия


    1. alan008
      25.10.2024 09:19

      Коммит, грёбаные любители автоматизированного перевода :-)


  1. kruslan
    25.10.2024 09:19

    Представим, что у нас есть проект с некоторым количеством файлов, директории в и поддиректории в файлах

    3 раза перечитал, так и не понял (((

    А по поводу merurial - норм, но то, что крупные компании с крупными проектами от него отказываются (например, attlasian) наводит на мысль, что предназначено не для всех... Правда, я с ним работал только в 1м проекте и особо не увидел разницы с git.


    1. infinity92 Автор
      25.10.2024 09:19

      Представим, что у нас есть проект с некоторым количеством файлов, директорий и поддиректорий с файлами.

      Это имелось ввиду. Спасибо поправил.


  1. wl2776
    25.10.2024 09:19

    у Git есть Github, у Mercurial платформы нет.

    Есть Bitbucket, уже заметили в комментариях.

    И любопытно, что же помешало появиться аналогу гитхаба для mercurial?


    1. infinity92 Автор
      25.10.2024 09:19

      Есть Bitbucket, уже заметили в комментариях.

      Если не ошибаюсь, GitHub первым запустил функционал pull-request. Мне кажется, это повлияло на его популярность.

      И любопытно, что же помешало появиться аналогу гитхаба для mercurial?

      Мне кажется, это уже не имело смысла. Людям зашел GitHub, и Git набрал популярность, поэтому с коммерческой точки зрения это было не нужно. А возможно, были какие-то другие коммерческие причины этому.


      1. wl2776
        25.10.2024 09:19

        Да, у меня такие же мысли. Но это только догадки. Вот если бы были реальные инсайты, это было бы интересно.

        Первые хостинги гита, Gerrit и ещё кто-то (забыл) - это ж сущий кошмар, про pull requests там и не слышали, а пользователей редактировать - только руками в консоли, запросами sql к базе.


    1. zzzzzzerg
      25.10.2024 09:19

      На хабре недавно был перевод статьи от создателя Github, о том, почему, на его взгляд, победил Github - Почему GitHub на самом деле победил: история глазами сооснователя / Хабр, там есть немного про BitBucket.