Как ветки и теги git хранятся на дисках?

Недавно я проверил одно из моих git-репозиториев на работе, в котором было более 10000 веток и более 30000 тегов. Общий размер репо после свежего клона составляет 12 гигов. Я уверен, что нет никаких причин иметь 10000 филиалов. Поэтому я считаю, что они занимают значительное количество места на дисках. Итак, мои вопросы таковы

  1. Как ветви и теги хранятся на дисках, например, какая структура данных используется, какая информация хранится для каждой ветви?
  2. Как я могу получить метаданные о филиалах? например, когда была создана эта ветка, каков ее размер.

3 ответа

Решение

Все ссылки на git (ветки, теги, заметки, тайники и т. Д.) Используют одну и ту же систему. Это:

  • сами ссылки, и
  • "reflogs"

Reflogs хранятся в .git/logs/refs/ на основе ссылочного имени, за одним исключением: HEAD хранятся в .git/logs/HEAD скорее, чем .git/logs/refs/HEAD,

Ссылки приходят либо "свободные", либо "упакованные". Упакованные ссылки находятся в .git/packed-refs, который представляет собой простой файл пар (SHA-1, refname) для простых ссылок, а также дополнительную информацию для аннотированных тегов. "Свободные" рефери в .git/refs/name, Эти файлы содержат либо необработанный SHA-1 (вероятно, самый распространенный), либо буквенную строку ref: сопровождаемый именем другой ссылки для символических ссылок (обычно только для HEAD но вы можете сделать другие). Символические ссылки не упакованы (или, по крайней мере, я не могу этого добиться:-)).

Упаковка тегов и "незанятых" головок веток (тех, которые не обновляются активно) экономит пространство и время. Ты можешь использовать git pack-refs сделать это. Тем не мение, git gc Запускает git pack-refs для вас, как правило, вам не нужно делать это самостоятельно.

Итак, я собираюсь немного расширить тему и объяснить, как Git хранит что. Это объяснит, какая информация хранится, и что конкретно имеет значение для размера хранилища. Как справедливое предупреждение: этот ответ довольно длинный:)

Git объекты

Git - это, по сути, база данных объектов. Эти объекты бывают четырех разных типов и все идентифицируются хешем SHA1 их содержимого. Четырьмя типами являются BLOB-объекты, деревья, коммиты и теги.

капля

BLOB -объект - это самый простой тип объектов. Хранит содержимое файла. Таким образом, для каждого содержимого файла, которое вы храните в своем Git-репозитории, в базе данных объектов существует один объект blob. Поскольку он хранит только содержимое файла, а не метаданные, такие как имена файлов, это также механизм, который предотвращает многократное сохранение файлов с одинаковым содержимым.

дерево

Поднимаясь на один уровень вверх, дерево является объектом, который помещает BLOB-объекты в структуру каталогов. Одно дерево соответствует одному каталогу. По сути, это список файлов и подкаталогов, каждая запись которого содержит режим файла, имя файла или каталога и ссылку на объект Git, принадлежащий этой записи. Для подкаталогов эта ссылка указывает на объект дерева, который описывает подкаталог; для файлов эта ссылка указывает на объект blob, хранящий содержимое файла.

совершить

BLOB-объектов и деревьев уже достаточно для представления полной файловой системы. Чтобы добавить версионность, у нас есть объекты коммитов. Коммит-объекты создаются всякий раз, когда вы что-то делаете в Git. Каждый коммит представляет собой снимок в истории ревизий.

Он содержит ссылку на объект дерева, описывающий корневой каталог репозитория. Это также означает, что для каждого коммита, который фактически вносит некоторые изменения, требуется, по крайней мере, новый объект дерева (вероятно, больше).

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

И наконец, коммит также содержит метаданные, которые, как вы ожидаете, будут иметь коммит: Автор и коммиттер (имя и время) и, конечно, сообщение о коммите.

Это все, что необходимо для полноценной системы контроля версий; но, конечно, есть еще один тип объекта:

Тег

Объекты тегов являются одним из способов хранения тегов. Точнее говоря, объекты тегов хранят аннотированные теги, которые являются тегами, которые имеют - подобно коммитам - некоторую метаинформацию. Они созданы git tag -a (или при создании подписанного тега) и требуют сообщения тега. Они также содержат ссылку на объект фиксации, на который они указывают, и тег (имя и время).

Рекомендации

До сих пор у нас была полная система управления версиями с аннотированными тегами, но все наши объекты идентифицируются по их хэшу SHA1. Это, конечно, немного раздражает в использовании, поэтому у нас есть кое-что еще, чтобы сделать это проще: ссылки.

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

