Pull to refresh

Comments 148

А злодей сказал бы «спасибо за указание на новый действенный способ внутрикорпоративной интриги».
У нас в команде простое правило — ребейзи у себя локально сколько влезет, но если коммит уже попадал в общий репозиторий — то его нельзя ребейзить (и push --force делает только тимлид в экстремальных ситуациях).

Поэтому обычно и большая часть истории линейная, и для удобства ревью можно коммиты клеить, но когда идёт слияние веток разных разработчиков — то это мерж. Поэтому таких историй нет и вообще видно реально сделанные коммиты, а не некое совместное творчество коммитера и ребейзера, и восстановить реально существовавшую точку в истории всегда можно.

Любой инструмент нужно применять к месту и со здравым смыслом, а фанатики ребейза — они как любые фанатики. (Ц) Кэп.
Согласен. Автор рассмотрел только два крайних случая — либо глобальный ребейз в проекте компании, либо глобальный мердж. Вариант хорошей организации проекта не рассмотрен. Например в центральные репозитории force push запрещен, а локально до пуша хоть ребейзи, хоть интерактивно ребейзи, хоть амендь коммиты…
Force push тут вообще ни при чем (то, что он неприемлем, по-моему очевидно). Пример был создан как раз при локальном rebase, это стандартный workflow.
Ок. Тогда согласен. Хотя на моей памяти таких случаев не было. Чаще проблемы от неудачного разруливания конфликтов.
При локальном ребейзе обычно автор ребейзит свои патчи. С реальным кодом, такие засады бывают редко, обычно все же возникают конфликты, которые пользователь должен разрулить руками. Частично эта проблема ловится на ревью, частично ее ловят тесты.

PS: на моей памяти подобная штука происходила при накладывании OpenVZ патча, когда там sysctl-ли неуправляемо переезжали в другие подсистемы.
Объясните мне хабралогику, пожалуйста.
Когда я написал то же самое в комментариях к другой статье
habrahabr.ru/post/179045/#comment_6213807
Вся ветка сплошь заминусована.
Логики нет никакой. В комментарии выше идёт посыл «ребейзи у себя локально сколько влезет, и таких историй не будет», хотя в статье как раз и показан пример, когда локальный rebase теряет важный контекст, и может ввести в заблуждение.
К слову, та ваша ветка как раз и побудила меня составить этот пример для наглядности.
Как уже указали, ваш комментарий не имеет ничего общего со статьёй. ;)
В статье как раз показан пример вполне себе локального ребейза.
+35 к этому комменту наглядно показывает, как внимательно хабраюзеры читают статьи и комментарии к ним ;)
Совершенно верно! Я как-то на один из подобных постов отвечал, что народ путает терминологию, а именно: опубликованные vs. публичные ветки или репозитории. Вот в первых как раз git rebase ... противопоказан.
а можно же после rebase делать merge --no-ff и все будет красивенько и удобненько
В таком случае всё равно изменится контекст и будет выглядеть, как будто Вася написал «2*3=5», хотя он этого не писал.
А при беглом просмотре Вася бы и не заметил ошибки.
Т.е. данный кусочек кода не был покрыт тестами, хотя проект очень важен для компании?
Мы же не в идеальном мире живем, в реальности в проекте не 8 строчек, в ветке не 1 коммит, а покрытие кода тестами не 100-процентное. И при слиянии (как через merge, так и через rebase) ошибку можно проглядеть, тем более, если слияние без конфликтов (как в примере).
Только при merge в истории сохранится информация, чем руководствовался автор при написании кода коммита, и в какой момент возникла ошибка (при слиянии, а не при написании кода). А при rebase — нет. А не зная причин ошибки, исправить её может быть труднее.
Согласен с теми, кто написал, что былина интересная, как раз то чтение, которые приятно читать на Хабре под конец рабочего для, +.

Выводы из конкретного примера однако неоднозначные. Почему объектом критики выбран rebase?

Почему бы не «наехать» на:

— Алгоритм текствого diff'а/patch'а конкретного в git
— Алгоритм текствого diff'а/patch'а вообще

Алгоритм текстового diff'а — эвристика, а уж применение его как patch — эвристика в квадрате. Стоит удивляться и восхищаться, что при этом метод rebase был реализован *для специальных целей* и на практике работает весьма неплохо.

Учить «пионеров», не нюхавших CVSа, не юзать rebase направо и налево конечно стоит, но использовать для этого недостатки алгоритма diff — «не очень красиво» (хотя еще раз, былина получилась интересная). Для пущего эффекта, статью можно было бы озаглавить «Как я похакал diff» или вообще по-ализаровски: «В git найдена критическая уязвимость» ;-).
Да ладно, суть примера не в том, как diff работает, а в том, что rebase контекст меняет.
Ну т.е. другой алгоритм diff'а безусловно мог бы показать конфликт в конкретно этом примере, но лишь потому, что я пытался ужать его в несколько строк. В реальном проекте изменения Пети и Васи могут быть вообще в разных файлах, так что алгоритм diff'а может быть каким угодно, но смена контекста всё равно может привести к похожим последствиям.

Кстати, немного отвлекаясь от темы, в git-diff можно использовать разные алгоритмы:
--diff-algorithm={patience|minimal|histogram|myers}
  Choose a diff algorithm. The variants are as follows:
  default, myers
    The basic greedy diff algorithm. Currently, this is the default.
  minimal
    Spend extra time to make sure the smallest possible diff is produced.
  patience
    Use "patience diff" algorithm when generating patches.
  histogram
    This algorithm extends the patience algorithm to "support low-occurrence common elements".

Кроме того, есть опции --word-diff и прочие.
Давайте уволим ещё старшего QA (за непокрытие) и менеджера проекта (за накосячившего QA) =)

