Git: система контроля версий

Концепции

Working Directory  →  Staging Area (Index)  →  Local Repository  →  Remote Repository
    (файлы)              (git add)               (git commit)         (git push)

Working Directory: файлы на диске, текущее состояние. Staging Area: промежуточная зона, которая определяет что попадёт в следующий коммит. Commit: снимок состояния проекта. Неизменяемый, имеет хеш (SHA). Branch: указатель на коммит. Дешёвый, просто ссылка, не копия. HEAD: указатель на текущий коммит/ветку, то где ты сейчас. Remote: удалённый репозиторий (GitHub, GitLab, etc.).

Начальная настройка

git config --global user.name "Имя Фамилия"
git config --global user.email "email@example.com"

# Редактор по умолчанию
git config --global core.editor "nvim"

# Ветка по умолчанию
git config --global init.defaultBranch main

# Автоматически настраивать upstream при push
git config --global push.autoSetupRemote true

# Красивый diff
git config --global diff.algorithm histogram

# Авторебейз при pull
git config --global pull.rebase true

# Rerere — запоминает разрешение конфликтов
git config --global rerere.enabled true

Основные команды

Создание и клонирование

git init                          # инициализация в текущей директории
git clone <url>                   # клонировать репозиторий
git clone <url> <dir>             # клонировать в конкретную директорию
git clone --depth 1 <url>         # shallow clone (только последний коммит)

Состояние и информация

git status                        # текущее состояние
git status -s                     # краткий формат
git log --oneline                 # история коммитов (кратко)
git log --oneline --graph --all   # граф всех веток
git log -p                        # история с diff-ами
git log --since="2 weeks ago"     # за последние 2 недели
git log --author="Имя"            # коммиты конкретного автора
git log -- path/to/file           # история конкретного файла
git show <commit>                 # посмотреть конкретный коммит
git blame <file>                  # кто какую строку менял

Добавление и коммит

git add <file>                    # добавить файл в staging
git add -p                        # интерактивный staging по кускам (hunk)
git commit -m "message"           # коммит с сообщением
git commit                        # коммит — откроется редактор
git commit -am "message"          # add + commit для tracked файлов
git commit --amend                # изменить последний коммит

Diff

git diff                          # изменения в working dir (не staged)
git diff --staged                 # изменения в staging (будут в коммите)
git diff main..feature            # разница между ветками
git diff HEAD~3                   # изменения за последние 3 коммита
git diff --stat                   # только статистика (какие файлы, сколько строк)
git diff --name-only              # только имена изменённых файлов

Ветки

git branch                        # список локальных веток
git branch -a                     # все ветки (включая remote)
git branch <name>                 # создать ветку
git branch -d <name>              # удалить ветку (safe)
git branch -D <name>              # удалить ветку (force)
git branch -m <old> <new>         # переименовать ветку
git switch <branch>               # переключиться на ветку
git switch -c <branch>            # создать и переключиться
git switch -                      # вернуться на предыдущую ветку

Remote

git remote -v                     # список remote-ов
git remote add origin <url>       # добавить remote
git fetch                         # скачать изменения (без merge)
git fetch --prune                 # + удалить ссылки на удалённые ветки
git pull                          # fetch + merge (или rebase, если настроено)
git push                          # отправить на remote
git push -u origin <branch>       # push + установить upstream
git push origin --delete <branch> # удалить ветку на remote

Merge и Rebase

# Merge — создаёт merge-коммит, сохраняет историю
git merge <branch>

# Rebase — переносит коммиты поверх другой ветки, линейная история
git rebase main                   # перебазировать текущую ветку на main

# Интерактивный rebase — переупорядочить, склеить, отредактировать коммиты
git rebase -i HEAD~3              # последние 3 коммита
# pick   — оставить коммит
# squash — склеить с предыдущим (сохранить сообщение)
# fixup  — склеить с предыдущим (выбросить сообщение)
# reword — изменить сообщение
# drop   — удалить коммит

# Отмена
git merge --abort                 # отменить merge
git rebase --abort                # отменить rebase

Когда merge, когда rebase:

  • rebase: для своих feature-веток, чтобы подтянуть main. Линейная, чистая история.
  • merge: для вливания feature в main. Сохраняет контекст “когда что влили”.
  • Никогда не rebase то, что уже запушено и используется другими.

Stash

git stash                         # спрятать текущие изменения
git stash -m "описание"           # спрятать с описанием
git stash -u                      # включая untracked файлы
git stash list                    # список stash-ей
git stash pop                     # достать последний stash + удалить
git stash apply                   # достать последний stash (не удалять)
git stash drop                    # удалить последний stash
git stash pop stash@{2}           # достать конкретный stash

