Не только разработчикам-новичкам, но и ярым профессионалам приходится прибегать к отмене каких-либо изменений. И тогда, первое, что приходит на ум, — это команда git revert
, как самый безопасный способ. И тут есть подводные камни, про которые я хочу рассказать.
Возьмем простую ситуацию: разработчик решает реализовать математические функции. Но на половине пути понимает, что данную задачу было бы хорошо декомпозировать, допустим, на две подзадачи:
- Реализовать арифметические операции (сложение, вычитание, деление и т.д.)
- Реализовать числовые операции (максимальное значение, минимальное значение, модуль числа и т.д.)
Проверять будет проще да и тестировать. Но он уже начал ее реализовывать, коммиты уже созданы, и что же делать? Не переписывать же!
Рассмотрим дерево коммитов. Видим, что наш разработчик создал ветку functions
, класс Arithmetic, отвечающий за реализацию арифметических операций (коммит А), и класс Numerical, отвечающий за реализацию числовых операций (коммит N). Итого два класса и два коммита.
git revert
Решено, дабы ничего не переписывать, наследоваться от functions
и создать две ветки numerical
и arithmetic
. И соответственно отменить ненужные коммиты. То есть выполнить git revert N
в ветке arithmetic и git revert A
в ветке numerical. Гениально и просто!
Работа кипит и осталось дело за малым — смерджить мастер с данными ветками.
И что же мы получили? Ни класса Arithmetic, ни класса Numerical!
А все дело в том, что команда git revert
создает новый коммит с отменой изменений и не удаляет из истории коммиты. И в нашем случае после слияния веток получается 4 коммита:
A ? N ? revert A ? revert N
То есть вариант с отменой изменений с помощью команды revert
вышел нам боком.
git reset
И тут мы вспоминаем, что есть такая команда как reset
, вот она в отличии от revert
точно удаляет коммиты из истории. Но есть одно НО… она сбрасывает все коммиты до указанного. Такое поведение нам не подходит, так как мы хотим выбрать какие коммиты удалить.
git rebase
Есть еще одно решение — использовать команду git rebase
для отмены изменений.
Вернемся к моменту создания двух веток numerical
и arithmetic
и выполним
git rebase -i –root
Теперь на уровне каждого коммита, который мы хотим отменить заменим pick на drop. И тогда выбранные нами коммиты сбросятся из истории. Например в ветке numerical
:
Тогда в истории у нас останутся только нужные нам коммиты.
Теперь при слиянии веток в master
получим оба класса.
Данный метод рабочий, только при условии работы в частной ветке, но если эти манипуляции провести в общей ветке, то при публикации (git push
) git
сообщает, что ветка устарела, так как в ней отсутствуют коммиты и отменяет публикацию.
Чтобы не бороться с git, старайтесь декомпозировать задачи заранее, а то можете словить сюрприз. Сталкивались ли вы с такими ситуациям, и если да, то как выходили из них?
fshp
Ребейз на root? Да вы, батенька, извращенец.
Пожалуйста, не пытайтесь это повторить в реальном репозитории. Используйте конкретную базу для ребейза.
ragann Автор
В моем случае, когда количество коммитов можно посчитать на пальцах одной руки, то почему бы нет? Но в реальной жизни, возможно, да, не лучший вариант с root.
fshp
Количество коммитов это полбеды (но да, в репозитории подобному Linux это будет больно). В вашем варианте будут убраны из истории ветки мерж-коммиты (которых в вашем примере не было, а они будут в реальности), коммиты продублируются. Это в лучшем случае. В худшем вы потеряете изменения, которые были внесены в мерж-коммитах при разруливании конфликтов и сами словите их.
Можно попробовать использовать rebase-merges, однако он гораздо сложнее. В ежедневной практике никто не будет его использовать, это уже исключительные случаи.
Я люблю ребейз и постоянно его использую. Я всегда призываю своих коллег использовать ребейз фичеветок на последний мастер, у нас даже в гитлабе стоит запрет на мерж веток, которые не могут быть смержены через ff.
Однако ваша статья из разряда вредных советов. Если новичок будет выполнять ваши примеры, то в лучшем случае плюнет на все и склонирует репозиторий заново (ведь вы не рассказали как откатить изменения через reflog).
Вам бы стоило рассказать про rebase на конкретный коммит, onto rebase и пожалуй про fork-point (раз уж вы косвенно упомнули force push).