Комментарии 37
origin\master
простите, но всё же
origin/master
(не обратный, а прямой слэш; и все опробованные мной на Windows Git-клиенты так показывают)Вы пишете для новичков, на своём опыте осмелюсь утверждать, что их обычно путает (а то и пугает) любое несоответствие «учебника» с реальном выводом..
Очень настороженно отношусь к п.11 про взаимный merge веток. Зачем мне в моей фича-ветке свежие изменения из других веток? Мне кажется, что пока в этом нет супер острой необходимости, то ни в коем случае не надо мёржить мастер в фичу. Это сильно портит историю, тяжело потом посмотреть общий diff веток, чтобы оценить все изменения (в процессе code review, например).
Кто имеет убеждения по этому поводу, поделитесь пожалуйста своим мнением.
Хотя и тут есть исключения, ведь rebase с конфликтами — та еще боль и иногда проще создать лишний merge-коммит, вместо того, чтобы тратить время на разрешение кучи конфликтов.Слышал разные страшилки по поводу rebase и конфликтов, но на практике ни с чем подобным ни разу не столкнулся. Можете пояснить, в чём принципиальное отличие в разрешении конфликтов при merge и при rebase? Ведь конфликты в любом случае надо разрешать, и merge сам за вас это не сделает.
При мерже конфликты разрешаются один раз, так как комит один. А ребэйз — считай, по черри-пику на каждый коммит исходной ветки на новый корень. И каждый чреват конфликтами. Исправил — продолжил — снова то же самое… Особенно скучно ковырять такие конфликты, когда в двух ветках постоянно идёт работа в одном и том же месте — например, дописывается в конец один и тот же файл. У меня это скрипт миграции.
А было ещё такое, что разрешил конфликт, а гит заявил, что коммитить нечего и продолжить ребейз отказался.
Так что ребейз теряет свои прелести с ростом количества коммитов и особенно — при наличии мест, которые подвержены правкам всегда и отовсюду.
При мерже конфликты разрешаются один раз, так как комит один. А ребэйз — считай, по черри-пику на каждый коммит исходной ветки на новый корень.Но общее число конфликтов от этого ведь не меняется. Да, возможно придётся править те же самые файлы, но я вижу в этом только плюс — если коммиты делать не хаотично, а отражать ими атомарные изменения, то и пошагово разрешать конфликты куда удобней, меньше вероятность запутаться и допустить ошибку.
А зачем такие монструозные rebase? Выполнялось параллельно две, обычно не связанные между собой, работы, в конце изменения слились. Один раз исправил все конфликты, один раз проверил работоспособность, один раз с этим разобрался. В итоге есть две ветки, в каждой из которых можно пошагово проверить наличие той или иной проблемы. Или же определить, что проблема появилась именно в момент слияния.
Я часто сталкивался с мотивацией "иметь прямую историю", и в большинстве случаев только ради этого и делались rebase разной степени сложности. Однако, такой подход мне кажется лишённым смысла, он не только не даёт никакой пользы, а скорее разрушает историю, уничтожает истинный ход событий. Может, есть и другие причины так делать?
По-сути, выбор rebase vs merge — это аналогично тому, как с автотестами/ручными тестами: «перенести боль на начало» vs «отложить боль на потом» (в первом случае общее кол-во боли будет меньше).
Естественно, надо понимать, что rebase — это то, что ты делаешь в локальном репозитории, merge — это вливание готовой ветки в мастер по завершении фичи.
Что касается количества конфликтов — оно таки больше при ребейзе, так как один и тот же местный конфликт может повторяться много раз.
Сделайте один файл и две ветки. Допишите файл в конец, по 10 коммитов в каждой ветке. Сделайте ребейз. Откиньтесь на спинку табуретки и насладитесь процессом.
Ребейз вынуждает перестраивать промежуточные состояния, единственный смысл существования которых заключается в атомарности предыдущего коммита. И ради чего — ради потери структуры?
Отложите боль на потом, не ковыряйте болячку. Дождитесь перевязки, быстрее заживёт.
Я часто применяю ребейз для новой фичи, если в девелопе появились новые коммиты. И никогда — обратно в девелоп. Как не храню все файлы проекта в одной папке, так и не храню коммиты на одной линии. Девелоп у меня — только мержи по поводу окончания веток — и это тот разрез истории, который я никак не хочу терять.
Линейная история, по-моему, вещь в себе, красота ради красоты, которая может стоить много времени. А если что-то дороже обходится, а пользу реальную заменяет пользой умозрительной — нуивонафф, не буду...
А зачем такие монструозные rebase?А в чём монструозность? Обычный случай — есть ветка фичи с N коммитами. При ребейзе в некоторых коммитах возникли конфликты, последовательно их разрулил и всё.
Один раз исправил все конфликты, один раз проверил работоспособность, один раз с этим разобрался.Не понимаю, в чём принципиальное различие — исправить N конфликтов в одном коммите мержа или те же N конфликтов в нескольких коммитах при ребейзе. Количество работы то же самое, но в случае ребейза решаешь конфликты только одного коммита за раз, что лично я нахожу весьма удобным, т.к. делается это в порядке хронологии, небольшими порциями и у каждой порции есть конкретный контекст. В случае большого количества изменений это порой единственный способ не запутаться при мерже.
В итоге есть две ветки, в каждой из которых можно пошагово проверить наличие той или иной проблемы. Или же определить, что проблема появилась именно в момент слияния.В случае ребейза слияние будет распределено по коммитам, т.е. проверять нужно будет на один шаг меньше. А в случае проблем сразу понятно, при слиянии чего именно что-то пошло не так.
Однако, такой подход мне кажется лишённым смысла, он не только не даёт никакой пользы, а скорее разрушает историю, уничтожает истинный ход событий.Истинный ход событий в общей истории никому не интересен, как и избыточные коммиты вроде «fix bug». Это только усложняет поиск нужной точки в истории при необходимости. По-хорошему бранч перед мержем надо причесать, всякие «fix bug» накатить fixup'ами на соответствующие коммиты. Т.е. да, править локальную историю, но в этом и философия — никому не надо в деталях знать как это разрабатывалось (и сколько кофе вы при этом выпили), интересует только максимально презентабельный результат в общей истории.
Не понимаю, в чём принципиальное различие — исправить N конфликтов в одном коммите мержа или те же N конфликтов в нескольких коммитах при ребейзе.
Для меня есть разница в том, что в момент мержа видно весь масштаб всех изменений. Если где-то возник конфликт, то посмотрев на изменения в общем, можно проверить и другие потенциальные места, где конфликта нет, но влияние изменений есть. В случае же с rebase приходится работать с каждым своим комитом по отдельности, и при возникновении конфликта гораздо тяжелее оценить где что-то могло тоже испортиться. Если нет никаких автоматических тестов, то можно очень много времени тратить на эту актуализацию через rebase. Плюс ко всему, на своей практике я пока сделал вывод о том, что фича обычно не требует неотложной синхронизации с основной кодовой базой. Фичу обычно можно делать и полностью отдельно, и неважно что там в основной ветке.
Вкратце, мой поинт такой: если делать merge в конце, то в этот момент можно как следует погрузиться в процесс слияния изменений. Перед глазами есть сразу полная картина работы, и можно один раз вдумчиво всё проверить и исправить. В случае же с rebase это не просто размазывается во времени, а как будто бы даже отнимает больше времени из-за постоянных ресолвингов одного и того же. Слышал, что есть инструмент git rerere
, но не пробовал.
Истинный ход событий в общей истории никому не интересен
Не очень понял этот момент. Я очень часто прибегаю к git blame
, чтобы разобраться зачем и когда была изменена та или иная строка.
интересует только максимально презентабельный результат в общей истории
А что для вас "презентабельно"? Мы стараемся, чтобы в каждом комите был минимальный набор изменений, плюс максимально понятное описание зачем так было сделано. При просмотре истории 2 и более лет давности порой возникают вопросы "что здесь происходит? почему так было сделано?", а commit message тоже не очень хороший. В итоге получается так, что инструмент просто не работает из-за нашей неорганизованности в прошлом.
Если где-то возник конфликт, то посмотрев на изменения в общем, можно проверить и другие потенциальные места, где конфликта нет, но влияние изменений есть. В случае же с rebase приходится работать с каждым своим комитом по отдельности, и при возникновении конфликта гораздо тяжелее оценить где что-то могло тоже испортиться.Посмотреть на изменения в общем можно и в процессе ребейза. И в случае ребейза можно переписать коммиты с учётом влияния изменений, что только упростит чтение истории в будущем.
Вкратце, мой поинт такой: если делать merge в конце, то в этот момент можно как следует погрузиться в процесс слияния изменений. Перед глазами есть сразу полная картина работы, и можно один раз вдумчиво всё проверить и исправить. В случае же с rebase это не просто размазывается во времени, а как будто бы даже отнимает больше времени из-за постоянных ресолвингов одного и того же. Слышал, что есть инструмент git rerere, но не пробовал.Не вижу ничего плохого в «размазывании во времени», на вдумчивость это никак не влияет, наоборот даёт возможность сузить поле зрения. Тут я полностью согласен с IvanPonomarev — лучше делать ребейз чаще и решать конфликты сразу меньшей кровью, чем ждать до последнего и хвататься за голову от их количества.
Не очень понял этот момент. Я очень часто прибегаю к git blame, чтобы разобраться зачем и когда была изменена та или иная строка.Я имею в виду, что истинный оригинальный порядок/время коммитов, который теряется при ребейзе, не так уж важен.
А что для вас «презентабельно»? Мы стараемся, чтобы в каждом комите был минимальный набор изменений, плюс максимально понятное описание зачем так было сделано.Это я и понимаю под «презентабельностью». Но идеальную историю редко можно сделать, не переписывая её. Вот допустили вы ошибку пару коммитов назад, делать amend уже поздно. Большинство в таком случае просто закоммитит фикс с комментарием вроде «fix a mistake», и это нормально в процессе разработки. Но я сторонник того, чтобы перед мержем в основную ветку чистить историю от таких коммитов с помощью rebase, объединяя их с теми, где эта ошибка была допущена.
А как тогда быть с long-lived ветками? Ведь процесс с rebase подходит только для веток типа "поработал-смержил-удалил". После merge в условный мастер потом ведь не получится на него же поребейзить.
Но раньше я тоже любил ребейзить. Приходил утром, фетчил, ребейзил, именно ради минимизации конфликтов. Но вот за последние пару лет я больше склонился к тому, что фича она почти по определению не обязана иметь сильной связи с какими бы ни было другими ветками. Спокойно работаю в ветке, пока не будет условно готово. Когда готово – наступает следующий этап объединения изменений. Потом можно ветку не удалять, а продолжать с ней работать дальше.
Стало интересно, как же работают над тем же ядром Linux? Там ведь наверное каждые полчаса происходят тонны изменений.
Надеюсь, мои ответы не выглядят как спор ради спора, я просто хочу разобраться и понять плюсы и минусы, ну и своими мыслями поделиться.
После мержа в мастер на него ребейзить можно. Нельзя ребейзить ветку, которая сама в себе имеет мерж-коммиты, т.к. ребейз подразумевает линейную структуру источника.
Это же, по сути, лишь куча последовательных (!) черри-пиков с последующим переносом указателя ветки-источника.
Черри-пик можно сделать на что угодно. Но если ветка от корня не вытягивается в линию — тут-то ребейз и невозможен
При ребейзе (если в мастер/девелоп) теряется не столько время коммита, сколько его глобальное предназначение и группировка (принадлежность к ветке).
А ребэйз — считай, по черри-пику на каждый коммит исходной ветки на новый корень. И каждый чреват конфликтами.
Ну так надо как можно чаще рибейзить свою ветку — тогда меньше будет проблем.
например, дописывается в конец один и тот же файл. У меня это скрипт миграции.
Мне кажется, что это общая проблема, связанная именно со скриптами миграции. Они плохо версионируются, потому что скрипт миграции — не исходный код, а лог изменений. Его или принципиально по-другому версионировать надо (но я не слышал, чтобы какие-то системы контроля версий специально поддерживали такие логи), либо идемпотентный DDL мог бы сильно выручить (но он тоже пока нигде хорошо не реализован)
Как можно чаще делать вещь в себе? Ради чего?
При работе над веткой это имеет смысл, пока обходится бесплатно или почти бесплатно, и я это делаю. Но ребейз фичи при слиянии в мастер/девелоп — это потеря структуры в части смысловой или целевой группировки коммитов, а оно мне не надо и даже вредно.
Тем более, слияние в мастер и даже в девелоп регламентируется отнюдь не соображениями красоты и линейности, но своевременностью и состоянием готовности.
Теперь представим, что у тебя ветка в 20-30 коммитов. Пять минут назад ты делал ребейз своей ветки на главную. Тем временем главная обновилась. Сейчас делаешь ребейз ещё раз, мы же делаем это почаще, да?.. Тыдыщ, пересоздаются 30 коммитов, и каждый — потенциальный скандалист.
Ребейз — очень тяжело масштабируемое явление.
Мы в своей команде пока пришли к мнению, что превращать ветку в fast-forward не очень-то и выигрышно. Если это была реальная ветка с несколькими коммитами, связанными между собой по смыслу, пусть лучше это будет именно merge, чтобы было явно видно: была какая-то работа, вот так-то её решали, такие N изменений применены. Иногда даже делаем git merge --no-ff
.
Rebase применяем только когда это было N коммитов, не связанных между собой, или же "ветка" состоит из одного коммита.
Как показала практика, хорошая история изменений довольно важна, т.к. регулярно приходится возвращаться к коду, написанному год и более назад. Внимание к оформлению истории порой сильно выручает.
Где-то баги поправили, тесты дописали, документацию, функционал расширили. Лучше быть в курсе изменений касающихся вашего фронта работы, чем в последний момент разгребать конфликты слияния и пытаться понять почему оно не работает.
Идея как раз в том, что обычно твоя ветка на то и ветка, чтобы там были изолированные изменения. Это как атомарные коммиты, никто ведь не спорит в логичности такого подхода. Я придерживаюсь мнения, что ветки тоже должны бы быть атомарными. Разгребать конфликты что так, что так придётся – или постепенно, или один раз в конце. По-моему, уж лучше сделать это именно один раз и в конце, чтобы охватить все эти проблемные места, находясь в этом контексте.
Если подтягивать другие изменения в свою ветку, то не всегда можно быть уверенным, что они не порушат какое-то состояние. Потом вместо поиска проблем только в своей истории изменений, ты будешь вынужден учитывать ещё и какие-то левые изменения из других веток.
Если у кого-то есть богатый практический опыт по этой теме, давайте обсудим.
А представим ситуацию. У нас есть мастер и релиз. После мердж мастера в релиз его начали тестировать. И обнаружили несколько багов которые критичны. Их там же поправили и отправили релиз пользователям. А что дальше делать? Думаю не стоит копи пастить код. Лучше сделать мердж в мастер в котором идёт дальнейшая работа всех разработчиков.
Раз удалённый репозиторий (origin) такой же
Origin — это не удаленный репозиторий. Это имя по-умолчанию для удаленного репозитория, которых кстати часто бывает больше одного.
git-scm.com/book/ru/v1/%D0%9E%D1%81%D0%BD%D0%BE%D0%B2%D1%8B-Git-%D0%A0%D0%B0%D0%B1%D0%BE%D1%82%D0%B0-%D1%81-%D1%83%D0%B4%D0%B0%D0%BB%D1%91%D0%BD%D0%BD%D1%8B%D0%BC%D0%B8-%D1%80%D0%B5%D0%BF%D0%BE%D0%B7%D0%B8%D1%82%D0%BE%D1%80%D0%B8%D1%8F%D0%BC%D0%B8
В гите можно делать сотнями разных способов, а новичкам надо рассказывать, как делать правильно. А то ведь и вправду начнут мёржить изменения во все стороны.
На мой взгляд, до новичков очень важно доносить следующее:
- Говорить о Git нужно всегда в контексте принятого у вас процесса разработки и CI/CD. Git работает в синергии с CI/CD. Если в компании есть Git и нет CI/CD, то это неэффективность в квадрате, т. к. тогда уж лучше применять более простой SVN.
- Процесс должен быть построен так, что главная ветка должна быть защищённой от пуша. В неё можно только мёржить, и только такой код из других веток, который прошёл quality gates вашей CI/CD системы. Таким образом, в вашем мастере должен быть гарантированно стабильный код, и фичи, разработка которых гарантированно завершена.
- Ни в коем случае не надо мёржить во все стороны (впрочем, если у вас правильный процесс с защищённым мастером, то не очень-то вы и хаотично помёржите в мастер). Не надо бояться rebase. Отведя локальную ветку с выполняемой вами задачей, работайте с ней и выполняйте rebase часто, пере-отводясь от верхушки мастера по мере поступления новых стабильных коммитов в мастер.
- Выполняя частый rebase, вы «переносите боль на начало». Откладывая всё до финального merge, вы «откладываете боль на потом». Как и в других аналогичных инженерных практиках (например, связанных с автотестированием), общее количество боли будет меньше в первом случае.
- Завершив работу, открывайте Pull Request, успешное закрытие которого сопровождается слиянием с мастером. Условием возможности слияния обязательно должно быть прохождение quality gates вашей CI/CD системы и желательно code review / approval от других разработчиков
1. Скопировать (но не rebase) изменения с ветки А на ветку Б. При нормальной работе в команде разработчиков rebase просто делать нельзя, он уничтожает по умолчанию исходную ветку.
2. Как найти общий коммит для двух веток.
3. Как найти какой коммит УДАЛИЛ конкретную строку из старой версии. Ну и заодно — рассказать про git blame.
1. Скопировать (но не rebase) изменения с ветки А на ветку Б. При нормальной работе в команде разработчиков rebase просто делать нельзя, он уничтожает по умолчанию исходную ветку.
создаёте ещё одну ветку С на том же коммите, что и А, и уже её рибейсите (что по сути, набор cherry-pick'ов) на Б — вот у вас и копия (только если делать
git cherry-pick
есть ключ -x
, чтобы сообщение коммита содержало ID-коммита, откуда было скопировано изменения)2. Как найти общий коммит для двух веток.
git merge-base
3. Как найти какой коммит УДАЛИЛ конкретную строку из старой версии...
git log -Sстрока
(подробнее в git log --help)Новичкам (и не только) крайне советую курс от Google «How to Use Git and GitHub» на Udacity.
Он бесплатный, небольшой (рельно за 2 дня не напрягаясь пройти), но «технический» на примерах и отлично будет дополнять данный цикл статей, при этом последняя лекция там про взаимодействие с GitHub.
Про rebase крайне интересно было бы послушать таким же человеческим языком. Иногда делаю rebase, чтобы слить слишком мелкие коммиты в более осмысленные покрупнее, но что именно при этом происходит, не задумывался.
Git: советы новичкам – часть 2