Как удалить коммиты из origin/master
Реальный случай. Один разработчик трижды слил ветку task5 c задачей в мастер и отправил изменения в репозиторий в origin/master. Спустя 3 дня другой разработчик начинает готовить релиз, находит эту ветку в мастере и понимает, что с этой веткой task5 в мастере выкатываться нельзя. Эти коммиты нужно убрать, а ветку task5 вернуть на доработку.
Навали git push -f
Все осложняется тем, что task5 лежит в мастере уже 3 дня и другие разработчики могли обновить свои локальные мастера
и получить этот злополучный task5 в свой мастер и свои новосозданные ветки, ответвленные от мастера.
Это значит, что если просто удалить коммиты и перезаписать историю через git push -f
, то любой разработчик,
кто уже подсосал изменения вновь их запушит на сервер, сам того не подозревая.
Давай git revert
Плюс как я уже писал выше, есть merge-коммиты, которые также нужно отменить. А merge-коммиты, как известно
имеют двух родителей, и такой коммит нельзя просто отменить командой git revert
, как обычный коммит.
Нужно явно указывать, какого именно родителя мы хотим отменить:
git revert abc123de -m 1
# или
git revert abc123de -m 2
И тонкость здесь в том, что если при сливании ветки разработчик разрешал некоторые конфликты, то для того, чтобы отменить merge, эти конфликты нужно разрешать в обратном порядке. В итоге, спустя несколько часов, мне так и не удалось в помощью git revert вернуть ветку в нужное состояние, все время были какие-то отличия.
Решение
В итоге было найдено решение с cherry-pick. Сперва мы все же перезаписываем мастер:
git reset --hard master~3
git push -f
После этого все ветки всех разработчиков, которые содержат коммиты task5 нужно пересоздать. Каждый разработчик обновляет перезаписанный мастер: git fetch
Находит список коммитов в своей ветке:
git log master..task12 --oneline --date-order --reverse
> e2cbdab commit message...
> 66f27c9 commit message...
> 96b4af3 commit message...
> 5d073df commit message...
> 6bfadbb commit message...
После этого находим те коммиты, которые сделал разработчик в текущей ветке (первые три коммита в нашей ситуации будут из ветки task5), и переносим их в отдельную ветку.
git checkout -b task12_new # Создаем новую чистую ветку
git cherry-pick 5d073df 6bfadbb # Переносим все коммиты из старой ветки
# в той последовательности, в которой были созданы
# На этом этапе могут возникнуть конфликты, это вполне нормально,
# разрешаем их
git branch -D task12 # После этого можно удалить старую ветку
git push origin :task12 # Отправляем удаление
git branch -m task12_new task12 # И переименовываем ветку обратно
Дополнительно
Получить список веток, которые нужно обновить (нужен список хешей коммитов, от которых избавляемся):
git branch --contains b2cbdab; git branch --contains b6f27c9; git branch --contains d6b4af3
Список коммитов в текущей ветке через пробел:
printf -- "%s " `git log master.. --oneline --pretty=format:"%h" --date-order --reverse`
К недостаткам данного метода можно отнести то, что это должен делать каждый разработчик со всеми своими ветками. Если кто-то этого не сделает, или пропустит ветку, то позже благополучно запушит изменения в мастер.
Скорее всего нечто подобное можно реализовать и через git revert
и через git rebase
, но кунг-фу автора
не хватило, чтобы осилить эти способы. git cherry-pick
был последним вариантом, который мы пробовали, и который почти
сразу у нас получился.