Перейти к содержанию

1. Необходимый базис Git

Цель темы: понимать, что такое Git и чем он принципиально отличается от других систем контроля версий; знать, где физически хранятся данные и как устроены базовые команды.


Определения терминов (в контексте Git)

Система контроля версий (СКВ)

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

В контексте темы важно разделение на два подхода:

  • Централизованная СКВ (Subversion, CVS, Perforce): один сервер хранит полную историю; клиенты получают только рабочую копию и обращаются к серверу для коммитов и истории. Без доступа к серверу работа сильно ограничена.
  • Распределённая СКВ (Git, Mercurial): у каждого участника есть полная копия репозитория, включая всю историю. Коммиты и просмотр истории возможны локально; обмен с другими выполняется через push/pull по желанию.

Репозиторий

Репозиторий — хранилище истории проекта: все коммиты, ветки, теги и служебная информация Git. Физически это каталог .git в корне проекта (при использовании git init в существующей папке) или каталог, который сам является корнем (при git clone). Вся «магия» Git — внутри .git; остальные файлы в проекте — рабочая копия.

Рабочая директория (working tree)

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

Индекс (staging area)

Индекс — промежуточная область между рабочей директорией и историей. Вы явно добавляете в него файлы командой git add; коммит (git commit) сохраняет в репозиторий только то, что в индексе. Так вы контролируете, какие изменения попадут в следующий коммит (например, не включаете временные или лишние файлы).

Коммит (commit)

Коммит — снимок состояния индекса в конкретный момент времени, сохранённый в репозитории с уникальным хешем (SHA-1), сообщением, автором и указателем на родительский коммит(ы). История проекта — цепочка таких коммитов.

Ветка (branch)

Ветка — подвижный указатель на один из коммитов. При новом коммите указатель текущей ветки сдвигается вперёд. Разные ветки позволяют вести параллельные линии разработки (например, main и feature/login).

HEAD — указатель на текущую позицию в истории. Обычно HEAD указывает на ветку (например, refs/heads/main), а та — на последний коммит этой ветки. При переходе на конкретный коммит (detached HEAD) HEAD указывает напрямую на коммит.

Объекты Git: blob, tree, commit

  • Blob — объект, хранящий содержимое одного файла (без имени; имя хранится в tree).
  • Tree — объект, описывающий каталог: список имён файлов/каталогов и ссылок на blob’ы и другие tree.
  • Commit — объект коммита: ссылка на tree (снимок файлов), ссылка на родительский коммит(ы), автор, сообщение и т.д.

Эти объекты лежат в .git/objects/ и образуют граф истории.


Git vs централизованные СКВ

Критерий Git (распределённый) SVN (централизованный)
История и коммиты Полная копия у каждого Только на сервере
Работа офлайн Коммиты, просмотр логов, ветки Почти невозможна
Коммит Локально, мгновенно Только при доступе к серверу
Ветки Локальные, лёгкие, быстрые Часто копии каталогов на сервере
Конфликты При слиянии/ребейзе локально или при push Часто при каждом commit/update

Пример

После git clone можно делать коммиты, создавать ветки и смотреть git log без интернета. В SVN для коммита и просмотра полной истории нужен доступ к серверу.


Структура каталога .git

Понимание структуры помогает при отладке и при работе с хуками и конфигурацией.

.git/
├── HEAD              # куда указывает HEAD (текущая ветка или коммит)
├── config            # настройки репозитория
├── objects/          # все объекты (blob, tree, commit)
│   └── [первые 2 символа хеша]/
│       └── [остальные 38 символов]
├── refs/
│   ├── heads/        # ссылки на коммиты по именам веток (branch)
│   └── tags/         # теги
├── index             # индекс (staging area) в бинарном виде
├── logs/             # журналы перемещений HEAD и веток (reflog)
└── hooks/            # скрипты на события (pre-commit, post-merge и т.д.)
  • Коммиты физически хранятся в .git/objects/ по хешу.
  • Ветки — это файлы в refs/heads/, в каждом файле один SHA-1 последнего коммита ветки.
  • Текущая ветка задаётся тем, на что указывает HEAD (обычно ref: refs/heads/main).

