Страницы

Поиск по вопросам

вторник, 30 октября 2018 г.

git: как “нейтрализовать” коммит?

Есть бажный коммит, после него еще несколько коммитов, которые пока нельзя деплоить. Необходимо как-то выкинуть изменения этого коммита из проекта, затем развернуться из следующего перед ним коммита, а потом продолжить работу с последнего коммита, но исключая изменения бажного. Дайте, пожалуйста, последовательность действий.


Ответ

затем развернуться из следующего перед ним коммита
Подробная информация в вопросе Как вернуться (откатиться) к более раннему коммиту?. Кратко - когда всё почините, сможете просто перейти в нужный коммит или даже новую ветку создать в нём.
выкинуть изменения этого коммита из проекта,
Про это будет весь ответ дальше.
Самый важный вопрос:
Успели ли вы запушить эту ветку на удалённый репозиторий? Возможно ли, что другой разработчик уже получил с удаленного репозитория коммит B или один из последующих и продолжает работать над ним. Если ответ - да, то в общем случае желательно revert, но не rebase, cherry-pick и прочее, что переписывает историю Git, так как это создает большие проблемы с интеграцией.
Поскольку вам нужно развернуться из C, не содержащего изменений из B, то почти наверняка историю переписывать придётся. В таком случае предупредите коллег заранее. Им придётся аналогичным образом ребейзить свои изменения, например с коммита D на D'
Обозначим начальную ситуацию на следующей схеме:
A - B - C - D ↑ branchname (HEAD)
A, B, C, D — коммиты в ветке branchname. B - "плохой коммит", его нам нужно удалить. Коммит C нужно задеплоить. (HEAD) — местоположение указателя HEAD. ↑ обозначает коммит, на который указывает определенная ветка или указатель.
Вариант 1 - через rebase -i
Плюс - меньше мусора в истории. И можно гордиться, что освоил rebase. Плюс - позволяет развернуться из С, не содержащего изменений из B. Минус - переписывает историю, опасно при командной работе.
Команды:
git checkout branchname git rebase -i A git checkout -b deployme C'
В качестве A мы указываем коммит сразу перед тем, который будем исключать. При этом A останется на месте. Откроется редактор, в нём будут коммиты в обратном порядке. Меняем pick на drop, обозначая что мы хотим выкинуть этот коммит.
drop 323d4e6 comment for B pick 31da2b9 comment for C pick 24d420c comment for D
# Rebase c8893f9..24d420c onto c8893f9 (3 command(s)) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit
Результат
A - C' - D' ↑ branchname (HEAD) ↑ deployme (HEAD)
У новых коммитов теперь другой предок, поэтому это - другие, новые коммиты (хотя содержимое то же за вычетом B). Новая ветка deployme смотрит на коммит C'
Вариант 2 - через revert
Плюс - не переписывает историю и безопасен при командной работе. Проще чем rebase. Минус - ошибочный коммит останется в истории. Плохо, если там информация, которую нельзя показывать или код, который стыдно публиковать. :) Минус - коммит C по-прежнему будет содержать изменения от B
Команды:
git checkout branchname git revert B
Результат:
A - B - C - D - xB ↑ branchname (HEAD)
Новый коммит xB содержит изменения, которые отменяют изменения коммита B. Однако у нас есть проблема: коммит C по-прежнему содержит изменения от B
Немного подробнее о revert
Документация тут, пятый пункт
В дополнение:
Если в процессе ошибётесь или протеряете не тот коммит: Как отменить откат изменений (восстановить потерянный коммит)? Заодно, при желании, можно что-нибудь сделать и с другими коммитами: Как разделить/склеить старый комит?

Комментариев нет:

Отправить комментарий