Как отменить и удалить конкретный коммит Git из текущей ветки

GitBeginner
Практиковаться сейчас

Введение

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

Просмотр истории коммитов

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

Начнем с просмотра истории коммитов. Мы будем использовать флаг --oneline для отображения каждого коммита в одной строке и --graph для отображения истории коммитов в виде графа, что полезно для визуализации веток и слияний.

Выполните следующую команду в терминале:

git log --oneline --graph

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

* 7d3d24a (HEAD -> master) chore: Add last-commit file
* 8a9f1b3 docs: Update documentation in file1
* c2e4d6f fix: Add a temporary file that should be removed
* 5e1a3b2 feat: Add file2.txt
* 1b4c0a9 feat: Add file1.txt

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

Уделите немного времени, чтобы просмотреть историю. Вы будете манипулировать этими коммитами на следующих шагах.

Отмена последнего коммита с помощью git reset

Иногда вы делаете коммит и сразу же понимаете, что допустили ошибку. Команда git reset идеально подходит для такого сценария. Она может переместить указатель HEAD текущей ветки на предыдущий коммит, эффективно "отменяя" один или несколько коммитов.

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

Отменим последний коммит "chore: Add last-commit file".

git reset --soft HEAD~1

Здесь HEAD~1 относится к коммиту непосредственно перед текущим HEAD.

Теперь проверьте статус вашего репозитория:

git status

Вы увидите, что last-commit.txt теперь указан в разделе "Changes to be committed" (Изменения для коммита). Это означает, что сам коммит был отменен, но изменения в файле остались подготовленными.

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   last-commit.txt

Наконец, просмотрите лог еще раз, чтобы убедиться, что коммит удален из истории:

git log --oneline --graph
* 8a9f1b3 (HEAD -> master) docs: Update documentation in file1
* c2e4d6f fix: Add a temporary file that should be removed
* 5e1a3b2 feat: Add file2.txt
* 1b4c0a9 feat: Add file1.txt

Как вы можете видеть, последний коммит был удален из лога, но ваша работа безопасно ожидает в области подготовленных файлов.

Отмена коммита с помощью git revert

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

Сначала очистим рабочую директорию, повторно закоммитив изменения из предыдущего шага.

git commit -m "chore: Add last-commit file again"

Теперь предположим, мы хотим отменить коммит, который добавил file2.txt. Этот коммит находится дальше в нашей истории. Мы отменим его. Мы можем сослаться на него по его позиции относительно HEAD. Глядя на лог из Шага 1, "feat: Add file2.txt" является четвертым коммитом сверху, поэтому мы можем сослаться на него как на HEAD~3.

Выполните команду revert:

git revert HEAD~3

Это откроет ваш редактор текста по умолчанию (по умолчанию vim) с предварительно заполненным сообщением коммита, например "Revert 'feat: Add file2.txt'". Вы можете просто сохранить и закрыть файл, чтобы принять сообщение по умолчанию.

В vim введите :wq и нажмите Enter, чтобы сохранить и выйти.

Теперь снова проверьте лог:

git log --oneline --graph

Вы увидите новый коммит наверху: "Revert 'feat: Add file2.txt'".

* 1e2d3f4 (HEAD -> master) Revert "feat: Add file2.txt"
* 7d3d24a chore: Add last-commit file again
* 8a9f1b3 docs: Update documentation in file1
* c2e4d6f fix: Add a temporary file that should be removed
* 5e1a3b2 feat: Add file2.txt
* 1b4c0a9 feat: Add file1.txt

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

ls

Вы заметите, что file2.txt больше не присутствует.

Удаление коммита с помощью интерактивного rebase

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

Наша цель — удалить коммит с сообщением "fix: Add a temporary file that should be removed". Глядя на текущий лог, этот коммит теперь является HEAD~3.

Запустите процесс интерактивного rebase:

git rebase -i HEAD~4

Эта команда откроет ваш текстовый редактор (по умолчанию vim) со списком последних 4 коммитов.