Отмена изменений

# Убрать файл из staging (не трогая содержимое)
git restore --staged <file>

# Откатить файл к последнему коммиту (потеря изменений!)
git restore <file>

# Откатить коммит, создав новый "обратный" коммит (safe)
git revert <commit>

# Сдвинуть HEAD назад (осторожно)
git reset --soft HEAD~1           # убрать коммит, изменения в staging
git reset --mixed HEAD~1          # убрать коммит, изменения в working dir
git reset --hard HEAD~1           # убрать коммит + изменения (потеря данных!)
--soft   →  коммит убран, файлы в staging (готовы к re-commit)
--mixed  →  коммит убран, файлы в working dir (надо git add снова)
--hard   →  коммит убран, файлы удалены (как будто ничего не было)

Reflog: страховочная сетка

git reflog                        # история всех перемещений HEAD
git reset --hard HEAD@{5}         # вернуться к состоянию 5 шагов назад

Reflog хранит всё, даже после reset --hard. Если потерял коммиты, ищи их тут.

Cherry-pick

git cherry-pick <commit>          # применить конкретный коммит в текущую ветку
git cherry-pick <c1> <c2> <c3>    # несколько коммитов
git cherry-pick --abort           # отменить

Теги

git tag v1.0.0                    # лёгкий тег
git tag -a v1.0.0 -m "Release"   # аннотированный тег
git push origin v1.0.0            # push тега
git push origin --tags            # push всех тегов

Dev-флоу: ежедневная работа

Feature Branch Workflow

Основной паттерн для работы в команде:

# 1. Обновить main
git switch main
git pull

# 2. Создать feature-ветку
git switch -c feature/user-auth

# 3. Работать, коммитить
git add -p                        # staging по кускам — контролируешь что попадёт в коммит
git commit -m "add login endpoint"
# ... ещё работа ...
git commit -m "add JWT validation"

# 4. Периодически подтягивать main
git fetch
git rebase origin/main            # перебазировать свои коммиты поверх актуального main

# 5. Перед push: навести порядок в коммитах
git rebase -i origin/main         # squash/fixup мелких коммитов

# 6. Push
git push

# 7. Создать Pull Request (через GitHub/GitLab UI или CLI)
gh pr create --fill                # GitHub CLI

Паттерн: быстрый фикс на main

git stash -m "текущая работа"     # спрятать незавершённую работу
git switch main
git pull
git switch -c fix/typo-in-readme
# ... фикс ...
git commit -am "fix typo in README"
git push
git switch -                      # вернуться на предыдущую ветку
git stash pop                     # достать свою работу

Паттерн: git add -p (частичный staging)

Самая полезная команда для чистых коммитов. Позволяет добавить в коммит только часть изменений файла:

git add -p
# y — добавить этот кусок
# n — пропустить
# s — разбить на более мелкие куски
# e — ручное редактирование куска
# q — выйти

Зачем: один файл может содержать изменения для разных логических задач. add -p позволяет разделить их по разным коммитам.

Паттерн: fixup-коммиты

Нашёл ошибку в коммите, который сделал 3 коммита назад:

# Исправить файл, затем:
git commit --fixup <commit-hash>  # создаст коммит "fixup! original message"

# Перед push — склеить fixup-ы автоматически:
git rebase -i --autosquash origin/main

Разрешение конфликтов

# При merge/rebase возникает конфликт:
<<<<<<< HEAD (или текущая ветка)
код из твоей ветки
=======
код из другой ветки
>>>>>>> feature/other

# 1. Отредактировать файл — оставить нужный код, убрать маркеры
# 2. git add <file>
# 3. git rebase --continue   (или git merge --continue)

Инструменты для разрешения конфликтов:

git mergetool                     # откроет настроенный merge-tool
# или настроить nvim как mergetool:
git config --global merge.tool nvimdiff

.gitignore

# Зависимости
node_modules/
vendor/
venv/
__pycache__/

# Билды
dist/
build/
*.o
*.pyc

# IDE
.idea/
.vscode/
*.swp
*.swo

# ОС
.DS_Store
Thumbs.db

# Секреты
.env
.env.local
*.key
*.pem
credentials.json

# Логи
*.log
logs/

Глобальный gitignore (для всех проектов):

git config --global core.excludesFile ~/.gitignore_global

Полезные алиасы

Добавить в ~/.gitconfig:

