Изучите Git с помощью Bitbucket Cloud

Узнайте, как отменять изменения в Git с помощью Bitbucket Cloud

Цель

Узнайте, как отменить изменения на локальной машине и в репозитории Bitbucket Cloud во время совместной работы с коллегами.

Краткое описание основной задачи

В этом обучающем руководстве рассматриваются команды git revert, git reset, git log и git status

Время Аудитория Обязательные условия

40 минут

В этом обучающем руководстве предполагается, что вы знакомы со следующими командами Git:

git clone, git commit, git pull и git push
У вас установлен Git
У вас есть аккаунт Bitbucket

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

В этом обучающем руководстве предполагается, что вы знакомы со следующими командами Git:

Если вы не знакомы с этими командами, мы поможем изучить Git с помощью Bitbucket Cloud. По завершении вернитесь на эту страницу и узнайте, как отменять изменения. Эти команды Git могут применяться в среде Windows или Unix. Для навигации по файловой системе в этом руководстве будут использоваться утилиты командной строки Unix.

Отмена изменений на локальной машине

Если изменение, которое вы хотите отменить, находится в вашей локальной системе и оно не было опубликовано в удаленном репозитории, выполнить отмену можно двумя способами:

Команда Определение

git revert

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

git reset

Универсальная команда Git для отмены изменений. У команды git reset есть ряд полезных опций, но в целях обучения мы рассмотрим следующие режимы:

  • --soft: только сброс указателя HEAD до выбранного коммита. Действие команды аналогично git checkout <номер коммита>, но в этом режиме указатель HEAD не открепляется.
  • --mixed: сброс указателя HEAD до выбранного коммита в истории и отмена изменений в индексе.
  • --hard: сброс указателя HEAD до выбранного коммита в истории, отмена изменений в индексе и отмена изменений в рабочем каталоге. В рамках данного обучения мы не будем демонстрировать работу команды reset в режиме hard.

Подробное описание принципа работы команды git reset приведено в разделе сайта git-scm.com Git Tools — Reset Demystified (Инструменты Git — Раскрытие тайн reset).

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

Форк репозитория

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

  1. Щелкните по следующему URL или введите его в адресную строку: https://bitbucket.org/atlassian/tutorial-documentation-tests/commits/all
  2. Щелкните на левой боковой панели значок +, а затем выберите Fork this repository (Сделать форк этого репозитория), изучите содержимое диалогового окна и щелкните Fork repository (Сделать форк репозитория).
  3. Перед вами появится обзор нового репозитория.
  4. Щелкните значок «+» и выберите Clone this repository (Клонировать этот репозиторий).
  5. Клонируйте репозиторий на свой компьютер.
  6. Перейдите в каталог, в котором находится клонированный репозиторий.

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

Поиск изменений в локальной системе

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

git status

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

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean

Согласно выводу команды git status, все данные соответствуют данным в удаленной главной ветке master, а отложенных изменений и изменений, готовых к коммиту, нет. В следующем примере мы внесем небольшие изменения в репозиторий и рассмотрим его в состоянии отложенных изменений. То есть мы рассмотрим репозиторий, находящийся в локальной системе, в котором файлы изменены, но не подготовлены (или не проиндексированы) для добавления в историю проекта.

Для выполнения следующего примера откройте файл myquote2.html. Внесите изменения в содержимое myquote2.html, сохраните их и закройте файл. Выполним команду git status еще раз, чтобы исследовать это состояние репозитория.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
 
Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git checkout -- <file>..." to discard changes in working directory)
 
 Modified: myquote2.html
 
no changes added to commit (use "git add" and/or "git commit -a")
--

Согласно выводу, в репозитории есть отложенные изменения для файла myquote2.html. Хорошие новости! Если отменяемое изменение не было добавлено в раздел проиндексированных файлов (как в примере выше), вы можете просто отредактировать файл и продолжить работу. Git начинает отслеживать изменения только после того, как они добавлены в раздел проиндексированных файлов и выполнен коммит в историю проекта.