Как сказали выше — «спасибо за сказку». Сказка не касалась таких (безусловно важных) тем, как контроль качества и управления разработкой бизснес-критикал проектов. Так что я думаю тема достаточно неплохо раскрыта. Простим же автору недостаточное раскрытие прочих важных, но не обозначеных в статье тем?
Программисты бывает двух типов — которые ещё не делают юнит-тесты и которые уже делают юнит-тесты.
Проект 1М+ строк кода, потом пришли к покрытию тестами. За сколько, как Вы думаете, покруют на 100%?
P.S. Да, кстати, важно не забывать что Code Coverage = 100% не означает 100% покрытия тестовых случаев.
Конечно, все ситуации покрыть малореально. А ведь ещё есть сроки и прочее, что ещё сильнее ограничивает время на написание тестов.
Я как раз на это намекаю, что тесты часто дописываются когда работа в полном разгаре, а не в самом начале.

У меня лично много проектов, которые я начинаю, а потом они идут в мусорку по ненадобности. Было бы грустно тратить время на юнит-тесты для таких, я бы тогда вообще ничего не успевал бы.
А вот когда проект пошёл удачно, то тогда уже начинаются тесты, фреймворки для тестирования, тестовые фиды, prod/qa/dev и прочие специи по вкусу и по задачам.
При текущей конкуренции на рынке это достаточно адекватный вариант разработки новых идей.
Я всегда начинаю проекты с тестов, но пишу минималку для проверки тех сценариев, что сразу в голову приходят. Это быстро. Остальные тесты пишет QA.
Это очень классно когда есть QA, много заинтересованных людей в компании и достаточно времени. А когда у тебя стартап из тебя одного или маленькая команда в два с половиной человека, то в одном лице может быть и разработчик и QA и менеджер. В этом случае приоритеты, к сожалению, идут не в пользу юнит-тестов.
Если писать в стиле TDD или похожем то времени на сам тест мало уходит, гораздо больше уходит на обдумывание всех случаев. Зато коду можно доверять. Вы ведь не хотите чтобы в стартапе у вас кому то из пользователей ненароком один мильен засчитался?
Тесты не панацея.
Ради интереса сделал:
создал файл, в master написал 2+2=4 (в столбик)
потом создал ветку исправил 2*3=5 (в столбик)
в master исправил 2*2=4 (в столбик)
и всё прекрасно слилось воедино. git ругнулся на то что есть конфликт, но kdiff3 его прекрасно разрулил без участия человека.

Rebase и Merge дали один и тот же результат. Кстати, вброшу ка я:
rebase и merge в 95% дают один и тот же результат, если конечно во время ребэйза чего-нибудь кардинально не менять.

Вообще и merge, и rebase опасны тем, что это объединение двух историй, а значит вероятны конфликты, неправильно принятые решения и т.д. Тут уже всё зависит от человека, а не от VCS.
Я специально не писал «2+2=4 (в столбик)», чтобы и merge и rebase прошли без конфликтов. См. репозиторий.
И да, rebase и merge дают идентичный результат, только с разной историей.
Я кстати, немного поспешил с ответом. Посмотрел репозиторий и историю.
Да в этом случае, как описали вы и показали на гитхабе, результат будет неправильным.
Если посмотреть на изменения Васи при варианте с ребейзом, видно, что он поменял 2 на 3, и 4 на 5. А произведение не трогал, и дальше по истории можно увидеть, что сумму на произведение поменял Петя. Какие вопросы к Василию?
Петя изменил 2+2=4 на 2*2=4. Корректно? Пожалуй, да.
А Вася якобы изменил 2*2=4 на 2*3=5. Корректно?
А Антон, который смержил 2*3=5? Корректно? Почему еще не уволен?
Вот видите, в варианте с merge четко видно причину ошибки: слияние.
А в варианте с rebase создается ложное впечатление, что был написан некорректный код.
Согласен. Рациональное зерно в этом есть.
Профит только в том, что Васе не придется оправдываться. Ведь баг будет как в первом, так и во втором случае.
Но когда все будут мержить branch в origin/branch, хотел бы я посмотреть как Антон будет морщиться при поиске бага.
Он не только морщится, он ещё и «периодически матерится, глядя на паутину слитых веток». :)
В обоих подходах есть свои плюсы и минусы. Важно о них знать, особенно о минусах того, который используешь.
Что бы не материться при виде путины скрытых меток, нужно использовать именованные ветки.
А т.к. их нет в GIT, то всем нужно обязательно в комментариях писать имя задачи/ветки.
Подобная ситуация справедлива для любого патча применяемого не для той ревизии из которой он получен. Например, мы сделали патч для ревизии 10, а применяем для ревизии 20. Допустим патч успешно применился, но код может оказаться неработоспособным.
А как это у вас автоматически мержится при ребэйзе?
Только что проверил, будет конфликт, что собственно ожидаемо.

Applying: 2+3=5
Falling back to patching base and 3-way merge…
Failed to merge in the changes.
Patch failed at 0001 2+3=5

И тут уж явно никак не закомитишь не правильно, собственно на чем вся статья и основана.

Что бы быть уверенным что мы говорим об одном и том же вот короткий скрипт, который воссоздает ваш вариант и воспроизводит конфликт при ребэйзе:

git init
touch test.txt

echo -e "2\n+\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2+2=4"

git checkout -b my
echo -e "2\n+\n3\n=\n5" > test.txt
git add test.txt
git commit -m "2+3=5"

git checkout master
echo -e "2\n*\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2*2=4"

git checkout my
git rebase master
См. ответ выше. Обновлю пожалуй статью, чтобы акцентировать внимание на том, что формат записи не «2+2=4 (в столбик)».
Я запутался. Должно быть в столбик или в строку? Потому что все скрины у вас в столбик. И если в столбик, то будет конфликт.
У меня вот так:
image
Конфликтов не было, я специально проверял, чтобы не дезинформировать ненароком.
Попробуйте сами:
git clone https://github.com/rabovik/RebaseVSMergeExample.git
cd RebaseVSMergeExample
git checkout -b my ab640a2
git rebase cae31030

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

