Comments 66
Но прежде чем объявлять pull request готовым к рецензирования, я удаляю эту историю (с помощью git reset --mixed $(git merge-base feature main)) и заново коммичу изменения, делю их на логические блоки и пишу обоснования, бит за битом. В результате неукоснительного следования этой методике вы можете применять git annotate где угодно и понять, почему любая строка кода написана именно так.
Я не могу передать словами, насколько велико влияние этой возможности на благополучие разработчика. Когда я спустя недели или месяцы читаю подобные сообщения в коммитах, работая над чем-то совсем другим, но связанным с этим кодом, то словно читаю маленькие любовные записки себе сегодняшнему от себя из прошлого. Эти комментарии снижают до нуля время поиска нужной информации.
Чего бы точно мне не хотелось, так это читать поэмы из 200 описаний коммитов. Может автору следует добавлять комментарии прямо в код (как это делают все нормальные программисты), а не изобретать для этого костыли?
Надо не комментарии писать, а код писать нормально. ¯\_(ツ)_/¯
Чего бы точно мне не хотелось, так это читать поэмы из 200 описаний коммитов. Может автору следует добавлять комментарии прямо в код (как это делают все нормальные программисты), а не изобретать для этого костыли?
А мне вот не хотелось бы читать поэму из ненужных комментариев в коде. Не знаю кто такие эти ваши нормальные разработчики, но я предпочту не работать с разработчиками, которые пишут changelog комментами к коду.
Ага, а потом натыкаешься на какой-то кусок, где намеренно нарушена инкапсуляция, в угоду производительности. Но пока понимаешь что именно случилось, понимаешь, что человек, написавший это уже уволен, баг-трекер куда ссылается комит - удалён 3 года назад, и твой единственный источник информации - лаконичное "fix bug 889482".
А комментарии не написаны, потому как в момент, когда это писалось, это выглядело нормально и в принципе использовалось повсеместно.
А комментарии не написаны, потому как в момент, когда это писалось, это выглядело нормально и в принципе использовалось повсеместно.
Это еще норм, может так статься, что комментарий будет, но написан был 3 года назад (когда данный класс/метод/etc вообще что-то другое делал) и с тех пор никто его не обновлял.
В этом случае можно хотя бы посмотреть в git на окружающий код, чтобы убедиться, что он верен/неверен, а в случае «fix bug 889482» только вешаться (или застрелиться).
Так одно другое не исключает. В комменте к коду будет какая-то невалидная хрень, а в комментах к коммиту — "fix bug". Комбо!
Я к тому, что в случае с неправильным комментарием довольно просто выяснить, когда он стал неправильным, поскольку вся история под рукой (да и вряд ли описываемая им сущность изменилась прям вот так кардинально, скорее всего просто какой-то частный случай по-другому стал обрабатываться, в общем, какие-то тонкости изменились, что, конечно же, плохо, но не катастрофично). Потому я бы именно этот вариант назвал бы «ещё норм», а вот «fix bug» — «может так статься».
Он пишет не описание того, что делает код, а причину внесения изменений и описания самих изменений. Писать это в коментариях довольно странно.
что если код не писали а удаляли, просто коммент оставлять одинокий?
Внезапно, но "серии коммитов" это не новое изобретение, и в Open Source используется очень часто со времён mailing list-ов. Специально для этого никакой функциональности не придумали, а просто пишут на первой строчке коммит-месседжа "feature xxx 1/3" и тд. В mailing list-ах это собственно уходило в тему письма, где и была видна связанная история коммитов, а учитывая что первая строчка коммита в git и выполняет роль своеобразной "темы", то можно её так и продолжать использовать.
Если не путаю, то вариант с rebase-ом в GitHub-е оставляет merge-коммит, хотя он пустой (или так можно выбрать). Помню, когда это увидел, остался очень доволен — по-моему, самое удобное решение:
- видно начало и конец ветки
- видны все коммиты ветки. Сквашить можно только совсем мелкие фиксы, типа "fix", "oops!", "а, черт, забыл к предыдущему". Это я делаю еще локально, когда никому эта грязная история не видна
Не создаёт ли это такой же мусор при графическом отображении истории, как стандартный merge?
Тут похоже спутали rebase и merge --no-ff.
Но прошу не пытаться использовать в английском языке русскую грамматику. Это выглядит крайне нелепо и нечитабельно. Я про попытку прикрутить падежи к английскому, в котором падежей нет.
Если хочется писать слово латиницей, то так и пишите: вариант с rebase в GitHub.
Но если очень хочется подчеркнуть падежи, то никто не мешает писать полностью по-русски: вариант с ребейзом в Гитхабе.
В нормальных системах контроля версий (привет, меркуриал), то, что хочет автор называется ветками. А в гите то, что называется ветками, на самом деле - автоперемещаемые теги.
--1--2--3--7--8------------------9-
\ /
\--4'--5'--6'--/
Именно так и надо делать, без всяких мерзких фаст-форвардов. Единственно если ветка из одного коммита, то нет смысла делать мердж коммит.
Зато откатить такую "группу коммитов" очень легко: git revert -m 1 HEAD
Неистово плюсую, честно говоря, вообще не понимаю, как можно по-другому, и, главное, зачем :)
Круто. Но если бы бранчи были полновесными (чтобы каждый коммит отмечался, как входящий в бранч, а не только указатель на head) – даже ребейз бы не потребовался: для просмотра чистой истории достаточно было бы поставить фильтр по бранчу, и всегда можно раскрыть полную в нужном месте.
Если запретить fast-forward merges – то можно эмулировать такое поведение через git log --first-parent master.
Да, в mercurial полновесные бранчи и история в результате выглядит куда яснее. Почему в git не добавят такое, чтобы каждый коммит помечался веткой, к которой он принадлежит - не понятно.
Потому что в гите есть более мощный и универсальный функционал: трейлеры. Которые можно автоматически добавлять к коммитам через хук. А потом по ним красиво форматировать историю или вести поиск.
sign-off пример такого трейлера.
Вы можете любую информацию добавлять какую захотите, вы не ограничены именем ветки.
А если коммит сразу в нескольких ветках находится, то какой он принадлежит?
Можно как в меркуриале, указывать ветку в которой коммит был зафиксирован. Но коммиты часто в мастер мержатся совсем не из той ветки, в которой были созданы. К тому же имя ветки не уникально.
Но ведь трейлер — это всего лишь по особому соглашению оформленные куски сообщения фиксации. Их и в Mercurial'е не составляет труда делать.
Это не совсем верно.
Да они хранятся в теле сообщения. Но для них есть целый интерпретатор https://git-scm.com/docs/git-interpret-trailers который может быть использован в git log.
Какая принципиальная разница где хранить метаинформацию: в сообщении или отдельной структурой? Главное иметь инструменты для работы с ней.
Я о том и говорю. Ничто не мешает прикрутить подобный интерпретатор и к Mercurial'у и получить все те же возможности, что предоставляет git. Точно также добавлять их по хуку, а потом форматировать историю или вести поиск. Так что неправильно говорить так, словно это киллер-фича git'а, которой нет у Mercurial'а.
Вы не правильно прочитали мои комментарии.
Выше спрашивается, почему в гите не хранится информация о ветке как в hg? Я же показал, что гит может ее хранить. Отдельное поле в структуре коммита для этого не нужно.
Может. Да вот только никто так не делает. Потому что это, вообще говоря, вещь необязательная. Хотя жизнь бы всем упростила изрядно. А было бы отдельное поле в структуре фиксации — и вопроса бы не возникло изначально.
Я работал с Mercurial, нету от этого большой пользы. Коммиты принадлежат ветке, поэтому сделать копию ветки нельзя. Можно только создавать другую пустую ветку от начального коммита и повторять коммиты патчами. Переименовать ветку нормально тоже нельзя, нужны всякие танцы с бубном. В Git всё гораздо проще, легкие ветки дают больше возможностей в управлении ими.
Ну что бы не быть голословным, вот реализация "киллер-фичи" меркуриала на коленке.
https://asciinema.org/a/424832
Из скринкаста не понятно, что это не часть заголовка коммита. Вот так выглядит лог без интерпретации трейлеров
О, может вы ещё и закрытие веток без их удаления сделаете?
Нет, можно конечно хуками, алиасами и защищёнными ветками на хостинге наворотить что-то подобное. Но это уже совсем велосипед.
Но у меня точно так же есть претензии к меркуриалу. Например форспуш ребейзнутой ветки, так что бы кишки от старой не красовались в истории.
Зато в Git можно сделать копию ветки для бэкапа перед rebase, а в Mercurial с этим проблема из-за привязки коммита к ветке. Да и с rebase и дальнейшим force push тоже все непросто.
А еще вот такое есть.
https://www.mercurial-scm.org/wiki/NamedBranches
Undoing a Bad Merge
Mercurial does not yet offer a foolproof way to back out an erroneous merge.
В Меркуриале ребейз не нужен. Он и в гите-то мало полезен.
В Меркуриале нет особенностей, которые заменяют ребейз. Поэтому он не "не нужен", а его там просто нет. Есть расширения для редактирования истории пока она не запушена, но с учетом сложностей отката неправильных действий ими сложно пользоваться. Все время проверяешь, все ли правильно сделал.
Может быть вам он мало полезен, а я им часто пользуюсь. Для редактирования истории по задаче, а не для переноса изменений на другой коммит.
Как это не нужен? Постоянно им пользуюсь при упорядочивании фиксаций в хранилище. Так-то при работе набрасываешь обычно по порядку, что сделал, и зачастую в фиксациях вперемешку изменения из разных логических частей. Перебазирование позволяет всё это выправить и привести в красивый, логичный и понятный вид. Ранее для этих целей пользовался очередями заплаток, но для них ощутимо больше телодвижений делать нужно.
Вы делаете кучу лишней работы, которая нередко приводит к неработающим промежуточным коммитам, что требует ещё большей работы по их исправлению.
Нет, я не делаю лишней работы и нет, это не приводит к лишним нерабочим промежуточным фиксациям. Наоборот, фиксаций становится меньше, а сами они — крупнее. Разумеется, все они рабочие (в отличие от первоначальной истории, где могут встречаться нерабочие фиксации, хотя я и стараюсь такого не допускать). Например, работаю я над новым функционалом, вчера зафиксировал часть работы, сегодня продолжаю и в процессе обнаруживается, что для её реализации надо бы сделать изменение, которое напрямую вроде бы к заявленной функциональности не относится, но без него её не реализовать. Или же пришлось поправить легаси, но для этого там надо бы навести порядок с форматированием, именованием сущностей, иногда поправить всплывшие баги и так далее (зачастую там та ещё помойка). Изменение делается, фиксируется и работа над функциональностью продолжается. А когда функциональность более-менее готова, делается перебазирование, чтобы история не выглядела вот так:
1. Работа над функциональностью.
2. Работа над функциональностью.
3. Правка форматирования в затронутом легаси.
4. Правка обнаруженных багов в легаси.
5. Другие незначительные правки в легаси.
6. Аккуратное внедрение изменений в легаси-код для работы новой функциональности.
7. Работа над функциональностью (попытка первая).
8. Работа над функциональностью (отмена первой попытки, попытка вторая).
9. Работа над функциональностью (отмена второй попытки, попытка третья, готово).
А выглядела вот так:
1. Правка форматирования в затронутом легаси.
2. Другие незначительные правки в легаси.
3. Правка обнаруженных багов в легаси.
4. Аккуратное внедрение изменений в легаси-код для работы новой функциональности.
5. Реализация новой функциональности.
Последнюю и читать и понимать проще, проще откатить, если вдруг выяснится, что что-то не так. Все ключевые изменения сосредоточены в одной фиксации, в истории нет мусорных действий вида «добавил кусок кода, следующей фиксацией его удалил». Объём работы, конечно, возрос, но незначительно, как говорится «профит перевешивает недостатки». Конфликты при перебазировании возникают достаточно редко, а если всё же возникают, то часто сами решаются стратегией «перебазируем не в самое начало цепочки фиксаций, а сначала в середину, потом в начало», либо же их решение значительно упрощается — не надо править 100500 мест конфликтов, а только один-два, да и то, как правило, они достаточно тривиальны.
Вся статья основана на одном предположении: код сломали в первом коммите, а пофиксили в последнем. Коммиты между ними сломаны.
Но мы же о ребейзе говорим. Что мешает сделать fixup первого коммита?
Необходимость после этого собрать и проверить каждую ревизию от первой до последней в ребейзнутых коммитах?
Это автоматизируется через rebase --exec
xkcd303 – "Compiling"
Зато у вас будет мотивация ускорить сборку.
Или поднимать уровень разработки, чтобы можно было доверять разработчикам в том, что они не творят дичь и промежуточные состояния собирабельны, а автоматическая сборка проверяла только итоговый результат мерджа.
В некоторых проектах (компиляторы) порой даже каждый отдельный мердж собирать и проверять долго и дорого — собирают группами всю очередь веток на мердж.
А так можно было?
Ну тогда закрытие ветки и отслеживание ветки-родителя тоже не нужно. Миллионы людей без этого как-то же живут.
Я ребейз использую чаще чем коммит.
Про группировку. Допустим, надо собрать поштучно кучу коммитов «в группу». Для этого уже есть cherry-pick. Создаём ветку в любой точке истории, переключаемся и тащим к себе коммиты. Хотим перетащить сразу кусок истории (aaaaa..bbbbb) — `git rebase --onto` в помощь.
Задача стояла «как поддерживать множество версий, принимать множество патчей во множество версий программы и не умереть». Важная особенность патча — его нельзя применить абы куда. Часто, набор патчей необходимо применить в определённом порядке. Вот и всё. Патчи слали по почте в конкретную ветку(и). А git — это автоматизация этой почты.
И продолжают так делать до сих пор. Git прост и банален. Этим и хорош. После этого по-другому смотришь и на коммиты, и на описание к ним, и на ветки и т.п.
Ради интереса можно посмотреть на историю ядра, увидеть как один и тот-же патч может быть слит в 10ки веток. Или, посмотрите на postgresql. Поддерживается и развивается сразу несколько версий. Посчитайте количество merge commits у postgresql репозитория.
Просто другой подход. Да, он тяжелее для новичков, да надо разбираться. Но и плюсы есть.
Ну и если начинаешь мыслить патчами — мелкими атомарными, логически законченными изменениями, то всё резко становится проще.
А тот же squash commits на ui — это кастыль: «я тут ходил по граблям, fix, fix, fix, revert, fix, bug-123, bug-123, bug-123, works now. всё, покакал. Ой, это в таком виде не хотят мержить в историю. завернём ка всё в один пакетик».
Я думаю, дело в том, что Linux и PostgreSQL не всё равно, как выглядит их публичная история разработки для мира, и для произвольных новых разработчиков. Так как это не коммерческая компания, то они могут без проблем ставить планку качества на достаточном уровне для разбивки на патчи, комментарии к патчам, и прочему.
В то же время, в коммерческой разработке в закрытом репозитории «fix, fix, fix [...] works now» — это норм. Никто всё равно не увидит. Работает — и ладно. Если начинать с этого уровня (или упасть до него), то поднять его очень сложно. Ведь для этого надо обладать политической волей и властью, чтобы объяснить тем, кто платит деньги, зачем они должны начать платить деньги за время, потраченное на более красивую историю коммитов с более пространными комментариями, если и так всё работает?
Мне иногда кажется, что компилятор игнорирует все мои комментарии…
Прибывал все эти git workflow и согласен с тс.
1) Сквошить комиты нельзя - история гита должна быть полной и жить до конца проекта.
2) Микро-комиты - полезная тема, много раз спасали. Делает историю легко читаемой и просто ревертить. В гите для этого есть построчные комиты. Еще комиты обязаны быть залинкованы с номером задачи.
3) Merge commit уродует историю и ветки. Неоднократно были серьезные проблемы с неверным мерджем, найти такие баги очень сложно. Rebase - более надежно, можно и без fast-forward.
4) develop - активно-рабочая ветка. Для этого нужно, чтобы merge-request проходил как можно скорее и вливался в develop.
5) Проблемы только с release-ветками, их приходиться собирать черепиками, благо все комиты последовательны.
6) Очень длительные ветки для фичей - зло. Где-то используют фитч-флаги для этого. Вливают недоделанную фичу и блокируют ее кодом. Но непонятно как красиво такое реализовать.
Неоднократно были серьезные проблемы с неверным мерджем, найти такие баги очень сложно.
А напишите пожалуйста подробности.
На Хабре есть шикарнейший детектив на эту тему "История потерянного коммита" https://habr.com/ru/company/badoo/blog/534800/
Программист неправильно разрешил конфликты, он и с rebase мог их неправильно разрешить. На фоне остального рефакторинга на ревью вполне могли и не заметить. PHPStorm показывает все изменения файла, git log --full-history
тоже показывает, git diff master task
показывает, что есть изменения этого файла, значит и Gitlab/GitHub будет показывать перед мержем мерж-реквеста. С просто git log
да, есть неочевидный момент, но вряд ли это можно назвать проблемой мержей.
Допустим создали ветку для небольшой фичи и сделали ее в один-два коммита. Затем после ревью добавилось еще несколько коммитов с мелкими правками (баги, рефакторинг). В этом случае обычно хранить историю фичи не имеет смысла, поэтому сливаю через squash & merge.
Другая ситуация — фича более сложная, потребовала несколько дней и коммитов, причем коммиты тут не просто мелкие правки, а этапы реализации фичи. В таком случае важно сохранить историю, поэтому сливаю через merge.
Rebase использую редко, если ветка из одного коммита и не несет особой важности.
Стараюсь делать каждый коммит маленьким (не больше +20/−20 строк)
Вот это точно абсолютно трешрвое ограничение. Ничего не запрещает быть коммиту и на 1000 строк. Далее, на самом деле содержимое сквош коммита и мерж коммита совпадают бит в бит, кроме списка парентов. Так уж гит устроен. Так вот, при мерже ветки в мастер на последний её коммит стоит вешать тег, тогда всё будет видно.
А разве, когда делаешь squash все коммит месседжи не остаются в этом коммите? Я все время так делаю, вырезая только всякие wip-и и fix-ы. Не понял проблемы автора, если честно.
Есть подозрение, что в "групповых" коммитах люди будут путаться не меньше, чем сейчас в "сквашнутых" во время "реверс-инжиниринга", если можно так выразиться... но это неточно :)
По идее merge commit и должен быть контейнером для коммитов из ветки и его коммиты должны быть видны в истории прямо под ним
"догонять" историю главной ветки — это практически всегда боль. И мерж-коммит, несмотря на неудобства с топологией flow, разрешает эту проблему автоматически. Хотя и там зачастую надо бы "причесать" историю от разных wip перед мержем.
Скваш вроде выглядит ещё легче, покуда все временные недоделки просто сливает в кучу. Но в будущем, при археологии по коду, он зачастую подставляет свинью. Когда хочется узнать, зачем было внесено какое-нибудь неочевидное изменение, делаешь annotate и внезапно видишь, что изменение является частью какого-то скваша на 10К строк. И ладно, если 9К из них — это какие-нибудь банальные переименования (хотя пролистывать и просматривать эти банальности тоже занимает время). Гораздо хуже, когда это результат чьей-то полугодовой работы, которую засквашили в один мега-коммит просто потому, что бить на части и аннотировать было лень. Особенно обламывает поиск регрессии автоматическим bisect. Накидал скриптик, он отработал минут 20 и говорит — о! нашёл! Бага возникла вот в этом коммите на 10К строк...
Наконец, что касается ребейса — да, конечный результат, можно сказать, идеален. Если заведомо подумал, поделил всё на отдельные небольшие логические коммиты, каждый из них собирается и даже тесты проходятся… правильно аннотировал… Но проблема в том, что абсолютно никто не гарантирует, что после ребейса эти коммиты будут так же собираться и тестироваться. Получается, вроде всё правильно сделал — но вот теперь давай-ка заново пройдись по своей пачке красивых коммитов (а их, допустим, полсотни), и для каждого сделай сборку и прогони тесты. Что? Лень? Ага. Тогда получаем в главной ветке пачку красиво оформленных коммитов, но по сути они могут оказаться тем же недоделанным wip. Потому что после ребейса их никто тщательно не протестировал.
Вот в этот момент в голову и приходит мысль, что, пожалуй, в этом случае мерж вместо ребейса — очень даже вариант. Он избавляет от надобности тратить ресурсы на тестирование, поскольку всё уже протестировано. Он не ломает bisect, поскольку всё по-прежнему собирается и может быть протестировано. Но да, таки делает не очень красивой историю.
Вывод — поддержание истории красивой требует ресурсов. Либо сразу делать правильные и логичные коммиты, либо потом потратить время на перекомпоновку. Но само собой оно не получится. Ребейс ради линейной истории потребует ещё больше ресурсов — на тесты/совместимость. Скваш, либо пуш непротестированного ребейса — медвежья услуга команде и самому себе, причём в случае публичного проекта этот технический долг практически невозможно вернуть (думаю, пользователи будут не очень рады, если вы вдруг решите внезапно сделать git push --force
в свой популярный проект на гитхабе). И только мерж в этом случае даёт разумный компромисс — и тестировать заново не надо, и не скваш.
Да с чего это после мерджа не надо тестировать? Вот как раз именно после него и надо, особенное если изменения большие. А в чем проблема прогнать все свои 50 коммитов после ребейза через тесты я не очень понимаю. У меня конечно не было еще настолько длинных веток, но я постоянно так делаю и считаю долгом хорошего разработчика убедится, что сборка и тесты проходят на всех коммитах. И если в случае с мерджем после мерджа что-то упадет, то тут непонятно, где возник конфликт, как искать проблемное место? В случае с ребейзом просто тестируй каждый коммит своей ветки, как обычно, первый который падает — проблемный.
думаю, пользователи будут не очень рады, если вы вдруг решите внезапно сделать git push --force
в свой популярный проект на гитхабе
В мастере этого делать конечно не надо. А ветки-фичи перед слиянием — так само милое дело.
Безусловно надо!
Всё дело в том, что после мержа - один мерж-коммит.
(предполагается очевидным, что master или нынешний main безусловно протестированы, скорее всего автоматически. А коммиты в ветке - тоже, за них отвечает разработчик ветки).
Просто в результате мержа в основной ветке появляется один(!) новый коммит, его недолго протестировать. А в результате ребейса туда выстраиваются все полсотни. И если тестировать только крайний из них - ну зачем тогда вообще весь ребейс? Верхний, допустим, будет в порядке. Но если на остальные неохота тратить ресурсы - то незачем и ребейсить.
Если вся суть линейной истории в том, что некоторые ключевые коммиты (хз, то ли помеченные тэгами, то ли специально именованные) собираются и проходят тесты, а остальные могут даже тупо не собраться - тогда да, мержить не надо, работает ребейс, но смысла от такой линейной истории тоже довольно мало (любой инцидент - и в git bisect придётся бОльшую часть коммитов помечать как пропущенные). А если хочется CI, т.е. чтобы каждый коммит в главную ветку представлял собой мини-релиз - то тут либо мерж (чтобы этот коммит был один), либо скрупулёзные тесты каждого коммита, попавшего в историю через rebase или cherry-pick
Функция, которую мне хотелось бы видеть в Git: группы коммитов