Теперь давайте отменим изменения, внесенные в файл myquote2.html. Поскольку это упрощенный пример с минимумом изменений, мы можем отменить их двумя способами. Если выполнить команду git checkout myquote2.html, репозиторий восстановит файл myquote2.html до версии предыдущего коммита. Если же выполнить команду git reset --hard, весь репозиторий будет возвращен к состоянию последнего коммита.

git log

Команда git log позволяет просматривать и фильтровать историю проекта, а также искать конкретные изменения. С помощью git status можно просматривать рабочий каталог и раздел проиндексированных файлов, в то время как git log показывает только историю коммитов.

Этот же журнал истории коммитов можно найти в пользовательском интерфейсе Bitbucket, обратившись к представлению коммитов в репозитории. Представление коммитов для нашего демонстрационного репозитория можно найти по ссылке https://bitbucket.org/dans9190/tutorial-documentation-tests/commits/all. Это представление будет похоже на вывод утилиты командной строки git log. С его помощью можно найти и указать коммит для отмены.

В следующем примере история состоит из нескольких изменений, но каждое изменение по сути является коммитом. Это то, что нам нужно найти и отменить.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

$ git log

commit 1f08a70e28d84d5034a8076db9103f22ec2e982c
Author: Daniel Stevens <dstevens@atlassian.com>
Date:   Wed Feb 7 17:06:50 2018 +0000

    Initial Bitbucket Pipelines configuration

commit 52f823ca251a132225dd1cc18ad768de8d336e84
Author: Daniel Stevens <dstevens@atlassian.com>
Date:   Fri Sep 30 15:50:58 2016 -0700

    repeated quote to show how a change moves through the process

