В системах вроде SVN, CVS, Perforce каждая версия файла хранится как разница (delta) от предыдущей. Чтобы получить файл недельной давности, система начинает с одной из версий и накатывает дельты в нужную сторону.
SVN / CVS / Perforce - модель дельт:
v1: полный файл
v2: дельта (3 строки изменены)
v3: дельта (1 строка добавлена)
v4: дельта (5 строк удалено)
↑
чтобы восстановить v4,
нужно применить дельты v1+v2+v3+v4
Git устроен иначе. Каждый коммит хранит полный снимок всего проекта через tree-объект. Не разницу - фотографию.
Git - модель снимков:
коммит 1: snapshot 1
коммит 2: snapshot 2
коммит 3: snapshot 3
↑
чтобы посмотреть коммит 3,
просто берём snapshot 3
Откуда экономия места
Кажется расточительным хранить весь проект на каждом коммите. Так и есть, и Git использует две хитрости:
Дедупликация по содержимому. Файл хранится через blob, адресуемый по SHA содержимого. Если файл не менялся между коммитами - у него тот же SHA, и Git переиспользует один и тот же blob. Реально на диске лежит «снимок дерева директорий с указателями на файлы», а не «снимок проекта целиком».
Дельты внутри packfile. При git gc или git push накопленные
объекты упаковываются в один файл, внутри которого похожие объекты
кодируются как дельты. Это оптимизация хранения, не модель данных.
Почему это важно
Разница между моделями - фундамент того, как Git работает.
- Ветка - это указатель на снимок, а не запись «отделилось в такой-то момент». Создание ветки = 40 байт. Переключение - мгновенное.
git logидёт по цепочкеparentкоммитов, читая готовые снимки. Не нужно реконструировать историю.git diffпоказывает разницу между двумя снимками - вычисляется на лету. Это вид, не способ хранения. Частая путаница.