Обычно существует три "типа" ссылок: ветви, теги и удаленные ветви. Они действительно работают одинаково и все указывают на фиксацию объектов; за исключением аннотированных тегов, которые указывают на объекты тегов (обычные теги тоже просто ссылки на коммиты). Разница между ними заключается в том, как вы их создаете, и в каком подпути /refs/ они хранятся. Я не буду сейчас это освещать, так как это объясняется почти в каждом уроке по Git; просто помните: ссылки, то есть ветки, чрезвычайно дешевы, поэтому не стесняйтесь создавать их практически для всего.

компрессия

Теперь, потому что Торек упомянул кое-что о сжатии Git в своем ответе, я хочу пояснить это немного. К сожалению, он перепутал несколько вещей.

Таким образом, обычно для новых репозиториев все объекты Git хранятся в .git/objects в виде файлов, идентифицированных по их хэшу SHA1. Первые два символа удаляются из имени файла и используются для разбиения файлов на несколько папок, просто чтобы стало немного легче ориентироваться.

В какой-то момент, когда история становится больше или когда она запускается чем-то другим, Git начинает сжимать объекты. Это делается путем упаковки нескольких объектов в один файл пакета. Как именно это работает, не так уж важно; это уменьшит количество отдельных объектов Git и эффективно сохранит их в отдельных индексированных архивах (в этот момент Git будет использовать дельта-сжатие между прочим). Файлы пакета затем сохраняются в .git/objects/pack и может легко получить несколько сотен МиБ в размерах.

Для ссылок ситуация несколько похожа, хотя намного проще. Все текущие ссылки хранятся в .git/refsнапример, филиалы в .git/refs/heads, теги в .git/refs/tags и удаленные филиалы в .git/refs/remotes/<remote>, Как упоминалось выше, это простые текстовые файлы, содержащие только 40-значный идентификатор объекта, на который они указывают.

В какой-то момент Git переместит старые ссылки любого типа в один поисковый файл: .git/packed-refs, Этот файл представляет собой длинный список хэшей и имен ссылок, по одной записи в строке. Ссылки, которые там хранятся, удаляются из refs каталог.

Reflogs

Торек также упомянул о них, reflogs - это просто журналы для ссылок. Они отслеживают, что происходит со ссылками. Если вы делаете что-либо, что влияет на ссылку (фиксация, извлечение, сброс и т. Д.), Тогда добавляется новая запись в журнале, чтобы просто регистрировать, что произошло. Это также дает возможность вернуться после того, как вы сделали что-то не так. Например, общий случай использования - это доступ к журналу после случайного сброса ветки туда, куда она не должна была пойти. Вы можете использовать git reflog чтобы посмотреть журнал и увидеть, куда ссылалась ранее. Поскольку свободные объекты Git не удаляются сразу (объекты, которые являются частью истории, никогда не удаляются), вы обычно можете легко восстановить предыдущую ситуацию.

Рефлоги, однако, локальны: они только отслеживают, что происходит с вашим локальным репозиторием. Они не передаются удаленным пользователям и никогда не передаются. Только что клонированный репозиторий будет иметь reflog с одной записью, это будет действие клона. Они также ограничены определенной длиной, после которой старые действия сокращаются, поэтому они не станут проблемой хранения.

Несколько заключительных слов

Итак, вернемся к вашему актуальному вопросу. Когда вы клонируете репозиторий, Git обычно уже получает репозиторий в упакованном формате. Это уже сделано, чтобы сэкономить время передачи. Ссылки очень дешевы, поэтому они никогда не являются причиной больших репозиториев. Однако, из-за природы Git, у одного текущего объекта коммита есть целый ациклический граф, который в конечном итоге достигнет самого первого коммита, самого первого дерева и самого первого большого двоичного объекта. Таким образом, хранилище всегда будет содержать всю информацию для всех ревизий. Это то, что делает репозитории с большой историей большими. К сожалению, вы ничего не можете с этим поделать. Ну, в какой-то момент вы могли бы отрезать более старую историю, но это оставит вас со сломанным репозиторием (вы делаете это путем клонирования с помощью --depth параметр).

А что касается вашего второго вопроса, как я объяснил выше, ветки - это просто ссылки на коммиты, а ссылки - только указатели на объекты Git. Так что нет, на самом деле нет метаданных о ветвях, которые вы можете получить от них. Единственное, что может дать вам идею, - это первый коммит, который вы сделали, переходя в своей истории. Но наличие ветвей не означает автоматически, что на самом деле есть ветвь, сохраняемая в истории (быстрое слияние и перебазирование работает против нее), и просто потому, что есть некоторое ветвление в истории, которое не означает, что ветвь (ссылка, указатель) все еще существует.

Примечание: в отношении пакетов ссылок процесс их создания должен быть намного быстрее с Git 2.2+ (ноябрь 2014 г.)

Смотрите коммит 9540ce5 Джеффа Кинга ( peff ):

ссылки: написать packed_refs файл с использованием stdio

Мы пишем каждую строку нового упакованного файла ссылок по отдельности, используя write() системный вызов (и иногда 2, если ссылка очищена). Поскольку каждая строка имеет длину всего около 50-100 байт, это создает много системных издержек.

Вместо этого мы можем открыть stdio обращаться с нашим дескриптором и использовать fprintf написать ему. Дополнительная буферизация для нас не проблема, потому что никто не будет читать наш новый файл pack-refs, пока мы не вызовем commit_lock_file (к этому моменту мы все сбросили).

На патологическом репозитории с 8,5 миллионами ссылок это сократило время запуска git pack-refs от 20 до 6 с.


Обновление, сентябрь 2016: Git 2.11+ будет включать цепочечные теги inpack-refs (" цепочечные теги и git clone --single-branch --branch tag ")

И тот же Git 2.11 теперь будет использовать полностью растровое растровое изображение.

См. Коммит 645c432, коммит 702d1b9 (10 сентября 2016 г.) Кирилла Смелкова ( navytux)
Помогает: Джефф Кинг ( peff )
(Объединено Юнио С Хамано - gitster - в коммите 7f109ef, 21 сентября 2016 г.)

pack-objects: использовать индекс растрового изображения при создании пакета, отличного от stdout

Пакетные растровые изображения были введены в Git 2.0 ( commit 6b8fda2, декабрь 2013 г.), из работы Google для JGit.

Мы используем растровое API для выполнения Counting Objects этап в пак-объектах, а не традиционная прогулка по графу объектов.

Сейчас (2016):

Начиная с 6b8fda2 (pack-objects: использовать растровые изображения при упаковке объектов), если хранилище имеет битовый индекс, pack-объекты могут значительно ускорить этап прохождения графа "Подсчет объектов".
Это, однако, было сделано только для случая, когда результирующий пакет отправляется на стандартный вывод, а не записывается в файл.

Возможно, вы захотите сгенерировать файлы дисков на диске для передачи специального объекта.
Было бы полезно иметь способ переопределить эту эвристику:
сообщить пакетным объектам, что, хотя он должен генерировать файлы на диске, все еще можно использовать битовые карты достижимости для выполнения обхода.


Примечание: GIt 2.12 иллюстрирует, что использование растрового изображения имеет побочный эффект git gc --auto

Смотрите коммит 1c409a7, коммит bdf56de (28 декабря 2016 г.) Дэвида Тернера ( csusbdt )
(Объединено Юнио С Хамано - gitster - в коммите cf417e2, 18 января 2017 г.)

Индекс растрового изображения работает только для отдельных пакетов, поэтому запрос инкрементной перепаковки с индексами растрового изображения не имеет смысла.

Инкрементные перепаковки несовместимы с растровыми индексами


Git 2.14 уточняет pack-objects

Смотрите коммит da5a1f8, коммит 9df4a60 (09 мая 2017 г.) Джеффа Кинга ( peff )
(Объединено Юнио С Хамано - gitster - в коммите 137a261 от 29 мая 2017 г.)

pack-objects: отключить повторное использование пакета для выбора объектов

Если определенные варианты, такие как --honor-pack-keep, --local, или же --incremental используются с пакетными объектами, тогда нам нужно скормить каждый потенциальный объект want_object_in_pack() чтобы увидеть, если это должно быть отфильтровано.
Но когда активируется оптимизация растрового изображения reuse_packfile, мы вообще не вызываем эту функцию и фактически пропускаем добавление объектов в to_pack список целиком

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

Эта проблема существует с момента появления кода повторного использования пакета в 6b8fda2 (pack-objects: использовать растровые изображения при упаковке объектов, 2013-12-21), но на практике это вряд ли возникнет.
Эти параметры обычно используются для упаковки на диске, а не для переноса пакетов (которые stdout), но мы никогда не разрешали повторное использование пакетов для пакетов, отличных от stdout (до 645c432 мы даже не использовали растровые изображения, на которые опирается оптимизация повторного использования; после этого мы явно отключали их, когда не упаковывали в stdout).

Другие вопросы по тегам