По роду своей работы я часто являюсь свидетелем «священных войн» между коллегами-программистами на тему того, какую систему контроля версий выбрать для того или иного проекта.
Роль системы контроля версий особенно остра в случаях разработки и поддержки проектов с длительной историей.
Существует множество вариантов инструментов, но я хочу сосредоточиться на двух, которые считаю наиболее многообещающими: Mercurial и Git. Далее мы попробуем рассмотреть возможности обеих систем с точки зрения их внутреннего устройства.
Немного истории
Толчком к созданию обеих систем, как Mercurial, так и Git, послужило одно событие в 2005 году.Все дело в том, что в вышеупомянутом 2005 году ядро Linux лишилось возможности бесплатно использовать систему контроля версий BitKeeper. После трех лет использования BitKeeper основные разработчики привыкли к распределенному рабочему процессу.
Автоматизированная работа с патчами значительно упростила процесс учета и слияния изменений, а наличие истории за длительный период времени позволило провести регрессию.
Другой важной частью процесса разработки ядра Linux была иерархическая организация разработчиков.
На вершине иерархии находился Диктатор и множество Лейтенантов, отвечающих за отдельные подсистемы ядра.
Каждый лейтенант принимал или отвергал отдельные изменения внутри своей подсистемы.
Линус, в свою очередь, отложил внесение изменений и опубликовал их в официальном репозитории ядра Linux. Любой инструмент, пришедший на смену BitKeeper, должен был реализовать такой процесс.
Третьим критическим требованием к будущей системе была скорость работы с большим количеством изменений и файлов.
Ядро Linux — очень большой проект, требующий тысяч отдельных изменений от тысяч разных людей.
Среди множества инструментов не нашлось подходящего.
Почти одновременно Мэтт Макколл и Линус Торвальдс выпустили свои системы контроля версий: Mercurial и Git соответственно.
Обе системы были основаны на идеях проекта Monotone, появившегося двумя годами ранее.
Сходства
Обе системы контроля версий имеют ряд общих особенностей:- ревизии связаны с контрольными суммами;
- история имеет вид ориентированного ациклического графа;
- Поддерживаются функции высокого уровня, в т.ч.
биссекция, ветвление и избирательная фиксация.
Различия
Несмотря на общность идей и высокоуровневую функциональность, низкоуровневые реализации систем во многом различаются.
Хранение истории
И Git, и Mercurial идентифицируют версии файлов по их контрольной сумме.Контрольные суммы отдельных файлов объединяются в манифесты.
В Git манифесты называются деревьями, в которых одни деревья могут указывать на другие.
Манифесты напрямую связаны с ревизиями/фиксациями.
Mercurial использует специальный механизм хранения Revlog для повышения производительности.
Каждый файл, помещенный в хранилище, связан с двумя другими: индексом и файлом данных.
Файлы данных содержат слепки и дельта-самородки, которые создаются только тогда, когда количество изменений отдельных файлов превышает определенный порог.
Индекс служит инструментом для эффективного доступа к файлу данных.
Дельты, возникающие в результате изменений файлов с контролем версий, добавляются только в файлы данных.
Для того чтобы объединить правки из разных мест файла в одну ревизию, используется индекс.
Редакции отдельных файлов формируют манифесты, а манифесты формируют коммиты.
Этот метод оказался очень эффективным при создании, поиске и вычислении различий в файлах.
Также к преимуществам метода можно отнести компактность по отношению к дисковому пространству и достаточно эффективный протокол передачи изменений по сети.
Модель хранения Git основана на больших двоичных объектных файлах (BLOB).
Каждая новая ревизия файла представляет собой полную копию файла, что обеспечивает быстрое сохранение ревизий.
Копии файлов сжаты, но все равно присутствует большое количество дублирования.
Разработчики Git использовали методы упаковки данных, чтобы снизить требования к хранению.
По сути, они создали что-то похожее на Revlog на определенный момент времени.
Полученные пакеты отличаются от Revlog, но имеют ту же цель — сохранять данные, эффективно используя дисковое пространство.
Поскольку Git хранит снимки файлов, а не их приращения, коммиты можно легко создавать и уничтожать.
Если во время анализа вам нужно посмотреть разницу между двумя разными коммитами, то в Git разница (diff) рассчитывается динамически.
Ветвление
Ветвление — очень важная часть систем управления конфигурациями, поскольку.оно позволяет параллельно разрабатывать новый функционал, сохраняя при этом стабильность старого.
Поддержка ветвления присутствует как в Git, так и в Mercurial. Различия в формате хранения истории отражаются и на реализации ветвления.
В Mercurial ветка — это маркер, постоянно прикрепленный к коммиту.
Этот знак является глобальным и уникальным.
Любой, кто извлекает изменения из удаленного репозитория, увидит все ветки своего репозитория и все коммиты в каждой из них.
Для Mercurial ветки — это публичное место разработки за пределами основного ствола.
Названия ветвей публикуются для всех участников, поэтому в качестве имен обычно используются номера стабильных во времени версий.
Ветки Git — это, по сути, просто указатели на коммиты.
В разных клонах репозитория ветки с одинаковым именем могут указывать на разные коммиты.
Ветки в Git можно удалять и фиксировать по отдельности (каждая из которых уникально идентифицируется локальным именем в исходном репозитории).
Практические аспекты использования
Различия в реализациях Git и Mercurial можно проиллюстрировать примерами.Mercurial позволяет легко фиксировать изменения, отправлять и извлекать их с поддержкой всей предыдущей истории.
Git не заботится о сохранении всей предыдущей истории, он просто фиксирует изменения и создает указатели на них.
Для Git не имеет значения, какой была предыдущая история или на какие указатели ссылались ранее, важно то, что актуально в текущий момент. Есть даже инструмент, гарантирующий сохранение локальной истории при извлечении изменений из внешнего хранилища — fast-forward merge. Если этот механизм включен, Git сообщит об изменениях, которые невозможно устранить без перемещения вперед по истории.
Эти ошибки можно игнорировать, если полученные изменения были ожидаемыми.
Когда вы откатываете фиксацию или выполняете слияние, Git просто меняет указатель ветки на предыдущую фиксацию.
Фактически, в любой момент времени, когда вам нужно вернуться к какому-то предыдущему состоянию, Git ищет в журнале соответствующую контрольную сумму и сообщает, какой коммит соответствует ей.
Как только что-то будет передано в Git, вы всегда сможете вернуться в это состояние.
Для Mercurial бывают случаи, когда полностью вернуться в исходное состояние невозможно.
Потому что Mercurial создает коммит на решение проблемы, но в некоторых случаях сложно вернуться к новому изменению.
В Mercurial существуют расширения для решения различных задач.
Каждое расширение хорошо решает свои проблемы, если оно существует само по себе.
Есть даже некоторые расширения, которые предоставляют схожую функциональность, но по-разному.
Например, давайте рассмотрим работу с отложенной историей.
Допустим, нам нужно записать изменения из рабочей копии без фиксации в репозитории.
Git предлагает использовать stash. Тайник — это коммит или ветка, которая не сохраняется в обычном месте.
Stash не отображается при перечислении ветвей, но всеми инструментами рассматривается как ветка.
Если аналогичная функциональность требуется Mercurial, то можно использовать чердачные или полочные надстройки.
Оба этих расширения хранят «отложенную» историю в виде файлов в хранилище, которые можно зафиксировать при необходимости.
Каждое расширение решает проблему немного по-своему, поэтому возникает некоторая несогласованность формата.
Другой пример — команда git commit --amend. Если вам нужно изменить самый последний коммит, например добавить что-то, что вы забыли, или изменить комментарий, команда git commit --amend создаст совершенно новый набор файловых объектов, деревьев и объектов коммита.
После этого указатель ветки обновляется.
Если вам затем понадобится откатить изменения, вам нужно всего лишь вернуть указатель на предыдущий коммит с помощью команды git reset --hard HEAD@{1}.
Чтобы повторить это в Mercurial, вам нужно будет откатить коммит, затем создать новый, затем импортировать содержимое последнего коммита с помощью расширения очереди, добавить к нему и сделать новый коммит. Следует отметить, что ни одно из перечисленных выше дополнений не использует возможности формата хранения Mercurial, и поэтому они существуют исключительно как автономные дополнения поверх него.
выводы
В последнем разделе статьи мне хотелось бы высказать собственное мнение по поводу выбора системы контроля версий.И Mercurial, и Git хороши в своих областях.
Например, для запуска проекта коммерческого программного обеспечения я предпочитаю Mercurial.
- Строгое управление историей в Mercurial гарантирует, что первоначальный источник ошибки может быть учтен и найден.
- После слияния с веткой в Git мы рискуем получить гига-патч со спрятанным где-то багом.
- Глобальные филиалы также предоставляют возможность контролировать работу коллег при регулярной синхронизации с центральным репозиторием.
Сами файлы меняются редко, а основные операции с ними — перемещение и добавление.
По моим собственным наблюдениям, папка репозитория Git с историей моей библиотеки по размеру сопоставима с рабочей копией, около 10%.
Источники знаний
- Главный источник
- Описание формата Mercurial
- Описание формата Git
- Общая справочная информация из Википедии
-
Как Я Добавил 6 Символов В Юникод
19 Oct, 24 -
Мир Во Всем Мире…
19 Oct, 24 -
.Xlsx На Службе Оператора Базы Данных
19 Oct, 24 -
Eee Top Сенсорный Экран «Все В Одном»
19 Oct, 24