27 июня вышел Git 2.37 с новым механизмом очистки файловой системы, её встроенным монитором и другими доработками. Подробности рассказываем к старту курса по Fullstack разработке на Python.


Новый механизм удаления недоступных объектов

Часто мы говорим о классификации объектов как «достижимых» и «недостижимых»: 

  • объект «достижим», когда есть хотя бы одна ссылка (ветвь или тег), с которой можно начать переход от коммитов к их родителям, от деревьев к их поддеревьям и т. д. и закончить в нужном месте; 

  • если такой ссылки нет, объект «недоступен».

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

В конфигурации Git внимательные читатели заметят параметр gc.pruneExpire. Он определяет «отсрочку»: насколько долго недостаточно устаревшие для полного удаления недоступные объекты остаются в покое. Это делается, чтобы смягчить состояние гонки, когда недостижимый объект, который должен быть удалён, становится доступным любому другому процессу (например, входящему обновлению ссылки или push) до удаления и оставляет репозиторий повреждённым.

Небольшая отсрочка заметно снижает вероятность встречи с этой гонкой, но приводит к вопросу о том, как отслеживать возраст недоступных объектов, которые не покинули репозиторий. Упаковать их вместе в один пакетный файл нельзя; все объекты в пакете имеют одинаковое время изменения, а потому обновление любого объекта перетаскивает вперёд их все. 

До Git 2.37 каждый уцелевший недостижимый объект записывался как несвязанный, а mtime отдельных объектов использовалось для хранения их возраста. Это может привести к серьёзным проблемам, когда слишком много новых недостижимых объектов не могут быть удалены.

В Git 2.37 представили cruft packs, позволяющие хранить недоступные объекты в одном пакетном файле, записывая возраст отдельных объектов во вспомогательную таблицу, которая вместе с пакетом хранится в *.mtimes.

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

$ git gc --cruft --prune=1.day.ago

В каталоге $GIT_DIR/objects/pack появится файл .mtimes с возрастом каждого недостижимого объекта за последние сутки:

$ ls -1 .git/objects/pack
pack-243103d0f640e0096edb3ef0c842bc5534a9f9a4.idx
pack-243103d0f640e0096edb3ef0c842bc5534a9f9a4.mtimes
pack-243103d0f640e0096edb3ef0c842bc5534a9f9a4.pack
pack-5a827af6f1a793a45c816b05d40dfd4d5f5edf28.idx
pack-5a827af6f1a793a45c816b05d40dfd4d5f5edf28.pack

Множество подробностей о крафт-пакетах мы ещё не рассмотрели, поэтому ожидайте более полный технический обзор в отдельном посте и в ближайшее время.

[исходники]

Встроенный монитор файловой системы для Windows и macOS

Один из факторов, сильно влияющих на производительность Git, — размер рабочего каталога. Когда вы запускаете, например, git status, в худшем случае Git должен просканировать весь рабочий каталог, чтобы выяснить, какие файлы изменились.

Git имеет собственный кеш файловой системы, чтобы во многих случаях не обходить весь каталог. Но, пока вы работаете, обновление кеша до фактического состояния может обойтись дорого.

В прошлом Git через хук интегрировался, например, с Watchman, заменяя дорогое обновление долго работающим демоном, который отслеживал состояние файловой системы более направленно.

В Git 2.37 Windows и macOS эта функция встроена, а это устраняет необходимость установки внешнего инструмента и настройки хука. Включить функцию можно параметром конфигурации core.fsmonitor.

$ git config core.fsmonitor true

После настройки конфигурации первый вызов git status займёт обычное количество времени, но следующие команды будут использовать отслеживаемые данные и выполняться значительно быстрее.

Описать в этом посте полную реализацию невозможно. На этой неделе заинтересованные читатели могут прочитать сообщение в блоге от Джеффа Хостетлера. Мы обязательно добавим ссылку.

 [исходники, исходники, исходники, исходники]

К широкому применению готов разреженный индекс

Ранее мы анонсировали функцию разреженного индекса, которая помогает ускорить выполнение команд Git при использовании функции разреженной проверки в большом репозитории.

