Работа с коммитами

Создание осмысленных коммитов и управление историей: amend, revert, reset, cherry-pick

Зачем нужно

  • Писать понятные сообщения коммитов для себя и команды
  • Исправлять ошибки в последнем коммите
  • Откатывать неудачные изменения
  • Переносить отдельные коммиты между ветками

Где используется

  • Ежедневная разработка — каждый коммит
  • Code review — история должна быть читаемой
  • Исправление ошибок — откат проблемных коммитов
  • Релизы — чистая и понятная история

Предпосылки

Анатомия хорошего коммита

Структура сообщения

<тип>: <краткое описание>     ← заголовок (до 72 символов)
                               ← пустая строка
<подробное описание>           ← тело (опционально)
                               ← пустая строка
<ссылки на задачи>             ← футер (опционально)

Пример

feat: добавить авторизацию через Google OAuth

Реализована авторизация через Google с использованием
passport-google-oauth20. Добавлены роуты /auth/google
и /auth/google/callback.

Closes #15

Правила хороших сообщений

  • Начинать с глагола в инфинитиве: "добавить", "исправить", "обновить"
  • Заголовок до 72 символов
  • Не ставить точку в конце заголовка
  • Один коммит = одна логическая задача
  • Тело объясняет зачем, а не что (что видно из diff)

git commit --amend — исправить последний коммит

# Ситуация: забыли добавить файл
git add forgotten-file.js
git commit --amend
# Откроется редактор — можно изменить сообщение

# Только изменить сообщение (без добавления файлов)
git commit --amend -m "Новое сообщение коммита"

# Добавить файл, но не менять сообщение
git add extra-file.js
git commit --amend --no-edit

Важно: --amend перезаписывает историю. Не используйте для коммитов, которые уже отправлены в удалённый репозиторий (pushed).

git revert — отмена коммита новым коммитом

# Отменить конкретный коммит (создаёт НОВЫЙ коммит с обратными изменениями)
git revert a1b2c3d

# Отменить последний коммит
git revert HEAD

# Отменить без автоматического коммита
git revert --no-commit a1b2c3d
# Изменения попадут в staging — можно отредактировать перед коммитом

# Отменить диапазон коммитов
git revert HEAD~3..HEAD
До revert:   A ── B ── C (проблемный)
После:       A ── B ── C ── C' (отмена C)

Revert безопасен — не перезаписывает историю, создаёт новый коммит.

git reset — перемещение HEAD

Три режима reset:

                     Working Dir    Staging Area    Commits
--soft               сохранены      сохранены       ← HEAD сдвинут
--mixed (default)    сохранены      очищена         ← HEAD сдвинут
--hard               очищена        очищена         ← HEAD сдвинут

--soft — откатить коммит, оставить всё в staging

git reset --soft HEAD~1
# Последний коммит отменён, но все изменения в staging area
# Удобно чтобы переделать коммит

--mixed — откатить коммит, оставить в working directory

git reset HEAD~1
# или
git reset --mixed HEAD~1
# Последний коммит отменён, изменения в working directory (unstaged)

--hard — полный откат (ОПАСНО!)

git reset --hard HEAD~1
# Последний коммит И все изменения УДАЛЕНЫ
# Используйте только если уверены!

# Откатить к конкретному коммиту
git reset --hard a1b2c3d

Практический пример

# Сделали 3 коммита, хотим объединить в один
git reset --soft HEAD~3
git commit -m "Реализовать модуль авторизации"
# Все 3 коммита сжаты в один

Отменить git reset

# Если случайно сделали reset --hard
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# f4e5d6a HEAD@{1}: commit: Важный коммит  ← вот он!

git reset --hard f4e5d6a
# Восстановлено!

git cherry-pick — перенос отдельных коммитов

# Перенести коммит из другой ветки в текущую
git cherry-pick a1b2c3d

# Перенести несколько коммитов
git cherry-pick a1b2c3d f4e5d6a

# Перенести без автоматического коммита
git cherry-pick --no-commit a1b2c3d
feature:  A ── B ── C ── D
                    ↓ cherry-pick C
main:     X ── Y ── C'

Когда использовать cherry-pick

  • Нужен один конкретный фикс из другой ветки
  • Hotfix: перенести исправление в release-ветку
  • Ошибочно закоммитили в неправильную ветку

Сравнение revert vs reset

git revert git reset
Создаёт новый коммит Да Нет
Перезаписывает историю Нет Да
Безопасно для shared-веток Да Нет
Можно отменить конкретный коммит Да Только последние N

Когда что использовать

Коммит УЖЕ в remote → git revert (безопасно)
Коммит ТОЛЬКО локально → git reset (чисто)
Нужен один фикс из другой ветки → git cherry-pick
Опечатка в последнем коммите → git commit --amend

git stash — временное сохранение

# Ситуация: работаете над фичей, нужно срочно переключиться
git stash
# Все изменения сохранены, рабочая директория чистая

# Вернуть изменения
git stash pop

# Посмотреть список stash
git stash list

# Применить конкретный stash (не удаляя его)
git stash apply stash@{1}

# Stash с сообщением
git stash push -m "Работа над формой входа"

# Удалить stash
git stash drop stash@{0}

# Удалить все stash
git stash clear

Частые ошибки

  • git reset --hard на shared-ветке — потеря чужих коммитов, конфликты при push
  • amend после push — придётся делать force push, что опасно
  • Огромные коммиты — "Обновить всё" с 50 изменёнными файлами нечитаемо
  • Revert merge-коммита без -m — Git не знает какого родителя сохранить
  • Забыть про reflog — после ошибочного reset можно восстановить данные через git reflog

Практика

  1. Сделайте 5 коммитов с разными изменениями
  2. Используйте git commit --amend чтобы исправить сообщение последнего
  3. Отмените предпоследний коммит через git revert
  4. Попробуйте git reset --soft HEAD~2 и объедините коммиты
  5. Создайте две ветки и перенесите коммит через cherry-pick
  6. Попробуйте git stash при переключении между ветками
  7. Изучите git reflog — найдите в нём предыдущие состояния

Связанные темы

Ресурсы