commit 4801b87c2147dce83f1bf31acfcffa6cb1d7e0a5
Merge: 1a6a403 3b29606
Author: Dan Stevens [Atlassian] <dstevens@atlassian.com>
Date:   Fri Jul 29 18:45:34 2016 +0000

    Merged in changes (pull request #6)

    Changes

Рассмотрим один из перечисленных коммитов внимательнее.

commit 52f823ca251a132225dd1cc18ad768de8d336e84
Author: Daniel Stevens <dstevens@atlassian.com>
Date:   Fri Sep 30 15:50:58 2016 -0700
 
    repeated quote to show how a change moves through the process

Вы можете заметить, что каждый комментарий к коммиту состоит из четырех элементов.

Элемент Описание

Хеш коммита

Буквенно-цифровая строка (генерируемая функцией SHA-1), по которой можно идентифицировать то или иное изменение

Автор

Лицо, сделавшее коммит изменения

Дата

Дата внесения изменения в проект с помощью коммита

Комментарий к коммиту

Текстовая строка с описанием изменения.

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


Поиск местонахождения конкретного коммита

Обычно отменяемое изменение находится где-то среди ранних записей истории проекта, а история может оказаться довольно длинной. Итак, давайте изучим две основные операции, используя для поиска конкретного изменения команду git log.

  1. В окне терминала перейдите на самый верхний уровень локального репозитория с помощью команды cd (change directory — сменить каталог).
$ cd ~/repos/tutorial-documentation-tests/

Введите команду git log --oneline. Параметр --oneline отобразит каждый коммит в отдельной строке, поэтому в окне терминала поместится больше записей из истории.

В любое время вы можете нажать клавишу q, чтобы закрыть журнал коммитов и вернуться к командной строке.

Вы увидите нечто подобное:

$ git log --oneline
1f08a70 (HEAD -> master, origin/master, origin/HEAD) Initial Bitbucket Pipelines configuration
52f823c repeated quote to show how a change moves through the process
4801b87 Merged in changes (pull request #6)
1a6a403 myquote edited online with Bitbucket
3b29606 (origin/changes) myquote2.html edited online with Bitbucket
8b236d9 myquote edited online with Bitbucket
235b9a7 testing prs
c5826da more changes
...
  1. Нажмите клавишу q, чтобы вернуться к командной строке.
  2. Найдите коммит с хешем c5826da и комментарием more changes в списке, образованном после выполнения команды git log. Кто-то не написал подробный комментарий к коммиту, поэтому необходимо выяснить, есть ли в нем необходимые нам изменения.
  3. Выделите и скопируйте хеш коммита c5826da из вывода команды git log в окне терминала.
  4. Введите команду git show, а затем вставьте скопированный хеш коммита или перепишите его и нажмите клавишу ввода. Вы увидите нечто подобное:
$git show c5826daeb6ee3fd89e63ce35fc9f3594fe243605
commit c5826daeb6ee3fd89e63ce35fc9f3594fe243605
Author: Daniel Stevens <dstevens@atlassian.com>
Date:   Tue Sep 8 13:50:23 2015 -0700

    more changes

diff --git a/README.md b/README.md
index bdaee88..6bb2629 100644
--- a/README.md
+++ b/README.md
@@ -11,12 +11,7 @@ This README would normally document whatever steps are necessary to get your app
 ### How do I get set up? ###

 * Summary of set up
-* Configuration
-* Dependencies
-* Database configuration
-* How to run tests
-* Deployment instructions
-* more stuff and things
:

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

Фильтрация вывода команды git log для поиска определенного коммита

Отфильтровать и скорректировать вывод команды git log можно с помощью следующих параметров:

Фильтр Описание Пример команды Результаты выполнения
-

Ограничивает количество отображаемых коммитов

git log -10

10 последних коммитов в истории

--after

--before

Ограничивает отображаемые коммиты установленными временными рамками

Вы также можете использовать --after "гггг-мм-дд" --before "гггг-мм-дд"

git log --after 2017-07-04

Все коммиты после 4 июля 2017 года

--author="имя"

Выводит список всех коммитов, автор которых совпадает с указанным

git log --author="Alana"

Все коммиты, у которых в поле имени автора содержится строка «Alana»

--grep="строка комментария"

Возвращает все коммиты, комментарий к которым содержит указанную строку

git log --grep="HOT-"

Все коммиты, содержащие строку «HOT-» в комментарии к коммиту

Это был очень краткий обзор команды git log. Если вам понравилось работать с ней, вероятно, вам будет интересно ознакомиться с расширенным учебным руководством по git log.

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

Сначала давайте просто отменим последний коммит в истории. Предположим, вы только что включили конвейеры непрерывной интеграции и непрерывной поставки (CI/CD) Bitbucket, но обнаружили, что скрипт работает не совсем так, как нужно.

  1. Введите в окне терминала команду git log --oneline.
  2. Скопируйте хеш второго коммита в журнале (52f823c), а затем нажмите клавишу q, чтобы закрыть журнал.
  3. Введите в окне терминала команду git reset --soft 52f823c. Если все нормально, команда должна запуститься в фоновом режиме. Вот и все, вы отменили ваше первое изменение. Теперь давайте посмотрим на результат этого действия.
  4. Введите в окне терминала команду git status. Вы увидите, что коммит был отменен, а изменения теперь остаются неподтвержденными. Это будет выглядеть примерно так:
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
 
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
 
    new file:   bitbucket-pipelines.yml
  1. Введите в окне терминала команду git log --oneline. Вы должны увидеть что-то увидите нечто подобное:
$ git log --oneline
52f823c repeated quote to show how a change moves through the process
4801b87 Merged in changes (pull request #6)
1a6a403 myquote edited online with Bitbucket
3b29606 (origin/changes) myquote2.html edited online with Bitbucket
8b236d9 myquote edited online with Bitbucket
235b9a7 testing prs
c5826da more changes
43a87f4 remivng
d5c4c62 a few small changes
23a7476 Merged in new-feature2 (pull request #3)
5cc4e1e add a commit message
cbbb5d6 trying a thing
438f956 adding section for permissions and cleaning up some formatting
23251c1 updated snipptes.xml organization into resources. other files misc changes
3f630f8 Adding file to track changes
...
  1. Вы увидите, что в качестве нового указателя HEAD для ветки указан требуемый коммит 52f823c.
  2. Нажмите клавишу q, чтобы закрыть журнал. Не закрывайте терминал, поскольку теперь, после изучения простого применения команды reset, мы рассмотрим чуть более сложный вариант.

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

Допустим, вы обнаружили, что запрос pull № 6 (4801b87) необходимо доработать, и при этом вы хотите поддержать чистоту истории, поэтому на этот раз вы будете сбрасывать указатель HEAD до коммита 1a6a403 с помощью команды git reset.

  1. Введите команду git log --oneline.
  2. Скопируйте хеш коммита 1a6a403 (myquote edited online with Bitbucket), созданного прямо перед запросом pull № 6, в котором мы хотим отменить изменения.
  3. Введите в окне терминала команду git reset 1a6a403. Вывод должен выглядеть примерно так:
$ git reset 1a6a403
Unstaged changes after reset:
M README.md
M myquote2.html

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

  1. Введите в окне терминала команду git status. Вывод должен выглядеть примерно так:

$ git status
On branch master
Your branch is behind 'origin/master' by 6 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)
 
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
 
    modified:   README.md
    modified:   myquote2.html
 
Untracked files:
  (use "git add <file>..." to include in what will be committed)
 
    bitbucket-pipelines.yml
 
no changes added to commit (use "git add" and/or "git commit -a")

Теперь Git вообще не отслеживает первое отмененное изменение (файл bitbucket-pipelines.yml). Это связано с тем, что команда git reset удалила изменение как из указателя HEAD ветки, так и из области отслеживания (или индекса) Git. Основной процесс выглядит несколько сложнее, чем описывается в этом руководстве, подробнее см. в руководстве по команде git reset.

  1. Введите в окне терминала команду git log --oneline.
1a6a403 myquote edited online with Bitbucket
8b236d9 myquote edited online with Bitbucket
43a87f4 remivng
d5c4c62 a few small changes
23a7476 Merged in new-feature2 (pull request #3)
5cc4e1e add a commit message
cbbb5d6 trying a thing
438f956 adding section for permissions and cleaning up some formatting
23251c1 updated snipptes.xml organization into resources. other files misc changes
3f630f8 Adding file to track changes
e52470d README.md edited online with Bitbucket
e2fad94 README.md edited online with Bitbucket
592f84f Merge branch 'master' into new-feature2 Merge branch  especially if it merges an updated upstream into a topic branch.
7d0bab8 added a line
879f965 adding to the quote file
8994332 Merged in HOT-235 (pull request #2)
b4a0b43 removed sarcastic remarks because they violate policy.
b5f5199 myquote2.html created online with Bitbucket
b851618 adding my first file
5b43509 writing and using tests

Согласно выводу команды log, история коммитов также была изменена и начинается с коммита 1a6a403. Для демонстрации следующего примера допустим, что мы хотим отменить сброс, который только что выполнили. В дальнейшем мы, возможно, захотим сохранить содержимое запроса pull № 6.

Отправка результатов отмены в Bitbucket

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

Чтобы поделиться сброшенной веткой с удаленной командой, необходимо использовать принудительную отправку изменений. Принудительная отправка изменений выполняется с помощью команды git push -f. Она уничтожит всю историю ветки, созданную после этой точки отправки.

Ниже приведен пример небезопасного сценария.

  • Разработчик A работает в ветке над новой возможностью.
  • Разработчик B работает в той же ветке над другой возможностью.
  • Разработчик B решает сбросить ветку к более раннему состоянию, когда разработчики A и B еще не приступили к работе.
  • Разработчик B принудительно отправляет сброшенную ветку в удаленный репозиторий.
  • Разработчик A делает запрос pull для этой ветки, чтобы получить обновления. Во время выполнения запроса pull разработчик A получает принудительное обновление, которое сбрасывает его локальную ветку к тому моменту времени, когда он еще не начал работать над новой возможностью. Соответственно, он теряет все коммиты.

Отмена команды git reset

До сих пор мы передавали в git reset SHA-хеши коммитов Git. Теперь вывод команды git log не показывает сброшенные коммиты. Как их вернуть? Git никогда не удаляет коммиты окончательно, хотя все указатели на них открепляются. Кроме того, Git хранит отдельный журнал со всеми перемещениями ссылок — reflog, или журнал ссылок. Проверить журнал ссылок можно с помощью команды git reflog.

1a6a403 HEAD@{0}: reset: moving to 1a6a403
1f08a70 HEAD@{1}: reset: moving to origin/master
1f08a70 HEAD@{2}: clone: from git@bitbucket.org:dans9190/tutorial-documentation-tests.git

Вывод команды git reflog похож на то, что показано в примере выше. Вы можете просмотреть историю действий в репозитории. Самая верхняя строчка является ссылкой на команду reset, которую мы выполнили для сброса запроса pull № 6. Теперь давайте сбросим выполнение команды reset, чтобы восстановить запрос pull № 6. Во втором столбце вывода команды reflog находится указатель на действие по изменению репозитория. Здесь HEAD@{0} — это ссылка на выполненную нами ранее команду reset. Мы не хотим отвечать на эту команду сброса, поэтому восстановим репозиторий до HEAD@{{1}}.

$ git reset --hard HEAD@{1}
HEAD is now at 1f08a70 Initial Bitbucket Pipelines configuration

Теперь давайте рассмотрим историю коммитов репозитория с помощью команды git log --oneline:

$git log --online
1f08a70 Initial Bitbucket Pipelines configuration
52f823c repeated quote to show how a change moves through the process
4801b87 Merged in changes (pull request #6)
1a6a403 myquote edited online with Bitbucket
3b29606 myquote2.html edited online with Bitbucket
8b236d9 myquote edited online with Bitbucket
235b9a7 testing prs
c5826da more changes
43a87f4 remivng
d5c4c62 a few small changes
23a7476 Merged in new-feature2 (pull request #3)
5cc4e1e add a commit message
cbbb5d6 trying a thing
438f956 adding section for permissions and cleaning up some formatting
23251c1 updated snipptes.xml organization into resources. other files misc changes
3f630f8 Adding file to track changes
e52470d README.md edited online with Bitbucket
e2fad94 README.md edited online with Bitbucket
592f84f Merge branch 'master' into new-feature2 Merge branch  especially if it merges an updated upstream into a topic branch.
7d0bab8 added a line
:

Здесь видно, что история коммитов репозитория была восстановлена до предыдущей версии, с которой мы экспериментировали. Коммит 4801b87 восстановлен, хотя был утерян при первой операции сброса. Команда git reflog является мощным инструментом для отмены изменений в репозитории. Подробнее о ее использовании см. на странице git reflog.

git revert

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

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

$ git log --online
1f08a70 Initial Bitbucket Pipelines configuration
52f823c repeated quote to show how a change moves through the process
4801b87 Merged in changes (pull request #6)
1a6a403 myquote edited online with Bitbucket
1f08a70 Initial Bitbucket Pipelines configuration
52f823c repeated quote to show how a change moves through the process
4801b87 Merged in changes (pull request #6)
1a6a403 myquote edited online with Bitbucket
3b29606 myquote2.html edited online with Bitbucket
8b236d9 myquote edited online with Bitbucket
235b9a7 testing prs
c5826da more changes
43a87f4 remivng
d5c4c62 a few small changes
23a7476 Merged in new-feature2 (pull request #3)
5cc4e1e add a commit message
cbbb5d6 trying a thing
438f956 adding section for permissions and cleaning up some formatting
23251c1 updated snipptes.xml organization into resources. other files misc changes
3f630f8 Adding file to track changes
e52470d README.md edited online with Bitbucket
e2fad94 README.md edited online with Bitbucket
592f84f Merge branch 'master' into new-feature2 Merge branch  especially if it merges an updated upstream into a topic branch.
7d0bab8 added a line
:

В этом примере будем работать с самым последним коммитом — 1f08a70. Предположим, что мы хотим отменить изменения, внесенные в этот коммит. Выполните следующую команду.

$ git revert 1f08a70

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

Вы только что узнали, как отменять изменения!

Вы закончили, поздравляем! Вы можете в любое время вернуться к этому учебному руководству или ознакомиться с разделом «Отмена изменений», чтобы изучить эту тему подробнее. Продолжайте в том же духе в Bitbucket!