Читать историю
История нужна, чтобы её читать. Вот основные способы.
git log --oneline # коротко: идентификатор и первая строка
git log # подробно: автор, дата, сообщение целиком
git log -p # с изменениями каждого коммита
git log -- index.html # только коммиты, менявшие этот файл
git show c92f3a1 # один коммит: сообщение и дифф
git diff b5e0d44 d41e7b0 # разница между двумя коммитами
Самые полезные на практике — история одного файла и разница между двумя коммитами. Первая отвечает на вопрос «кто и когда трогал этот файл», вторая — «что изменилось между вчерашней рабочей версией и сегодняшней сломанной».
Ещё один вопрос, который Git решает за секунды, — кто написал эту строку. Команда git blame с именем файла показывает у каждой строки коммит, автора и дату последнего изменения. Название пугающее («обвинить»), но смысл мирный: найти коммит, а по нему — сообщение с объяснением, зачем строка такая.
Многие редакторы и сервисы показывают то же самое кнопками: историю файла, автора строки, разницу версий. За ними стоят те же команды — и тот же вывод.
Несохранённое: restore
Откат начинается с вопроса: где лежит то, что нужно отменить? От ответа зависит команда.
Правка ещё не в коммите. Вы что-то поменяли в файле и хотите вернуть его к последнему сохранённому состоянию:
git restore index.html # вернуть файл к последнему коммиту
git restore --staged index.html # убрать из индекса, правку оставить
Первая команда выбрасывает несохранённые правки в файле. Вторая — мягкая: она только вынимает файл из индекса, а сами правки остаются в рабочей папке. Её используют, когда случайно добавили не то.
В старых инструкциях вместо git restore встречается git checkout -- файл: это то же самое, просто прежняя форма команды. У checkout много разных обязанностей, поэтому для понятности их разнесли по новым командам — restore для файлов и switch для веток.
Отправленное: revert
Правка уже в коммите, и коммит отправлен — у коллег он есть. Здесь действует второе правило курса: общую историю не переписывают. Инструмент для такого отката — git revert.
git revert c92f3a1
Команда создаёт новый коммит, который делает изменение, обратное указанному: добавленные строки удаляет, удалённые возвращает. Старый коммит остаётся в истории, а сверху появляется его отмена.
Почему не удалить ошибочный коммит? Потому что у коллег он уже есть, и на нём могли вырасти их коммиты. Если убрать его из общей истории, их копии перестанут совпадать с общей, и каждому придётся разбираться, что случилось. revert ничего не ломает: это обычный новый коммит, который все получат как любой другой.
Если отменяемый коммит затрагивает строки, которые потом меняли ещё раз, revert может остановиться на конфликте — Git не сможет сам решить, как отменить. Как разбирать конфликты, вы узнаете в шестом уроке; принцип тот же.
А если нужно просто посмотреть, как проект выглядел раньше, откатывать ничего не надо. Команда git switch --detach с идентификатором коммита переносит рабочую папку в прошлое «на посмотреть»: можно запустить старую версию и сравнить. Вернуться — git switch main. Git при этом предупредит о «detached HEAD»: это значит, что вы не в ветке, и новые коммиты здесь легко потерять. Подробнее — в уроке 10.
Своё неотправленное: reset
Коммит сделан, но никуда не отправлен — он есть только у вас. Тогда можно переписать собственную историю, и для этого есть git reset. Он переставляет текущую ветку на указанный коммит, как будто последующих не было.
git reset --soft HEAD~1 # убрать последний коммит, правки оставить в индексе
git reset HEAD~1 # то же, правки оставить в рабочей папке
git reset --hard HEAD~1 # убрать коммит и выбросить его правки
HEAD~1 значит «на один коммит назад от текущего». Три режима отличаются тем, что происходит с изменениями из убранного коммита:
| Режим | Коммит | Правки |
|---|---|---|
| --soft | убран | в индексе, готовы к новому коммиту |
| без ключа | убран | в рабочей папке |
| --hard | убран | выброшены |
Мягкие режимы удобны, чтобы переделать коммит: разбить его на два, поправить сообщение, добавить забытый файл. Для последнего есть и короткий путь — git commit --amend: он заменяет последний коммит новым, включив в него то, что сейчас в индексе, и позволяет поправить сообщение.
Коротко о выборе: несохранённое — restore; отправленное — revert; своё неотправленное — reset или --amend. Если сомневаетесь, отправлен ли коммит, — считайте, что отправлен, и берите revert: он безопасен всегда.