[alias]
    s = status -s
    l = log --oneline -20
    lg = log --oneline --graph --all -30
    d = diff
    ds = diff --staged
    co = switch
    cb = switch -c
    aa = add --all
    ap = add -p
    cm = commit -m
    ca = commit --amend --no-edit
    pu = push
    puf = push --force-with-lease
    pl = pull
    rb = rebase
    rbi = rebase -i
    cp = cherry-pick
    st = stash
    stp = stash pop
    last = log -1 --stat
    undo = reset --soft HEAD~1
    unstage = restore --staged
    wip = "!git add -A && git commit -m 'wip'"
    gone = "!git fetch -p && git branch -vv | grep 'gone]' | awk '{print $1}'"

Использование:

git s                             # git status -s
git l                             # последние 20 коммитов
git lg                            # граф веток
git ap                            # git add -p
git cm "add feature"              # git commit -m "add feature"
git undo                          # отменить последний коммит (изменения в staging)
git gone                          # показать ветки, удалённые на remote

GitHub / GitLab CLI

GitHub CLI (gh)

# Pull Requests
gh pr create --fill               # создать PR
gh pr list                        # список PR
gh pr view 123                    # посмотреть PR
gh pr checkout 123                # скачать PR в локальную ветку
gh pr merge 123 --squash          # merge PR

# Issues
gh issue create --title "Bug"     # создать issue
gh issue list                     # список issues

# Repos
gh repo clone owner/repo          # клонировать
gh repo fork                      # форкнуть

GitLab CLI (glab)

glab mr create --fill             # создать MR
glab mr list                      # список MR
glab mr checkout 123              # скачать MR
glab ci status                    # статус CI/CD

Продвинутые приёмы

Bisect: поиск коммита с багом

git bisect start
git bisect bad                    # текущий коммит — баг есть
git bisect good <commit>          # этот коммит — баг отсутствует
# git будет предлагать коммиты, а ты отвечаешь:
git bisect good                   # бага нет
git bisect bad                    # баг есть
# Найдёт коммит, внёсший баг (бинарный поиск по истории)
git bisect reset                  # выйти

Автоматический bisect:

git bisect start HEAD <good-commit>
git bisect run ./test.sh          # скрипт возвращает 0 = good, 1 = bad

Worktree: несколько рабочих копий

# Создать отдельную рабочую копию для другой ветки
git worktree add ../myapp-hotfix hotfix/critical
cd ../myapp-hotfix
# ... работать, не трогая основную копию ...
git worktree remove ../myapp-hotfix

Зачем: работать над hotfix-ом, не прерывая текущую задачу и не используя stash.

Partial clone и sparse checkout

# Для огромных репозиториев — скачать только нужную папку:
git clone --filter=blob:none --sparse <url>
cd repo
git sparse-checkout set src/backend

Хуки (hooks)

Скрипты, запускающиеся автоматически на определённые события:

ls .git/hooks/                    # доступные хуки

# Популярные:
# pre-commit  — проверки перед коммитом (линтер, тесты)
# commit-msg  — валидация сообщения коммита
# pre-push    — проверки перед push

Для управления хуками удобно использовать pre-commit:

pip install pre-commit
pre-commit install

Соглашения по коммитам (Conventional Commits)

<type>(<scope>): <description>

feat(auth): add JWT refresh token rotation
fix(api): handle null response from payment provider
docs(readme): update installation instructions
refactor(db): extract connection pool configuration
test(auth): add integration tests for login flow
chore(deps): update Go to 1.22

Типы: feat, fix, docs, style, refactor, test, chore, perf, ci, build.

Частые проблемы

Закоммитил в main вместо feature-ветки:

git switch -c feature/my-work     # создать ветку на текущем коммите
git switch main
git reset --hard HEAD~1           # откатить main (изменения уже в feature-ветке)

Закоммитил секрет (.env, ключ):

# Если ещё не запушил:
git reset --soft HEAD~1           # убрать коммит
# убрать секрет из файлов, добавить в .gitignore
git commit

# Если запушил — секрет скомпрометирован. Ротируй ключи.
# Удалить из истории:
git filter-repo --path .env --invert-paths

Push отклонён (non-fast-forward):

git pull --rebase                 # подтянуть remote + перебазировать свои коммиты
git push

Нужен force push (после rebase):

git push --force-with-lease       # безопасный force push (не перезапишет чужие коммиты)
# НЕ используй --force — он перезапишет всё без проверки

Отменить всё и начать сначала:

git reflog                        # найти нужное состояние
git reset --hard HEAD@{n}         # вернуться к нему