У вас есть комит с описанием «2+3=5», но это же неверное описание, вы должны описать что делает патч, а не состояние кода в момент комита, таким образом описание должно быть «заменили 2 на 3 и 4 на 5». Вот теперь все встает сразу на свои места.

Иначе в таком случае ЛЮБАЯ git операция подходит под статью. Возьмем git cherrypick — снова не верно, rebase — неверно, merge — неверно. И все от неправильного описания патча и его трактовки.
Ниже уже ответили, но я пожалуй добавлю для полной ясности.
1) Rebase используется так же, как у вас в скрипте, возможно вас смутила запись git rebase cae31030, — так это потому, что в репозитории я rebase уже делал, и мастера уже нет; если повторять с нуля, то будет git rebase master.
2) Любой rebase с мастером + мердж в мастер — это и есть перенос патчей в мастер.
3) Rebase работает верно, только при rebase происходит потеря контекста. Изначально коммит это патч+контекст, при rebase же остаётся только патч, а контекст меняется.
4) Какие должны быть сообщения коммитов — вопрос дискуссионный, статья не про это. Отмечу только, что если написать, как предполагаете вы, то в репозитории не останется даже намека на то, какие причины побудили Васю написать этот код.
5) Не любая операция искажает историю. Rebase и cherrypick — да, контекст теряют. Merge — сохраняет историю неизменной.

В остатке: если хотим иметь в репозитории набор патчей — используем rebase, если важна правдивая история — используем merge.
1) можете написать, что не так в скрипте в комменте первого уровня? Почему он приводит к конфликту, которого не было у вас? Напишите, если не сложно, свой вариант скрипта, который покажет безконфликтный rebase
сорри, поспешил.
нашёл ниже по треду
UFO just landed and posted this here
А при чем тут запушенные данные?
UFO just landed and posted this here
В статье нет ни слова про rebase запушенных данных.
Описан стандартный юзкейс при пуше своего коммита.
Назревает необходимость в системе контроля версий для систем контроля версий :-)
Исходники git-а лежат в git-е. Думаю это верно для большинства систем контроля версий.
проще: пользуемся mercurial — у них идеологически нет команды rebase
Да и для освоения она легче чем git
UFO just landed and posted this here
ну это всегда так было — проще всего тот дистрибутив linux которым пользуется ближайший к вам админ, и даже если это gentoo — он с админом будет проще любого debian :)
Я к примеру въехал в mercurial довольно быстро, а вот в гит до сих пор путаюсь в командах.
git — сложнее:
— не логичные команды
— нет именованных веток
— нужно указывать ветку при pull или push или дополнительно настраивать, что бы происходило автоматом
UFO just landed and posted this here
В Hg логики тоже не нашёл, этим он не легче.

Например:
git branch -a
git tag

git branch name
git tag -a name


hg branch name
hg tag name

hg branches
hg tags


Чушь, всегда переключался по имени ветки.

Прочитайте сначала, что такое именованная ветка. И их действительно нет в git'е.
pqr7.wordpress.com/2010/10/10/a-guide-to-branching-in-mercurial/

push да, а при pull какая разница? Легко решается алиасами.

Не легко. У меня никто в команде этого делать не умеет. И мало того, на всяких сходках программистов, никто мне не сказал, как же это сделать. Все ссылались на чтение мана. В Hg это делается с помощью одной команды «hg push».
UFO just landed and posted this here
Если вы говорите, что нет почти никому не нужной штуки с несоответсвующим именем, то так и говорите, потому что в русском языке «именованная ветка» — ветка, которая имеет имя, и в гите ветки имена-таки имеют. (P.S. Ссылка нерабочая)

Вроде как у всех кому давал — открывается. И эта никому не нужная штука, одна из основных фич из-за которых большие проекты переходят/остаются на Hg.
Эта одна из самых нужных фич, для тимлидов, которая позволяет легко отслеживать все изменения проекта в истории.
В Git, например, не сохраняется история, к какой ветке принадлежал commit после merge.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Спросите у коллег, которые раньше работали в Мегаплане, на сколько проще работать с Hg, они вам расскажут.
UFO just landed and posted this here
Я перешел с SVN на Hg за 2-3 дня.
Потом осваивал не меньше полу-месяца Git(что бы полноценно управлять репозиторем и настраивать deploy)
Опыт тесной работы как с Git, так и с Hg более года.
UFO just landed and posted this here
Переходил по манам + советом помогал 1 человек, который внедрял в своё время Git в одной крупной it-конторе
UFO just landed and posted this here
Извиняюсь, не так выразился.
С Hg мне никто не помогал. Я сам разобрался за 2 дня.
А как Git осваивал, описал выше.

Hg на порядок проще.
UFO just landed and posted this here
UFO just landed and posted this here
Сколько уже этой фраз лет отбиваются поклонники Git от простой истины:
«В Git не хранится история commit'a, а поэтому это сложный в работе интсрумент»

Результатом этого мы можем взглянуть на множество примеров известных продуктов. Там просто не сливают ветки, что бы можно было нормально работать. Они одни и те же изменения копируют из ветки в ветку и делают commit.
Ну к примеру PostgreSQL: github.com/postgres/postgres/network
Или каждый commit подписывают в комментарии, к какой «named branch» он относится, к примеру ядро линукса.

Я прежде чем составить свое мнение о работе изучил около сотни репозиториев известных больших продуктов. Результат один: история, в какой ветке был создан commit — необходима. В Hg она есть и ничего «эмулировать» там не надо.
UFO just landed and posted this here
Что значит мои предпочтения?! Это исследование репозитариев всеми известных больших проектов.
Ссылки и направления куда копать, я вам дал — убедитесь сами.
UFO just landed and posted this here
Вы собираете проекты из веток или просто делаете push/pull?
UFO just landed and posted this here
Гугл подсказывает, что можно даже проще, всего лишь настроить конфиг:
git config --global push.default current

Да, вы сами ответили положительно на моё утверждение. Без google с git очень тяжело работать.
И тот же .gin/config не так уж и по настраиваешь по памяти, не потратив времени на освежение памяти из доков по разделам remote и branch
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
c Hg не нужен Google.
Один раз ознакомившись, ты уже не разучишься, как на велосипеде — вот в чем прелесть.
У меня команда, которая больше года работала с GIT и которая в должной мере не могла его осилить при всех попытках, пересела на Hg за 2 дня и начала успешно работать. Спустя неделю, ребята уже каждый под себя TortouseHg заточил с custom visual diff tools.
UFO just landed and posted this here
UFO just landed and posted this here
И мартышку можно научить делать последовательность комманд.

Вот пример, который был недавно. Тестировщик.
По бумажке работал с Git. Выливал изменения в основную ветку, после тестирования их в своей ветке, таким образом, что бы тестовые изменения в эту ветку не попали.
В Hg он выбросил инструкцию спустя два дня. Хотя до этого, бумажка с последовательностью команд Git у него висела очень долго.

Показатель?
Если нет, тогда мы совсем по разному с вами глядим на этот мир.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
да, кстати, если не согласны с каким пунктом — отпишитесь. А то как то не понятно, где я заблуждаюсь.
Есть у них rebase, использовать только не принято. Нет push -f и push --delete, но статья не о том.
Точнее, push -f не удалит никаких изменений.
rebase в Hg можно подключить, но по умолчанию это расширение не подключено.
UFO just landed and posted this here
да, мне уже заметили комментарием выше, что я погорячился с отсутствием команды rebase. Только это не отменяет того факта, что она практически не используется в mercurial — ну и по умолчанию она выключена как вы сами заметили.
А изучать матчасть этой команды нет никакого желания (и без нее я вполне обхожусь), но спасибо за предложение.
UFO just landed and posted this here
если бы это было действительно так, с чего бы класть этот плагин в коробку?

я полагаю — для удобства людям которые ранее использовали git и считают подмену истории в системе контроля версий нечто само собой разумеющимся.
б) если бы это было действительно так, с чего бы класть этот плагин в коробку?

Rebase — это расширение и по умолчанию отключено, а merge находится в ядре.
Команды rebase нет наверное ни в какой другой системе контроля версий. Это подчеркивает, насколько развит git и насколько у него большая community, что в нем реализованы *специальные* возможности для *специальных* целей, которые другим системам и не снились. Конечно же, Капитан подсказывает, что всем подряд использовать специальные возможности ни к чему.
UFO just landed and posted this here
Напишите в какой, я после того, как освоил git, за другими пристально не слежу, но быть в курсе эволюции всей области хочется.

Собственно, я по-моему слышал о rebase-плагине для bazaar, но плагин это не совсем то… Когда мне нужен будет rebase для bazaar (что иногда случается), я не побегу искать плагин, а матернусь и с'merge'у. (А merge'ы у bzr особо плохие по сравнению с git'ом, они фактически squash'ат по-умолчанию, докопаться по внутренней истории можно, но нужно давать дополнительные ключи в bzr log -p и т.д.; итог: по практичности далеко до git'а).
У mercurial это стандартное дополнение (т.е. присутствует сразу после установки, но отключено). Если напишете hg rebase, не включив его, то получите
hg: неизвестная команда 'rebase'
'rebase' предоставляется следующим расширением:

    rebase        команда для перемещения наборов ревизий к другому предку

наберите "hg help extensions" для справки по включению расширений
.
Nice! Не знаете, много ли пользователей hg включают и используют его? С одной стороны, логика не включения — это advanced'нутая фишка, и ее неразумное использование приводит к примерам, как в этой статье. С другой стороны, это advanced'нутая фишка, и требуется много глаз и рук, чтобы сделать ее реально пригодной и удобной в использовании.

Я помню как rebase в git был весьма нервотрепным делом. Собственно, и сейчас там есть неочевидные моменты (например, если какой-то патч уже в upstream'е, то может выйти конфликт, после резолюции которого будут нулевые изменения в рабочей копии, которые соответственно нельзя за'add'ить в индекс, а значит нельзя сделать rebase --continue; нужно знать, что в это момент нужно дать rebase --skip).
UFO just landed and posted this here
А почему «записывают на бумажке хэши» без пруфлинка? :-E

> Если «удобной и пригодной» == «один в один как в git»

Нет, это имеющей как можно более простой процесс применения и с возможно меньшим количество corner cases. Ну и en.wikipedia.org/wiki/Principle_of_least_astonishment. git в этом не идеален, конкретный пример я привел — когда должен был бы работать rebase --continue, «почему то» требуется rebase --skip (детали почему простым пользователям не интересны).

> Ну и да, rebase — детерминированная операция

Статья выше аргументирует, что нет.

Спасибо за ссылку на Phases, рад знать, что фичи hg развиваются. Уверен, что тот, кому нужно маркировать коммиты как public/draft/secret его сразу находит и использует. Увы, в mercurial достаточно своих некрасивостей и нурешений принципа least user surprise…
UFO just landed and posted this here
> какой пруфлинк?

Это была ирония на вашу иронию. Просто вы дали три ссылки подряд, какой hg хороший, что можно было подумать, что про «на бумажке» тоже прям из какого-то man git скопировано ;-).

> То, что git никак не помечает, выпинан ли коммит куда-нибудь или нет

Вы явно что-то путаете. Это SVN никак не помечал (и то в 2.0 или около исправили), был ли коммит смержен, например при мерже одной и той же ветки дважды вылезут конфликты. git разумеется ведет историю merge'ей и все работает, как надо.

Это не относится к rebase, потому что rebase работает не на уровне истории основной ветки, а на уровне последовательности отдельных патчей в нашей ветке. Наши коммиты попросту применяются один за другим к *совершенно новой истории upstream'а*, и либо применяется, либо patch «видит», что он уже применен (тогда из нашей ветки этот патч уходит), либо есть конфликт (и пользователь исправляет). Автор этой статьи вот нашел, как подстроить конфликт, который patch git'а не заметил. Кулхацкер. Итог, при rebase'е нечего «записывать», кроме самих патчей, что git делает неплохо, но поскольку patch — штука эвристическая, то ошибиться может.

> а разве --interactive не добавляет corner cases в rebase?

Дай бог многим системам такого пользовательского интерфейса, как в git rebase --interactive. Все в вашем любимом текстовом редакторе, помощь перед глазами, все весьма очевидно — хочешь переставить коммиты — переставь строки в редакторе. Я никогда не пользуюсь git merge --squash — зачем мне помнить об этом corner case'е, если git rebase --interactive позволяет добиться и его эффекта, и многих других.
UFO just landed and posted this here
Сейчас в mercurial пилят changeset obsolescense: хотя эта возможность и предполагается как замена MQ, я в первую очередь вижу в ней возможность полностью безопасного изменения истории (в репозитории остаются все изменения, просто при переписывании mercurial указывает, что данное изменение является obsolete, указывает, что данное изменение является заменой другого (или наоборот, указывает, какое изменение является заменой данного — не узнавал, в метаданных какого изменения всё это сохраняется), а также по‐умолчанию отдаёт только не‐obsolete изменения (дополнительно скрывая уже имеющиеся)).

Есть и дополнение, автоматическим образом разрешающее часть проблем вроде «изменение в вашем репозитории имеет obsolete предка» и д.р. Поищите «mercurial changeset obsolescense». То есть, теперь уже «mercurial changeset evolution».
Такого рода изменения, кстати, хорошо показывают, у кого лучше архитектура. Я лично не знаю ни одного примера добавления возможности в, так сказать, ядро git с самого момента начала его мною использования. В Mercurial же спокойно добавляют largefiles, phases и changeset evolution, да ещё и без проблем с совместимостью.

Не говоря уже о том, что рабочий прототип на Python с использованием довольно неплохого внутреннего API (позволяющего в т.ч. изменять поведение встроенных команд), написать гораздо легче, чем тот же прототип для git на C без специальной поддерки изменения поведения команд со стороны уже имеющегося кода.
Кстати, в случае с mercurial с или без changeset evolution тот, кто делал rebase, мог бы восстановить контекст и объяснить действия Василия (с changeset evolution это мог бы дополнительно сделать любой, кто сделал pull до rebase; правда требуется либо настройка сервера, чтобы он не считал находящиеся на нём изменения публичными, либо принудительное выставление фаз администратором на сервере и rebase его же силами). Без changeset evolution rebase делает резервные копии удаляемых изменений в .hg/strip-backup, с — просто оставляет их в репозитории (никакого автоматического gc!). Git же оставляет эти резервные копии на милость своего gc.
Ах, да, и еще, под «rebase в git» и «нет ни в какой другой системе», я понимаю так же и rebase --interactive, т.е. не просто перекинуть «свой» блок коммитов на (новый) верх «чужой ветки», а вообще легко и удобно переписывать историю.
И это есть. Другое дело, что в mercurial такое действие предлагается проделать двумя командами: есть просто rebase и histedit, который может перетасовать/убрать изменения как rebase -i, но не перенести их в другую ветку.
еще можно делать git merge --squash, получается что все одним коммитом и в истории красота
Спасибо автору за хороший пример. Хочу еще раз отметить, что ошибка возникает и в том и другом случае, просто в случае с rebase сложнее понять историю и контекст ее внесения.

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

Git очень гибок, предоставляет кучу возможностей и допускает разные сценарии работы с ним, но требует хорошего понимания что происходит и что может случиться.

У нас в проекте сейчас используется стратегия rebase (+squash), что позволяет держать историю коммитов красивой, компактной и почти линейной. Проект не очень большой, и ошибки такого рода у нас возникают очень редко (если вообще возникают), благодаря изолированности изменений (как правило разные разработчики редко правят одни и те же файлы) и тестам. Ну а если вдруг возникают — то у нас это не повод кого-то увольнять, а повод покрыть этот код тестом и исправить ошибку. Причина появления ошибки вторична.

Выбор, конечно, должен быть осознанным. Но ведь как чертовски хорошо, что Git нам дает право на этот выбор!

Ваш КО.
Прочитал выше — не понимаю, что конкретно не корректно. Я специально проверил прежде чем писать комментарий. Все получилось как у автора, конфликта при rebase не было, а ошибка в итоге появилась. Пробовал именно такой сценарий, который используеся у нас (и не только, он довольно стандартен). Опишу подробнее по шагам, чтобы прояснить:

1. Есть master в котором есть файл с таким содержимым:

сумма
2
и
2
равна
4

2. Есть 2 разработчика, каждый создает свой бранч (feature1, feature2)
3. В feature1 вносится изменение: слово «сумма» меняется на «произведение». Коммитит.
4. feature1 мержится в мастер (не важно через pull request или нет)
5. В feature2, другой разработчик меняет «2» и «4» на «3» и «5» соответсвенно. (У него по-прежнему «сумма», а не «произведение», потому код верный). Коммитит.
6. В feature2 делается git rebase master. Тут rebase проходит автоматически, конфликта НЕ возникает, в коде появляется ошибка, как описал автор.
7. feature2 мержится в мастер. В результате ошибка оказывается в мастере.

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

Если коротко то сейчас это выглядит так:
1. есть код в котором последнее состояние: 2*2=4
2. есть патч который заменяет 2 и 4 на 3 и 5
3. если такой патч применить, он сооствественно пройдет без конфликтов, и результат будет 2*3=5

И это не потому что ребэйз плохой, это потому что им изначально не верно воспользовались + неверное описание комитов.
В вашем примере скрипта делается абсолютно то же самое, только формат записи не такой, как у меня. Посмотрите внимательней.
Почему не такой? Все тоже самое в столбик, только нету слов сумма и произведение, вот добавил и их, ничего не изменилось:
Обновленный скрипт
git init
touch test.txt

echo -e "сумма\n2\n+\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2+2=4"

git checkout -b my
echo -e "сумма\n2\n+\n3\n=\n5" > test.txt
git add test.txt
git commit -m "2+3=5"

git checkout master
echo -e "произведение\n2\n*\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2*2=4"

git checkout my
git rebase master

«сумма\n2\n+\n2\n=\n4» != «сумма\n2\nи\n2\n=\n4»
Я специально подбирал формат записи такой, чтоб конфликтов не было.
Не поленился, подправил ваш скрипт, и запустил. Конфликтов нет, всё как в статье.
Исправленный скрипт
git init
touch test.txt

echo -e "сумма\n2\nи\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2+2=4"

git checkout -b my
echo -e "сумма\n2\nи\n3\n=\n5" > test.txt
git add test.txt
git commit -m "2+3=5"

git checkout master
echo -e "произведение\n2\nи\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2*2=4"

git checkout my
git rebase master

Спасибо, я уже тоже сам проверил, действительно нету, уж учень мало информации что бы он был. Но пример конечно сильно искусственный, на практике вероятность его встретить очень мала. А история от мержа будет не красивой, сложной при просмотре, не говоря уже о том что если понадобится перенести историю в виде патчей на другое дерево исходных кодов.

Как вы видите, всего один символ и у нас выкидывает конфликт.
Но мораль rebase vs merge ясна, спасибо за терпеливость и помощь с форматом! :)
Я бы не стал утверждать, что вероятность мала. На практике изменения Пети и Васи будут разделены не 1 символом, а десятками строк, а скорее и вообще в разных файлах будут. И конфликта скорее всего также не будет.
У меня прям сейчас открыт репозиторий, в котором однозначно при rebase вместо merge была бы масса «битых» ревизий.
Некрасивая история при merge — это конечно аргумент. Только возникает вопрос на подумать. Что мы хотим от истории: достоверности и информативности или красоты?
Кстати, cherry-pick коммитов при merge — совсем не сложное дело, если коммиты сгруппированы в отдельные законченные понятные ветки: берешь все коммиты ветки и переносишь. А с rebase надо ещё разобраться, с какого коммита нужная функциональность начинается, и на каком заканчивается.
Имел ввиду, перенос патчей на полностью новое дерево, ни как не связанное с текущим репозиторием. Вероятность такого события где то равна проблеме с ребэйзом.

> А с rebase надо ещё разобраться, с какого коммита нужная функциональность начинается, и на каком заканчивается.
Обычно в комитах есть номера тикетов, к чему относится та или иная фича/баг, поиск не вызывает особых проблем. Кстати что происходит, когда замерженную фичу надо обновить по каким либо причинам?
Открывается ветка с тем же именем, выполняются коммиты, ветка вливается в master (или куда принято). Всё то же самое, что и при rebase, только коммиты останутся сгруппированы в отдельной небольшой ветке (фича целиком окажется в 2 ветках: основной и дополнительной).
Кстати, на сколько я понимаю, выборку коммитов, созданных в конкретной ветке, удобнее всего делать в Mercurial: там в коммите сохраняется имя ветки. Возможно пользователи этой DVCS расскажут подробнее.
Если вам надо просто отменить слияние, то вы просто откатываете (revert, ни в коем случае не прочие манипуляции с историей) изменение, в котором слияние было произведено (если, конечно, первый родитель правильный: если делать слияние feature -> master, а не наоборот с последующим fast-forward merge. Документация предостерегает, что при повторном слиянии надо сначала откатить изменение с предыдущим откатом изменений и только затем делать повторное слияние).

С Mercurial вы действительно можете узнать, какой ветке принадлежало изменение. А можете и не узнать, если вместо веток использовались закладки (с некоторыми оговорками — то же, что и ветки в Git). Или отдельные каталоги вместо веток. Или программист поленился задать имя ветки (одна ветка с двумя головами — нормальная ситуация, иногда удобно). В общем, зависит от степени разгильдяйства программистов и мер защиты от оного. Как правило, сделать выборку действительно легче.

Для Git где‐то вроде лежит сложный скрипт на perl, который делает то же самое, с некоторой вероятностью даже давая верный результат. При использовании модели git flow при отсутствии отклонений от неё особых проблем с определением ветки не возникает.
Спасибо.
Вообще, отмена слияний в git дело не хитрое, главное понять принцип. Вот кстати, хорошая статья об этом, может кому пригодится.
С Mercurial вы действительно можете узнать, какой ветке принадлежало изменение. А можете и не узнать, если вместо веток использовались закладки (с некоторыми оговорками — то же, что и ветки в Git). Или отдельные каталоги вместо веток.

Хм. Есть всё-таки что-то пугающее в этом зоопарке видов веток, — он всегда меня останавливал, когда я думал на досуге изучить Mercurial.
При использовании модели git flow при отсутствии отклонений от неё особых проблем с определением ветки не возникает.

Да, простой поиск по commit message выдаст все merge-коммиты нужной ветки. Я просто предполагал, что в Mercurial это может быть изящнее.
Например, при задаче: в репозиторий Foo сделать cherry-pick всех коммитов ветки feature из репозитория Bar. В git нужно будет отобрать нужные последовательности коммитов, и для каждой сделать cherry-pick. А в Mercurial?
Например, при задаче: в репозиторий Foo сделать cherry-pick всех коммитов ветки feature из репозитория Bar. В git нужно будет отобрать нужные последовательности коммитов, и для каждой сделать cherry-pick. А в Mercurial?
И в git, и в mercurial можно скормить команде cherry-pick (git) или transplant (mercurial, находится в одном из стандартных расширений) диапазон. В mercurial вместо диапазона можно написать что‐нибудь посложнее. hg transplant -b {branch name} или hg rebase --keep --source {branch name} пересадит ветку. То же самое может сделать hg transplant 'branch(branch name)', но я не знаю, как всё это работает с уже применёнными изменениями. hg transplant отказывается пересаживать родителя текущего изменения, git cherry-pick не смущается, даже если его попросить пересадить HEAD. hg rebase -r {revisions}, кстати, тоже работает, только отказывается если ревизии не связаны.