pick fd5a181 fix: Add a temporary file that should be removed
pick 5f27a4d docs: Update documentation in file1
pick 284be6f chore: Add last-commit file again
pick 8a460c5 Revert "feat: Add file2.txt"

## Rebase 9403080..8a460c5 onto 9403080 (4 commands)
#
## Commands:
## p, pick <commit> = use commit
## d, drop <commit> = remove commit
## ...

Чтобы удалить коммит, измените слово pick на drop (или d) для строки, содержащей "fix: Add a temporary file that should be removed".

Измените это:

pick fd5a181 fix: Add a temporary file that should be removed

На это:

drop fd5a181 fix: Add a temporary file that should be removed

В vim нажмите i, чтобы войти в режим вставки, внесите изменения, затем нажмите Esc, чтобы выйти из режима вставки. Сохраните и выйдите, набрав :wq и нажав Enter. Git переиграет оставшиеся коммиты поверх новой истории.

Проверьте лог, чтобы увидеть результат:

git log --oneline --graph

"Плохой" коммит теперь удален из истории.

* a5b4c3d (HEAD -> master) chore: Add last-commit file again
* f9e8d7c docs: Update documentation in file1
* 1e2d3f4 Revert "feat: Add file2.txt"
* 5e1a3b2 feat: Add file2.txt
* 1b4c0a9 feat: Add file1.txt

Также проверьте файлы в директории. Файл bad-commit-file.txt удален.

ls

Восстановление удаленного коммита с помощью git reflog

Что делать, если вы случайно удалили коммит? Git ведет запись всех изменений указателя HEAD в специальном журнале, называемом reflog. Это ваша страховка. Вы можете использовать его для поиска и восстановления потерянных коммитов.

Просмотрим reflog, чтобы найти историю, которую мы только что переписали.

git reflog

Вы увидите список выполненных вами действий. Найдите строку, которая гласит rebase (finish): returning to refs/heads/master. Запись непосредственно под ней, скорее всего, HEAD@{1}, представляет состояние вашей ветки до rebase.

a5b4c3d (HEAD -> master) HEAD@{0}: rebase (finish): returning to refs/heads/master
1e2d3f4 HEAD@{1}: rebase (start): checkout HEAD~3
...

Мы можем восстановить нашу ветку до этого предыдущего состояния с помощью git reset --hard. Эта команда является разрушительной и отбросит любые несохраненные изменения, поэтому используйте ее осторожно.

Сбросим нашу ветку до состояния перед rebase, которое в данном случае является HEAD@{1}.

git reset --hard HEAD@{1}

Вы увидите сообщение, подтверждающее, что HEAD теперь находится в состоянии до начала rebase.

HEAD is now at 1e2d3f4 Revert "feat: Add file2.txt"

Теперь в последний раз проверьте лог:

git log --oneline --graph

Вы должны увидеть историю такой, какой она была до rebase:

* f461400 (HEAD -> master) Revert "feat: Add file2.txt"
* acea45c chore: Add last-commit file again
* c04b3f5 docs: Update documentation in file1
* 9403080 feat: Add file2.txt
* ee39412 feat: Add file1.txt

Обратите внимание, что коммит "fix: Add a temporary file that should be removed" по-прежнему отсутствует — это правильно! Команда git reset --hard HEAD@{1} вернула нас в состояние до начала интерактивного rebase, а не до удаления коммита. Интерактивный rebase успешно удалил этот нежелательный коммит из нашей истории. Вы можете выполнить ls, чтобы убедиться, что bad-commit-file.txt по-прежнему отсутствует. reflog — это бесценный инструмент для восстановления после ошибок в Git.

Резюме

В этой лабораторной работе вы освоили практические методы управления историей коммитов Git. Вы начали с изучения истории с помощью git log. Затем вы отработали отмену коммитов с помощью git reset --soft, безопасное возвращение изменений с помощью git revert и удаление конкретных коммитов с помощью мощной команды git rebase -i. Наконец, вы узнали, как использовать git reflog в качестве страховочной сетки для восстановления коммитов, удаленных из истории. Эти навыки необходимы для поддержания чистой и управляемой истории проекта.