Модуль 2 · Первый репозиторий в VS Code

Справочник по Git и GitHub · модуль 2 · первый репозиторий в VS Code

Глава 2.6

2.6

Отмена изменений:
discard, amend, revert, reset

В главе разобраны способы отменить сделанное в Git. Какой из них нужен, зависит от того, где находится правка: только в файле, в последнем коммите или в коммите, сделанном раньше. Для каждого способа показаны кнопка в VS Code или команда, что меняется в истории и что можно потерять. В конце — как вернуть коммит, стёртый по ошибке.

Категория
Основы работы
Уровень
Начальный
Что получится
Умение отменить правку, исправить коммит и откатить старый
Сколько занимает
Около тридцати минут за компьютером

§1Где находится правка и чем её отменять

В главе 2.3 было показано, что правка проходит три места: рабочую папку, индекс и историю. Отмена устроена так же: способ зависит от того, куда правка успела дойти.

РАБОЧАЯ ПАПКА правка сохранена в файле ИНДЕКС правка отобрана плюсом ИСТОРИЯ правка вошла в коммит Discard Changes git restore − у файла git restore --staged последний: Amend, Undo старый: revert git commit --amend, git revert, git reset
Под каждой зоной — способ отменить то, что в ней лежит. Возврат из индекса кнопкой «минус» разобран в главе 2.3, остальные способы — в этой.

Все способы делятся на две группы. Первые меняют только файлы и индекс, история проекта остаётся прежней. Вторые меняют историю: убирают коммит, заменяют его другим или добавляют новый. Для второй группы действует правило: коммиты, которые уже отправлены на GitHub, не убирают и не заменяют. Их отменяют только новым коммитом.

Зачем это нужно в работе

  • Пробная правка не подошла. Цвета поменяли, результат хуже прежнего. Правку нужно выбросить и вернуть файл к последнему коммиту.
  • Опечатка в сообщении или забытый файл. Коммит только что сделан, и в нём ошибка. Исправить его проще, чем делать второй коммит «исправил опечатку».
  • Правка на сайте оказалась ошибкой. Коммит неделю назад поменял отступы, и теперь их нужно вернуть. Остальные коммиты после него трогать нельзя.
  • Что-то стёрто по ошибке. Git записывает в журнал каждое действие с историей, и по этой записи стёртый коммит можно вернуть.

В главе используется проект sait-vizitka в том виде, в каком он остался после главы 2.5: одиннадцать коммитов, семь из них на GitHub ещё не отправлены.

§2Шаг 1. Отменить правку в файле

Первый случай: правка есть только в файле. В индекс и в историю она не попадала.

Пробуем тёмное оформление. В styles.css в блоке body меняем местами цвета фона и текста:

styles.cssбыло
body {
  margin: 0;
  padding: 60px;
  font-family: system-ui, sans-serif;
  background: #F4F1EC;
  color: #1B1A19;
}
styles.cssстало
body {
  margin: 0;
  padding: 60px;
  font-family: system-ui, sans-serif;
  background: #1B1A19;
  color: #F4F1EC;
}

Заодно создаём в корне проекта файл proba.html, чтобы посмотреть на тёмную тему отдельно. Сохраняем оба файла.

proba.htmlновый файл
<!doctype html>
<html lang="ru">
<body>
  <h1>Проба тёмной темы</h1>
</body>
</html>
Панель Source Control: в разделе Changes proba.html с буквой U и styles.css с буквой M
Два изменения: новый файл proba.html (U) и изменённый styles.css (M).

Результат не понравился. Нажимаем на styles.css в панели, чтобы увидеть, что будет отменено:

Окно сравнения styles.css: строки background и color из последнего коммита красным, новые значения зелёным
Красным — как было в последнем коммите, зелёным — пробные цвета. Отмена вернёт красные строки.

Наводим курсор на styles.css в списке Changes и нажимаем круговую стрелку Discard Changes. VS Code переспрашивает:

Окно подтверждения: Are you sure you want to discard changes in 'styles.css'? Кнопки Cancel и Discard File
«Вы уверены, что хотите отменить изменения в styles.css?» Кнопка Discard File возвращает файл к состоянию из последнего коммита. На macOS окно выглядит как системное, текст в нём тот же.

Нажимаем Discard File. Файл пропадает из списка, в редакторе снова светлые цвета.

Та же отмена в терминале:

терминал в папке проектаответа нет
git restore styles.css

Команда ничего не печатает. После неё git status --short не показывает styles.css: файл совпадает с последним коммитом.

На полях: отменённую правку не вернуть

Discard перезаписывает файл версией из последнего коммита. Правка в историю не попадала, поэтому Git её не хранит, и в корзину она тоже не попадает. Если среди выбрасываемого есть что-то нужное, сначала сделайте коммит или скопируйте текст. Один участок файла отменяет круговая стрелка в окне сравнения — она рядом со значком Stage Block из главы 2.3.

Что получилось.styles.css снова такой, как в последнем коммите, пробные цвета стёрты. В панели остался proba.html.

§3Шаг 2. Удалить новый файл

В панели остался второй пробный файл — proba.html. С новым файлом отмена работает иначе.

У proba.html прошлой версии нет: файл новый, в коммитах его не было. Круговая стрелка у такого файла означает удаление. Наводим курсор на proba.html и нажимаем её:

Окно подтверждения: Are you sure you want to DELETE the following untracked file: 'proba.html'? You can restore this file from the Trash. Кнопки Cancel и Move to Trash
«Удалить неотслеживаемый файл proba.html? Его можно будет восстановить из корзины». Кнопка Move to Trash переносит файл в корзину системы.

Новый файл VS Code переносит в корзину системы, и оттуда его можно достать. В терминале новый файл удаляет команда git clean. Она стирает файл сразу, минуя корзину:

терминал в папке проектаответ: Removing proba.html
git clean -f proba.html

Что получилось.Пробных файлов в проекте нет, список изменений пуст.

§4Шаг 3. Исправить последний коммит

Следующий случай: правка уже в коммите, и этот коммит последний. Сначала сделаем такой коммит с двумя ошибками, потом исправим.

Добавляем на страницу контактов ссылку на профиль GitHub. В kontakty.html под строкой с почтой появляется новая строка:

kontakty.htmlбыло
  <h1>Контакты</h1>
  <p>Почта: student@example.com</p>
</body>
kontakty.htmlстало
  <h1>Контакты</h1>
  <p>Почта: student@example.com</p>
  <p><a href="https://github.com/MaximBytecamp">Профиль на GitHub</a></p>
</body>

В конец styles.css добавляем цвет ссылок:

styles.cssв конец файла
a {
  color: #C2410C;
}

Отбираем плюсом только kontakty.html и коммитим с сообщением «Добавил сылку на GitHub».

В коммите две ошибки: опечатка в сообщении и забытый styles.css. Коммит последний и на GitHub не отправлен, поэтому его можно исправить. Второй коммит для этого не нужен.

Раздел Graph: верхний коммит «Добавил сылку на GitHub» с меткой main
Коммит с опечаткой «сылку» в графе. В него вошёл только kontakty.html.
  1. Отбираем плюсом styles.css — он войдёт в исправленный коммит.
  2. В поле сообщения пишем правильный текст: «Добавил ссылку на GitHub».
  3. Нажимаем стрелку справа от кнопки Commit и выбираем Commit (Amend).
Панель: в поле сообщения «Добавил ссылку на GitHub», в Staged Changes styles.css, открыто меню кнопки Commit с пунктами Commit, Commit (Amend), Commit & Push, Commit & Sync
Меню кнопки Commit. Пункт Commit (Amend) заменяет последний коммит новым: в него войдут прежние файлы и то, что лежит в Staged Changes, а сообщение возьмётся из поля.
Раздел Graph: верхний коммит «Добавил ссылку на GitHub» с меткой main
После исправления в графе один коммит с правильным сообщением. Коммита с опечаткой в списке нет.