Несколько более сложный пример: взять все ревизии из ветки foo, вносившие изменение в файл bar: hg transplant 'branch(foo) and file(bar)'.
Хорошие примеры, спасибо!
* В первом примере с rebase не --source, а --base. В отличие от --source, делает то же, что и git rebase.
UFO just landed and posted this here
Можно и так. Только transplant — хорошее, запоминающееся имя (т.к. заимствованные слова на его базе всё время на слуху), а graft я как несколько раз видел, так и не запомнил. Судя по словарю является медицинским и садоводческим термином. И с двумя разговорными вариантами, не имеющими к цели команды отношения, — взятка/взяточничество и (брит.) работа. Кальк и заимствований на основе graft я ни разу не слышал, само слово без контекста «есть такая команда в Mercurial» — тоже.
UFO just landed and posted this here
забавно.
Вот такой
git init

echo -e "2\n+\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2+2=4"

git checkout -b my
echo -e "2\n+\n3\n=\n5" > test.txt
git add test.txt
git commit -m "2+3=5"

git checkout master
echo -e "2\n*\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2*2=4"

git checkout my
git rebase master


скрипт приводит к конфликту, а вот такой

git init

echo -e "+\n2\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2+2=4"

git checkout -b my
echo -e "+\n2\n3\n=\n5" > test.txt
git add test.txt
git commit -m "2+3=5"

git checkout master
echo -e "*\n2\n2\n=\n4" > test.txt
git add test.txt
git commit -m "2*2=4"

git checkout my
git rebase master


нет. Подозреваю, что что-то нечисто с правилами автомерджа. Я всегда косо смотрел на его принципиальную осуществимость. Строчки строчками, а без контекста всё равно никак.
Всё верно. При префиксной нотации конфликта нет, я просто записал её словами.

На мой взгляд, никаких проблем с правилами мерджа нет.
В первом случае перед гитом стоит задача «найди строку „2“ между „+“ и „=“ и замени её на „3“», и не может её выполнить, т.к. нужные строки выглядят как «2,*,2,=,4» (вместо ожидаемого "+" стоит "*").
А во втором — «найди строку „2“ между „2“ и „=“ и замени её на „3“», и успешно её решает, т.к. строки выглядят как "*,2,2,=,4".
«4» на «5» меняется в обоих случаях без конфликта.
честно говоря, не вижу никакого оправдания такой дискриминации польской нотации (или такого фаворитизма инфиксной).
Не сочтите за пеар, написал вот тут свои выводы о.
ну т.е. технически вы правы — проблем с правилами мерджа нет, они едины. Однако то, что они выполняют сематически эквивалентную замену в одном случае и не выполняют в другом — общая и вобщем нерешаемая проблема, которая отлично проиллюстрирована вашим примером.
Вообще, можно написать стратегию слияний, игнорирующую различные строки вокруг изменяемой, чтобы конфликт выдавался только если одна и та же строка была изменена в обеих ветках. Тогда конфликта не будет в обоих случаях.
Также как можно написать и стратегию, чтобы конфликты вылезали чаще, например всегда, когда изменен один и тот же файл.
По моей субъективной оценке, обе эти крайности окажут скорее негативное влияние на эффективность разработки среднестатистического проекта, нежели позитивное. Нужен компромисс, например тот, что есть сейчас.
А вот и результирующая картинка дерева во время ребэйза, где видно и формат записи, и то что конфликт, и то что конфликт именно на тех местах где и должен быть:

На мой взгляд, вы торопитесь с выводами, говоря что тут rebase притянут за уши, и что мы им не правильно пользуемся. Описанный мной сценарий довольно популярен, и является рекомендованным, например, при разработке Ruby on Rails.

Если представить, что оба разработчика из моего примера не являются core members и не могут пушить в мастер, а только шлют пулл реквесты и при этом соблюдают рельсовый contribution guide, то именно так в жизни это и будет выглядеть. Шаги 4 и 7 будут выполнены через пулл реквесты.

Посмотрите, пожалуйста, внимательнее. Тут совсем не «Все что вы делаете это переносите патч в другую ветку». Тут классическая история, когда во время разработки feature2, мастер (естественно) ушел вперед. Поэтому чтобы влить feature2 назад в master, мы делаем сначала rebase, тем самым записывая изменения произведенные в feature2 поверх уже нового мастера. После этого шлем pull request (ну или мержим feature2 в мастер сами).
Это как раз пример того, что патч уже устарел. И если будет ревью, то в патче должы будут отказать и отправить назад на доработку.
Что значит «как раз»? Этот пример и был изначально. «Патч» устарел, именно поэтому и делается rebase. После этого (если тесты проходят), о какой еще доработке идет речь? Да, возможно, будет ревью, но такая ошибка легко может быть не замечена при ревью и попадет в мастер.
Очень интересный пример. Не думал о такой возможности. Спасибо! :-)
Тяжёлый некропостинг, но эта статья всплыла как аргумент в свежем обсуждении на RSDN, поэтому прокомментирую.

1. Принципиальное: каждый, кто пользуется rebase, должен понимать, что это особенная разновидность мержа (в общем смысле) — что он получает результат, на который уже воздействует другая ревизия, чем та, на которой он до того основывался.
Это почти тавтология, пока не встаёт вопрос о том, что кто-то не хочет понимать последствия этого факта. А именно — что результат надо проверять заново.
Как минимум для этого надо проглядеть глазами, что получилось, и проверить тестами. Будут это ручные тесты, автотесты или что-то ещё — уже вопрос местного workflow, хотя автотесты, конечно, лучше всего, и форсированные, как через CI средство; а также peer review, без которого тоже разработка чего-то серьёзного имеет слишком мало смысла.

