Урок 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 log и git show
git log --oneline— история по строке на коммит;git log -n 5— только пять последних коммитов;git log --oneline --graph --all— все ветки сразу, с линиями слияний;git show 3e677f8— один коммит целиком: автор, дата, сообщение и изменения.
Отменить правки в файле: git restore
Файл испорчен, а коммита с правками ещё нет — вернуть его к состоянию последнего коммита:
git restore README.mdgit restore файл выбрасывает незакоммиченные правки в этом файле без возврата: их нет ни в истории, ни в корзине. Если правки могут пригодиться, сначала сделайте коммит или отложите их через git stash.
Файл уже добавлен в индекс, а в коммит его включать не нужно — уберите из индекса, правки в самом файле останутся:
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
Unstaged changes after reset:
M README.mdgit reset --hard стирает не только коммиты, но и все незакоммиченные правки в рабочей папке, ничего не спрашивая. Перед ним проверьте git status и git diff. Убранные коммиты ещё можно найти через git reflog (ниже), а незакоммиченные правки — уже нет.
Отменить отправленный коммит: git revert
reset переписывает историю. Если коммит уже отправлен на сервер и его забрали другие, переписывать нельзя — у них останется старая история, и при следующем обмене всё перепутается. Для этого есть git revert: он создаёт новый коммит, который делает обратные изменения.
git revert 3e677f8Git откроет редактор с готовым сообщением Revert "…" — сохраните и закройте его (без редактора — git revert --no-edit 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, вернуть ветку на него:
git reset --hard 00bcdfdЖурнал reflog — только в вашей копии репозитория, на сервер он не отправляется. Записи в нём хранятся не вечно: о коммитах, которых нет ни в одной ветке, — по умолчанию около месяца. Восстанавливайте сразу.
Что выбрать
| Задача | Команда |
|---|---|
| Выбросить правки в файле до коммита | git restore файл |
| Убрать файл из индекса | git restore --staged файл |
| Переделать последний коммит, который ещё не отправлен | git reset --soft HEAD~1 |
| Отменить коммит, который уже на сервере | git revert хеш |
Вернуть то, что убрал reset | git reflog, потом git reset --hard хеш |
Коротко
git diff— неиндексированные правки,git diff --staged— то, что в индексе.git restore файл— выбросить правки,--staged— убрать из индекса.git reset --soft HEAD~1убирает коммит, оставляя изменения;--hardудаляет их.- Отправленные коммиты отменяют через
git revert— он добавляет обратный коммит. git reflogпомогает найти коммит после неудачногоreset.