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

Системы контроля версий и Git

На каком бы языке программирования вы ни работали — в команде, фрилансером или для себя — без системы контроля версий не обойтись. Эта статья — короткий обзор Git и его экосистемы.

Что такое контроль версий

Система контроля версий (СКВ, VCS — Version Control System) регистрирует изменения файлов так, чтобы в любой момент можно было:

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

Типичный сценарий: выложили новую версию в продакшен, начали работать над новыми фичами — и вдруг обнаружили баг в старой. Нужно вернуться к рабочей версии, исправить и не потерять начатую работу. СКВ это делает естественно.

Без СКВ многие пытаются обойтись копированием каталогов (my_project_v1, my_project_v2_final, my_project_FINAL_FINAL). Минусы такого подхода:

  • избыточность: дублируется весь код, а не только изменения;
  • нет коллективной работы: двум людям невозможно править одновременно;
  • нет истории: непонятно, что именно изменилось и зачем.

Виды СКВ

Локальные (исторический интерес)

Первые СКВ — например, RCS (1985) — хранили историю в локальной БД. Решали только проблему избыточности (через дельта-компрессию).

Централизованные (CVS, Subversion, Perforce)

Один центральный сервер хранит все версии, клиенты получают копии нужных. Долго были стандартом индустрии. Недостатки:

  • сервер — единая точка отказа;
  • нет работы оффлайн (всё через сервер);
  • сложно ветвиться и сливать.

Распределённые (Git, Mercurial, Bazaar)

Каждый клиент имеет полную копию репозитория со всей историей. Сервер (если есть) — точка синхронизации, а не единый источник истины. Преимущества:

  • работа оффлайн;
  • быстрые локальные операции;
  • любой клон может стать новым центральным репозиторием;
  • мощное ветвление и слияние.

На сегодня Git — стандарт де-факто (~95% open-source проектов). В этой статье говорим только про Git.

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

Почти все операции — локальные

Чтобы посмотреть историю, сделать коммит или сравнить версии, Git'у не нужен сервер. Это:

  • очень быстро (история — в .git/);
  • работает без интернета (в поезде, в самолёте);
  • сетевые операции — только push/pull/fetch.

Целостность данных

Каждый объект Git (файл, коммит, дерево) идентифицируется по SHA-1 (с переходом на SHA-256). Невозможно незаметно изменить файл — Git это заметит. Контрольные суммы — фундамент архитектуры Git.

Чаще всего данные только добавляются

Практически любая операция Git'а добавляет данные в репозиторий, а не удаляет. Это значит:

  • отменить операцию обычно можно — данные ещё в .git/;
  • ошибки восстанавливаются (через reflog);
  • редкие «по-настоящему опасные» команды — git reset --hard, git push --force, git clean -fd.

Три состояния файлов

Это ключевое для понимания Git'а. Файл может находиться в одном из трёх состояний:

Состояние Что значит
Modified (изменён) поменялся, но не подготовлен к коммиту
Staged (подготовлен) отмечен для следующего коммита
Committed (зафиксирован) сохранён в локальной базе

Стандартный workflow:

flowchart LR
    A[Рабочий каталог] -->|git add| B[Индекс / staging]
    B -->|git commit| C[Локальный репозиторий]
    C -->|git push| D[Удалённый репозиторий]
    D -->|git pull / fetch| C

Установка Git

macOS

# через Homebrew
brew install git

# или через Xcode CLT
xcode-select --install

Linux

# Debian/Ubuntu
sudo apt install git

# Fedora/RHEL
sudo dnf install git

# Arch
sudo pacman -S git

Windows

Скачайте инсталлятор с gitforwindows.org. В комплекте — git, Git Bash (Unix-подобный shell), SSH-клиент и (опционально) графические инструменты.

После установки проверьте версию:

git --version
# git version 2.45.0

Первоначальная настройка

Git хранит настройки в трёх уровнях:

  • --system — для всех пользователей системы;
  • --global — для текущего пользователя (хранятся в ~/.gitconfig);
  • по умолчанию — для конкретного репозитория (.git/config).

Имя и почта

Каждый коммит содержит автора. Установите имя и email до первого коммита:

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

Редактор по умолчанию

git config --global core.editor "code --wait"     # VS Code
git config --global core.editor "vim"             # Vim
git config --global core.editor "nano"            # Nano

Современные удобства

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

# Цветной вывод
git config --global color.ui auto

# Авто-rebase при pull (избегает мёрж-коммитов)
git config --global pull.rebase true

# Сохранять учётные данные на 1 час
git config --global credential.helper "cache --timeout=3600"

# На macOS — использовать Keychain
git config --global credential.helper osxkeychain

# Включить интеллектуальную обработку line endings
git config --global core.autocrlf input    # macOS / Linux
git config --global core.autocrlf true     # Windows

