December 23, 2021

5 способов исправить ошибки с помощью Git

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

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

Отмена некоторых локальных изменений

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

Давайте рассмотрим пример сценария с некоторыми "локальными" изменениями (они же изменения, которые мы еще не коммитили).

У нас есть проблемный файл general.css. Давайте сначала разберемся с ним. Изменения, которые мы внесли, пошли в совершенно неправильном направлении. Давайте отменим все эти действия и воссоздадим последнее закоммиченное состояние этого файла:

$ git restore css/general.css

Обратите внимание, что в качестве альтернативы я мог бы использовать команду git checkout для достижения того же результата. Но поскольку у git checkout так много разных задач и значений, я предпочитаю более новую команду git restore (которая ориентирована исключительно на такие задачи).

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

И снова на помощь приходит git restore - но на этот раз с помощью -p, потому что мы хотим спуститься на уровень "patch":

$ git restore -p index.html

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

Вы заметите, что я ввел "n" для первого фрагмента (чтобы сохранить его) и "y" для второго фрагмента (чтобы отбросить его). После завершения процесса видно, что сохранился только первый, необходимый фрагмент изменений - как мы и хотели!

Возврат определенного файла в предыдущее состояние

Иногда требуется восстановить определенный файл до определенной версии. Например, вы знаете, что index.html работал нормально в какой-то более ранний момент времени, но теперь это не так. Тогда вы хотите вернуть время назад, но только для этого конкретного файла, а не для всего проекта!

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

$ git log -- index.html

Оно показывает нам только те коммиты, в которых был изменен index.html, что довольно полезно для поиска ошибки.

Если вам нужно больше информации и вы хотите посмотреть на содержимое этих коммитов, вы можете попросить Git показать вам фактические изменения в этих коммитах с помощью флага -p:

$ git log -p -- index.html

После того как мы нашли неправильный коммит, в котором мы испортили наш чудесный маленький файл, мы можем исправить ошибку. Мы сделаем это, восстановив файл на версии, предшествующей неверному коммиту! Это важно: мы хотим восстанавливать файл не в том коммите, в котором была допущена ошибка, а в последнем хорошем состоянии - то есть за один коммит до этого!

$ git checkout <bad-commit-hash>~1 -- index.html

Добавление ~1 к хэшу плохого коммита даст указание Git'у сделать именно это: перейти на одну версию раньше ссылающегося коммита.

Выполнив эту команду, вы увидите, что index.html в вашей локальной рабочей версии изменен: Git восстановил для нас последнюю правильную версию файла!

Восстановление потерянной версии с помощью Reflog

Еще одним замечательным инструментом исправления в арсенале Git'а является "Reflog". Его можно рассматривать как журнал, в котором Git протоколирует все движения указателя HEAD, происходящие в вашем локальном репозитории - такие вещи, как коммиты, чек-ауты, слияния и ребейзы, cherry-pick'и и сбросы. Все наиболее интересные действия прекрасно отображаются в этом журнале!

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

Допустим, вы были уверены, что ваши последние коммиты никуда не годятся: вы хотели от них избавиться и поэтому использовали git reset для возврата к предыдущей версии. В результате "плохие" коммиты исчезли из вашей истории коммитов - как вы и хотели.

Дальше вы замечаете, что это была плохая идея: на самом деле коммиты были не так уж плохи! Но плохая новость, конечно, в том, что вы только что стёрли их из истории коммитов вашего репозитория!

Это классический случай для инструмента Git'а Reflog! Давайте посмотрим, как он может спасти вас:

$ git reflog
  • Во-первых, Reflog очень легко открыть: для этого достаточно выполнить простой git reflog.
  • Во-вторых, вы заметите, что записанные состояния отсортированы в хронологическом порядке, причем самое новое находится вверху.
  • Если вы присмотритесь, то увидите, что самый верхний (то есть самый новый) элемент - это действие "сброс". Что мы и сделали 20 секунд назад. Очевидно, журнал работает.
  • Если теперь мы захотим отменить наш непроизвольный "сброс", мы можем просто вернуться к прежнему состоянию - что также аккуратно показано здесь! Мы можем просто скопировать хэш коммита этого предыдущего состояния в буфер обмена и продолжить работу оттуда.

Чтобы восстановить прежнее состояние, мы можем либо снова использовать git reset, либо просто создать новую ветку:

$ git branch happy-ending e5b19e4

Как мы можем с радостью убедиться, наша новая ветка содержит коммиты, которые, как мы думали, потеряли из-за случайной ошибки при git reset!

Восстановление удаленной ветки

Рефлог может быть полезен и в других ситуациях. Например, когда вы по неосторожности удалили ветку, которую действительно (!) не следовало удалять. Давайте посмотрим на наш пример:

$ git branch
  * feature/analytics
    master

Представим, что наш заказчик проекта говорит нам, что прекрасная функция analytics, над которой мы работали, больше не нужна. Конечно же, мы удалим соответствующую ветку feature/analytics!

feature/analytics - это наша текущая ветка HEAD. Чтобы иметь возможность удалить ее, мы должны сначала переключиться на другую ветку:

$ git checkout master
$ git branch -d feature/analytics
error: The branch 'feature/analytics' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature/analytics'.

Git говорит нам, что мы собираемся сделать что-то действительно значимое: поскольку feature/analytics содержит уникальные коммиты, которые больше нигде не присутствуют, удаление этой функции уничтожит некоторые (потенциально ценные) данные. Что ж... поскольку функция больше не нужна, мы можем продолжать:

$ git branch -D feature/analytics
Deleted branch feature/analytics (was b1c249b).

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

И снова мы сталкиваемся с неприятной ситуацией, когда мы могли потерять ценные данные! Так что давайте посмотрим, сможет ли Reflog спасти нас еще раз:

$ git reflog

Прежде чем мы решили удалить нашу ветку, нам пришлось выполнить git checkout, чтобы от нее переключиться (поскольку Git не позволяет удалять текущую ветку). Конечно, эта процедура также была зарегистрирована в Reflog. Чтобы восстановить удалённую ветку, мы можем теперь просто взять предыдущее состояние в качестве отправной точки для новой ветки:

$ git branch feature/analytics b1c249b

И вуаля: наша ветка восстановилась!

Перенос коммита в другую ветку

В заключение приведем еще одну классику из серии "О нет!": коммит в не той ветке.

Сегодня во многих командах действует правило, запрещающее коммиты непосредственно в давно работающие ветки, такие как "main", "master" или "develop". Как правило, новые коммиты должны попадать в эти ветки только через слияния/ребейзы. И все же мы иногда забываем и делаем коммит напрямую...

Давайте рассмотрим следующий сценарий в качестве примера.

Мы должны были сделать коммит на feature/newsletter, но по неосторожности нажали на master. Давайте рассмотрим решение проблемы шаг за шагом.

Во-первых, мы должны убедиться, что на этот раз мы находимся в правильной ветке, поэтому мы проверяем feature/newsletter:

$ git checkout feature/newsletter

Теперь мы можем безопасно перенести этот коммит с помощью команды cherry-pick:

$ git cherry-pick 26bf1b48

Мы можем взглянуть на feature/newsletter и увидим, что теперь коммит существует и здесь.

Пока всё хорошо. Но cherry-pick не удалил коммит из master ветки, где его никогда не должно было быть. Поэтому нам придётся навести порядок и там:

$ git checkout master
$ git reset --hard HEAD~1

Мы переключаемся обратно на master и затем используем git reset, чтобы удалить ненужный коммит из истории. Наконец, все вновь исправлено.

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

Источник: https://www.sitepoint.com/5-ways-to-undo-mistakes-with-git/