Отмена изменений

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

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

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

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

Поиск утерянного: просмотр старых коммитов

В основе любой системы управления версиями лежит идея хранения «безопасных» копий проекта, чтобы у разработчиков не возникало опасений безвозвратно испортить базу кода. Когда в проекте сохранена история коммитов, можно повторно оценивать и анализировать любые ранее выполненные коммиты. Один из лучших инструментов для просмотра истории репозитория 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 обычно указывает на главную ветку master или другую локальную ветку. Но при загрузке предыдущего коммита указатель HEAD указывает уже не на ветку, а непосредственно на сам коммит. Такая ситуация называется состоянием «открепленного указателя HEAD», и ее можно представить следующим рисунком:

Обучающее руководство по Git: переключение на предыдущий коммит

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

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

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

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

Для просмотра коммита «Make some important changes to hello.txt» можно использовать команду git checkout в следующем виде:

 git checkout a1e8fb5

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

 git checkout master

Предполагается, что вы ведете разработку в ветке по умолчанию — master. После возврата в ветку master вы можете выполнить либо 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 не существует и его можно считать «отмененным». Но к сожалению, если вам нужна предыдущая ветка (допустим, master), такая стратегия не подходит. Поэтому рассмотрим другие стратегии отмены. Более детальную информацию и примеры см. в нашей подробной статье о git checkout.

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

Предположим, мы вернулись к исходному примеру истории коммитов. В эту историю включен коммит 872fa7e. На этот раз давайте попытаемся выполнить отмену путем возврата. Если мы запустим команду 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

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

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

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

Отмена неотправленных изменений

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

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

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

Раздел проиндексированных файлов

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

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

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

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

Резюме

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

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

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

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