SSH-ключи для GitHub/GitLab

Аутентификация по паролю давно устарела. Используйте SSH:

# Создать ключ
ssh-keygen -t ed25519 -C "you@example.com"

# Запустить агент и добавить ключ
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

# Скопировать публичный ключ
cat ~/.ssh/id_ed25519.pub        # вставьте на GitHub → Settings → SSH keys

Проверка:

ssh -T git@github.com
# Hi username! You've successfully authenticated.

Выбор хостинга

Хостинг Особенности
GitHub Самый популярный, активная экосистема, бесплатные приватные репо.
GitLab Cloud + self-hosted, встроенный CI/CD, есть бесплатная версия Community Edition.
Codeberg Некоммерческий, fork Gitea, без слежки и без AI-обучения на коде.
Bitbucket От Atlassian, тесно интегрирован с Jira.
Собственный Gitea, GitLab CE, Forgejo — если нужны полный контроль и конфиденциальность.

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

Создание репозитория

Способ 1: с нуля

mkdir my-project && cd my-project
git init
git add README.md
git commit -m "Initial commit"

Способ 2: клонирование существующего

git clone https://github.com/user/repo.git
git clone git@github.com:user/repo.git              # через SSH
git clone https://github.com/user/repo.git my-dir   # в свой каталог

Статус и история

git status              # что изменилось
git status -s           # короткий формат

git log                 # история коммитов
git log --oneline       # компактно
git log --graph --oneline --all  # с ветками

git diff                # неподготовленные изменения
git diff --staged       # подготовленные изменения
git diff main..feature  # между ветками

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

git add file.txt        # один файл
git add src/            # каталог
git add -p              # интерактивно по кускам (patch mode)
git add .               # всё в текущем каталоге (осторожно!)

git commit -m "feat: добавил поиск"
git commit               # откроет редактор для многострочного сообщения
git commit -am "fix"     # add + commit для отслеживаемых файлов

Игнорирование файлов

Создайте файл .gitignore в корне:

# Комментарий — эта строка игнорируется

# Python
__pycache__/
*.pyc
.venv/
.env

# Go
/bin/
*.exe

# IDE
.idea/
.vscode/

# Логи и временные
*.log
*.tmp
.DS_Store

# Скомпилированные
build/
dist/

# Отслеживать lib.a, даже если игнорируем все .a
*.a
!lib.a

Готовые шаблоны для языков и инструментов: github.com/github/gitignore.

Удаление и переименование

git rm file.txt              # удалить и убрать из индекса
git rm --cached secrets.env  # убрать из индекса, оставить на диске
git mv old.txt new.txt       # переименовать

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

git restore file.txt              # отменить неподготовленные изменения файла
git restore --staged file.txt     # снять с индекса (не удалить изменения)
git restore --source=HEAD~1 file.txt  # вернуть файл из предыдущего коммита

git revert <commit>               # создать новый коммит, обращающий указанный
git reset HEAD~1                  # «отменить» последний коммит (мягко — изменения остаются)
git reset --hard HEAD~1           # отменить ПОЛНОСТЬЮ (осторожно!)

Просмотр изменений

git show <commit>                 # детали коммита
git blame file.txt                # кто и когда менял каждую строку
git log -p file.txt               # история изменений конкретного файла
git log -S "string"               # коммиты, добавившие/убравшие строку

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

git remote -v                                    # список удалённых
git remote add origin git@github.com:u/r.git    # добавить
git remote remove origin                         # удалить
git remote rename origin upstream                # переименовать

git fetch origin                                 # забрать обновления, не сливая
git pull origin main                             # fetch + merge (или rebase)
git push origin main                             # отправить ветку
git push -u origin feature-x                     # отправить и установить tracking
git push --force-with-lease                      # безопасный force-push

--force vs --force-with-lease: --force перезаписывает удалённую ветку безусловно — можно случайно затереть чужую работу. --force-with-lease проверяет, что вы видели последние изменения и не перезатрёте чужой push. Всегда используйте именно --force-with-lease.

Ветвление

Главная сила Git'а — лёгкое и быстрое ветвление.

git branch                       # список локальных веток
git branch -a                    # с удалёнными
git branch feature-x             # создать ветку
git switch feature-x             # переключиться (современная команда)
git switch -c feature-y          # создать + переключиться
git checkout feature-x           # старая команда переключения

git merge feature-x              # слить в текущую ветку
git merge --no-ff feature-x      # с явным merge-коммитом
git rebase main                  # перебазировать текущую ветку на main

git branch -d feature-x          # удалить слитую ветку
git branch -D feature-x          # удалить принудительно (даже несли́тую)
git push origin --delete feature-x  # удалить удалённую ветку

Популярные workflow

