понедельник, 8 августа 2011 г.

Inside .bzr: что внутри служебного каталога .bzr. Часть 1: просто ветка

В своей книге Version Control by Example Эрик Синк (Eric Sink) пишет:
Version control tools are more like cars than clocks. 
Clock users have no need to know how a clock works behind the dials. We just want to know what time it is. Those who understand the inner workings of a clock can’t tell time any more skillfully than the rest of us. 
Version control tools are more like cars. Lots of people, including me, use cars without knowing much about how they work. However, people who really understand cars tend to get better performance out of them.
Мне нравится это сравнение. В самом деле, если вы понимаете как внутри системы вертятся колесики, вы сможете более осознано управлять ею.

В документации к bzr вопросам внутреннего устройства системы уделяется меньше внимания, чем могло быть. Отчасти потому что разработчики bzr исходили из посылки, что для успешного использования bzr знания о внутреннем устройстве необязательны (в отличие от git, где без знания как устроен репозиторий  вы не имеете права использовать его).

Предлагаю рассмотреть внутреннее устройство служебного каталога .bzr, и что там живет внутри, это даст вам понимание о разнице между веткой, разделяемом репозитории (shared repository) и различным типам checkouts.
(Далее)
Кстати, внутрь служебных каталогов типа .bzr заглядывать не поощряется, тем более, менять там что-то, ибо это может быть чревато фатальными последствиями. Однако в ходе этой статьи мы будем внимательно смотреть и изучать в отдельно созданных ветках и репозиториях, так что ничего не бойтесь.
Обычная ветка

Начнем с объекта, создаваемого по умолчанию командой bzr init -- это обычная ветка.

C:\work\bzr-day\Inside>bzr init branch
Created a standalone tree (format: 2a)


C:\work\bzr-day\Inside>cd branch
C:\work\bzr-day\Inside\branch>

Для того, чтобы исследуемый объект не был совсем пустым, добавим файл и каталог и зафиксируем их в виде новой ревизии.

C:\work\bzr-day\Inside\branch>bzr mkdir spam
added spam


C:\work\bzr-day\Inside\branch>echo > eggs


C:\work\bzr-day\Inside\branch>bzr add
adding eggs


C:\work\bzr-day\Inside\branch>bzr st
added:
  eggs
  spam/


C:\work\bzr-day\Inside\branch>bzr ci -m "initial revision"
Committing to: C:/work/bzr-day/Inside/branch/
added eggs
added spam
Committed revision 1.

Заходим внутрь каталога .bzr и что мы там видим:

branch/
branch-lock/
checkout/
repository/
branch-format
README

Четыре каталога и 2 файла.

Посмотрим, что нам предлагает прочитать в нем файл README:

This is a Bazaar control directory.
Do not change any files in this directory.
See http://bazaar.canonical.com/ for more information about Bazaar.

Нам опять рассказывают о том, что ничего не надо менять здесь. Мы и не собираемся ничего менять, но должен заметить, что в некоторых очень особенных ситуациях кое-что менять внутри .bzr нужно уметь. Если вы не умеете -- не меняйте.

Посмотрим второй файл branch-format:

Bazaar-NG meta directory, format 1

Это не просто текстовый файл, это идентификатор формата служебного каталога. Порча этого файла приведет к невозможности работы с данной веткой. Если в результате сбоя диска или других неприятностей вы потеряете этот файл в вашем рабочем проекте, то можете восстановить его простым копированием из нормальной ветки. Этот формат не менялся уже очень давно, и я сильно сомневаюсь у вас есть активные ветки в архаичных форматах.

Из 4х каталогов branch, branch-lock, checkout, repository интересными для нас будут только branch, checkout и repository, каталог branch-lock предназначен для блокировок совместного доступа и в мирное время он пуст.

Историческое отступление

Файл branch-format и каталог branch-lock имеют такое название с доисторических времен, когда Bazaar только зарождалась. Для обратной совместимости с древними форматами они оставлены без изменений.

В остальных каталогах файл-маркер формата называется просто format, а каталог для блокировок -- lock.

Файлы-маркеры форматов (format) нужны bzr для того, чтобы знать как правильно работать с соответствующим объектом. На вопрос "зачем базару сто тысяч миллионов форматов" я отвечу в другой статье.

Продолжаем.

Каталоги branch, checkout, repository соответствуют 3м базовым объектам в bzr: ветке, рабочей копии и репозиторию. Собственно каталог .bzr, содержащий их, является контейнером и называется control directory. Все эти 4 объекта и их форматы отображаются в выводе команды bzr info -v:

C:\work\bzr-day\Inside\branch\.bzr>bzr info -v
Standalone tree (format: 2a)
Location:
  branch root: C:/work/bzr-day/Inside/branch

Format:
       control: Meta directory format 1
  working tree: Working tree format 6
        branch: Branch format 7
    repository: Repository format 2a - rich roots, group compression and chk inventories

...

Секция, озаглавленная Format, показывает нам форматы для всех 4х объектов. Эти форматы, как я уже сказал, идентифицируются файлами format в соответствующих подкаталогах.

Компонент (каталог) branch

Компонент branch содержит указатель на последнюю ревизию в данной ветке, а также теги в данной ветке. Кроме того он содержит конфигурационный файл, специфичный для данной ветки.

Посмотрим внутрь .bzr/branch:

lock/
branch.conf
format
last-revision
tags

Каталог lock неинтересен -- он для блокировок.

Файл format содержит маркер формата компонента:

Bazaar Branch Format 7 (needs bzr 1.6)

(В выводе команды bzr info мы видели строку Branch format 7 -- совпадает).

Файл last-revision -- это тоже маркер. Собственно это -- указатель на последнюю ревизию в данной ветке (так называемый tip). Содержимое файла -- это пара revno revid:

1 ru_bzr@googlegroups.com-20110806154658-b9b3oat6e8tzsibe

Первое число -- это номер последней ревизии, а строка -- это уникальный идентификатор ревизии. Если мы посмотрим журнал для последней зафиксированной ревизии, то увидим эти же данные:

C:\work\bzr-day\Inside\branch\.bzr\branch>bzr log --show-ids -r-1
------------------------------------------------------------
revno: 1
revision-id: ru_bzr@googlegroups.com-20110806154658-b9b3oat6e8tzsibe
committer: Базарный день
branch nick: branch
timestamp: Sat 2011-08-06 18:46:58 +0300
message:
  initial revision

Самой ревизии в .bzr/branch мы не видим -- ревизии хранятся в .bzr/repository.

Файл tags -- это хранилище для тегов ветки. Пока мы не создали ни одного тега этот файл пустой. При добавлении тегов командой bzr tag в этот файл будут добавляться данные о тегах, в виде словаря где ключ -- это имя тега, а значение -- это идентификатор ревизии, на которую указывает тег. Сам словарь кодируется при помощи Bencode.

Файл branch.conf наиболее интересен с точки зрения пользователя. Это файл конфигурации для текущей ветки. В нем могут храниться любые настройки и параметры, которые нужны для ветки. В частности, там хранятся пути/URL к связанным веткам: parent_location (для pull/missing/merge), push_location (для push), submit_location (для merge/send). Кроме того, плагин QBzr использует branch.conf для сохранения данных о коммитах, или кодировке файлов (qdiff/qannotate/qcat/qviewer). Этот файл должен иметь кодировку UTF-8, и в принципе если вам нужно, то его можно редактировать вручную, или менять параметры в нем с помощью команды bzr config.

Наиболее важно то, что для простых веток содержимое этого файла некритично для работы. А вот в случае bound branch / heavyweight checkout или stacked branch в файле branch.conf содержится важная информация, поэтому порча этого файла может нарушить нормальную работу. Подробнее об этом мы поговорим позднее.

Компонент (каталог) checkout

Компонент checkout хранит служебную информацию, необходимую для работы с рабочим деревом (working tree). Если ветка не имеет рабочего дерева (например, на сервере), то компонент будет отсутствовать.

Посмотрим внутрь .bzr/checkout:

lock/
shelf/
conflicts
dirstate
format
views

Назначение каталога lock и файла format -- аналогичное рассмотренным ранее.

Каталог shelf предназначен для работы функциональности shelve / unshelve -- именно там хранятся те правки, которые вы отобрали в команде shelve.

Файл dirstate хранит информацию о файлах и каталогах в вашем рабочем дереве: о последней зафиксированной ревизии каждого элемента, об изменениях и т.д. Этот файл достаточно важен для нормальной работы, при его порче единственное, что можно сделать -- это создать рабочее дерева заново и скопировать туда последние незафиксированные изменения (если нужно).

Файл conflicts как следует из его названия хранит информацию о текущих конфликтах требующих решения.

Файл views хранит информацию о настроенных видах (см. команду bzr view).

Кроме этих файлов и каталогов в .bzr/checkout еще могут быть созданы следующие файлы и каталоги:

файл merge-hashes создается в результате merge и служит для отслеживания факта модификаций файлов после merge, используется командой revert для определения того, безопасно ли делать revert конкретному файлу или там могут находиться пользовательские изменения.

два каталога limbo и pending-deletions -- это коротко живущие каталоги, создаваемые во время выполнения команд pull, push, merge. Эти каталоги не должны существовать после нормального завершения команд. Однако при сбоях в работе указанных команд эти каталоги остаются на диске и мешают дальнейшей работе. Единственный способ исправить сложившуюся ситуацию -- это нужно просто их удалить, особенно если они пустые.

Компонент (каталог) repository

Компонент repository -- это собственно хранилище истории: ревизий, текстов файлов в каждой ревизии, состояния рабочего дерева для каждой ревизии, а также подписей к ревизиям. Самостоятельные ветки имеют собственный репозиторий, ветки же создаваемые внутри shared repository используют репозиторий общего хранилища. Об этой особенности мы говорили ранее.

Начиная с bzr версии 1.0 основным форматом репозитория стал так-называемый pack-based формат. Краткое техническое описание формата можно прочитать в документации для разработчиков bzr: http://doc.bazaar.canonical.com/bzr.2.3/developers/packrepo.html#technical-notes. Также начиная с bzr 2.0 основным форматом репозитория стала улучшенная версия описанного формата 2a (CHKGroupCompress, CHK = Content Hash Key), которая улучшила сжатие данных в репозитории и позволила ускорить операции по сохранению и извлечению данных. В документации разработчиков есть только некоторые мысли касательно дизайна нового формата: http://doc.bazaar.canonical.com/bzr.2.3/developers/groupcompress-design.html, для нас же главное, что в формате 2a добавился еще один индекс. См. далее.

Посмотрим внутрь .bzr/repository:

indices/
lock/
obsolete_packs/
packs/
upload/
format
pack-names

Назначение каталога lock и файла format -- аналогичное рассмотренным ранее.

Файл pack-names содержит список пакетированных (pack) файлов. Этот список содержит базовые имена файлов в каталогах packs и indices. Если посмотреть этот файл в текстовом редакторе, то можно увидеть, что он закодирован (сжат для экономии места). Для просмотра его в читаемом виде можно применить скрытую команду bzr dump-btree:

C:\work\bzr-day\Inside\branch\.bzr\repository>bzr dump-btree pack-names
(('42d1a7283aac1a3d35f93593af36b836',), '162 161 238 72 183', None)

Показанные данные все равно имеют свои особенности, как мы видим, для нас же главное -- это первый элемент: 42d1a7283aac1a3d35f93593af36b836. Это имя пакетированного файла и индексов к нему.

Посмотрим внутрь каталога packs: там находится единственный файл 42d1a7283aac1a3d35f93593af36b836.pack и как мы можем видеть его имя файла соответствует информации из pack-names + расширение.

Отличительная деталь: имена пакетированных файлов совпадают с md5 суммой для их содержимого. Этот факт рекомендуется использовать для проверки целостности пакетированных файлов.

C:\work\bzr-day\Inside\branch\.bzr\repository\packs>md5sum 42d1a7283aac1a3d35f93593af36b836.pack
42d1a7283aac1a3d35f93593af36b836 *42d1a7283aac1a3d35f93593af36b836.pack

