W3docs

Введение

Узнайте, как Git переписывает историю с помощью git commit --amend, git reflog и git rebase — что делает каждая команда и как её применять безопасно.

В Git история — это последовательность коммитов, фиксирующих изменения проекта с течением времени. Каждый коммит имеет уникальный хеш и ссылается на предшествующий коммит (или несколько). В большинстве случаев вы только добавляете новые коммиты в эту цепочку. Но иногда нужно изменить уже существующее — исправить опечатку в сообщении коммита, объединить несколько беспорядочных коммитов в один чистый или восстановить работу, которую вы считали потерянной.

Именно для этого предназначены инструменты перезаписи истории Git. Три команды, к которым вы будете обращаться чаще всего:

  • git commit --amend — заменяет самый последний коммит.
  • git reflog — показывает каждую позицию, на которую указывал HEAD, чтобы вы могли восстановить «потерянные» коммиты.
  • git rebase — воспроизводит серию коммитов поверх нового базового коммита, при необходимости редактируя их в процессе.

На этой странице дан рабочий обзор каждого инструмента. В более подробных главах по ссылкам выше рассматривается полный набор параметров.

Золотое правило перезаписи истории: никогда не переписывайте коммиты, которые вы уже отправили в общую ветку. Перезапись изменяет хеши коммитов, поэтому у всех, кто успел получить старые коммиты, возникнут конфликты. Свободно переписывайте историю на своих локальных ветках — и не трогайте общую историю.

Изменение последнего коммита с помощью git commit --amend

Легко забыть добавить файл в индекс, допустить опечатку в сообщении коммита или закоммитить слишком рано. Вместо того чтобы создавать отдельный коммит с исправлением, git commit --amend позволяет заменить последний коммит исправленным.

Чтобы изменить только сообщение последнего коммита:

git commit --amend -m "Add login validation"

Чтобы добавить забытый файл к последнему коммиту, сначала проиндексируйте его, а затем выполните amend без изменения сообщения:

git add forgotten-file.js
git commit --amend --no-edit

Флаг --no-edit сохраняет существующее сообщение и повторно использует его. За кулисами amend не редактирует старый коммит на месте — он создаёт совершенно новый коммит (с новым хешем) и перемещает ветку, чтобы она указывала на него. Исходный коммит остаётся позади, на него никто не ссылается.

Поскольку amend создаёт новый хеш, действует то же золотое правило: изменяйте только те коммиты, которые вы не отправляли в ветку, используемую совместно с другими.

Полное руководство см. в разделе git commit --amend.

Восстановление потерянных коммитов с помощью git reflog

Reflog (журнал ссылок) фиксирует каждое изменение верхушки ваших веток и HEAD — каждый коммит, переключение, сброс, слияние и rebase. Это ваша страховочная сеть: даже после перезаписи или жёсткого сброса старые коммиты по-прежнему хранятся в репозитории, и reflog помнит, где они были.

Просмотрите журнал командой:

git reflog

Типичный вывод выглядит так:

a1b2c3d HEAD@{0}: commit: Add login validation
9f8e7d6 HEAD@{1}: reset: moving to HEAD~1
4c3b2a1 HEAD@{2}: commit: Work in progress

Каждая запись содержит хеш коммита, ссылку HEAD@{n} (сколько перемещений назад) и описание произошедшего. Если вы случайно сбросили коммит, найдите его в reflog и верните:

git reset --hard HEAD@{2}

Команда git reflog также имеет подкоманды: git reflog show, git reflog expire и git reflog delete. Учтите, что записи reflog хранятся только локально в вашем репозитории и со временем истекают (90 дней по умолчанию для достижимых записей), поэтому reflog — инструмент восстановления, а не постоянная резервная копия. Подробнее см. в разделах git reflog и git reset.

Перебазирование на новую основу с помощью git rebase

git rebase берёт серию коммитов и воспроизводит их поверх другого базового коммита. Главное преимущество — линейная, читаемая история: вместо коммита слияния, связывающего две ветки, ваша работа выглядит как аккуратная линия коммитов.

Существует два режима:

  • Стандартный режим применяет коммиты текущей ветки поверх верхушки другой ветки. Это альтернатива слиянию при интеграции обновлений из main:
git rebase main
  • Интерактивный режим позволяет переупорядочивать, редактировать, объединять или удалять коммиты в процессе воспроизведения. Добавьте флаг -i и укажите коммит, поверх которого нужно выполнить rebase:
git rebase -i <base-branch>

Распространённая интерактивная цель — «последние N коммитов», например последние три:

git rebase -i HEAD~3

Подробнее см. в разделах git rebase и git rebase interactive.

Редактирование коммитов во время интерактивного rebase

При запуске интерактивного rebase Git открывает редактор со списком выбранных коммитов, каждый из которых предваряется действием. Вы меняете ключевое слово действия, чтобы управлять тем, что произойдёт:

  • pick — оставить коммит как есть (по умолчанию).
  • reword (r) — приостановить выполнение для редактирования сообщения этого коммита.
  • squash (s) — объединить этот коммит с предыдущим, совместив их сообщения в редакторе.
  • fixup (f) — как squash, но отбросить сообщение этого коммита и оставить только сообщение предыдущего (без запроса редактора).
  • edit (e) — приостановить выполнение, чтобы вы могли изменить содержимое коммита.
  • drop (d) — полностью удалить коммит.

Типичный список задач rebase выглядит так:

pick a1b2c3d Add login form
squash 9f8e7d6 Fix typo in label
fixup 4c3b2a1 Adjust spacing

Здесь второй и третий коммиты объединяются в первый, и в результате остаётся единственный аккуратный коммит.

Почему переписанные коммиты получают новые идентификаторы

Rebase не перемещает исходные коммиты — он создаёт новые коммиты с теми же изменениями, но другими родителями, а значит, другими хешами. Старые коммиты становятся недостижимыми (но их ещё можно восстановить через reflog в течение некоторого времени). Даже коммиты, помеченные как pick, получают новые идентификаторы, если любой предшествующий им в последовательности коммит был изменён, — ведь их родитель изменился.

Объединение коммитов в процессе интерактивного rebase

Именно поэтому золотое правило так важно: новые хеши означают расхождение истории для всех, у кого были старые коммиты.

Практика

Практика
Какие из следующих утверждений верны относительно механизмов перезаписи истории в Git?
Какие из следующих утверждений верны относительно механизмов перезаписи истории в Git?
Was this page helpful?