Сэкономьте 15% на всех хостинговых услугах

Проверьте свои навыки и получите скидку на любой тарифный план

Используйте код: Skills Начать
Рубрики
Linux VPS

Структура Git-репозитория: Полное техническое руководство

Git — это распределённая система контроля версий, которая хранит историю проекта в виде направленного ациклического графа (DAG) неизменяемых объектов-снимков. Каждый Git-репозиторий построен из трёх логических зон — рабочего каталога, индекса промежуточной области и хранилища объектов внутри .git/ — а также набора лёгких указателей (ветки, теги, удалённые репозитории), которые обеспечивают навигацию по истории. Понимание того, как эти уровни взаимодействуют, — это разница между механическим использованием Git и его применением с хирургической точностью.

Если вы самостоятельно размещаете свои репозитории на VPS, знание этой внутренней структуры позволяет восстанавливаться после сбоев, проектировать эффективные CI/CD-пайплайны и проверять каждый байт истории вашего проекта без использования сторонних платформ.

Модель трёх зон: как Git перемещает данные

Прежде чем углубляться в отдельные компоненты, усвойте модель потока данных, которая управляет каждой операцией Git:

Working Directory  -->  Staging Area (Index)  -->  .git/ Object Store
     (edit)               (git add)                  (git commit)

Изменения перемещаются слева направо при создании коммита и справа налево при восстановлении или сбросе. Каждая команда Git — это по сути операция чтения или записи в одной или нескольких из этих зон.

Рабочий каталог

Рабочий каталог (также называемый рабочим деревом) — это представление вашего проекта в файловой системе в определённом состоянии после переключения. Когда вы выполняете git clone или git checkout, Git восстанавливает файлы из сжатых объектов в .git/objects/ и записывает их в этот каталог.

Файлы в рабочем каталоге существуют в одном из четырёх состояний:

  • Неотслеживаемые — Git никогда не видел этот файл; он существует только на диске.
  • Отслеживаемые, неизменённые — файл точно соответствует последнему зафиксированному снимку.
  • Отслеживаемые, изменённые — файл отличается от последнего зафиксированного снимка, но не был добавлен в индекс.
  • Отслеживаемые, удалённые — файл был удалён с диска, но удаление не было добавлено в индекс.

Важный нюанс, который сбивает с толку многих разработчиков: рабочий каталог — это не простая копия репозитория. Git восстанавливает его, читая объекты-деревья и распаковывая объекты-блобы. Если .git/ не повреждён, вы всегда можете заново сгенерировать рабочий каталог с нуля — обратное неверно.

Разреженное переключение для больших монорепозиториев

В репозиториях с десятками тысяч файлов (характерных для архитектур монорепозиториев) можно ограничить пути, которые Git материализует в рабочем каталоге:

git sparse-checkout init --cone
git sparse-checkout set services/api services/auth

Это незаменимо на VPS с ограниченным дисковым I/O, поскольку Git пропускает распаковку блобов для путей за пределами конуса.

Область подготовки (индекс)

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

git add <file>          # Stage a specific file
git add -p              # Interactively stage hunks within a file
git add -u              # Stage all tracked modifications and deletions
git status              # Compare working directory and index against HEAD
git diff --cached       # Show diff between index and HEAD

Зачем нужен индекс

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

Граничный случай — повреждение индекса: если системный сбой прерывает git add, файл индекса может быть повреждён. Симптомы включают зависание git status или вывод странных результатов. Восстановление:

rm .git/index
git reset

Git перестраивает индекс из HEAD, не затрагивая рабочий каталог.

Индекс как регистр конфликтов слияния

Во время конфликта слияния индекс одновременно хранит три версии каждого конфликтующего файла (стадии 1, 2 и 3 — базовая, наша, их). Именно поэтому git diff --cached не показывает ничего полезного в середине конфликта; для просмотра всех трёх стадий вам нужен git diff --cc или инструмент слияния.

Каталог .git/: анатомия хранилища объектов

Каталог .git/ и есть репозиторий. Всё остальное — рабочий каталог, удалённые клоны — является производным от него. Удаление .git/ превращает репозиторий в обычный каталог без истории.

.git/
├── HEAD
├── config
├── description
├── index
├── COMMIT_EDITMSG
├── hooks/
├── info/
├── logs/
│   ├── HEAD
│   └── refs/
├── objects/
│   ├── info/
│   └── pack/
└── refs/
    ├── heads/
    ├── remotes/
    └── tags/

