Логічно кожна версія файлу - окремий blob з повним вмістом. Новий об'єкт спершу записується як loose object - окремий файл у .git/objects/xx/, стиснений zlib. Для файлу на 20 КБ, зміненого в 100 комітах, це 100 майже однакових копій.
Packfile вирішує це на рівні зберігання. git gc (і передача по мережі) збирає об'єкти в один файл:
.git/objects/pack/
├── pack-4f2a....pack ← самі об'єкти
├── pack-4f2a....idx ← індекс: хеш → зміщення в pack
├── pack-4f2a....rev ← зворотний індекс (зміщення → позиція)
└── pack-4f2a....bitmap ← бітмапи досяжності (для швидкого clone/fetch)
Дельта-стиснення: частина об'єктів зберігається не повністю, а як дельта від іншого об'єкта - інструкції «скопіювати байти з базового об'єкта» й «вставити нові байти».
git verify-pack -v .git/objects/pack/pack-*.idx | sort -k3 -n | tail
# хеш тип розмір розмір-у-pack зміщення [глибина база]
Особливості, що відрізняють Git від «diff між версіями»:
- база обирається евристично, а не за історією: Git сортує об'єкти за типом, ім'ям файлу й розміром і шукає схожі у ковзному вікні. Дельта може будуватися від файлу з іншої гілки чи навіть з іншим ім'ям;
- зазвичай база - новіша версія, а старі зберігаються як дельти від неї: свіжі файли, які читають найчастіше, дістаються без розпакування ланцюжка;
- ланцюжки дельт обмежені глибиною (
pack.depth, за замовчуванням 50) - баланс між розміром і швидкістю читання; - логічна модель не змінюється: для всіх команд об'єкт - повний знімок, дельти - лише деталь зберігання.
Передача по мережі: при fetch і push сторони узгоджують, які коміти вже є в отримувача, і сервер формує тонкий pack (thin pack) - дельти можуть посилатися на об'єкти, що вже є в клієнта. Тому fetch після невеликих змін передає кілобайти.
Практичні наслідки:
- бінарні файли (зображення, архіви, дампи) погано стискаються дельтами - кожна версія займає майже повний розмір, і репозиторій росте назавжди. Звідси Git LFS;
git gc --aggressiveперебудовує дельти з більшим вікном - повільно, і для більшості репозиторіїв виграш мізерний;git count-objects -vHпоказує, скільки займають loose objects і pack-и: велика кількість loose objects - сигнал, що автоматичне обслуговування не запускається.