Часто при работе с очень большим репозиторием локальное присутствие всего содержимого не нужно. Если компания использует один монорепозиторий, вас могут интересовать только его части.

Частичные клоны позволяют загружать только необходимые объекты. Не менее важный компонент уравнения — разреженный индекс. Благодаря ему индекс репозитория — ключевая структура данных, которая отслеживает содержимое вашего следующего коммита, какие файлы были изменены и многое другое — отслеживает только интересные вам части репозитория.

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

В 2.37.0 окончательные интеграции введены в git show, git sparse-checkout и git stash. Из всех интеграций наибольший прирост производительности получил git stash: команда считывает и записывает индексы несколько раз в одном процессе и иногда ускоряется почти на 80% (все подробности — в этом треде).

[исходники, исходники, исходники]

Чтобы узнать больше, ознакомьтесь с примечаниями к версии 2.37 или любой предыдущей версии в репозитории Git.

Интересные факты

Теперь обратимся к не столь важным темам этого релиза.

Говоря о разреженных проверках, в этом релизе мы не рекомендуем использовать стиль определения разреженной проверки non-cone.

git sparse-checkout поддерживает два типа шаблонов, определяющих, какие части вашего репозитория следует извлекать: cone и non-cone, позволяющий указывать отдельные файлы с помощью синтаксиса в стиле .gitignore, может сбивать с толку при правильном использовании и имеет проблемы с производительностью: в худшем случае все шаблоны должны пытаться сопоставляться со всеми файлами. И самое важное: он несовместим с разреженным индексом, который повышает производительность применения разреженного извлечения для всех команд Git, с которыми вы знакомы.

По этим и многим другим причинам стиль шаблонов non-cone устарел в пользу --cone и в будущем релизе будет удалён.

[исходники]

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

У core.fsyncMethod появилась «пакетная обработка», которая в поддерживаемых файловых системах при записи множества отдельных файлов может дать значительное ускорение. Режим работает путём постановки множества обновлений в кеш с обратной записью диска перед выполнением единственного fsync(), заставляющего диск очищать этот кеш. Затем файлы атомарно перемещаются на место, а к моменту попадания в каталог объекта гарантируют fsync-надёжность.

Сейчас этот режим поддерживает только пакетную запись несвязанных объектов и будет включён, только если core.fsync содержит значение loose-objects. В синтетическом тесте добавления 500 файлов в репозиторий с помощью git add (каждый из которых приводит к созданию нового несвязанного объекта) новый режим batch по сравнению с отсутствием fsync вообще накладывает лишь скромный штраф.

В Linux добавление 500 файлов без fsync() занимает 0,06 секунды и 1,88 секунды с fsync() после каждой записи несвязанного объекта и всего 0,15 секунд с новой пакетной функцией fsync(). Другие платформы показывают аналогичное ускорение, ярким примером является Windows, где цифры составляют 0,35, 11,18 и всего 0,41 секунды соответственно.

[исходники]

Если вы когда-нибудь задавались вопросом: «Что изменилось в моём репозитории со вчерашнего дня?», один из способов выяснить это — параметр --since, который поддерживается всеми стандартными командами просмотра ревизий — log и rev-list и т. д.

Эта опция работает, начиная с указанных коммитов и рекурсивно проходит по родителям каждого коммита, останавливая обход, как только обнаруживает коммит старше указанной в --since даты. Но иногда, особенно при перекосе часов это может привести к запутанным результатам.

Предположим, у вас три коммита: C1, C2 и C3, где C2 — родительский для C3, а C1 является родителем C2. Если и C1, и C3 записаны за последний час, но C2 старше одного дня (возможно, из-за отставания часов коммитера), тогда обход с --since=1.hour.ago покажет только C3, ведь просмотр C2 заставляет Git остановить обход.

Если вы ожидаете, что история репозитория несёт некоторый перекос часов, вместо --since можно воспользоваться --since-as-filter, который печатает только коммиты после указанной даты, но не останавливает свой обход, увидев более раннюю дату.

[исходники]

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

Даже в простом примере попытка вспомнить объектный фильтр для клонирования репозитория требует заклинания:

$ git config remote.origin.partialCloneFilter

