Git — инструмент, которым пользуются все, но по-настоящему понимают немногие. Большинство разработчиков запоминают десяток команд и работают по инерции, пока не сталкиваются с merge-конфликтом, потерянным коммитом или непонятным detached HEAD. В этот момент начинается паника и git push --force.
Эта статья — не справочник по командам, а учебный материал. Цель — чтобы после прочтения вы понимали, что Git делает на самом деле, и могли спокойно разбираться с любой ситуацией.
Это первая статья серии про Git. Если вы только настроили окружение (как это сделать) — Git логично следующий шаг.
В статье
- Что такое Git и три состояния файла
- Первые шаги
- Как Git устроен внутри
- Ветки: merge vs rebase
- Конфликты
- Работа с remote
- Командный workflow
- Частые проблемы
- Минимальный ежедневный цикл
Что такое Git и три состояния файла
Git — это система контроля версий: он хранит историю изменений проекта так, что в любой момент можно посмотреть, кто и что менял, и вернуться к любому прошлому состоянию. Это не бэкап (бэкап — слепок «как было») и не облачное хранилище (Dropbox синхронизирует файлы, но не понимает смысл изменений).
Git распределённый: у каждого разработчика на диске лежит полная копия истории, а не только последняя версия. Можно работать и коммитить без интернета; сервер (GitHub, GitLab) — лишь общая точка обмена, а не «главный и единственный» репозиторий.
Ключ к пониманию — три состояния файла:
working directory → staging area → repository
(рабочая папка) (индекс) (история коммитов)
git add ────────────► git commit ────────►
- working directory — файлы, с которыми вы работаете прямо сейчас;
- staging area (индекс) — что попадёт в следующий коммит;
- repository — зафиксированная история.
Staging area часто кажется лишней, но в ней весь смысл: вы решаете, что именно войдёт в коммит, а не сваливаете всё подряд. Это позволяет делать осознанные коммиты — один коммит = одно логическое изменение.
Первые шаги
Один раз настройте, кто вы, — иначе первый git commit упрётся в ошибку «please tell me who you are»:
git config --global user.name "Имя Фамилия"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main # ветка по умолчанию — mainДальше — базовый цикл:
git init # создать репозиторий в текущей папке
git clone <url> # склонировать существующий
git status # что изменено, что в staging
git add file.txt # добавить в staging (git add . — всё)
git commit -m "message" # зафиксировать staged-изменения
git log --oneline # история коммитов
git diff # что изменено, но ещё не в staging
git diff --staged # что в staging, но ещё не закоммиченоgit status — самая полезная команда новичка: она почти всегда подсказывает, что делать дальше. А .gitignore описывает, что не должно попадать в репозиторий: артефакты сборки, зависимости (node_modules/, target/), секреты (.env), файлы IDE. Правило простое: в Git хранится исходник, а не то, что из него генерируется.
Раз staging — это осознанный выбор, держите наготове git add -p: он показывает изменения кусками (hunks) и спрашивает по каждому, добавлять ли. Так из перемешанных правок легко собрать один чистый коммит «по теме», а остальное оставить на следующий. Если кусок слишком крупный — клавиша s внутри git add -p разбивает его на более мелкие (можно добавить буквально пару строк).
Как Git устроен внутри
Понимание внутренностей снимает 90% страха. Главное прозрение: коммит — это не diff, а snapshot (полный слепок дерева файлов на момент коммита). Git хранит содержимое как объекты, адресуемые по хешу:
- blob — содержимое файла;
- tree — каталог (список blob’ов и вложенных tree);
- commit — snapshot (ссылка на tree) + автор, сообщение и ссылка на родительский коммит;
- каждый объект адресуется хешем своего содержимого — поэтому одинаковые файлы не дублируются, а история неизменяема.
И второе прозрение: ветки и теги — это просто указатели на коммиты. Ветка main — это файл с хешем одного коммита; git commit сдвигает указатель вперёд. HEAD — указатель на «где вы сейчас». Создать ветку дёшево, потому что это создать один маленький указатель, а не копировать файлы.
git reflog # журнал, куда двигался HEAD — даже «потерянные» коммиты тут
git switch -c rescue <hash> # спасти найденный коммит в новую ветку (безопасно)
git checkout <hash> # только ПОСМОТРЕТЬ это состояние — это detached HEAD (см. «Частые проблемы»)git reflog — главная страховка: даже если вы «потеряли» коммит (сбросили ветку, отменили слияние), он почти всегда находится здесь ещё недели. Зная это, перестаёшь бояться экспериментировать.
Ветки: merge vs rebase
Ветка изолирует работу: вы пилите фичу, не трогая main.
git switch -c feature # создать ветку и переключиться (старое: git checkout -b)
git switch main # переключиться обратно
git branch # список ветокИнтегрировать изменения можно двумя способами:
- merge — создаёт коммит-слияние, объединяющий две ветки. История сохраняется как есть, со всеми ответвлениями. Если
mainне уходил вперёд — происходит fast-forward: указатель просто сдвигается, без merge-коммита. - rebase — «переносит» ваши коммиты поверх свежего
main, переписывая их. История становится линейной, как будто вы начали работу от последнего состояния.
исходно: A───B───C (main)
\
D───E (feature)
merge feature: A───B───C───M (main) M — merge-коммит
\ /
D───E
rebase main: A───B───C───D'──E' (feature) D',E' — те же изменения,
новые коммиты поверх maingit merge feature # влить feature в текущую ветку (merge-коммит)
git rebase main # перенести коммиты текущей ветки поверх mainПравило безопасности: rebase хорош для локальных, ещё не опубликованных коммитов (чистая линейная история перед PR), но опасен для уже запушенных — он переписывает хеши, и у коллег, забравших старые коммиты, история разойдётся. Не делайте rebase того, что уже разделили с командой.
Конфликты
Конфликт возникает, когда merge/rebase не может автоматически совместить изменения: один и тот же участок файла правили по-разному в двух ветках. Git помечает спорное место:
<<<<<<< HEAD
ваш вариант
=======
вариант из вливаемой ветки
>>>>>>> featureПодпись «ваш вариант / вариант из вливаемой ветки» верна для обычного merge. При rebase стороны могут восприниматься наоборот (HEAD — это уже переносимая основа, а не «ваша» ветка), поэтому не полагайтесь на подписи слепо — ориентируйтесь на содержимое блоков и подсказки IDE.
Разрешить — значит оставить нужный код (свой, чужой или комбинацию), убрать маркеры, затем git add файла и завершить операцию (git commit для merge или git rebase --continue). Удобнее это делать в IDE/редакторе с тремя панелями, чем вручную в CLI. Лучшая профилактика — мелкие частые коммиты и регулярная синхронизация с main: чем меньше расхождение, тем реже и проще конфликты.
Работа с remote
Remote — это удалённая копия репозитория (на GitHub/GitLab). По умолчанию она называется origin.
git remote -v # какие remote настроены и куда они ведут
git branch -vv # с какой remote-веткой связана локальная (tracking)
git fetch # скачать изменения, НЕ трогая рабочую ветку
git pull # fetch + интеграция в текущую ветку
git push # отправить свои коммиты
git push -u origin feat # первый push новой ветки + связать с remoteПолезно сразу видеть, куда вы пушите (git remote -v) и с чем связана ветка (git branch -vv) — это снимает половину «а почему не туда ушло».
Важно различать: fetch только скачивает обновления (можно спокойно посмотреть, что пришло), а pull = fetch + интеграция их в вашу ветку. Сама интеграция — это merge или rebase, в зависимости от настроек. origin — обычно ваш основной remote; upstream принято называть оригинальный репозиторий, если вы работаете с форком.
Отдельный осознанный выбор — git pull --rebase против git pull с merge: rebase даёт линейную историю без «лишних» merge-коммитов от каждой синхронизации. Многие команды ставят его по умолчанию (git config pull.rebase true).
Командный workflow
В команде Git — это процесс, а не только команды:
- GitHub Flow — короткие feature-ветки от
main, Pull Request, ревью, merge. Просто и подходит большинству. - Trunk-Based Development — все коммитят близко к
mainмелкими порциями, ветки живут часы. Требует хорошего CI. - Git Flow — ветки
develop/release/hotfix; мощно, но тяжеловесно — оправдано при релизах по расписанию.
Поверх этого:
- Pull / Merge Request — code review до попадания в
main: обсуждение, проверки CI, апрув. - Conventional Commits — формат сообщений (
feat:,fix:,docs:) — делает историю читаемой и позволяет автоматизировать changelog. - Защита веток — запрет прямого push в
main, обязательный PR и зелёный CI. Базовая гигиена командной работы.
Одно простое правило закрывает большинство командных проблем: в main руками не коммитим — всегда через ветку и Pull Request.
Частые проблемы
Перед любым «опасным» действием (reset, rebase, --force) сначала осмотритесь — это снимает панику и часто показывает, что всё цело:
git status # что в рабочей папке и в staging
git log --oneline --graph --decorate # где ветки и куда указывает HEAD
git branch # на какой ветке вы сейчас
git reflog # куда двигался HEAD — страховкаШпаргалка по типичным «ой»:
| Ситуация | Решение |
|---|---|
| Закоммитил не то (локально) | git reset --soft HEAD~1 — снять последний коммит, изменения остаются в staging |
| Нужно отменить уже опубликованный коммит | git revert <hash> — создаёт обратный коммит, история не переписывается |
| «Потерял» коммит | git reflog → git switch -c rescue <hash> (безопасно) |
| detached HEAD (вы «вне ветки») | git switch -c new-branch — закрепить текущее состояние веткой |
| Взять один коммит из другой ветки | git cherry-pick <hash> |
| Отложить незавершённую работу | git stash → git stash pop |
⚠️ Осторожно с git reset --hard. Он может безвозвратно удалить незакоммиченные изменения из рабочей папки (не только сдвигает указатель). Иногда их ещё реально достать (local history в IDE, уже добавленное в staging как объекты), но рассчитывать на это не стоит. Применяйте, только если точно уверены, что правки не нужны; сомневаетесь — сначала git stash, он их сохранит.
Про git push --force: он переписывает удалённую историю и может затереть чужие коммиты. На общих ветках (main) — почти всегда нельзя. На своей feature-ветке после rebase — используйте безопасный вариант git push --force-with-lease (он откажется пушить, если на remote появилось чужое).
Минимальный ежедневный цикл
Если свести всё к привычке, рабочий день в Git выглядит так:
git switch main && git pull --ff-only # основная ветка (может зваться иначе) — обновиться от свежего
git switch -c feature/x # ветка под задачу (не коммитим в main)
# ... правки ...
git status # осмотреться
git add -p # собрать чистый коммит из нужных кусков
git commit -m "feat: x" # зафиксировать
git push -u origin feature/x # отправить и связать ветку
# открыть Pull Request → ревью → mergeЭтого цикла и понимания «коммит — snapshot, ветка — указатель, reflog — страховка» достаточно, чтобы пользоваться Git уверенно, а не по инерции.
Полезно один раз настроить алиасы и подпись коммитов в ~/.gitconfig, но это уже после того, как базовый цикл стал привычным.
Главное — не бояться: благодаря reflog и неизменяемости коммитов почти любое действие в Git обратимо. Понимание «коммит — это snapshot, ветка — это указатель» превращает магию в предсказуемый инструмент.

Комментарии