Базовые команды

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

git init

Создаёт каталог .git в текущей папке и превращает её в корень репозитория. После этого можно делать коммиты.

mkdir my-project && cd my-project
git init
# Initialized empty Git repository in /path/to/my-project/.git/

Добавление изменений в индекс

git add <файл или каталог>
git add .          # все изменения в текущем каталоге и подкаталогах
git add -p <файл> # интерактивно по кускам (patch)

Файл переходит в состояние «staged» и попадёт в следующий коммит.

echo "Hello, World" > readme.txt
git add readme.txt
git status
# On branch main
# Changes to be committed: readme.txt (new file)

Создание коммита

git commit -m "Краткое сообщение о изменении"

Сохраняет текущее состояние индекса как новый коммит и сдвигает текущую ветку на этот коммит.

git commit -m "Add readme"
# [main (root-commit) a1b2c3d] Add readme
#  1 file changed, 1 insertion(+)
#  create mode 100644 readme.txt

Просмотр состояния и истории

git status        # что изменено / в индексе / не отслеживается
git log           # полная история (q — выход)
git log --oneline # краткий вид, один коммит — одна строка
git log -5        # последние 5 коммитов

Практика

Перед каждым коммитом полезно выполнять git status: так вы не забудете добавить нужные файлы и не попадёт лишнее.


Примеры из production

Деплой по коммиту/ветке

В CI/CD часто собирают артефакт из конкретного коммита или ветки. Понимание того, что коммит — неизменяемый снимок с уникальным хешем, объясняет, почему деплой «по хешу» воспроизводим: один и тот же коммит даёт один и тот же код.

# В пайплайне обычно что-то вроде:
git checkout $COMMIT_SHA
docker build -t app:$COMMIT_SHA .

Аудит и расследование

При инциденте смотрят, что менялось: git log -p -- path/to/file или git blame. Знание, что история хранится локально и в удалённом репозитории одинаково (при синхронизации), позволяет проводить аудит и по клонированной копии.

Откат без потери истории

В production предпочитают revert (новый коммит, отменяющий изменения), а не reset (переписывание истории), чтобы не ломать общую историю и уже развернутые коммиты. Понимание разницы «коммит как снимок» vs «указатель ветки» важно при выборе стратегии отката.


Паттерны использования

Паттерн Описание
Атомарные коммиты Один коммит — одна логическая правка (одна фича или один фикс). Упрощает ревью, откат и бисект.
Осмысленные сообщения Сообщение коммита ясно описывает «что и зачем», лучше по шаблону (например, «feat: add login form»).
Проверка через git status Перед коммитом смотреть, что в индексе; не коммитить случайно лишнее (секреты, билды).
Использование .gitignore Заранее исключать артефакты сборки, зависимости, локальные конфиги — меньше шансов добавить их по ошибке.

Антипаттерны

Антипаттерн Почему плохо Что делать вместо
Коммитить всё подряд В историю попадают билды, зависимости, секреты; репозиторий раздувается, растёт риск утечки. Добавлять в коммит только нужные файлы; держать актуальный .gitignore.
Пустые или бессмысленные сообщения Невозможно понять по истории, зачем был коммит; сложно искать изменения и откатываться. Писать короткое, но содержательное сообщение на каждом коммите.
Игнорирование индекса git add . без просмотра — риск закоммитить временные файлы или конфиги с паролями. Использовать git add -p или выборочно git add <file>, проверять git status и git diff --staged.
Один гигантский коммит Трудно ревьюить, откатывать и искать причину бага (bisect). Дробить изменения на логические коммиты.

Дополнительные материалы