HEAD — это обычный текстовый файл, содержащий либо символическую ссылку (указывающую на ветку), либо необработанный SHA-1-хэш (состояние отсоединённого HEAD).

cat .git/HEAD
# ref: refs/heads/main        <-- on a branch
# a3f1c9d...                  <-- detached HEAD

Отсоединённый HEAD — это не состояние ошибки; оно является намеренным, когда вы переключаетесь на тег или конкретный коммит для проверки. Опасность заключается в создании коммитов в состоянии отсоединённого HEAD: эти коммиты доступны только через reflog до тех пор, пока вы не прикрепите их к ветке.

git checkout -b rescue-branch   # Attach detached commits to a new branch

config

Файл конфигурации локального репозитория. Он переопределяет глобальные (~/.gitconfig) и системные (/etc/gitconfig) настройки. Распространённые записи:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
[remote "origin"]
    url = git@github.com:user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
    remote = origin
    merge = refs/heads/main

На самостоятельно размещённом сервере вы будете часто редактировать этот файл напрямую при смене URL удалённых репозиториев или настройке uploadpack.allowReachableSHA1InWant для частичных клонов.

refs/

Каталог refs/ содержит обычные текстовые файлы, каждый из которых хранит один SHA-1-хэш. Это именованные указатели, которые делают DAG Git доступным для навигации.

Тип ссылкиПутьОписание
Локальная веткаrefs/heads/<name>Указывает на последний коммит ветки
Ветка отслеживания удалённого репозиторияrefs/remotes/<remote>/<name>Локальный кэш последнего коммита удалённой ветки
Лёгкий тегrefs/tags/<name>Указывает непосредственно на объект-коммит
Аннотированный тегrefs/tags/<name>Указывает на объект-тег, который указывает на коммит
Stashrefs/stashУказывает на коммит stash

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

Объекты Git: неизменяемое ядро

Всё, что хранится в .git/objects/, является адресуемым по содержимому: имя файла — это SHA-1-хэш (или SHA-256 в новых версиях Git) содержимого объекта. Это делает Git изначально защищённым от несанкционированного изменения — изменение любого байта меняет хэш, нарушая цепочку.

Четыре типа объектов

Тип объектаЧто хранитУказывает на
БлобНеобработанное содержимое файла (без имени файла, без прав доступа)Ничего
ДеревоСписок каталога: имена файлов, права доступа, SHA блобов/деревьевБлобы и другие деревья
КоммитАвтор, коммиттер, временная метка, сообщение, SHA родителя(ей)Одно дерево + ноль или более родительских коммитов
ТегИдентификатор создателя тега, временная метка, сообщение, GPG-подписьОбычно коммит

Прямая проверка объектов

# Show the type of any object
git cat-file -t a3f1c9d

# Show the content of any object
git cat-file -p a3f1c9d

# Show the tree of the current HEAD commit
git ls-tree HEAD

# Show a specific blob's content
git show HEAD:src/main.py

Отдельные объекты и файлы пакетов