Что произошло, видно по имени коммита. Слово HEAD в команде означает коммит, на котором вы сейчас стоите, — в графе он отмечен меткой main:

терминалкоманда и ответ
git show --stat --oneline HEAD
7531a77 Добавил ссылку на GitHub
 kontakty.html | 1 +
 styles.css    | 4 ++++
 2 files changed, 5 insertions(+)

До исправления коммит назывался 171a006, после — 7531a77. Amend создаёт новый коммит с исправленным содержимым и ставит его на место старого. Имя коммита вычисляется из содержимого (глава 2.4), поэтому новое содержимое даёт новое имя.

То же в терминале: отобрать забытый файл и заменить последний коммит.

терминал в папке проектато же, что плюс и Commit (Amend)
git add styles.css
git commit --amend -m "Добавил ссылку на GitHub"
На полях: только до отправки

Если коммит уже на GitHub, у сервера и у коллег осталась старая версия 171a006, а у вас — новая 7531a77. Истории разошлись, и обычная отправка не пройдёт. Поэтому правило такое: amend применяют к коммитам, которые ещё не отправлены. В графе это коммиты выше метки origin/main.

Что получилось.Вместо коммита с опечаткой в истории исправленный коммит с обоими файлами. Число коммитов не изменилось.

§5Шаг 4. Отменить последний коммит целиком

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

Задаём цвет заголовку. В styles.css в блок h1 добавляем строку:

styles.cssбыло
h1 {
  font-size: 34px;
}
styles.cssстало
h1 {
  font-size: 34px;
  color: #C2410C;
}

Сразу коммитим с сообщением «Покрасил заголовок». Потом становится видно, что цвет заголовка совпал с цветом ссылок и их не различить. Коммит сделан рано: правку нужно доработать.

Для этого у раздела CHANGES есть меню. Наводим курсор на строку CHANGES, нажимаем «…» справа и выбираем Commit → Undo Last Commit.

Меню «…» раздела CHANGES, подменю Commit с пунктами Commit, Commit Staged, Commit All, Undo Last Commit и вариантами Amend
Подменю Commit. Пункт Undo Last Commit отменяет последний коммит. В этом же подменю есть варианты Amend: Commit Staged (Amend) и Commit All (Amend).
Панель после отмены: в поле сообщения «Покрасил заголовок», в Staged Changes styles.css, в графе верхний коммит «Добавил ссылку на GitHub»
Коммита «Покрасил заголовок» в графе больше нет. Его правка вернулась в Staged Changes, а сообщение — в поле ввода.

После Undo Last Commit коммит исчезает из истории, а его содержимое остаётся в индексе. В терминале этой кнопке соответствует команда reset с ключом --soft. Запись HEAD~ означает коммит перед текущим, HEAD~1 — то же самое. Подробно reset разобран в шаге 6.

терминал в папке проектаответа нет
git reset --soft HEAD~

Меняем цвет заголовка на более тёмный, чтобы он отличался от ссылок:

styles.cssбыло
h1 {
  font-size: 34px;
  color: #C2410C;
}
styles.cssстало
h1 {
  font-size: 34px;
  color: #9A3412;
}

Отбираем файл плюсом и делаем коммит заново с тем же сообщением — оно уже стоит в поле. То же в терминале:

терминал в папке проектато же, что плюс и Commit
git add styles.css
git commit -m "Покрасил заголовок"

Что получилось.Коммит «Покрасил заголовок» сделан заново, с цветом темнее ссылок.

§6Шаг 5. Отменить старый коммит

Третий случай: ошибка в коммите, сделанном давно, и после него уже есть другие коммиты. Amend и Undo Last Commit работают только с последним коммитом, здесь нужен другой способ.

