GitLFS: Объемные файлы в Git-репозитории

История Git LFS началась в 2014-м году, когда в разработчики GitHub столкнулись с тем, что некоторые пользователи кладут в репозиторий огромные файлы и потом начинают жаловаться на проблемы с производительностью репозитория😒. Эта история типична для команд, которые активно работают с медиа файлами, с CAD программами и которым хочется версионировать их также как и остальные файлы. Однако ванильный Git плохо работает с большими бинарниками по нескольким причинам:

  1. Операция git status сканирует все файлы в директории и для каждого вычисляет хеш. Чем больше файлы, тем сложнее эта операция.
  • Даже при небольших изменениях бинарные файлы, как правило, меняются сильно. В результате delta-сжатие работает плохо, и размер репозитория растет как на дрожжах. В результате очень быстро репозиторий доходит до состояния, когда git clone может выполняться часами.

Чтобы побороть эту проблему инженеры GitHub приняли очень простое и логичное решение - хранить такие файлы отдельно в хранилище а-ля S3 и предоставить отдельный API для работы с ними. В результате родилось расширение для Git с названием Git LFS (Git Large File System), которое сразу было сделано открытым стандартом, в результате сейчас Git LFS поддерживается всеми современными git сервисами (GitHub, GitLab, Bitbucket, GitVerse и т. д.)

Как работает под капотом

Для того, чтобы все сработало, на локальную машину нужно установить отдельную утилиту Git LFS. После установки и выполнения например следующей команды

git lfs track "*.psd"

git lfs будет встраиваться в процесс git add, git checkout, git push, git pull.

Так например, для git add psd файла содержимое файла будет заменено на так называемый файл-указатель следующего вида:

version https://git-lfs.github.com/spec/v1
oid sha256:abcdef123456...
size 2048576

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

Когда выполняется git push для этого файла, то оригинальный файл будет отправлен на Git LFS сервер, и только если эта операция прошла успешно, то в основной репозиторий будет отправлен файл-указатель.

Когда выполняется git checkout, то LFS-фильтр получает на вход файл-указатель из индекса и скачивает из LFS-хранилище реальный файл по его хешу.

Аналогично, когда выполняется git clone, то скачивается индекс с полной историей коммитов, где есть только работа с файлами-указателями, а с LFS-хранилища скачиваются только те версии больших файлов, на которые есть ссылки в последней версии изменений.

Кстати, если ваш коллега забудет поставить git LFS на своей машине, то когда он скачает репозиторий, то вместо оригинальных файлов будет видеть файлы-указатели :)

Когда применять

  • Если вы добавили в репозиторий несколько бинарников на 10 Мб, то спешить включать Git LFS может и не стоит, но если в репозитории завелись бинарные файлы размером больше 1Мб, и если они регулярно добавляются/меняются/удаляются, то это явный сигнал, что надо включать Git LFS.

  • Если файлы сверх большие (порядка сотен Мб), хоть и не меняются, то лучше всего вынести их во внешнее хранилище, смысла клать их в репозиторий особого нет. Но если очень хочетя, то тогда сразу с включенным Git LFS.

  • Если файлы бинарные, но не большие (килобайты), например картинки на сайте, то LFS не нужен, ванильный git отлично с работает с такими файлами.