<style>.lazy{display:none}</style>Переписывание истории в Git

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

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

Содержание

Введение

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

В Git существует несколько механизмов для хранения истории и сохранения изменений. В их число входят такие команды, как git commit --amendgit 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».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *