В Git 2.56 войдёт набор прикладных улучшений: подкоманда drop для экспериментального инструментария git history, низкоуровневые операции над ссылками в git refs, опция удаления влитых веток и подсказка о git pull в git status. Одновременно объявлен план выпусков, ведущий к Git 3.0: декабрьский релиз 2026 года получит номер 2.98 как сигнал о приближении перехода, в апреле 2027 года выйдут 2.99 с долгосрочной поддержкой и 3.0 одновременно с ним, и 3.0 станет базой для всех последующих релизов.
Что именно меняется в 2.56
В экспериментальный инструментарий git history добавлена подкоманда drop. Она принимает идентификатор коммита и удаляет его из истории текущей ветки, переигрывая все коммиты, добавленные после него. Это более простой способ избавиться от нежелательного коммита, но команда отказывается работать, если в истории есть коммиты слияния, — из-за этого она неприменима во многих репозиториях.
git refs получает подкоманды create, delete, update и rename для низкоуровневой работы со ссылками: в отличие от git branch, которая управляет ветками как привычными пользователю объектами, эти подкоманды работают со ссылками напрямую и позволяют создавать, удалять, изменять и переименовывать их без привязки к веткам. git branch обзаводится опцией --delete-merged, которая удаляет локальные ветки, влитые в их удалённую отслеживаемую веткулокальная копия ветки из удалённого репозитория, за изменениями которой следит Git; влитость определяется тем, что все коммиты локальной ветки уже присутствуют в соответствующей удалённой отслеживаемой ветке. Попытка удалить ветку через git branch -d теперь завершается неудачей с полезным сообщением, если эта ветка используется для bisectionпоиск коммита, внёсшего проблему, делением диапазона пополам. git status для ветки, отстающей от отслеживаемой, предлагает выполнить git pull. Блокировка файла конфигурации повторяется при неудаче — это снимает раздражение, когда несколько команд пытаются изменить файл одновременно. У git add появляется опция --resolved, добавляющая только файлы с разрешёнными конфликтами слияния.
Что заявлено для 3.0
Git 3.0 будет содержать ряд нарушений совместимости. Наиболее значительное — переход по умолчанию на хеш-функцию SHA-256криптографическая хеш-функция, выбранная преемником SHA-1 вместо SHA-1, применявшейся с самого начала. хешидентификатор объекта, вычисляемый по его содержимому используется для идентификации каждого объекта — файлов, деревьев каталогов, коммитов — и для проверки цепочки коммитов, поэтому смена хеша затрагивает совместимость репозиториев. SHA-1 давно считается слабым.
Второе изменение — приём идентификаторов объектов только в нижнем регистре. Сейчас два внешне разных идентификатора, например f00f00 и F00F00, оказываются одним и тем же, и эта неоднозначность порождала ошибки и уязвимости.
Третье — переход на механизм хранения ссылок reftableбинарный файл, оптимизированный по объёму и скорости доступа вместо хранения каждой ссылки отдельным файлом под .git/refs/. Это важно при большом числе ссылок: в репозитории Android их более 800 000.
Четвёртое — Rust станет обязательным для сборки Git. После 3.0 любая платформа без работающего компилятора Rust не сможет обновиться.
Почему переход на SHA-256 затянулся
Git перешёл на усиленную реализацию SHA-1 по умолчанию в версии 2.13.0, но SHA-1 всё ещё слаб. Среди кандидатов на замену рассматривались SHA-256, SHA-512/256, SHA-256x16, K12 и BLAKE2bp-256; в конце 2018 года проект выбрал SHA-256. Переход шёл пошагово: сначала появился API хеш-функции, затем — расширение objectFormatнастройка репозитория, задающая основной формат хеша для его объектов, ещё без указания совместимого формата; после этого добавили compatObjectFormatнастройку, задающую второй, совместимый формат хеша, чтобы репозиторий мог принимать имена объектов обоих форматов, и лишь затем — поддержку fetch и push при обмене с репозиториями на SHA-1. Неэкспериментальная поддержка SHA-256 появилась в Git 2.42 в 2023 году, но часть связующего кода для взаимодействия со старыми репозиториями заняла больше времени.
Главной задержкой была поддержка на крупных хостингах: GitLab поддерживает SHA-256 с 2024 года, Forgejo тоже, а GitHub всё ещё отсутствует. Выпускать версию Git, создающую несовместимые с GitHub репозитории, — тревожная перспектива. libgit2 блокером не станет: Patrick Steinhardt добавил в неё поддержку reftable, а поддержка SHA-256 была включена по умолчанию в августе.
Как устроен reftable
Файл reftable состоит из секций: сначала идёт first_block с заголовком и первым блоком ссылок, затем последовательности блоков ссылок, индексов, блоков объектов и логов, в конце — footer. Блоки ссылок хранят сами ссылки, индексы позволяют быстро находить нужный блок, блоки объектов содержат объекты, а логи — записи о переходах ссылок. Каждый блок ссылок начинается с байта 'r', за которым следует длина блока, затем записи ссылок, таблица смещений restart-точекместо в блоке, с которого можно начать восстановление имени без опоры на предыдущую запись, счётчик restart-точек и, возможно, padding.
Префиксное сжатие работает так: в записи ссылки поле prefix_length задаёт, сколько ведущих байт имени предыдущей записи нужно скопировать, чтобы получить имя текущей. Это значение обязано быть нулевым для первой записи в блоке и для любой записи, чей offset перечислен в таблице restart-смещений. Для restart-точек prefix_length принудительно равен нулю — именно поэтому с них можно начинать восстановление имени. В одном блоке поддерживается не более 65535 restart-точек. Для репозиториев с множеством ссылок рекомендуются большие размеры блоков (64 KiB) и менее частые restart-точки (каждые 64) — это даёт лучшее сжатие за счёт большего числа ссылок, сжимаемых относительно предыдущей.
Версия 2 формата добавляет в заголовок 4-байтовый идентификатор хеша: "sha1" для SHA-1 и "s256" для SHA-256. Для обратной совместимости при записи SHA-1-таблиц рекомендуется версия 1.
Как включается reftable
Чтобы включить reftable в репозитории, в конфигурации задаётся версия формата репозитория и расширение хранения ссылок:
[core]
repositoryformatversion = 1
[extensions]
refStorage = reftableСочетание core.repositoryFormatVersion=1 и заполнения extensions.* гарантирует, что все версии Git новее v0.99.9l завершатся с ошибкой вместо попытки работать с репозиторием, выдав сообщение об ошибке.
Для обратной совместимости со старыми клиентами, чтобы они продолжали распознавать каталог как git-репозиторий, в нём должны присутствовать фиктивные файлы: .git/HEAD (обычный файл с содержимым ref: refs/heads/.invalid), каталог .git/refs/ и обычный файл .git/refs/heads.
Сами данные ссылок хранятся в каталоге $GIT_DIR/reftable/ в виде набора reftable-файлов, а порядок их применения задаётся файлом $GIT_DIR/reftable/tables.list, где файлы перечислены от самого старого (базового) к самому новому. Читатели должны прочитать tables.list и искать по стеку в обратном порядке — последний reftable проверяется первым. Для получения согласованного снимка нужно открыть все упомянутые файлы и, если какой-то отсутствует, начать заново.
Запись изменений устроена так: хотя reftable-файлы неизменяемы, мутации выполняются путём создания нового reftable и атомарного добавления его в стек через блокировку tables.list.lock. Из-за единственного файла блокировки репозиторий однопоточный для писателей, и они могут ожидать с backoff, прерываясь, если репозиторий слишком занят. Удаление ссылки хранится явно как tombstone: устанавливается type в 0x0 и опускается поле value в записи ссылки, что перекрывает любые утверждения о существовании ссылки из более ранних файлов стека.
Сосуществование SHA-1 и SHA-256
Расширение, задающее второй, совместимый формат хеша, позволяет репозиторию работать сразу с двумя формами имён объектов — например, objectFormat = sha256 вместе с compatObjectFormat = sha1. Для сосуществования двух форматов служит translation table — двунаправленное отображение между SHA-1-именами и SHA-256-именами объектов.
Поиск в ней идёт как обычный поиск объекта: сначала объект ищется в idx-файлах, затем читается соответствие порядка имён SHA-1 порядку имён в паке, проверяется полная таблица SHA-1-имён и берётся соответствующая запись из полной таблицы SHA-256-имён. Если объекта нет в idx, проверяется loose-объектобъект, хранящийся отдельным сжатым файлом, а не внутри общего пак-файла: строки читаются из loose-object-idx до совпадения. Формат этого файла — строки вида «sha256-имя, пробел, sha1-имя, перевод строки», имена в шестнадцатеричном виде, файл не отсортирован. Все операции, создающие новые объекты (например, git commit), добавляют их в соответствующий индекс, поэтому такое отображение возможно для всех объектов в хранилище.
Замеры reftable
В документации приводятся замеры занимаемого места. Для packed-refs и reftable: android — 62.2 M против 36.1 M (58.0% от исходного), rails — 1.8 M против 1.1 M (57.7%), git — 78.7 K против 48.1 K (61.0%), git (heads) — 332 b против 269 b (81.0%). Средние размеры на ссылку и объект: для android 33 bytes и 5 bytes, для rails 29 bytes и 4 bytes, для git 50 bytes и 4 bytes, для git (heads) 33 bytes и 0 bytes. Для логов: 149 932 записи для 43 061 ссылок — $GIT_DIR/logs занимает 173 M (1209 bytes на запись), reftable — 5 M (37 bytes на запись).
По скорости на сканировании 866k ссылок, поиске по имени и по SHA-1: coldпервый прогон, когда данные ещё не в кеше операционной системы — reftable 112 ms, 33.9 usec, 323.2 usec; hotповторный прогон, когда данные уже в кеше — reftable 20.2 usec, 320.8 usec. Три числа в каждой строке — это время сканирования всех ссылок, время поиска ссылки по имени и время поиска по SHA-1 соответственно. Отдельно отмечено, что bzip2 сжимает большой packed-refs (например, 62 MiB до 23 MiB, 37%), но не поддерживает произвольный доступ к отдельной ссылке. В сравнении с альтернативным форматом Michael Haggerty reftable быстрее для поиска (51% быстрее, 11.2 usec против 5.4 usec), но файл немного больше (+ ~3.2%, 28.3M против 29.2M).
Ограничения и открытые вопросы
Переход на SHA-256 по умолчанию сдерживается поддержкой на крупных хостингах: GitLab и Forgejo её имеют, а GitHub — нет, и когда GitHub добавит поддержку, остаётся неясным. Открытым остаётся и вопрос о смене поведения с идентификаторами: carlson хочет принимать только нижний регистр, но почти наверняка найдётся тот, кто полагается на старое поведение.
Переход на reftable тоже отложен до 3.0, и хотя libgit2 уже готова, carlson упоминал её как потенциальную проблему. Поддержка Rust пока пробная, но после 3.0 платформы без рабочего компилятора Rust не смогут обновляться. Решение о том, будет ли следующий выпуск 3.0, ещё не объявлено публично и ждёт решения Hamano.