Из этого кажется, что с rebase сложнее. Но:

2. В случае логического конфликта, проблемы с merge того же порядка, что и с rebase, а часто и серьёзнее.

Логический конфликт — это примерно как в этой статье, когда нет текстуального конфликта. Но и в случае текстуального конфликта может быть проблема: тот, кто будет решать конфликт, хоть его и увидит, может ошибиться в решении.

В статье это обойдено очень «хитро»:

>> — Ага, нашел. В коде «2*3=5», ещё бы оно работало. Так, это появилось, когда слили 2 ветки.

Так вот — кто "слили"? Это конкретное действие слияния, за которое кто-то несёт ответственность.

Что у автора статьи мерж — это какое-то событие уровня стихийного бедствия, за которое никто не отвечает. Стукнула молния, пожгла дом, оползень унёс, мерж сломал продукт — ok, никто не виноват, пусть страховая платит.

А это не так. Мерж — не зря отдельный коммит, у которого есть все данные для того, чтобы иметь его состояние и предков. По крайней мере в Git. И он должен быть проверен автотестами, ручными тестами, или что там ещё делается для этого, а если изменений немного — он и визуально должен быть проверен в затронутых местах.

(Да, часто это игнорируют. В продукте, на который я сейчас трачу основное время, именно так: конкретный коммит проверяется — Gerrit посылает его в Jenkins на автотесты; но, если потребовалось слияние, результат просто фиксируется в таком виде. Но у нас хотя бы ежесуточный прогон голов активных веток находит проблемы. За последний год мы ломали мержем дважды. Альтернатива хуже: мы могли бы разрешить отправку merge commits с тем, чтобы Gerrit принимал только fast-forward отправки, но это резко замедлило бы работу за счёт того, что каждый коммит шёл бы по несколько кругов. Так что мы сознательно тут допускаем послабления и оставляем время для разгребания проблем. Повторяю: сознательно.)

Этот мерж в статье — кто был ответственен за проверку результата? У него есть конкретный автор — Антон. У него были тесты в помощь? Вот с этого надо было начинать, а также с того, почему Антон мержит ветки с работой Пети и Васи (а не Петя или Вася, которые лучше помнят и знают, что там происходило...), и почему Васю в одной вселенной уволили, а Антона в другой — нет.

А если перестать прятать голову в песок и признать ответственность и за мержи тоже — то окажется, что они не имеют никакого мистического преимущества. И по сравнению с rebase:
1) Мержи маскируют реальный конфликт, откладывая его на потом.
2) Они усложняют диагностику и ретроспективу. Больше веток для поиска (git bisect имеет сложную политику для движения по нескольким веткам DAG). Сложнее читать диффы по сравнению с двумя и более родителями, чем с одним.
3) За счёт основывания на более ранних версиях (если не ограничено — может оказаться, что база для влитой ветки вообще многолетней давности), проблемы пунктов (1) и (2) могут усиливаться до фатальности. Например, может оказаться, что использованная foo() 5 лет назад и та foo(), что сейчас, вообще не имеют ничего общего, кроме названия. В случае rebase автор коммита получает хотя бы напоминание, что он основывается на свежем состоянии.
Кое-какие замечания всё же по комментарию.

Во-первых, математически rebase vs. merge конечно же разное. Линейность истории, обеспечиваемая первым никак не гарантируется последним.

Во-вторых, чтобы слияние было отдельным изменением в истории — это надо форсировать.

В-третьих, и пожалуй главных, надо отличать публичные ветки от опубликованных, если первые — просто публично доступные, то вторые подразумевают неломание истории.

Учитывая вышесказанное, модель разработки может быть такой, что мейнтенер проекта делает только слияния, то девелоперы в своих топик-ветках спокойно делают смену родительского дерева через git pull --rebase origin master. Глобальное тестирование, понятное дело, работает поверх основной ветки. Разработчики могут в ручном режиме говорить интеграционным тестам прогнать их конкретную публичную (если проект закрытый, то в рамках компании) ветку.

P.S. Может быть пересечёмся в этом году — пообщаемся более детально за пивком :)
привет :)

> математически rebase vs. merge конечно же разное. Линейность истории, обеспечиваемая первым никак не гарантируется последним.

Хм, вроде линейность тут и не требовалась?

> Во-вторых, чтобы слияние было отдельным изменением в истории — это надо форсировать.

Ну да. Или оно само, задетектив расхождение веток (собственно, при git pull так и получается — Вася успел закинуть свой коммит, у Пети свой локальный, он говорит pull — оно мержится). Здесь в статье вообще какой-то особый странный вариант получился — мержит кто-то третий, не проверив результат сразу, в результате надо искать последствия уже через фиг знает сколько времени. В нормальном месте, мне кажется, так не работает (см. мой комментарий — если проверяется, взорвалось бы ближайшей ночью, а если нет, то почему не проверяется?)
Вариант, как в Linux kernel, я не учитываю — он явно вне контекста статьи, и там rebase всё равно форсирован(!), пока не принята финальная версия патчей.

> Учитывая вышесказанное, модель разработки может быть такой, что мейнтенер проекта делает только слияния,

И тогда ему отвечать за качество результата. Непроверенная нерабочая ветка перед слиянием — и как тогда он вообще работает?

> Глобальное тестирование, понятное дело, работает поверх основной ветки. Разработчики могут в ручном режиме говорить интеграционным тестам прогнать их конкретную публичную (если проект закрытый, то в рамках компании) ветку.

Ну у нас сейчас всё так и работает. Но прогон через плановый комплект тестов обязателен, чтобы изменение вообще было доступно к мержу в основную ветку (этот контроль возложен на Gerrit). Сводить тут всю нагрузку на одного мейнтейнера как-то неудобно.

> P.S. Может быть пересечёмся в этом году — пообщаемся более детально за пивком :)

Дождёмся конца карантинов и можно будет планировать :)
Sign up to leave a comment.

Articles