Отступы страницы решено вернуть к 40px. Их увеличил коммит «Увеличил отступы страницы» из главы 2.3. После него сделано ещё семь коммитов, и все они нужны.

Для такого случая есть revert — новый коммит, который делает обратное указанному: убирает добавленные им строки и возвращает удалённые. Сам старый коммит остаётся в истории.

В VS Code 1.138 кнопки для revert нет, понадобится терминал. Сначала копируем имя коммита: правой кнопкой по нему в разделе GRAPH → Copy Commit Hash.

Контекстное меню коммита «Увеличил отступы страницы» в графе: Open Changes, Checkout, Create Branch, Cherry Pick, Copy Commit Hash и другие пункты
Меню коммита в графе. Пункта Revert в нём нет; Copy Commit Hash копирует полное имя коммита для команды.

Открываем терминал (Ctrl + `) и вставляем имя после команды. Ключ --no-edit оставляет сообщение, которое Git составит сам. Без него откроется редактор для сообщения. Если в главе 1.3 вы оставили редактором Vim, сообщение сохраняется и редактор закрывается так: Esc, затем :wq и Enter.

терминал в папке проектакоманда и ответ
git revert --no-edit 121e750
Auto-merging styles.css
[main 9f5312d] Revert "Увеличил отступы страницы"
 1 file changed, 1 insertion(+), 1 deletion(-)

Имя коммита у вас будет своим — то, которое скопировали из графа. Первая строка ответа сообщает, что Git сам сопоставил правки в styles.css. Вторая — что создан коммит 9f5312d; перед его именем стоит метка main, та же, что в графе. Что в новом коммите:

терминалфрагмент ответа
git show HEAD
commit 9f5312d038b94569434acb99241c171738962489
Author: Student <student@example.com>

    Revert "Увеличил отступы страницы"

    This reverts commit 121e750ab4239f5782cb63dc5f58a5b03b154b1c.

-  padding: 60px;
+  padding: 40px;

Одна строка изменена в обратную сторону: было 60px, стало 40px. В сообщении Git указал, какой коммит отменён. Остальные коммиты не тронуты.

Раздел Graph: сверху новый коммит Revert «Увеличил отступы страницы» с меткой main, ниже «Покрасил заголовок» и остальные
Revert добавил коммит в начало истории. Старый коммит «Увеличил отступы страницы» остался на своём месте ниже.

Ассоциация: исправление в бухгалтерской книге

В жизни

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

В Git

Revert — такая же обратная запись. Ошибочный коммит остаётся в истории, над ним появляется коммит с обратными изменениями и ссылкой на него в сообщении. Файлы становятся такими, как до ошибки.

Где не совпадаетСумму с обратным знаком вычесть можно всегда. Git вычитает строки, и если после ошибочного коммита те же строки кто-то менял ещё раз, revert остановится с конфликтом: Git не сможет сам решить, какой вариант оставить. Конфликты разберём в главе 3.4.

Revert подходит и для коммитов, которые уже на GitHub: он только добавляет коммит, прежние остаются на месте. Поэтому отправленные коммиты отменяют через revert.

Что получилось.В истории новый коммит Revert "Увеличил отступы страницы", отступ снова 40px. Остальные коммиты на месте.

§7Шаг 6. Три режима reset на пробном коммите

Revert исправляет ошибку новым коммитом. Команда reset действует иначе: она убирает коммиты из истории. Это нужно, когда коммит сделан по ошибке и на GitHub ещё не отправлен.

Reset возвращает историю к указанному коммиту. Все коммиты после него исчезают из графа.

Правка из коммита записана в трёх местах: в истории, в индексе и в файле. Reset с любым ключом убирает коммит из истории. Ключ --soft, --mixed или --hard определяет, вернутся ли к предыдущему коммиту индекс и файл:

ИСТОРИЯ ИНДЕКС ФАЙЛ STYLES.CSS после коммита «Проба reset» коммит есть «Проба reset» 40px правка отобрана 40px правка в файле --soft git reset --soft HEAD~ коммит убран сверху revert 40px правка отобрана 40px правка в файле --mixed git reset HEAD~ коммит убран сверху revert 34px как в коммите revert 40px правка в файле --hard git reset --hard HEAD~ коммит убран сверху revert 34px как в коммите revert 34px файл перезаписан
Розовая клетка — правка 40px на месте. Голубая клетка — место вернулось к коммиту revert. С каждым ключом голубых клеток на одну больше.

Ассоциация: заклеенная коробка из главы 2.3

В жизни

Коробку с вещами заклеили и подписали, а потом передумали. Её можно расклеить и оставить вещи внутри. Можно выложить вещи на стол. Можно выбросить вместе с коробкой.

В Git

--soft расклеивает коробку: правки остаются в индексе. --mixed выкладывает их на стол: правки остаются в файлах, индекс пуст. --hard выбрасывает: правки стёрты.

Где не совпадаетВыброшенную коробку не вернуть. Коммит после --hard Git ещё хранит, и его можно достать по журналу — это шаг 7. Незакоммиченные правки после --hard пропадают насовсем.

Проверим это на пробном коммите: сделаем его и отменим тремя способами по очереди. На каждом кадре слева панель Source Control, справа открытый файл styles.css. В styles.css увеличиваем размер заголовка:

styles.cssбыло
h1 {
  font-size: 34px;
  color: #9A3412;
}
styles.cssстало
h1 {
  font-size: 40px;
  color: #9A3412;
}

Отбираем файл плюсом и коммитим с сообщением «Проба reset». То же в терминале:

терминал в папке проектато же, что плюс и Commit
git add styles.css
git commit -m "Проба reset"
Окно VS Code: в графе сверху коммит «Проба reset», список изменений пуст, в файле styles.css строка font-size: 40px
До reset. В графе сверху коммит «Проба reset», на кнопке 11↑. Список изменений пуст. В файле строка 10: font-size: 40px.

Режим --soft: правка остаётся в индексе и в файле

Отменяем пробный коммит с ключом --soft. Запись HEAD~ означает коммит перед текущим — здесь это коммит revert:

терминал в папке проектаответа нет
git reset --soft HEAD~
Окно VS Code после reset --soft: в Staged Changes файл styles.css с буквой M, в графе сверху коммит Revert, в файле строка font-size: 40px
После --soft. Коммита «Проба reset» в графе нет, сверху revert. Файл стоит в Staged Changes. В строке 10 по-прежнему 40px.

Убран только коммит. Правка отобрана в индекс, поэтому для нового коммита достаточно нажать Commit. Кнопка Undo Last Commit из шага 4 выполняет эту же команду.

Возвращаем пробный коммит: пишем в поле сообщение «Проба reset» и нажимаем ✓ Commit. То же в терминале:

терминал в папке проектато же, что Commit
git commit -m "Проба reset"

Режим --mixed: правка остаётся только в файле

Этот режим включается, если ключ не указать совсем:

терминалкоманда и ответ
git reset HEAD~
Unstaged changes after reset:
M	styles.css
Окно VS Code после reset без ключа: в Changes файл styles.css с буквой M, раздела Staged Changes нет, в файле строка font-size: 40px
После --mixed. Коммита нет, файл стоит в Changes, раздела Staged Changes нет. В строке 10 по-прежнему 40px.

Убраны коммит и отбор в индекс. Файл не менялся, но для нового коммита его нужно отобрать заново. Возвращаем пробный коммит ещё раз: плюс у файла, сообщение «Проба reset», ✓ Commit.

терминал в папке проектато же, что плюс и Commit
git add styles.css
git commit -m "Проба reset"

Режим --hard: правка стёрта в индексе и в файле

Последний режим стирает и коммит, и его правку:

терминалкоманда и ответ
git reset --hard HEAD~
HEAD is now at 9f5312d Revert "Увеличил отступы страницы"
Окно VS Code после reset --hard: список изменений пуст, на кнопке Sync Changes 10, в файле строка font-size: 34px
После --hard. Коммита нет, список изменений пуст, на кнопке снова 10↑. В строке 10 теперь 34px: Git перезаписал файл.

Убраны коммит, отбор в индекс и сама правка в файле. Строка font-size: 40px осталась только в стёртом коммите. Если бы в папке лежали другие незакоммиченные правки, они бы тоже стёрлись, и вернуть их нельзя.

Итог трёх опытов:

КомандаПанель Source ControlФайл styles.cssКак сделать коммит заново
git reset --soft HEAD~Файл в Staged Changes40pxCommit
git reset HEAD~
(то же, что --mixed)
Файл в Changes40pxПлюс у файла и Commit
git reset --hard HEAD~Список пуст34pxПравки нет: её вносят заново или возвращают коммит по журналу (шаг 7)
На полях: reset и отправленные коммиты

Reset убирает коммиты из истории, как amend заменяет последний. Для коммитов, которые уже на GitHub, действует то же правило: история у вас и на сервере разойдётся. Отправленные коммиты отменяют через revert.

Что получилось.Пробный коммит стёрт вместе с правкой, размер заголовка снова 34px. Коммит revert из шага 5 на месте.

§8Шаг 7. Вернуть стёртый коммит

Пробный коммит стёрт командой reset --hard. Git при этом его не удалил: коммит можно найти по журналу и вернуть.

Git ведёт журнал действий с историей: каждый коммит, amend, reset и revert записывается туда вместе с именем коммита, который после этого действия стал текущим. Журнал называется reflog и хранится только на вашем компьютере.

терминалкоманда и ответ
git reflog -12
9f5312d HEAD@{0}: reset: moving to HEAD~
837cf71 HEAD@{1}: commit: Проба reset
9f5312d HEAD@{2}: reset: moving to HEAD~
62b060b HEAD@{3}: commit: Проба reset
9f5312d HEAD@{4}: reset: moving to HEAD~
df14b2a HEAD@{5}: commit: Проба reset
9f5312d HEAD@{6}: revert: Revert "Увеличил отступы страницы"
91fb5a6 HEAD@{7}: commit: Покрасил заголовок
7531a77 HEAD@{8}: reset: moving to HEAD~
17d24cf HEAD@{9}: commit: Покрасил заголовок
7531a77 HEAD@{10}: commit (amend): Добавил ссылку на GitHub
171a006 HEAD@{11}: commit: Добавил сылку на GitHub

Журнал читается сверху вниз, от последнего действия к первому. Запись HEAD@{1} означает «состояние на одно действие раньше», HEAD@{2} — на два, и так далее. В журнале все шаги этой главы, снизу вверх:

  • HEAD@{11} и HEAD@{10} — коммит с опечаткой и его исправление через amend (шаг 3);
  • HEAD@{9} и HEAD@{8} — поспешный коммит и Undo Last Commit. VS Code записал отмену как reset: moving to HEAD~ (шаг 4);
  • HEAD@{7} — тот же коммит, сделанный заново, HEAD@{6} — revert (шаг 5);
  • HEAD@{5}–HEAD@{0} — три опыта шага 6: каждый раз коммит «Проба reset» и reset. У трёх пробных коммитов разные имена — df14b2a, 62b060b, 837cf71: это три отдельных коммита.

Последний пробный коммит 837cf71 стоит в строке HEAD@{1}. Reset делает последним любой указанный коммит, в том числе убранный из истории. Возвращаемся к нему той же командой reset --hard, указав вместо HEAD~ запись из журнала:

терминалкоманда и ответ
git reset --hard 'HEAD@{1}'
HEAD is now at 837cf71 Проба reset
Панель: кнопка Sync Changes 11↑, в графе сверху снова коммит «Проба reset»
Коммит «Проба reset» вернулся в граф, на кнопке снова 11↑.

Ключ --hard здесь сработал так же, как в шаге 6: файл приведён к коммиту, который стал последним. В styles.css снова 40px, список изменений пуст.

Вместо HEAD@{1} можно написать имя коммита из журнала: git reset --hard 837cf71. Кавычки вокруг HEAD@{1} нужны в PowerShell: без них фигурные скобки там означают другое. В терминале macOS и Linux команда работает и без кавычек.

Так же достаются старые версии после amend: git show 171a006 покажет коммит с опечаткой, хотя в истории его уже нет. Git хранит такие коммиты не меньше тридцати дней и только потом удаляет.

Пробный коммит был нужен только для опыта. Убираем его той же командой, что в шаге 6:

терминалкоманда и ответ
git reset --hard HEAD~
HEAD is now at 9f5312d Revert "Увеличил отступы страницы"

Проверяем, что проект вернулся к состоянию после шага 5:

терминалкоманды и ответ
git log --oneline -3
9f5312d Revert "Увеличил отступы страницы"
91fb5a6 Покрасил заголовок
7531a77 Добавил ссылку на GitHub
git status --short

Сверху коммит revert, пробного коммита в списке нет. git status --short ничего не напечатал: незакоммиченных правок нет, в styles.css снова font-size: 34px. В журнале git reflog при этом прибавились две записи: возврат к 837cf71 и последний reset.

Что получилось.Стёртый коммит возвращён по журналу и затем убран. Проект в том же состоянии, что после шага 5: четырнадцать коммитов, десять из них на GitHub ещё не отправлены, сверху коммит revert.

§9Справка: какой способ выбрать

Что нужноВ VS CodeКомандаМеняет историю
Выбросить правку в файлеКруговая стрелка Discard Changes у файлаgit restore файлНет. Правка теряется
Выбросить правку в одном участкеКруговая стрелка в окне сравненияgit restore -p файлНет
Удалить новый файлКруговая стрелка у файла с U → Move to Trashgit clean -f файлНет
Вернуть файл из индекса− у файла в Staged Changesgit restore --staged файлНет. Правка остаётся в файле
Исправить последний коммитСтрелка у Commit → Commit (Amend)git commit --amendДа, только до отправки
Отменить последний коммит, правки сохранить«…» → Commit → Undo Last Commitgit reset --soft HEAD~Да, только до отправки
Отменить старый или отправленный коммитКнопки нетgit revert --no-edit хешДобавляет коммит
Вернуть проект к коммиту, всё новее стеретьКнопки нетgit reset --hard хешДа, только до отправки. Незакоммиченное теряется
Найти стёртый коммитКнопки нетgit reflogНет, только показывает

Перед отменой

Где правкаВ файле, в индексе, в последнем коммите или в старом — от этого зависит способ.
Отправлен ли коммитЕсли коммит в графе ниже метки origin/main, он уже на GitHub: только revert.
Нет ли в папке нужного незакоммиченногоDiscard и reset --hard стирают его без возможности вернуть. Если сомневаетесь, сначала сделайте коммит.

§10Частые вопросы

Обычно думаютВсё, что сделано в Git, можно вернуть.
На самом делеВернуть можно то, что было в коммите: для этого есть история и reflog. Правка, которая не попала ни в один коммит, после Discard или reset --hard пропадает насовсем.
Обычно думаютRevert удаляет коммит из истории.
На самом делеRevert добавляет новый коммит с обратными изменениями. Отменённый коммит остаётся в истории, и в сообщении нового коммита указано его имя.
Обычно думаютAmend дописывает изменения в существующий коммит.
На самом делеAmend создаёт новый коммит и ставит его на место последнего. У нового коммита другое имя, старый остаётся в reflog.
Обычно думаютUndo Last Commit стирает сделанную работу.
На самом делеКоммит убирается, а его правки возвращаются в Staged Changes вместе с сообщением. Это git reset --soft: файлы не меняются.
Обычно думаютReset и revert делают одно и то же.
На самом делеReset делает указанный коммит последним, и коммиты новее него пропадают из истории. Revert оставляет историю как есть и добавляет коммит. Для отправленных коммитов подходит только revert.

§11Проверьте себя

Вы полчаса правили index.html, результат не нужен. Коммита после начала правок не было. Как вернуть файл?

Круговая стрелка Discard Changes у файла в панели или git restore index.html. Файл станет таким, как в последнем коммите. Выброшенные правки не вернуть.

Коммит только что сделан, в сообщении опечатка. На GitHub его ещё не отправляли. Что сделать?

Написать правильное сообщение в поле и выбрать Commit (Amend) в меню кнопки Commit, или выполнить git commit --amend -m "правильный текст". Последний коммит заменится новым.

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

git revert --no-edit хеш. Коммит уже на сервере, поэтому историю переписывать нельзя. Revert добавит новый коммит с обратными изменениями, и его можно отправить как обычно.

Чем git reset --soft HEAD~ отличается от git reset --hard HEAD~?

Оба убирают последний коммит из истории. С --soft его правки остаются в индексе, готовые к новому коммиту. С --hard они стираются, а файлы становятся такими, как в предыдущем коммите.

После git reset --hard пропал нужный коммит. Как его вернуть?

Выполнить git reflog, найти строку с этим коммитом и вернуться к нему: git reset --hard имя-коммита или git reset --hard 'HEAD@{N}'.

Почему после amend у коммита другое имя?

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

§12Шпаргалка по главе

Правка в файле Discard Changes git restore файл
Новый файл Discard → Move to Trash git clean -f файл
Последний коммит Commit (Amend) Undo Last Commit git commit --amend git reset --soft HEAD~
Старый коммит git revert --no-edit хеш добавляет коммит
Режимы reset --soft — правки в индексе --mixed — правки в файлах --hard — правки стёрты
Вернуть стёртое git reflog git reset --hard хеш

Итог главы

Способ отмены выбирают по тому, где находится правка. Discard Changes, в терминале git restore, возвращает файл к последнему коммиту, и выброшенная правка пропадает насовсем. Новый файл VS Code переносит в корзину. Последний коммит исправляет Commit (Amend) — он создаёт новый коммит на месте старого, с другим именем, — а Undo Last Commit убирает коммит и возвращает его правки в индекс. Старый коммит отменяет git revert: он добавляет коммит с обратными изменениями и ничего не удаляет из истории, поэтому подходит и для коммитов на GitHub. Amend, Undo Last Commit и git reset переписывают историю, их применяют только к неотправленным коммитам. reset --hard стирает и коммиты, и незакоммиченные правки. Коммиты после него можно найти в журнале git reflog и вернуть. На этом модуль 2 закончен. Следующий модуль посвящён веткам: как вести несколько линий работы в одном проекте и собирать их вместе.

Откуда взяты факты
  1. Кадры и ответы команд получены на клоне проекта sait-vizitka в Git версии 2.51.1 и VS Code версии 1.138.
  2. Команды git restore, git clean, git commit --amend — документация git-restore, git-clean, git-commit.
  3. Режимы git reset и работа git revert — документация git-reset и git-revert.
  4. Журнал git reflog и срок хранения записей — документация git-reflog и git-gc (параметр gc.reflogExpireUnreachable, по умолчанию 30 дней).
  5. Кнопки Discard Changes, Commit (Amend) и Undo Last Commit — документация VS Code, раздел Source Control.
Глава 2.6 · Отмена изменений · Справочник по Git и GitHub Автор: Макаров Максим Николаевич