GitHub Flow — простой и популярный:

  1. Создаём ветку от main.
  2. Коммитим изменения.
  3. Открываем Pull Request.
  4. Получаем review, обсуждаем, исправляем.
  5. Сливаем в main.
  6. Удаляем ветку.

Git Flow (более сложный, для релизных циклов):

  • main — продакшен;
  • develop — основная ветка разработки;
  • feature/*, release/*, hotfix/* — короткоживущие ветки.

Для большинства проектов GitHub Flow проще и хватает.

Conventional Commits

Современная конвенция для сообщений коммитов:

<type>(<scope>): <subject>

<body>

<footer>

Типы:

  • feat: — новая фича;
  • fix: — исправление бага;
  • docs: — изменения только в документации;
  • style: — форматирование, без логики;
  • refactor: — переделка без изменения поведения;
  • test: — добавление/изменение тестов;
  • chore: — рутинная работа (зависимости, конфиги);
  • build: — изменения в сборке;
  • ci: — CI/CD-конфиги.

Примеры:

feat(auth): добавить вход по Google

fix(api): исправить race condition в обработчике webhook

docs(readme): обновить инструкцию запуска

refactor: вынести вычисление цены в отдельный модуль

BREAKING CHANGE: переименовали /api/users в /api/v2/users

Зачем это нужно:

  • читаемая история;
  • автоматическая генерация changelog;
  • автоматическое определение версии (по feat: минорная, по fix: патч, по BREAKING CHANGE мажорная).

Полезные команды

Stash — отложить изменения

git stash                        # отложить все незакоммиченные изменения
git stash list                   # список «отложенных»
git stash pop                    # вернуть последние и удалить из stash
git stash apply                  # вернуть, но оставить в stash
git stash drop                   # удалить последний
git stash push -m "wip auth"     # с описанием

Сценарий: вы работаете над фичей, но срочно нужно исправить баг в main. git stash → переключаетесь в main → правите баг → возвращаетесь → git stash pop.

Cherry-pick — взять конкретный коммит

git cherry-pick <commit-sha>     # применить один коммит из другой ветки

Reflog — спасение жизни

git reflog                       # история всех HEAD-перемещений
git reset --hard HEAD@{3}        # вернуться к тому, что было «3 шага назад»

Reflog хранит локально все перемещения HEAD в течение ~90 дней. Если случайно сделали git reset --hard и потеряли коммиты — почти наверняка их можно вернуть через reflog.

Bisect — двоичный поиск регрессии

git bisect start
git bisect bad                   # текущий коммит — сломан
git bisect good v1.0             # v1.0 — работала
# Git переключает на середину истории; вы тестируете и говорите good/bad
git bisect good                  # или git bisect bad
# ... повторяете, пока Git не найдёт виновный коммит
git bisect reset                 # выйти из режима bisect

Незаменим для поиска регрессий в большой истории.

Чего НЕ нужно делать

  • Коммитить секреты (пароли, токены, приватные ключи). Если случайно — токен скомпрометирован, история всё равно содержит его навсегда. Используйте git secrets, gitleaks или pre-commit хуки.
  • git push --force в общую ветку (main, develop). Используйте --force-with-lease и только для собственных feature-веток.
  • Хранить большие бинарные файлы в Git. Используйте Git LFS, S3 или другое внешнее хранилище.
  • Очень большие коммиты «через раз». Лучше много маленьких атомарных коммитов.
  • git pull --rebase в общей ветке после push. Если уже отправили — теперь только мёрж.

Современные дополнения

  • GitHub CLI (gh) — работать с PR, issues, актами и релизами из терминала: gh pr create, gh issue list, gh repo clone.
  • lazygit — TUI для Git с горячими клавишами для всех операций.
  • tig — просмотрщик истории в терминале.
  • delta — красивый цветной diff поверх Git.
  • pre-commit — фреймворк для запуска хуков перед коммитом (линтер, форматтер, проверка секретов).
  • husky — то же для проектов на Node.js/TypeScript.

Где учиться дальше

  • Pro Git — официальная книга, переведена на русский.
  • Learn Git Branching — визуальный интерактивный туториал.
  • GitHub Skills — официальные курсы.
  • Oh Shit, Git!?! — как откатить типичные косяки.

Контрольные вопросы

  • В чём принципиальное отличие распределённой СКВ от централизованной?
  • Какие три состояния файла бывают в Git и через какие команды они переходят?
  • Чем git pull отличается от git fetch?
  • Что такое --force-with-lease и почему он безопаснее обычного --force?
  • Когда применять git merge, а когда git rebase?
  • Что хранит файл .gitignore и какие шаблоны он поддерживает?
  • Что такое Conventional Commits и какие плюсы они дают?
  • Как восстановить случайно удалённый коммит?
  • Зачем нужен git bisect?
  • Какие данные никогда не должны попадать в Git-репозиторий?