Глиф Учебники

Урок 4. История и отмена изменений

Курс «Git» · урок 4 из 9 · 4 минуты чтения

Главная польза Git — в истории: можно посмотреть, что менялось, и отменить неудачное. В этом уроке — как читать историю и как отменять изменения на каждом из трёх этапов: в рабочей папке, в индексе и в коммитах.

Что изменилось: git diff

git diff без параметров показывает изменения в рабочей папке, которые ещё не добавлены в индекс:

Терминал
$ git diff
diff --git a/README.md b/README.md
index 81b9246..bc05270 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
 # Список дел
 - купить хлеб
+- купить молоко

Строки с + добавлены, с - удалены, без знака — неизменный контекст вокруг. Другие варианты:

КомандаЧто показывает
git diffИзменения, ещё не добавленные в индекс
git diff --stagedТо, что уже в индексе и войдёт в коммит
git diff 5e634b3 3e677f8Разницу между двумя коммитами
git diff HEAD~1Разницу между прошлым коммитом и рабочей папкой

HEAD — текущий коммит, на котором вы стоите. HEAD~1 — коммит перед ним, HEAD~2 — на два раньше.

git diff git diff --staged git diff HEAD~1 Рабочая папка файлы на диске Индекс то, что войдёт в коммит Коммиты HEAD~2 HEAD~1 HEAD
git diff сравнивает рабочую папку с индексом, git diff --staged — индекс с последним коммитом HEAD, а git diff HEAD~1 — рабочую папку с коммитом перед HEAD.

Что было: git log и git show

Отменить правки в файле: git restore

Файл испорчен, а коммита с правками ещё нет — вернуть его к состоянию последнего коммита:

Bash
git restore README.md

git restore файл выбрасывает незакоммиченные правки в этом файле без возврата: их нет ни в истории, ни в корзине. Если правки могут пригодиться, сначала сделайте коммит или отложите их через git stash.

Файл уже добавлен в индекс, а в коммит его включать не нужно — уберите из индекса, правки в самом файле останутся:

Bash
git restore --staged README.md

Вернуть файл к версии из любого коммита — git restore --source=5e634b3 README.md.

Команды git restore и git switch появились в Git 2.23. В старых версиях и старых инструкциях то же делают git checkout -- файл и git reset HEAD файл. Подсказки самого git status всегда соответствуют вашей версии.

Убрать последние коммиты: git reset

git reset переносит ветку на более ранний коммит — более поздние коммиты из неё уходят. Что станет с их изменениями, решает ключ:

КомандаКоммитИзменения из него
git reset --soft HEAD~1убраностаются в файлах и в индексе
git reset HEAD~1убраностаются в файлах, но не в индексе
git reset --hard HEAD~1убранудалены вместе со всеми незакоммиченными правками
git reset --soft HEAD~1 коммит убран индекс изменения файлы изменения можно поправить и закоммитить заново git reset HEAD~1 коммит убран индекс нет файлы изменения перед коммитом снова нужен git add git reset --hard HEAD~1 коммит убран индекс нет файлы удалены и незакоммиченные правки тоже
Все три вида git reset убирают последний коммит, но с --soft его изменения остаются в индексе и в файлах, без ключа — только в файлах, а с --hard удаляются вместе с незакоммиченными правками.

Самый частый случай — коммит сделан рано или с ошибкой в сообщении: git reset --soft HEAD~1 возвращает всё в индекс, можно поправить и закоммитить заново.

Терминал
$ git reset HEAD~1
Unstaged changes after reset:
M    README.md

git reset --hard стирает не только коммиты, но и все незакоммиченные правки в рабочей папке, ничего не спрашивая. Перед ним проверьте git status и git diff. Убранные коммиты ещё можно найти через git reflog (ниже), а незакоммиченные правки — уже нет.

Отменить отправленный коммит: git revert

reset переписывает историю. Если коммит уже отправлен на сервер и его забрали другие, переписывать нельзя — у них останется старая история, и при следующем обмене всё перепутается. Для этого есть git revert: он создаёт новый коммит, который делает обратные изменения.

Bash
git revert 3e677f8

Git откроет редактор с готовым сообщением Revert "…" — сохраните и закройте его (без редактора — git revert --no-edit 3e677f8). В истории останутся оба коммита: исходный и отменяющий, — и это нормально: видно, что и когда было отменено.

git reset HEAD~1 main 5e634b3 3e677f8 История переписана коммит 3e677f8 ушёл из ветки main, а на сервере и у коллег остался — при обмене всё перепутается git revert 3e677f8 main 5e634b3 3e677f8 новый Revert "Добавлен первый пункт" делает обратные изменения, оба коммита остаются в истории — так отменяют уже отправленное
git reset HEAD~1 переносит ветку main назад, и коммит 3e677f8 уходит из неё, а git revert 3e677f8 добавляет новый коммит с обратными изменениями, и оба коммита остаются в истории.

Спасательный круг: git reflog

Git записывает, куда указывал HEAD после каждой команды: коммиты, переключения, reset. Даже после git reset --hard старые коммиты какое-то время лежат в репозитории:

Терминал
$ git reflog
27ca428 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
00bcdfd HEAD@{1}: commit: Третий пункт
27ca428 (HEAD -> main) HEAD@{2}: commit: Второй пункт

Нужный коммит — 00bcdfd, вернуть ветку на него:

Bash
git reset --hard 00bcdfd

Журнал reflog — только в вашей копии репозитория, на сервер он не отправляется. Записи в нём хранятся не вечно: о коммитах, которых нет ни в одной ветке, — по умолчанию около месяца. Восстанавливайте сразу.

Что выбрать

ЗадачаКоманда
Выбросить правки в файле до коммитаgit restore файл
Убрать файл из индексаgit restore --staged файл
Переделать последний коммит, который ещё не отправленgit reset --soft HEAD~1
Отменить коммит, который уже на сервереgit revert хеш
Вернуть то, что убрал resetgit reflog, потом git reset --hard хеш

Коротко

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

1. Что показывает git diff --staged?
2. Коммит уже отправлен на сервер, коллеги его забрали. Как его отменить?
3. Чем git reset --soft HEAD~1 отличается от git reset --hard HEAD~1?
4. Как найти коммит, потерянный после git reset --hard?