В Git 2.37 узнать это намного проще, например через git remote -v:

$ git remote -v
origin    git@github.com:git/git.git (fetch) [tree:0]
origin    git@github.com:git/git.git (push)

Между квадратными скобками легко увидеть, что удалённый original использует фильтр tree:0

Эта работа подготовлена ​​Абхрадипом Чакраборти, студентом Google Summer of Code, одним из трёх студентов, которые присоединились к работе над Git в этом году.

[исходники]

Говоря об удалённой настройке, отметим, что Git 2.37 поставляется с поддержкой предупреждения или выхода при обнаружении учётных данных в виде простого текста, хранящихся в вашей конфигурации с новой настройкой transfer.credentialsInUrl.

Не рекомендуется хранить учётные записи в виде простого текста в конфигурации вашего репозитория, поскольку это вынуждает убедиться, что у вас есть соответствующие ограничительные права на файл конфигурации. Помимо хранения незашифрованных данных в состоянии покоя, Git часто передаёт полный URL с учётными данными другим программам, открывая их в системах, где другие процессы имеют доступ к списку аргументов конфиденциальных процессов. В большинстве случаев рекомендуется использовать механизм учётных данных Git или такие инструменты, как GCM.

Этот новый параметр позволяет Git либо игнорировать, либо останавливать выполнение, когда он видит один из элементов этих учётных данных, устанавливая для transfer.credentialsInUrl значение "warn" или “die” соответственно. Значение по умолчанию "allow" ничего не делает.

[исходники, исходники]

Если вы когда-либо использовали git add -p для постепенного добавления содержимого рабочего дерева, то, возможно, знакомы с «интерактивным режимом» git add или git add -i, из которых git add -p не поддерживается.

В дополнение к "patch"  git add -i поддерживает "status", "update", "revert", "add untracked", "patch» и "diff". До недавнего времени режим git add -i фактически был написан на Perl. Эта команда стала последним предметом длительных усилий по портированию команд Git с Perl на C. Это позволяет использовать библиотеки Git без порождения подпроцессов, которые могут быть непомерно дорогими на некоторых платформах.

Повторная реализация команды git add -i на языке С появилась в выпусках Git уже версии 2.25.0. В более поздних версиях эта повторная реализация находилась в режиме «тестирование» по выбору. Git 2.37 поддерживает повторную реализацию на C по умолчанию, поэтому пользователи Windows должны заметить ускорение git add -p.

[исходники, исходники, исходники, исходники, исходники, исходники, исходники]

И последнее, но не менее важное: разработчикам Git также предстоит много интересной работы, например улучшение рабочего процесса локализации, улучшение вывода CI с помощью GitHub Actions и сокращение утечек памяти во внутренних API.

Если вы заинтересованы в том, чтобы внести свой вклад в Git, сейчас, как никогда, интересное время для начала. Ознакомьтесь с этим руководством, чтобы получить несколько советов по началу работы.

Подводная часть айсберга

Это всего лишь пример изменений из последней версии. Чтобы узнать больше, ознакомьтесь с примечаниями к выпуску 2.37 или любой предыдущей версии в репозитории Git.

А пока Git развивается, мы поможем прокачать ваши навыки или с самого начала освоить профессию, актуальную в любое время.

Выбрать другую востребованную профессию.

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


  1. webhamster
    01.07.2022 13:24
    +2

    В Linux добавление 500 файлов без fsync() занимает 0,06 секунды и 1,88
    секунды с fsync() после каждой записи несвязанного объекта и всего
    15 секунд с новой пакетной функцией fsync(). Другие платформы показывают
    аналогичное ускорение, ярким примером является Windows, где цифры
    составляют 0,35, 11,18 и всего 0,41 секунды соответственно.

    Может быть, 0,15 сек имелось в виду?


    1. stranger777
      01.07.2022 13:32

      Большое спасибо, поправили!


  1. BadSam
    01.07.2022 13:29

    Неплохо так


  1. vassabi
    02.07.2022 21:55

    прочитал релиз изменений ...

    как много оказывается я не использую из гита в своих проектах! (потому что там меньше 100 файлов)