Сами пакетированные файлы содержат много блоков различной информации в сжатом виде (zlib + CHKGroupCompress) и для извлечения конкретной информации bzr использует индексы для определения того, где именно в файле лежат нужные данные.

Индексы хранятся в каталоге indices. Посмотрим:

42d1a7283aac1a3d35f93593af36b836.cix
42d1a7283aac1a3d35f93593af36b836.iix
42d1a7283aac1a3d35f93593af36b836.rix
42d1a7283aac1a3d35f93593af36b836.six
42d1a7283aac1a3d35f93593af36b836.tix

Опять же имена файлов-индексов соответствуют имени пакетированного файла. Расширения файлов-индексов соответствуют различной информации, которую bzr собирается извлекать.
  • cix -- индексы для восстановления данных, закодированных по алгоритму CHKGroupCompress.
  • tix -- индексы для извлечения содержимого файлов в рабочем дереве.
  • rix -- индексы для извлечения данных о ревизиях.
  • iix -- индексы для извлечения инвентаря ревизий (списка файлов и каталогов в конкретной ревизии + указание на содержимое файлов). Это аналог файлов dirstate из компонента checkout.
  • six -- индексы для извлечения gpg-подписей к ревизиям (если таковые имеются).
Индексы также закодированы, для их просмотра в раскодированном виде нужно использовать команду bzr dump-btree.

Каталоги upload и obsolete_packs имеют вспомогательный характер.

В каталоге upload создаются новые пакетированные файлы с временным именем, после чего переименовываются согласно их md5 и переносятся в каталог packs. После успешного окончания операций каталог upload должен быть пуст. Если в результате сбоя там остаются какие-то файлы, то их нужно удалять.

Каталог obsolete_packs служит в качестве мусорки. Туда переносятся ненужные более пакетированные файлы и их индексы после перепаковки мелких пакетированных файлов в более крупные.

Bazaar при каждой новой ревизии создает один новый пакетированный файл. Каждый пакетированный файл записывается только один раз и туда больше ничего не дописывается. Это сделано чтобы гарантировать целостность данных. Однако, при таком подходе количество пакетированных файлов будет очень большим, что плохо скажется как на скорости работы, так и на используемом дисковом пространстве. Поэтому каждые 10/100/1000/10000/.... (10 в n-й степени) ревизий bzr автоматически осуществляет переупаковку мелких файлов в более крупные. По окончанию операции старые файлы не удаляются сразу, а сначала переносятся в каталог obsolete_packs, на случай если их нужно будет восстановить.

Переупаковку репозитория можно делать и вручную, запустив команду bzr pack. Это имеет смысл периодически делать для репозиториев на центральном сервере, чтобы все работало максимально оптимально.

После успешной перепаковки файлы из каталога obsolete_packs можно удалить. Команда bzr pack имеет ключ --clean-obsolete-packs однако его использование предваряется предупреждением, что в случае сбоя диска во время выполнения этой операции могут быть потеряны файлы. Более безопасным способом на Linux будет сделать что-то вроде sync && sync и затем удалить файлы вручную.

Следует отметить, что bzr сам удалит файлы из obsolete_packs при следующей перепаковке (автоматической или ручной). Поэтому если дискового пространства у вас достаточно, то можно этим не заморачиваться.

Однако для целей сравнения размеров репозиториев (с другими DVCS например) имеет смысл вручную перепаковаться и удалить файлы из obsolete_packs. Так результат будет более оптимальный.

Автоматическая перепаковка иногда может быть нежелательной, в новых версиях bzr работают над тем, чтобы сделать ее настраиваемой.

Заключение

Мы рассмотрели внутреннее устройство служебного каталога .bzr для базового объекта -- ветки. В последующих частях мы рассмотрим внутренее устройство других объектов -- shared repository, bound branch, stacked branch, lightweight checkout и увидим, что все они строятся из рассмотренных блоков в нужных комбинациях.

Комментариев нет:

Отправить комментарий