Перейти к основному содержимому

4.13. Справочник-шпаргалка по Git

Разработчику Архитектору Инженеру

Справочник-шпаргалка по Git

Часть 1. Архитектура и внутреннее устройство

1.1. Объектная модель Git

Git хранит все данные в виде неизменяемых объектов четырёх типов: blob, tree, commit, tag. Все объекты идентифицируются SHA-1-хешем их содержимого (в современных версиях Git допускается SHA-256, но пока не является стандартом).

  • Blob (binary large object) — представляет содержимое файла без имени и метаданных. Имя файла хранится не в blob, а в tree.
  • Tree — похож на директорию: содержит список имён файлов/поддиректорий и соответствующих им SHA-хешей blob или других tree. Обеспечивает структуру файловой системы на момент коммита.
  • Commit — ссылается на один tree (состояние файловой системы), содержит метаданные: автора, дату, сообщение, а также ссылку на родительский(-ые) коммит(ы). Первоначальный коммит не имеет родителей; при слиянии — два или более.
  • Tag — объект, содержащий ссылку на commit (или другой объект), имя, необязательное сообщение и цифровую подпись (если используется git tag -s). Лёгкие теги (git tag v1.0) — это просто ссылки в refs/tags/, не создающие отдельный объект.

Все объекты физически хранятся в .git/objects/: сначала 2 символа хеша — имя подкаталога, остальные 38 — имя файла. Для экономии места Git применяет zlib-сжатие и пакует объекты в pack-файлы (*.pack) при выполнении git gc, git repack или при передаче по сети (git push/fetch).

1.2. Ссылки (refs) и их роль

Ссылки в Git — это текстовые файлы, содержащие SHA-хеш (обычно 40-символьный). Они делятся на категории:

  • HEAD — специальная ссылка, указывающая на текущую позицию: либо на ветку (ref: refs/heads/main), либо напрямую на коммит (состояние detached HEAD).
  • Ветки (refs/heads/*) — подвижные указатели на коммиты. При создании коммита ветка, на которую ссылается HEAD, автоматически продвигается вперёд.
  • Удалённые ветки (refs/remotes/*) — зеркала веток с удалённых репозиториев, обновляются только при git fetch или git pull. Не являются локальными ветками, но могут быть использованы как точки отсчёта (git checkout origin/main → detached HEAD).
  • Теги (refs/tags/*) — либо лёгкие (ссылки), либо аннотированные (отдельные объекты).
  • Заметки (notes) — механизм добавления произвольных метаданных к коммитам без изменения их хеша (git notes add).

В .git/packed-refs хранятся сжатые ссылки — Git использует его для ускорения доступа при большом количестве веток/тегов.

1.3. Индекс (staging area): не просто «буфер»

Индекс — это бинарный файл (.git/index), описывающий точное состояние, которое будет зафиксировано в следующем коммите. Он содержит:

  • пути файлов (в кодировке UTF-8);
  • статусы (mode, тип);
  • SHA-1 blob для каждого пути;
  • временные метки и размеры (для быстрого определения изменился ли файл);
  • этапы слияния (в случае конфликтов — 3 записи на файл: base, ours, theirs).

Индекс не является просто «списком добавленных файлов»: он хранит полное дерево, включая удалённые файлы и конфликты. Это позволяет Git мгновенно реконструировать коммит без перечитывания файловой системы.

Команда git status сравнивает три состояния:

  1. HEAD ↔ индекс → показывает, что будет закоммичено;
  2. индекс ↔ рабочая директория → показывает, что можно добавить.

Команда git diff без аргументов показывает различия между индексом и рабочей директорией. git diff --cached (или --staged) — между HEAD и индексом.


Часть 2. Базовые операции и их семантика

2.1. Инициализация и клонирование

git init [--bare]

Создаёт новый репозиторий в текущей директории. Без --bare создаётся рабочая копия с подкаталогом .git. С --bare создаётся только содержимое .git — такой репозиторий нельзя использовать для редактирования, только для push/pull. Используется для «центральных» репозиториев на серверах.

git clone <url> [<dir>]
git clone --depth 1 <url> # shallow clone
git clone --branch <branch> <url>
git clone --recurse-submodules <url>

git clone выполняет четыре действия:

  1. Создаёт директорию и инициализирует репозиторий (git init);
  2. Добавляет remote origin по указанному URL;
  3. Загружает все объекты и ссылки (git fetch);
  4. Создаёт локальную ветку, соответствующую remote.<origin>.HEAD (обычно main или master), и проверяет её.

Опция --depth N создаёт «мелкий» репозиторий — без полной истории, только последние N коммитов. Удобно для CI/CD или быстрой сборки, но ограничивает возможности git log, git blame, git bisect. Позже можно углубить историю: git fetch --unshallow.

2.2. Фиксация изменений: от add до commit

git add <pathspec>...
git add -u # только уже отслеживаемые (modified/deleted)
git add -A # все изменения (новые, изменённые, удалённые)
git add -p # интерактивное добавление по ханкам

git add читает содержимое файлов, создаёт blob-объекты (если их ещё нет), обновляет записи в индексе. Для больших файлов Git проверяет, изменились ли только временные метки и размер — если нет, пересчёт хеша пропускается (ускорение).

Важно: git add не удаляет физически файлы из рабочей директории — только помечает их на удаление в индексе. Файлы удаляются из файловой системы только при git rm --cached (если нужно убрать из индекса без удаления) или git rm (и из индекса, и с диска).

git commit [-m "message"] [--amend] [--no-edit] [-v]

Создаёт новый commit-объект:

  • tree берётся из текущего состояния индекса;
  • родитель — текущий HEAD;
  • автор/коммитер — из user.name/user.email;
  • сообщение — либо из -m, либо из редактора (core.editor).

При --amend текущий HEAD перезаписывается: создаётся новый коммит с тем же родителем, что и предыдущий HEAD, но с обновлённым tree и/или сообщением. Предыдущий коммит остаётся в объектной базе, пока не будет удалён сборщиком мусора. Это локальная операция, безопасна до push; после — требует --force.

Флаг -v (--verbose) добавляет в редактор diff коммита — помогает проверить вносимые изменения перед фиксацией.


Часть 3. Ветвление, слияние и перебазирование

3.1. Модель ветвления в Git: указатели, а не копии

В отличие от систем, где ветка — это физическая копия файлов (например, SVN), в Git ветка — это подвижная ссылка на коммит. Создание ветки мгновенно, поскольку выполняется только запись ссылки в refs/heads/<name> и обновление HEAD (при переключении). Никакие файлы при этом не копируются и не дублируются.

git branch <branch-name> [<start-point>]

Создаёт новую ссылку <branch-name>, указывающую на коммит, заданный <start-point> (по умолчанию — текущий HEAD). Не переключает на неё.

git checkout <branch-name>
git switch <branch-name> # современная альтернатива
git switch -c <new-branch> # = git checkout -b

Команда checkout исторически совмещала переключение веток и восстановление файлов. В Git 2.23+ рекомендуется использовать switch для смены веток и restore для манипуляций с файлами — это повышает читаемость и безопасность.

При переключении Git:

  1. Обновляет HEAD, чтобы он ссылался на новую ветку;
  2. Обновляет индекс и рабочую директорию в соответствии с tree, на который указывает эта ветка;
  3. Если в рабочей директории есть несохранённые изменения, которые конфликтуют с целевым состоянием, переключение отклоняется — это защита от потери данных.

Состояние detached HEAD возникает, когда HEAD указывает напрямую на коммит (например, git checkout abc123 или git checkout origin/main). В этом режиме новые коммиты создаются, но ни одна ветка не продвигается. После переключения на ветку такие коммиты становятся «висячими» и могут быть потеряны при сборке мусора — если не сохранить их хеш, например, через git branch rescue abc123.

3.2. Слияние (merge): интеграция изменений

git merge <branch>
git merge --no-ff <branch>
git merge --squash <branch>
  • Fast-forward (ff) — происходит, если текущая ветка является предком целевой. Git просто перемещает указатель вперёд, не создавая нового коммита. История остаётся линейной.
  • True merge — создаётся новый коммит с двумя родителями: текущий HEAD и <branch>. Запускается, если ветки расходились. Это единственный способ сохранить информацию о параллельной разработке.

Флаг --no-ff всегда создаёт merge-коммит, даже если возможен fast-forward. Это полезно в workflow, где важна прозрачность: например, при интеграции feature-ветки в develop, чтобы в будущем можно было легко откатить всю функцию как один коммит (git revert -m 1 <merge-commit>).

Флаг --squash объединяет все изменения из <branch> в индекс текущей ветки, но не создаёт коммит и не сохраняет историю источника. Подходит для интеграции экспериментальных или «грязных» веток, когда детали промежуточных коммитов не важны.

Стратегии слияния

Git поддерживает несколько встроенных стратегий:

  • resolve — базовая стратегия для двух веток;
  • recursive (по умолчанию) — обрабатывает самослияния (например, при rebase), поддерживает --ours, --theirs;
  • octopus — для слияния более двух веток одновременно (только если возможно без конфликтов);
  • ours — полностью игнорирует изменения из других веток, сохраняя текущее дерево; используется в специфических сценариях интеграции;
  • subtree — пытается обнаружить, что одна ветка является поддеревом другой (редко используется вручную).

3.3. Перебазирование (rebase): линеаризация истории

git rebase <base>
git rebase -i <base>
git rebase --onto <newbase> <oldbase> <branch>
git rebase --continue / --abort / --skip

rebase переносит серию коммитов из одной линии истории на другую, пересоздавая их с новыми хешами. Это достигается путём:

  1. Временного сохранения патчей (git format-patch);
  2. Сброса ветки до <base>;
  3. Последовательного применения патчей с возможной адаптацией.

Поскольку хеши меняются, rebase меняет историю. Это допустимо только для локальных, неопубликованных коммитов. Публичная ветка после rebase требует git push --force-with-lease, что может нарушить работу других разработчиков.

Интерактивное перебазирование (-i)

Открывает редактор со списком коммитов (от старых к новым), где каждая строка имеет вид:

pick abc123 commit message

Доступные команды:

  • pick — применить коммит как есть;
  • reword — изменить сообщение;
  • edit — остановиться для редактирования (можно изменить файлы, сделать git add, git commit --amend);
  • squash — объединить с предыдущим коммитом, сохраняя его сообщение;
  • fixup — объединить, но отбросить своё сообщение;
  • drop — пропустить коммит;
  • exec <command> — выполнить shell-команду после коммита (например, make test).

Это мощный инструмент для «причёсывания» истории перед интеграцией: удаление отладочных коммитов, группировка связанных изменений, унификация формулировок.

rebase --onto: перенос поддиапазона

Сценарий: у вас есть feature, ответвившаяся от main, но часть изменений уже была вмёрджена в main через другую ветку. Вы хотите перебазировать только новые коммиты.

git rebase --onto main <last-common-commit> feature

Это отрежет цепочку коммитов после <last-common-commit> и наложит её на main.

3.4. Конфликты: диагностика и разрешение

Конфликт возникает, когда Git не может автоматически объединить изменения в одном и том же участке файла. Рабочая директория остаётся в состоянии конфликта, а в индексе появляются три записи на файл:

  • stage 1 — общая база (<<<<<<< HEAD в файле);
  • stage 2 — текущая ветка (ours);
  • stage 3 — входящие изменения (theirs).

Команды:

git status                          # показывает конфликтующие файлы
git diff --ours / --theirs / --base # сравнить с каждой стороной
git checkout --ours <file> # взять версию текущей ветки
git checkout --theirs <file> # взять версию входящей ветки
git add <file> # после ручного разрешения — пометить как resolved
git mergetool # запустить GUI-инструмент (meld, kdiff3 и др.)

После разрешения всех конфликтов — git add и git commit (для merge) или git rebase --continue (для rebase). Важно: git add удаляет записи stage 2 и 3, оставляя только stage 0 — это сигнал системе, что конфликт разрешён.


Часть 4. Работа с удалёнными репозиториями

4.1. Remote: концепция и управление

Remote — это сокращённое имя для URL репозитория, используемое в командах fetch, push, pull.

git remote add <name> <url>
git remote rename <old> <new>
git remote remove <name>
git remote set-url <name> <newurl>
git remote -v # показать все remotes с URL

Каждый remote имеет набор конфигурационных параметров в .git/config:

[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
pushurl = ... # отдельный URL для отправки
push = ... # правила по умолчанию (редко используется)

Запись fetch определяет, как и куда сопоставляются ссылки при git fetch. По умолчанию — все локальные ветки в refs/remotes/origin/.

4.2. Fetch и push: асимметричные операции

git fetch [<remote>] [<refspec>...]
git fetch --prune # удалить локальные ссылки на удалённые ветки, которых больше нет
git fetch --tags # явно получить теги (по умолчанию — не все)

fetch только загружает объекты и обновляет удалённые ветки, не затрагивая рабочую директорию. Это безопасная операция.

refspec — шаблон +<src>:<dst>, где + означает force-update. Примеры:

  • git fetch origin main:fixes/main — загрузить origin/main и создать/обновить локальную ветку fixes/main;
  • git fetch origin :temp — создать локальную ветку temp, ссылающуюся на тот же коммит, что и HEAD на origin (редко, но полезно для бэкапа).
git push [<remote>] [<refspec>...]
git push --force-with-lease # безопасный force-push
git push --set-upstream origin main # установить tracking

Без refspec push отправляет текущую ветку, но только если она имеет upstream-связь. Иначе — ошибка.

--force перезаписывает удалённую ветку, игнорируя её текущее состояние. --force-with-lease проверяет, что локальная копия удалённой ветки (origin/main) совпадает с её состоянием на сервере. Это предотвращает случайную перезапись чужих коммитов.

4.3. Pull: fetch + merge (или rebase)

git pull
git pull --rebase
git config pull.rebase true # сделать rebase по умолчанию

git pull — это ярлык для git fetch + git merge FETCH_HEAD. Вариант с --rebase делает git rebase, что сохраняет линейную историю.

Рекомендация: в feature-ветках — pull --rebase, чтобы избежать лишних merge-коммитов. В shared-ветках (например, main) — только fetch + ручное merge, чтобы явно контролировать интеграцию.


Часть 5. Отладка и восстановление состояния

5.1. git reflog: журнал изменений ссылок

reflog — это локальный журнал операций, затрагивающих HEAD, ветки и другие ссылки. Записи сохраняются локально и не передаются при push/fetch. По умолчанию хранятся 90 дней (для достижимых объектов) или 30 дней (для недостижимых — garbage-collectable). Это основной инструмент восстановления после «потери» коммитов.

git reflog
git reflog show <branch> # reflog для конкретной ветки
git reflog expire --expire=now --all && git gc # очистка (осторожно!)

Формат записи:
abc123 HEAD@{1}: commit: fix typo
def456 HEAD@{2}: checkout: moving from main to feature

Каждая запись содержит:

  • SHA-1 состояния, в котором была ссылка после операции;
  • номер операции (@{n});
  • тип операции и контекст.

Примеры восстановления

  1. Случайный git reset --hard
    Был сделан git reset --hard abc123, но abc123 был не тем коммитом.
    git reflog, ищем запись перед reset (например, HEAD@{1}), затем:

    git reset --hard HEAD@{1}
  2. Коммит в detached HEAD, который «исчез» после checkout
    После git checkout abc123, git commit, git checkout main коммит остался без ветки.
    git reflog, находим HEAD@{n} с нужным коммитом, затем:

    git branch rescue abc123   # или HEAD@{n}
  3. Отмена git rebase, который сломал историю
    git reflog, находим запись до rebase (HEAD@{k}), затем:

    git reset --hard HEAD@{k}

Важно: reflog работает только с локальными операциями. Если репозиторий клонирован заново — reflog теряется.


5.2. git reset: изменение положения HEAD, индекса и рабочей директории

Команда reset имеет три режима, определяемых флагом (или отсутствием):

РежимHEADИндексРабочая директорияПрименение
--softОтмена коммита, но сохранение изменений в индексе (например, чтобы изменить сообщение или добавить файлы)
--mixed (по умолчанию)Отмена коммита и выгрузка изменений из индекса в рабочую директорию (подготовка к повторной подготовке коммита)
--hardПолный возврат в состояние коммита — все несохранённые изменения теряются
git reset --soft HEAD~1           # отменить последний коммит, оставить изменения в индексе
git reset HEAD~2 # отменить два коммита, изменения — в рабочей директории
git reset --hard origin/main # синхронизировать локальную ветку с удалённой (опасно!)

⚠️ Предупреждение: --hard без предварительного бэкапа (например, git stash или git branch backup) может привести к безвозвратной потере данных.


5.3. git revert: безопасная отмена изменений

В отличие от reset, revert не перезаписывает историю. Он создаёт новый коммит, который вносит противоположные изменения по отношению к указанному коммиту.

git revert <commit>
git revert -n <commit> # подготовить изменения, но не коммитить
git revert -m 1 <merge-commit> # отменить merge (указать, какую родительскую ветку сохранить)

Для merge-коммита обязательна опция -m <parent-number> (1 — наша ветка, 2 — входящая). Без неё revert откажет: Git не знает, в какую сторону «откатывать» двухродительский коммит.

revert — единственный рекомендуемый способ отмены изменений в публичных ветках, поскольку он сохраняет целостность истории и не нарушает работу других разработчиков.


5.4. git cherry-pick: выборочное применение коммитов

Применяет изменения из одного или нескольких коммитов как новые коммиты в текущую ветку. Полезно при backport’е исправлений или извлечении отдельных функций без полного слияния.

git cherry-pick <commit>
git cherry-pick A^..B # диапазон (исключая A, включая B)
git cherry-pick --continue / --abort / --skip

Особенности:

  • Создаются новые коммиты с новыми хешами;
  • Если коммит уже был применён ранее (например, через merge), Git распознаёт это по содержимому изменений и пропускает дубликат (apply с --skip);
  • При конфликтах — как при rebase: разрешить, git add, git cherry-pick --continue.

Ограничение: cherry-pick не переносит связи — если коммит ссылался на issue или другой коммит через fixes #123, это не восстанавливается автоматически.


5.5. git bisect: бинарный поиск по истории для локализации ошибки

Автоматизирует процесс нахождения первого «плохого» коммита, в котором появился баг.

git bisect start
git bisect bad HEAD # текущее состояние — сломано
git bisect good v1.0 # известная хорошая точка
# Git переключается на середину → тестируем →
git bisect good # или bad
# Повторяем, пока не найдётся первый bad-коммит
git bisect reset # выйти

Можно автоматизировать с помощью скрипта:

git bisect run ./test.sh

где test.sh возвращает:

  • 0 — коммит хороший;
  • 1–124, 126–127 — коммит плохой;
  • 125 — пропустить (например, сборка сломана не по нашей вине).

bisect особенно эффективен в больших проектах с длинной историей — вместо линейного просмотра 1000 коммитов требуется ~10 шагов.


Часть 6. Продвинутые темы

6.1. git worktree: параллельные рабочие копии

Позволяет иметь несколько рабочих директорий, связанных с одним репозиторием — без клонирования. Каждая worktree имеет своё дерево файлов, но разделяет общую объектную базу и ссылки.

git worktree add ../feature-branch feature
git worktree list
git worktree remove ../feature-branch
git worktree prune # удалить "мёртвые" worktree (например, после rm -rf)

Ограничения:

  • Нельзя иметь две worktree на одну и ту же ветку;
  • Нельзя удалить main worktree (ту, где лежит .git);
  • Индексы и HEAD — изолированы.

Практическое применение:

  • Тестирование сборки на main, пока ведётся разработка в feature;
  • Поддержка нескольких версий (например, v1-support, v2-dev) одновременно.

6.2. git submodule и git subtree: управление зависимостями

Submodule

Подмодуль — это отдельный репозиторий, встроенный в поддиректорию. В основном репозитории хранится только SHA-1 коммита подмодуля.

git submodule add <url> [<path>]
git submodule update --init --recursive
git submodule foreach 'git checkout main'

Плюсы: полная изоляция, чёткая привязка к версии.
Минусы: сложность обновления (git submodule update --remote), необходимость явного init, частые ошибки при клонировании (--recurse-submodules обязателен).

Subtree

Объединяет историю подпроекта в основную ветку через merge с --squash или subtree-стратегию.

git subtree add --prefix=lib/foo <repo> <ref>
git subtree push --prefix=lib/foo <repo> <branch>

Плюсы: единая история, нет внешних зависимостей при клонировании.
Минусы: история подпроекта «размазывается», сложнее выделить изменения, относящиеся только к нему.

Рекомендация: для статичных библиотек — subtree; для активно развивающихся, независимых компонентов — submodule или внешние пакетные менеджеры (npm, NuGet, pip).


6.3. git replace и git filter-repo: переписывание истории

git replace

Создаёт локальную замену одного объекта другим, без изменения хешей. Не влияет на push.

git replace <old-commit> <new-commit>
git replace -d <commit> # удалить замену
git filter-branch -- --all # применить замены рекурсивно (устаревшее)

Практическое применение: исправление авторства, исправление сообщений без пересборки всей истории.

git filter-repo (современная замена filter-branch)

Инструмент для безопасного, быстрого и гибкого переписывания истории. Требует установки отдельно (не входит в стандартную поставку).

Частые задачи:

  • Удаление файла из всей истории:
    git filter-repo --path secrets.json --invert-paths
  • Изменение email во всех коммитах:
    git filter-repo --email-callback 'return email.replace(b"old", b"new")'
  • Удаление тегов, веток, уменьшение размера репозитория.

⚠️ После filter-repo необходимо сделать git push --force, и все участники должны переклонировать репозиторий.


Часть 7. Конфигурация, оптимизация и работа с большими репозиториями

7.1. Уровни конфигурации и приоритеты

Git поддерживает три уровня настроек, которые применяются иерархически (нижестоящий переопределяет вышестоящий):

УровеньКомандаФайлОбласть действия
Системныйgit config --system/etc/gitconfigВсе пользователи системы
Глобальныйgit config --global~/.gitconfig или ~/.config/git/configТекущий пользователь
Локальныйgit config.git/configТолько текущий репозиторий

Проверить, откуда берётся конкретная настройка:

git config --show-origin --get user.email

Ключевые параметры:

  • user.name, user.email — обязательны для commit и tag;
  • core.editor — редактор для сообщений (vim, code --wait, nano);
  • core.autocrlf — управление окончаниями строк:
    • true (Windows): LF → CRLF при checkout, CRLF → LF при commit;
    • input (Unix/macOS): CRLF → LF при commit, но не обратно;
    • false: без преобразований (только если вы контролируете окружение явно);
  • core.safecrlf — предупреждение при невозможности обратного преобразования;
  • core.filemode — учитывать ли биты исполнения (false на Windows);
  • core.precomposeunicode (macOS) — корректная работа с именами файлов в UTF-8 (включено по умолчанию с Git 2.20+);
  • init.defaultBranch — имя ветки по умолчанию (main, master);
  • pull.rebase, branch.autosetuprebase — поведение по умолчанию для pull;
  • credential.helper — кеширование учётных данных (например, cache, store, manager-core на Windows).

Для временного использования параметров — переменные окружения:
GIT_AUTHOR_NAME="CI Bot" git commit --amend --no-edit


7.2. .gitattributes: управление поведением по файлам

Файл .gitattributes (в корне или поддиректориях) задаёт правила обработки файлов при операциях. Имеет приоритет над глобальными настройками.

Часто используемые директивы:

АтрибутНазначение
text=autoGit сам решает, текстовый ли файл; при checkout нормализует EOL
text eol=lfПринудительно использовать LF при checkout (для скриптов, .sh, .py)
text eol=crlfПринудительно CRLF (.bat, .cmd)
binaryОтключает EOL-конверсию и diff (эквивалентно -text -diff)
diff=customИспользовать пользовательский драйвер diff (например, для JSON)
merge=customКастомная стратегия слияния (например, merge=union для списков)
linguist-language=PythonПодсказка для GitHub Language Stats

Пример:

*.sh      text eol=lf
*.bat text eol=crlf
*.png binary
*.json diff=json
CHANGELOG diff

Для включения JSON-pretty diff:

git config diff.json.textconv "jq -S ."

Теперь git diff file.json будет показывать семантические различия, а не побайтовые.


7.3. Оптимизация и обслуживание репозитория

Сборка мусора и упаковка

Git автоматически запускает git gc при достижении порогов (по умолчанию — после 65536 loose-объектов или 50 pack-файлов), но в активных репозиториях стоит делать это вручную:

git gc --aggressive --prune=now
  • --aggressive — более тщательная упаковка (медленнее, но лучше сжатие);
  • --prune=now — немедленно удалить объекты старше указанного времени (по умолчанию — 2 недели).

Для очень больших репозиториев:

git repack -d -l -A --window=250 --depth=250
  • -d — удалить старые pack-файлы;
  • -l — использовать только локальное сжатие (без --delta-base-offset);
  • -A — оставить достижимые loose-объекты;
  • --window, --depth — параметры поиска дельт (выше — лучше сжатие, но дольше).

Анализ размера

Определить, что занимает место:

# Топ-10 файлов по общему размеру в истории
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print substr($0,6)}' \
| sort -k2 -n -r \
| head -n 10

# Размер по веткам
git branch --format='%(refname:short)' | while read b; do
echo -n "$b: "
git ls-tree -r -l $b | awk '{sum += $4} END {print sum/1024/1024 " MB"}'
done

Если обнаружены большие бинарники — рассмотрите git filter-repo для их удаления или переход на Git LFS.


7.4. Работа с большими репозиториями: sparse-checkout, partial clone

Sparse-checkout

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

git clone --filter=blob:none --sparse <url>  # частичная загрузка объектов + sparse
cd repo
git sparse-checkout set dir1/ dir2/file.txt
git sparse-checkout init --cone # режим "cone" (иерархический, быстрее)

В режиме --cone (по умолчанию с Git 2.25+) правила применяются иерархически: если включена src/, то включаются все поддиректории. Можно использовать git sparse-checkout add/remove/set для управления.

Partial clone и promisor remotes

Git 2.19+ поддерживает частичную загрузку объектов:

git clone --filter=blob:none <url>     # не загружать blob-объекты (только trees/commits)
git clone --filter=tree:0 <url> # не загружать деревья глубже корня

При необходимости объект подгружается «лениво» при checkout, log -p, grep. Особенно эффективно с серверами, поддерживающими uploadpack.allowFilter (GitHub, GitLab ≥ 12.5).

Для CI-сборок — значительное ускорение клонирования при сохранении полной истории.


Часть 8. Анти-паттерны и типичные ошибки

8.1. «Сломанный» .gitignore

  • Ошибка: добавить файл в индекс, потом прописать его в .gitignore.
    Последствие: файл продолжает отслеживаться.
    Исправление:

    git rm --cached <file>
    git add .gitignore
    git commit -m "stop tracking <file>"
  • Ошибка: игнорировать файлы через git update-index --assume-unchanged или --skip-worktree.
    Последствие: изменения не видны в status, но при checkout, reset --hard — перезаписываются.
    Когда допустимо: только для локальных override-файлов (например, appsettings.Development.json), и только если точно понимаете разницу между assume-unchanged (оптимизация производительности) и skip-worktree (намеренное игнорирование изменений).

8.2. Использование git push --force без --with-lease

--force перезаписывает удалённую ветку независимо от её текущего состояния. Если другой разработчик успел запушить изменения — они потеряются.

Правильно:

git push --force-with-lease origin feature
# или ещё безопаснее — по ветке:
git push --force-with-lease=feature:feature

8.3. Смешивание merge и rebase в shared-ветках

Если ветка develop используется несколькими людьми, её нельзя перебазировать. Это нарушает линейность и приводит к дублированию коммитов при последующих pull.

Правило: переписывание истории допустимо только для локальных веток, не имеющих upstream или не опубликованных.

8.4. «Забытый» git add перед commit

Создаёт коммит без изменений, или с устаревшим состоянием индекса.
Профилактика:

  • Использовать git commit -a только для простых случаев (не работает с новыми файлами);
  • Настроить pre-commit hook для проверки непустого diff;
  • Использовать git status перед каждым commit.

8.5. Неправильное использование git stash

  • git stash без --include-untracked не сохраняет новые файлы;
  • git stash pop после конфликта может оставить изменения в индексе — лучше git stash apply, разрешить конфликты, git stash drop вручную;
  • Долгоживущие stash’и — плохая практика; для длительного хранения используйте ветки.