Переписывание истории

Переписывание истории

Введение

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

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

В Git существует несколько механизмов хранения истории и сохранения изменений. Вот эти механизмы: commit --amend, git rebase и git reflog. Это мощные инструменты для настройки рабочего процесса. По окончании работы с этим обучающим материалом вы будете знать команды, позволяющие реструктурировать коммиты Git, и сможете избежать проблем, с которыми приходится сталкиваться при перезаписи истории.

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

Команда git commit --amend — это удобный способ изменить последний коммит. Она позволяет объединить проиндексированные изменения с предыдущим коммитом без создания нового коммита. Ее также можно использовать для простого редактирования комментария к предыдущему коммиту без изменения состояния кода в нем. Но такое изменение приводит не только к редактированию последнего коммита, но и к его полной замене. Это означает, что измененный коммит станет новой сущностью с отдельной ссылкой. Для Git он будет выглядеть как новый коммит — на схеме ниже он отмечен звездочкой (*). Существует несколько распространенных сценариев использования команды git commit --amend. В следующих разделах мы приведем такие примеры.

Git commit amend

Изменение комментария к последнему коммиту Git

git commit --amend

Предположим, при выполнении коммита вы допустили ошибку в комментарии к нему. Выполнение этой команды в отсутствие проиндексированных файлов позволяет отредактировать комментарий к предыдущему коммиту без изменения состояния кода.

В процессе разработки регулярно случаются преждевременные коммиты. Очень просто забыть проиндексировать файл или использовать неправильный формат комментария к коммиту. Флаг --amend позволяет без труда исправить эти небольшие ошибки.

git commit --amend -m "обновленный комментарий к коммиту"

Добавление опции -m позволяет передать новый комментарий из командной строки, не открывая текстовый редактор.

Изменение файлов после коммита

В следующем примере показан распространенный сценарий разработки с использованием Git. Допустим, вы отредактировали несколько файлов и хотите сделать коммит за одну операцию. Но потом выясняется, что вы забыли добавить один из файлов в самом начале. Чтобы исправить эту ошибку, достаточно проиндексировать другой файл и выполнить коммит с флагом --amend:

# Отредактируйте файлы hello.py и main.py git add hello.py git commit
# Вспомните, что вы забыли добавить изменения из файла main.py git add main.py
git commit --amend --no-edit

Флаг --no-edit позволяет внести изменения в коммит без изменения комментария к нему. Итоговый коммит заменит неполный коммит. При этом все будет выглядеть так, словно изменения в файлах hello.py и main.py были сделаны за один коммит.

Не используйте amend для публичных коммитов

Измененные коммиты по сути представляют собой новые коммиты. При этом предыдущий коммит не остается в текущей ветке. Последствия этой операции аналогичны сбросу (reset) публичного состояния кода. Не изменяйте коммит, после которого уже начали работу другие разработчики. Эта ситуация только запутает разработчиков, и разрешить ее будет непросто.

Обзор

Если коротко, команда git commit --amend позволяет добавить новые проиндексированные изменения в последний коммит. С помощью коммита --amend можно добавлять изменения в индекс Git или удалять таковые из него. Если никаких изменений не проиндексировано, при использовании флага --amend вам все равно будет предложено изменить комментарий к последнему коммиту. Будьте осторожны при использовании флага --amend с коммитами, доступными другим членам команды. Изменение коммита, доступного другому пользователю, может привести к путанице и длительным устранениям конфликтов при слиянии.

Изменение старых или нескольких коммитов

Для изменения старых или нескольких коммитов используйте команду git rebase, чтобы объединить несколько коммитов в новый базовый коммит. В стандартном режиме команда git rebase позволяет в буквальном смысле перезаписать историю: она автоматически применяет коммиты в текущей рабочей ветке к указателю head переданной ветки. Поскольку новые коммиты заменяют старые, команду git rebase запрещено применять к коммитам, которые стали доступны публично, иначе история проекта исчезнет.

В таких или подобных случаях, когда важно сохранить чистую историю проекта, добавление опции -i к команде git rebase позволяет выполнить интерактивную операцию rebase interactive. Это дает возможность изменить отдельные коммиты в процессе вместо перемещения всех коммитов. Подробную информацию об интерактивной операции rebase и дополнительных командах перебазирования см. на странице git rebase.

Изменение файлов после коммита

Во время операции rebase команда редактирования (edit, илиe) остановит процесс на указанном коммите и позволит вам внести дополнительные изменения с помощью команды git commit --amend. Git прервет работу и выведет следующее сообщение:

Stopped at 5d025d1... formatting
Теперь можно исправить коммит с помощью
git commit --amend
Внеся изменения, выполните команду
git rebase --continue

Несколько комментариев

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

  • Опция reword, или r, остановит операцию rebase и позволит переписать отдельный комментарий к коммиту.
  • Опция squash, или s, приостановит операцию rebase для любых коммитов, отмеченных символом s, и вам будет предложено ввести один общий комментарий вместо нескольких отдельных. Подробнее об этом можно узнать в разделе о склеивании коммитов ниже.
  • Опция fixup, или f, позволяет объединить комментарии, как и squash. При этом, в отличие от squash, опция fixup не прерывает операцию rebase с открытием редактора для объединения комментариев к коммитам. В коммитах, отмеченных символом f, комментарии будут сброшены и заменены комментарием предыдущего коммита.

Склеивайте коммиты для поддержания чистой истории

Команда склеивания (s) позволяет в полной мере понять смысл rebase. Склеивание дает возможность указать коммиты, которые нужно объединить в предыдущие коммиты. Таким образом создается «чистая история». Во время перебазирования Git будет исполнять указанную команду rebase для каждого коммита. В случае склеенных коммитов Git откроет выбранный текстовый редактор и предложит объединить комментарии к указанным коммитам. Этот процесс можно представить следующим образом:

Обучающие материалы по Git: пример команды git rebase -i

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

Современные решения для хостинга Git (например, Bitbucket) предоставляют возможности «автосклеивания» при слиянии. Это позволяет автоматически выполнять rebase и склеивать коммиты ветки при использовании интерфейса решений для хостинга. Дополнительную информацию см. в разделе «Склеивание коммитов при слиянии ветки Git в Bitbucket».

Обзор

Команда git rebase позволяет изменять историю, а выполнение интерактивной операции rebase «подчищает» за вами следы. Теперь вы можете совершать и исправлять ошибки, оттачивая свою работу и сохраняя чистую, линейную историю проекта.

Страховка: git reflog

Журналы ссылок (reflog) — это механизм, который используется в Git для регистрации обновлений, применяемых к концам веток и другим ссылкам на коммиты. Reflog позволяет вернуться к коммитам, даже если на них нет ссылок из какой-либо ветки или метки. После перезаписи истории reflog содержит информацию о прежнем состоянии веток и позволяет при необходимости вернуться к этому состоянию. Каждый раз при обновлении конца ветки любым способом (переключение веток, загрузка новых изменений, перезапись истории или просто добавление новых коммитов) в reflog добавляется новая запись. В этом разделе мы рассмотрим команду git reflog и некоторые стандартные примеры ее использования.

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

git reflog

Отображает reflog локального репозитория.

git reflog --relative-date

Отображает reflog с относительными датами (например, 2 недели назад).

Пример

Чтобы понять команду git reflog, давайте разберем пример.

0a2e358 HEAD@{0}: reset: moving to HEAD~2
0254ea7 HEAD@{1}: checkout: moving from 2.2 to master
c10f740 HEAD@{2}: checkout: moving from master to 2.2

В вышеприведенной команде reflog показан переход из ветки master в ветку 2.2 и обратно. Отсюда можно выполнить жесткий сброс к старому коммиту. Последнее действие указано в верхней строчке с пометкой HEAD@{0}.

Если вы случайно переместитесь назад, reflog будет содержать главный коммит, указывающий на (0254ea7) до случайного удаления вами 2 коммитов.

git reset --hard 0254ea7

При использовании команды git reset можно вернуть главную ветку к более раннему коммиту. Это страховка на случай непреднамеренного изменения истории.

Важно отметить, что reflog обеспечивает страховку только на тот случай, когда изменения попали в коммит в локальном репозитории и в нем отслеживаются только перемещения концов веток репозитория. Кроме того, записи reflog имеют определенный срок хранения. По умолчанию этот срок составляет 90 дней.

Дополнительную информацию см. на странице git reflog.

Резюме

В этой статье мы обсудили несколько способов изменения истории и отмены изменений в Git. Мы вкратце рассмотрели процесс git rebase. Вот основные заключения.

  • Существует несколько способов переписать историю в Git.
  • Используйте команду git commit --amend для изменения своего последнего сообщения в журнале.
  • Используйте команду git commit --amend для внесения изменений в самый последний коммит.
  • Используйте команду git rebase для объединения коммитов и изменения истории ветки.
  • Команда git rebase -i обеспечивает более точный контроль над изменениями истории, чем обычный вариант git rebase.

Узнайте больше об описанных командах на соответствующих страницах: