Отмена коммитов и изменений

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

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

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

Друзья, поддержите нас вступлением в наш телеграм канал QaRocks. Там много туториалов, задач по автоматизации и книг по QA.

Просмотр старых коммитов

Суть любой системы контроля версий заключается в хранении “безопасных” копий проекта, чтобы вы никогда не беспокоились о том, что можете непоправимо испортить свою кодовую базу. Когда в проекте сохранена история, можно повторно оценивать и анализировать любые предыдущие коммиты. Один из лучших инструментов для просмотра истории репозитория Git — команда git log. В примере ниже мы используем git log для получения списка последних коммитов популярной графической библиотеки с открытым исходным кодом.

git log --oneline
e2f9a78fe Replaced FlyControls with OrbitControls
d35ce0178 Editor: Shortcuts panel Safari support.
9dbe8d0cf Editor: Sidebar.Controls to Sidebar.Settings.Shortcuts. Clean up.
05c5288fc Merge pull request #12612 from TyLindberg/editor-controls-panel
0d8b6e74b Merge pull request #12805 from harto/patch-1
23b20c22e Merge pull request #12801 from gam0022/improve-raymarching-example-v2
fe78029f1 Fix typo in documentation
7ce43c448 Merge pull request #12794 from WestLangley/dev-x
17452bb93 Merge pull request #12778 from OndrejSpanel/unitTestFixes
b5c1b5c70 Merge pull request #12799 from dhritzkiv/patch-21
1b48ff4d2 Updated builds.
88adbcdf6 WebVRManager: Clean up.
2720fbb08 Merge pull request #12803 from dmarcos/parentPoseObject
9ed629301 Check parent of poseObject instead of camera
219f3eb13 Update GLTFLoader.js
15f13bb3c Update GLTFLoader.js
6d9c22a3b Update uniforms only when onWindowResize
881b25b58 Update ProjectionMatrix on change aspect

Каждый коммит имеет уникальный идентификационный хэш SHA-1. Эти идентификаторы используются для перемещения по таймлайну коммитов и их повторного просмотра. По умолчанию git log будет показывать коммиты только для текущей выбранной ветки. Но вполне возможно, что нужный вам коммит находится в другой ветке. Для просмотра всех коммитов во всех ветках используется команда git log --branches=*. Команда git branch используется для просмотра и переключения на другие ветки. Так, с помощью сочетания git branch -a вам будет возвращен список имен всех известных веток. Просмотреть журнал коммитов одной из этих веток можно с помощью команды git log <имя_ветки>.

После того как вы нашли ссылку на нужный коммит в истории, вы можете использовать команду git checkout, чтобы переключиться на него. Git checkout – это простой способ “загрузить” любой из снимков состояния на компьютер разработчика. Во время разработки указатель HEAD обычно указывает на главную ветку main или другую локальную ветку. Однако, когда вы переключаетесь на более ранний коммит, HEAD больше не указывает на ветку — он напрямую связан с коммитом. Таким образом, HEAD становится “открепленным” от ветки, и это можно представить так:

git checkout на более ранний коммит

Переключение на предыдущую версию файла не перемещает указатель HEAD. Он остаётся на той же ветке и на том же коммите, что позволяет избежать открепления указателя HEAD. После этого можно выполнить коммит старой версии файла в новый снимок состояния, также, как и в случае других изменений. Соответственно, такое использование команды git checkout применительно к файлу позволяет откатиться к старой версии отдельного файла. Для получения дополнительной информации об этих двух режимах посетите страницу команды git checkout.

Просмотр старых версий

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

git log --oneline

Допустим, история вашего проекта выглядит следующим образом:

b7119f2 Continue doing crazy things
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt
9773e52 Initial import

Вы можете использовать git checkout для просмотра коммита “Make some important changes to hello.txt” следующим образом:

git checkout a1e8fb5

Таким образом, ваш рабочий каталог будет точно соответствовать состоянию коммита a1e8fb5. Вы можете просматривать файлы, компилировать проект, запускать тесты и даже редактировать файлы, не беспокоясь о потере текущего состояния проекта. Все, что вы здесь сделаете, не будет сохранено в вашем репозитории. Чтобы продолжить разработку, вам нужно будет вернуться к текущему состоянию проекта:

git checkout main

Предполагается, что вы ведете разработку в ветке main. При каждом возвращении в ветку main можно использовать команды git revert или git reset, чтобы отменить ненужные изменения.

Отмена коммита

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

git log --oneline
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt
9773e52 Initial import

Займемся отменой коммита 872fa7e Try something crazy.

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

С помощью команды git checkout мы можем перейти к предыдущему коммиту a1e8fb5, и вернуть репозиторий в состояние, предшествовавшее нашему экспериментальному коммиту. Переход к отдельному коммиту переводит репозиторий в состояние открепленного указателя HEAD. Это означает, что вы больше не работаете ни над одной веткой. При открепленном указателе HEAD все новые коммиты, которые вы создадите, не будут связаны с какой-либо веткой, пока вы не вернете ветки в положенное состояние. Такие коммиты без родителя подлежат удалению сборщиком мусора Git’а. Сборщик мусора запускается с заданным интервалом и навсегда уничтожает такие коммиты. Чтобы предотвратить удаление коммитов, нам нужно убедиться, что мы ведем работу в ветке.

Из состояния открепленного указателя HEAD мы можем выполнить команду git checkout -b new_branch_without_crazy_commit. Это создаст новую ветку с именем new_branch_without_crazy_commit и совершит переход в это состояние. Теперь репозиторий находится в новом таймлайне истории, где коммита 872fa7e больше не существует. На этом этапе мы можем продолжить работу в новой ветке, в которой коммита 872fa7e не существует, и его можно считать “отмененным”. К сожалению, если вам будет нужна предыдущая ветка (возможно, это была ваша основная ветка main), такой метод отмены не подойдет. Давайте рассмотрим другие стратегии.

Отмена публичного коммита с помощью git revert

Давайте вернемся к нашему первому примеру истории коммитов. К истории, где есть коммит 872fa7e. На этот раз давайте попробуем отменить коммит с помощью git revert. Если мы выполним команду git revert HEAD, Git создаст новый коммит с обратными изменениями, отменяющими последний коммит.  Это добавит новый коммит в текущую историю ветки, и теперь она будет выглядеть так:

git log --oneline
e2f9a78 Revert "Try something crazy"
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt
9773e52 Initial import

На данном этапе мы снова технически “отменили” коммит 872fa7e. Хотя запись 872fa7e по-прежнему существует в истории, новый коммит e2f9a78 отменил изменения 872fa7e. В отличие от нашей предыдущей стратегии отмены с помощью checkout, мы можем продолжать работать в той же ветке. Этот метод отмены является наилучшим для работы в общих репозиториях. Однако, если у вас есть требование поддерживать минималистичную историю в Git, такая стратегия может не подойти.

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

Рассмотрение этой стратегии отмены мы продолжим на том же примере. Команда git reset имеет множество применений и функций. Если мы выполним команду git reset --hard a1e8fb5, то история коммитов сбрасывается до указанного коммита. Теперь, при просмотре истории с помощью git log отобразится следующее:

git log --oneline
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt
9773e52 Initial import

Мы видим, что коммиты e2f9a78 и 872fa7e больше не существуют в истории. На этом этапе мы можем продолжить работу и создавать новые коммиты, а экспериментальных коммитов будто и не было. Такой метод отмены изменений оставляет историю максимально чистой. Отмена коммитов с помощью команды reset отлично подходит для локальных изменений, но при работе с общим удалённым репозиторием создаёт дополнительные сложности. Если у нас есть общий репозиторий, в который уже был выложен коммит 872fa7e, и мы попытаемся выполнить команду git push для ветки, в которой мы сбросили историю, Git обнаружит это и выдаст ошибку. Git будет считать, что публикуемая ветка не была обновлена, так как в ней отсутствуют коммиты. В таких сценариях для отмены изменений следует отдавать предпочтение команде git revert.

Отмена последнего коммита

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

Отмена незакоммиченных изменений

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

Рабочий каталог

Рабочий каталог обычно синхронизируется с локальной файловой системой. Чтобы отменить изменения в рабочем каталоге, можно просто отредактировать файлы в привычном вам редакторе. В Git есть несколько утилит, которые помогают управлять рабочим каталогом. Есть команда git clean, которая является удобной утилитой для отмены изменений в рабочем каталоге. Кроме того, команда git reset может быть вызвана с параметрами --mixed или --hard, чтобы сбросить изменения в рабочем каталоге.

Индекс

Команда git add используется для добавления изменений в раздел проиндексированных файлов (индекс). Git reset предназначена главным образом для отмены изменений в данном разделе. А команда git reset --mixed переместит все ожидающие изменения из индекса обратно в рабочий каталог.

Отмена публичных изменений

При совместной работе в удаленных репозиториях необходимо уделить особое внимание отмене изменений. Команда git reset обычно рассматривается как “локальный” метод отмены. Её следует использовать при отмене изменений в личной ветке. Она безопасно изолирует удаление коммитов от других веток, которые могут использоваться другими разработчиками. Проблемы возникают, когда команда git reset выполняется в общей ветке, а затем эта ветка отправляется на удаленный сервер с помощью git push. В этом случае Git блокирует выполнение команды push, и сообщает, что публикуемая ветка устарела, так как в ней отсутствуют коммиты, которые есть в ветке на сервере.

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

Резюме

Мы рассмотрели множество стратегий отмены изменений в Git. Важно помнить, что отменить изменения в проекте Git можно несколькими способами. Наиболее часто используемыми инструментами отмены изменений являются команды git checkout, git revert и git reset. Следует запомнить следующие ключевые моменты:

  • Обычно после коммита внесенные изменения отменить невозможно.
  • Используйте git checkout для просмотра истории коммитов и переключения на них.
  • Команда git revert – это предпочтительный метод для отмены изменений, сделанных в общем репозитории.
  • Команду git reset лучше всего использовать для отмены локальных изменений.

В дополнение к основным командам отмены мы рассмотрели и другие команды Git: git log для поиска потерянных коммитов, git clean для отмены незакомиченных изменений, git add для изменения индексирования.

Перевод статьи «Undoing Commits & Changes».

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

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