Изначально каждый объект хранится как отдельный сжатый файл в .git/objects/<2-char-prefix>/<38-char-suffix>. Это отдельные объекты. Со временем Git запускает git gc (сборку мусора), чтобы объединить отдельные объекты в файлы пакетов (.git/objects/pack/*.pack) с соответствующим индексом (.pack.idx).

Файлы пакетов используют дельта-сжатие — хранение разницы между похожими объектами вместо полных копий. Репозиторий с тысячами похожих текстовых файлов может значительно уменьшиться после упаковки. На VPS с ограниченной ёмкостью NVMe запуск git gc --aggressive на больших репозиториях перед архивированием является стандартной практикой.

git count-objects -vH    # Show loose object count and disk usage
git gc --aggressive      # Repack aggressively (CPU-intensive)
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail -20
# Find the 20 largest objects in the pack

История коммитов: направленный ациклический граф

Каждый объект-коммит содержит ровно один указатель на объект-дерево (снимок корневого каталога) и ноль или более указателей на родительские коммиты. Это формирует DAG, где:

  • Ноль родителей = начальный коммит (корневой коммит)
  • Один родитель = обычный коммит
  • Два родителя = коммит слияния
  • Три и более родителей = осьминожье слияние (редко, используется для одновременной интеграции многих функциональных веток)
git log --oneline --graph --all    # Visualize the full DAG
git log --format="%H %P"           # Show each commit's SHA and parent SHA(s)

Неизменяемость коммитов и перезапись истории

Поскольку SHA коммита является производным от его содержимого (включая SHA родителей), любая перезапись создаёт новый коммит с новым SHA. Операции git rebase, git commit --amend и git filter-repo не изменяют историю — они создают параллельную историю. Старые коммиты остаются в хранилище объектов до сборки мусора.

Именно поэтому принудительная отправка переписанной истории в общую ветку является деструктивной: локальные ветки коллег по-прежнему указывают на старую цепочку коммитов.

Ветки: лёгкие указатели

Ветка — это не что иное, как файл размером 41 байт, содержащий SHA-1-хэш. Создание ветки происходит мгновенно независимо от размера репозитория, поскольку Git записывает только один небольшой файл.

git branch feature/auth           # Create branch at current HEAD
git checkout -b feature/auth      # Create and switch in one step
git switch -c feature/auth        # Modern equivalent (Git 2.23+)
git branch -d feature/auth        # Delete (safe: refuses if unmerged)
git branch -D feature/auth        # Delete (force: regardless of merge status)

Внутреннее устройство веток

cat .git/refs/heads/main
# a3f1c9d8e2b1f4c7d9e0a1b2c3d4e5f6a7b8c9d0

Когда вы создаёте коммит в ветке, Git записывает SHA нового коммита в этот файл. Это и есть суть «продвижения указателя ветки».

Отслеживающие ветки и конфигурация upstream

Отношение отслеживания сообщает Git, с какой удалённой веткой следует сравнивать локальную ветку для отчётности о расхождении git status и поведения git pull.

git branch --set-upstream-to=origin/main main
git branch -vv    # Show tracking relationships and ahead/behind counts

Теги: постоянные маркеры в истории

Теги отмечают конкретные коммиты как значимые — как правило, выпуски программного обеспечения. В отличие от веток, теги не перемещаются при новых коммитах.

ХарактеристикаЛёгкий тегАннотированный тег
ХранениеФайл ссылки, указывающий на коммитОбъект-тег в хранилище объектов
МетаданныеОтсутствуютИмя создателя тега, email, дата, сообщение
GPG-подписьНевозможнаПоддерживается через git tag -s
Рекомендуется для релизовНетДа
Передача с git push --tagsДаДа
git tag v2.1.0                              # Lightweight tag at HEAD
git tag -a v2.1.0 -m "Release 2.1.0"       # Annotated tag
git tag -s v2.1.0 -m "Signed release"      # GPG-signed annotated tag
git push origin --tags                      # Push all tags to remote
git push origin v2.1.0                      # Push a specific tag

Критическая ошибка: git push по умолчанию не отправляет теги. Команды часто забывают об этом и публикуют примечания к релизу со ссылкой на тег, которого не существует на удалённом репозитории.

Удалённые репозитории: распределённое сотрудничество

Удалённый репозиторий — это именованный URL, хранящийся в .git/config. Ветки отслеживания удалённого репозитория (в refs/remotes/) — это локальные снимки веток удалённого репозитория только для чтения, обновляемые только при явном выполнении fetch.

git remote add origin git@github.com:user/repo.git
git remote -v                          # List remotes with URLs
git remote set-url origin <new-url>    # Change a remote URL
git fetch origin                       # Update remote-tracking branches
git fetch --prune                      # Remove stale remote-tracking branches
git push origin main                   # Push local main to remote
git push -u origin feature/auth        # Push and set upstream tracking

Несколько удалённых репозиториев

Один репозиторий может отслеживать несколько удалённых репозиториев — это распространено при поддержке форка наряду с основным репозиторием:

git remote add upstream git@github.com:original/repo.git
git fetch upstream
git merge upstream/main

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

Хуки: автоматизированное применение политик при каждом событии Git

Хуки — это исполняемые скрипты в .git/hooks/. Git вызывает их в определённых точках рабочего процесса. Они не передаются при git clone или git push — каждый разработчик (или сервер) должен устанавливать их самостоятельно. Это частый источник путаницы в командных средах.

Клиентские хуки

ХукТриггерРаспространённое использование
pre-commitПеред запросом сообщения коммитаЛинтинг, сканирование секретов, выполнение тестов
prepare-commit-msgПосле создания сообщения по умолчаниюВставка имени ветки в сообщение
commit-msgПосле того как пользователь написал сообщениеПрименение формата conventional commit
post-commitПосле записи коммитаЛокальные уведомления
pre-pushПеред выполнением git pushЗапуск полного набора тестов
pre-rebaseПеред началом rebaseПредотвращение rebase опубликованных веток

Серверные хуки

ХукТриггерРаспространённое использование
pre-receiveПеред обновлением ссылокПрименение защиты веток, отклонение принудительной отправки
updateДля каждой ссылки при полученииПрименение политик для отдельных веток
post-receiveПосле обновления всех ссылокЗапуск CI/CD, отправка уведомлений

Пример: хук pre-commit для обнаружения секретов

#!/usr/bin/env bash
# .git/hooks/pre-commit

if git diff --cached --name-only | xargs grep -lE '(AKIA|passwords*=|api_keys*=)' 2>/dev/null; then
    echo "ERROR: Potential secret detected in staged files. Commit aborted."
    exit 1
fi
exit 0

Сделайте его исполняемым:

chmod +x .git/hooks/pre-commit

Для распространения хуков в команде используйте инструмент наподобие Husky (для проектов на Node.js) или храните хуки в каталоге hooks/ в корне репозитория и создавайте символические ссылки на них при настройке проекта.

Reflog: защитная сеть

Reflog записывает каждое перемещение HEAD и указателей веток, включая операции, которые внешне уничтожают историю (жёсткие сбросы, rebase, изменённые коммиты). Он хранится в .git/logs/.

git reflog                          # Show HEAD movement history
git reflog show main                # Show movement history for a specific branch
git checkout HEAD@{3}               # Check out the state HEAD was in 3 moves ago
git branch recovered HEAD@{5}       # Recover commits by branching from a reflog entry

Записи reflog истекают через 90 дней по умолчанию (gc.reflogExpire). На производственном сервере рассмотрите возможность увеличения этого срока:

git config gc.reflogExpire 180
git config gc.reflogExpireUnreachable 30

Голые репозитории: размещение на стороне сервера

Голый репозиторий не имеет рабочего каталога. Он содержит только содержимое .git/ на корневом уровне. Голые репозитории — это правильный формат для централизованного размещения: они принимают отправки без осложнений, связанных с переключённой веткой.

git init --bare /srv/repos/myproject.git

Когда вы отправляете данные на GitHub, GitLab или самостоятельно размещённый Git-сервер, вы отправляете их в голый репозиторий. Если вы размещаете собственный Git-сервер на VPS с cPanel или обычном Linux VPS, голые репозитории в /srv/repos/ с SSH-доступом являются стандартной архитектурой.

Инициализация общего голого репозитория

# On the server
git init --bare --shared=group /srv/repos/project.git
chown -R git:developers /srv/repos/project.git

# On a developer's machine
git remote add origin git@yourserver.com:/srv/repos/project.git
git push -u origin main

Хранение объектов Git: размер, целостность и обслуживание

Проверка работоспособности репозитория

git fsck --full          # Verify object integrity (finds dangling and corrupt objects)
git fsck --lost-found    # Write dangling objects to .git/lost-found/

Поиск и удаление больших объектов

Случайно зафиксированные большие бинарные файлы — распространённая причина раздутых репозиториев. Определите их перед использованием git filter-repo для их удаления:

# Find the 10 largest objects by compressed size
git verify-pack -v .git/objects/pack/*.idx 
  | sort -k3 -rn 
  | head -10 
  | awk '{print $1}' 
  | xargs -I{} git cat-file -p {}
# Remove a file from all history (requires git-filter-repo)
git filter-repo --path path/to/large-file.bin --invert-paths

После фильтрации все участники должны повторно клонировать репозиторий — их локальные репозитории ссылаются на SHA-хэши, которых больше не существует в переписанной истории.

Сравнение: ключевые концепции репозитория Git

КонцепцияТипИзменяемыйХранится вПередаётся при push/fetch
БлобОбъектНет.git/objects/Да (если достижим)
ДеревоОбъектНет.git/objects/Да (если достижим)
КоммитОбъектНет.git/objects/Да (если достижим)
Аннотированный тегОбъектНет.git/objects/Только с --tags
ВеткаСсылкаДа.git/refs/heads/Да
Ветка отслеживания удалённого репозиторияСсылкаДа (при fetch).git/refs/remotes/Нет (локальный кэш)
Лёгкий тегСсылкаНет.git/refs/tags/Только с --tags
HEADСимволическая ссылка/хэшДа.git/HEADНет
ИндексБинарный файлДа.git/indexНет
ХукиСкриптыДа.git/hooks/Нет
ReflogЖурналДа (автоматически истекает).git/logs/Нет

Практическая матрица решений и ключевые выводы

Используйте этот контрольный список при настройке или аудите репозитория Git на вашей инфраструктуре:

Инициализация репозитория

  • Используйте git init --bare --shared=group для любого репозитория, который будет получать отправки от нескольких пользователей.
  • Храните голые репозитории за пределами веб-доступных каталогов (никогда не в /var/www/).

Работоспособность хранилища объектов

  • Запускайте git fsck --full после любого инцидента с хранилищем или ошибки файловой системы.
  • Планируйте периодический запуск git gc на долгоживущих репозиториях; автоматизируйте его через cron на вашем сервере.
  • Отслеживайте размер файлов пакетов с помощью git count-objects -vH; проводите расследование, если количество отдельных объектов превышает 1 000.

Гигиена веток и ссылок

  • Своевременно удаляйте слитые ветки; устаревшие ссылки накапливаются и замедляют операции git fetch --prune.
  • Используйте git fetch --prune в CI-пайплайнах, чтобы избежать действий с удалёнными удалёнными ветками.

Развёртывание хуков

  • Никогда не полагайтесь на .git/hooks/ для применения общекомандных политик — хуки не клонируются. Вместо этого используйте серверные хуки pre-receive или CI-шлюз.
  • Проверяйте серверные хуки после каждого обновления Git-сервера; пути интерпретаторов хуков могут измениться.

Безопасность на самостоятельно размещённых серверах

  • Ограничьте SSH-доступ для пользователя git с помощью принудительных команд (command= в authorized_keys).
  • Используйте git-shell в качестве оболочки входа для пользователя git, чтобы предотвратить выполнение произвольных команд.
  • Обеспечьте ваш сервер репозиториев действующим SSL-сертификатом, если вы открываете какой-либо веб-интерфейс (Gitea, GitLab, cgit).

Перезапись истории

  • Никогда не переписывайте историю в ветках, общих с другими, без скоординированного плана миграции.
  • После git filter-repo все участники должны повторно клонировать репозиторий; немедленно обновите URL удалённых репозиториев в CI/CD.

Аварийное восстановление

  • Увеличьте срок хранения reflog на производственных серверах (gc.reflogExpire = 180).
  • Храните вторичный голый клон на отдельном хосте в качестве резервной копии; простого git fetch с основного сервера достаточно.

Часто задаваемые вопросы

В чём разница между голым и неголым репозиторием Git?

Неголый репозиторий имеет рабочий каталог, в котором извлечены файлы, плюс подкаталог .git/, содержащий хранилище объектов. Голый репозиторий содержит только хранилище объектов в своём корне (без рабочего каталога) и является правильным форматом для общего сервера, принимающего отправки.

Можно ли восстановить коммиты после выполнения git reset --hard?

Да, при условии, что коммиты не были удалены сборщиком мусора. Запустите git reflog, чтобы найти SHA нужного коммита, затем git checkout -b recovery-branch <SHA>, чтобы прикрепить его к новой ветке. Записи reflog хранятся 90 дней по умолчанию.

Почему git push не передаёт мои теги?

По замыслу git push передаёт только коммиты, достижимые из явно указанных вами ссылок. Теги — это отдельные ссылки, и их нужно отправлять с помощью git push origin --tags (все теги) или git push origin <tagname> (конкретный тег).

Что происходит с индексом во время конфликта слияния?

Индекс одновременно хранит все три версии каждого конфликтующего файла: стадия 1 (общий предок/база), стадия 2 (ваша версия) и стадия 3 (их версия). Обычный git add записывает только стадию 0 (разрешённую). До тех пор, пока все конфликты не будут разрешены и добавлены в индекс, git commit откажется продолжать.

Чем клиентские хуки Git отличаются от серверных?

Клиентские хуки выполняются на машине разработчика и не применяются централизованно — любой разработчик может обойти их, удалив файл хука. Серверные хуки (pre-receive, update, post-receive) выполняются на сервере размещения и не могут быть обойдены клиентом, что делает их правильной точкой применения политик защиты веток, требований к проверке кода и триггеров CI/CD.

Linux Windows Безопасность
Linux
VPS Выделенные