В этой статье мы рассмотрим различные способы перезаписи и изменения истории в Git. Обсудим сильные и слабые стороны этих способов и приведем примеры того, как с ними работать. Также в этом материале мы покажем вам некоторые из наиболее распространенных причин перезаписи состояний кода и объясним, как избегать ошибок при таких операциях.
Содержание
- Введение
- Изменение последнего коммита
- Изменение старых или нескольких коммитов
- Страховка: git reflog
Введение
Основная задача Git – дать уверенность, что вы никогда не потеряете зафиксированные изменения. Но эта система также служит для того, чтобы предоставлять полный контроль над процессом разработки владельцу репозитория. Это включает в себя возможность точно определить, как будет выглядит история вашего проекта, но одновременно с этим появляется вероятность потери части изменений (коммитов). Git предоставляет команды для переписывания истории, но предупреждает, что использование этих команд может привести к потере содержимого.
В Git существует несколько механизмов для хранения истории и сохранения изменений. В их число входят такие команды, как git commit --amend
, git rebase
и git reflog
. Это мощные инструменты для настройки рабочего процесса.
Скачать одну из самых популярных книг по тестированию "Как тестируют в Google"
Изменение последнего коммита
Команда git commit --amend
– это удобный способ изменить последний коммит. Она позволяет объединить проиндексированные изменения с предыдущим коммитом вместо того, чтобы создавать еще один коммит. Также команду можно использовать для редактирования комментария к предыдущему коммиту без изменения кода в нем.
Но такое изменение не просто изменяет последний коммит, а полностью заменяет его. То есть изменённый коммит станет новой сущностью со своей собственной ссылкой. Для Git он будет выглядеть как совершенно новый коммит, что показано звездочкой (*) на схеме. Существует несколько распространенных сценариев использования git commit --amend
. В следующих разделах мы их рассмотрим.
Изменение комментария к последнему коммиту Git
git commit --amend
Допустим, вы только что выполнили коммит и допустили ошибку в комментарии к нему. Выполнение этой команды в отсутствие проиндексированных файлов позволяет отредактировать комментарий к предыдущему коммиту без изменения состояния кода.
В процессе разработки регулярно случаются случайные коммиты. Очень просто забыть проиндексировать файл или использовать неправильный формат комментария к коммиту. Флаг --amend
позволяет удобно исправить эти небольшие ошибки.
git commit --amend -m "an updated commit message"
Добавление аргумента -m
позволит передавать новый комментарий прямо из командной строки, без открытия текстового редактора.
Изменение файлов после коммита
В следующем примере рассмотрен распространенный сценарий разработки с использованием Git. Допустим, вы отредактировали несколько файлов и хотите все эти изменения внести в один коммит. Но, уже выполнив git commit
, вы заметили, что забыли добавить один из необходимых файлов. Для того чтобы исправить эту ошибку, достаточно проиндексировать забытый файл и выполнить коммит с флагом --amend
:
# Edit hello.py and main.py git add hello.py git commit # Realize you forgot to add the changes from main.py git add main.py git commit --amend --no-edit
Флаг --no-edit
позволит вам внести изменения в коммит без изменения комментария к нему. Новый коммит заменит предыдущий, и это будет выглядеть так, будто все изменения в файлах hello.py
и main.py
были сделаны за один коммит.
Не используйте --amend
для публичных коммитов
Изменённые коммиты по своей сути являются совершенно новыми коммитами, поэтому предыдущие коммиты не будут храниться в вашей текущей ветке. Последствия этой операции аналогичны сбросу (reset
) публичного состояния кода. Не изменяйте коммит, после которого уже начали работу другие разработчики. Такая ситуация только запутает разработчиков, и разрешить ее будет сложно.
Резюме
Если коротко, команда git commit --amend
позволяет добавить новые проиндексированные изменения в последний коммит. С помощью этой команды можно добавлять изменения в индекс Git или удалять их из него. Если никаких изменений не внесено, но при коммите вы использовали --amend
, то вам все равно будет предложено изменить комментарий к последнему коммиту. Будьте аккуратны при использовании флага --amend
с коммитами, доступными другим членам команды. Изменение такого коммита может привести к путанице и длительным устранениям конфликтов при слиянии.
Изменение старых или нескольких коммитов
Чтобы изменить старый коммит или сразу несколько коммитов, пригодится команда git rebase
, которая объединит несколько коммитов в новый базовый коммит. В стандартном режиме команда git rebase
позволяет в буквальном смысле перезаписать историю: она автоматически применяет коммиты в текущей рабочей ветке к указателю “head” переданной ветки. Поскольку новые коммиты заменяют старые, команду git rebase
запрещено применять к коммитам, которые уже были опубликованы. Иначе история вашего проекта просто исчезнет.
В таких и подобных случаях, когда важно сохранить чистую историю проекта, добавление аргумента -i
к команде git rebase
позволит запустить интерактивную операцию rebase
(rebase interactive
). Это даст вам возможность изменять в процессе только отдельные коммиты, а не заменять все существующие.
Для более подробного изучения
и других дополнительных команд rebase interactive
rebase
рекомендуем посетить страницу git rebase.
Изменение зафиксированных файлов
Во время операции rebase
команда редактирования (e
) остановит процесс на указанном коммите и позволит вам внести дополнительные изменения с помощью команды git commit --amend
. Git прервет работу и выведет следующее сообщение:
Stopped at 5d025d1... formatting You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
Несколько комментариев
Каждый стандартный коммит в Git содержит комментарий, поясняющий, что именно было изменено в коммите. Комментарии дают наглядное представление об истории проекта. Во время операции rebase
можно выполнить несколько команд для изменения комментариев к коммитам.
Склеивание коммитов для поддержания чистоты в истории проекта
Команда склеивания (s
) позволяет в полной мере понять смысл rebase
. При помощи склеивания можно указать коммиты, которые нужно присоединить к предыдущим. Таким образом создается «чистая история». Во время перемещения Git будет исполнять указанную команду rebase
для каждого коммита. При склеивании коммитов Git откроет текстовый редактор и предложит объединить комментарии к указанным коммитам. Этот процесс можно показать следующим образом:
Обратите внимание, что ID коммитов, измененных с помощью команды rebase
, отличаются от ID каждого из начальных коммитов. Коммиты со звездочкой (*) получат новый идентификатор, если предыдущие коммиты были переписаны.
Современные решения для хостинга Git (например, Bitbucket) предлагают возможности «автосклеивания» при слиянии. Эти возможности позволяют автоматически выполнять rebase
и склеивать коммиты при мерже ветки.
Дополнительную информацию по склеиванию коммитов можно посмотреть на странице “Склеивание коммитов при слиянии ветки Git в Bitbucket“.
Резюме
Команда git rebase
позволяет изменять историю проекта, а с помощью интерактивного выполнения rebase можно «подчистить» следы за собой. Теперь вы можете не бояться совершать и исправлять ошибки, полируя свою работу и сохраняя чистую, линейную историю проекта.
Страховка: git reflog
Справочные журналы (reflog
) – это механизм, используемый в Git для записи обновлений, применяемых к веткам и другим ссылкам на коммиты. git reflog
позволяет вернуться к коммитам, даже если на них не ссылается ни одна ветка или метка.
После перезаписи истории reflog
содержит информацию о старом состоянии веток и позволяет при необходимости вернуться к этому состоянию. Каждый раз при обновлении ветки любым способом (переключение веток, загрузка новых изменений, перезапись истории или просто добавление новых коммитов) в reflog добавляется новая запись.
Использование
Вывести журнал (reflog) локального репозитория:
git reflog
Вывести журнал с относительными датами (например, 2 недели назад).
git reflog --relative-date
Пример
Давайте рассмотрим стандартные ситуации, когда используется команда git reflog
.
0a2e358 HEAD@{0}: reset: moving to HEAD~2 0254ea7 HEAD@{1}: checkout: moving from 2.2 to main c10f740 HEAD@{2}: checkout: moving from main to 2.2
В этом примере показан переход из главной ветки в ветку 2.2 и обратно. Отсюда можно выполнить жесткий сброс к одному из старых коммитов. Последнее действие указано в верхней строчке с пометкой HEAD@{0}
.
Если вы случайно переместитесь назад, журнал будет содержать главный коммит, указывающий на (0254ea7)
до случайного удаления вами 2-х коммитов.
git reset --hard 0254ea7
Командой git reset
можно вернуть главную ветку к более раннему коммиту. Это страховка на случай непреднамеренного изменения истории.
Необходимо отметить, что git reflog
только предоставляет страховку на тот случай, когда изменения попали в коммит в локальном репозитории. В нем отслеживаются только перемещения концов веток репозитория. Кроме того, записи в журнале имеют определенный срок хранения. По умолчанию этот срок составляет 90 дней.
Дополнительную информацию по принципам работы команды и сценариям её использования смотрите на странице git reflog.
Итоги
В этой статье мы рассмотрели несколько способов изменения истории Git и отмены изменений. Мы вкратце разобрали процесс работы команды git rebase
. Основные выводы:
- Существует несколько способов переписать историю в Git
- Используйте команду
git commit --amend
-m
для изменения последнего комментария - Используйте команду
git commit --amend
, чтобы внести изменения в последний коммит - Используйте команду
git rebase
для объединения коммитов и изменения истории ветки - Команда
git rebase -i
позволяет осуществлять более точечный контроль над изменениями истории, чем обычный вариантgit rebase
.
Перевод статьи «Rewriting history».