Изменение поведения git merge в релизе 1.7.10 перевод

Git*
image
В соответствии с календарем релизов осталось всего несколько недель до заморозки списка фич следующего релиза git (1.7.10), в который войдет улучшение работы git merge, нарушающее обратную совместимость и ставящее «под удар» тех, кто использует merge в своих скриптах.
Мы решили последовать совету Джейка Эджа (Jake Edge): «Большинство свободных проектов обсуждают планируемые изменения до их реализации и дают пользователям возможности протестировать новые фичи задолго до релиза. Лучшая помощь проекту на этом этапе — четко обоснованные, конкретные описания существующих проблем, отсутствующей функциональности и т.д., а не бесконечный поток сообщений „Project XYZ ОТСТОЙ!!!11“ в списках рассылки или комментариях»

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


Когда git merge пытается выполнить слияние двух или более веток, генерируется описание — какие ветки участвовали в слиянии и, если автоматическое слияние успешно, — эта заготовка записывается как описание коммита без всякого вмешательства со стороны пользователя. Если же автоматическое слияние провалилось — мы даем пользователю шанс все исправить самостоятельно и использовать git commit для записи результата.

Большинство слияний проходит без всяких проблем, и именно это поведение привело к тому, что люди делают merge не задумываясь, даже в тех ситуациях когда необходимо объяснить причину этого действия. Никто не затрудняет себя вызовом git commit --amend для заполнения описания о причинах слияния после успешного merge.

Недавно в списке рассылки Git Линус заметил (и я согласен с ним) что такое поведение — ошибка дизайна системы, сделанная на самой заре существования Git. И теперь, начиная с 1.7.10, при запуске git merge в консоли (стандартный ввод и вывод присоединены к терминалу) открывается редактор для создания описания коммита, давая шанс пользователю объяснить причины своего поступка.

Мы бы хотели дать парочку рекомендаций, которые могут помочь нашим пользователям привыкнуть к этому изменению:

(1) Когда git merge запускается из командной строки возможны два варианта:
— вы вливаете обновленный upstream в ветку, в которой вы работаете над новым функционалом (topic branch). Подобное слияние без реальных на то причин — дурная практика. Подобное слияние в «неправильном» направлении должно производиться только в случае крайней необходимости, к примеру если ваша новая функциональность зависит от улучшений, недавно принятых в upstream. В противном случае, ваша ветка прекращает содержать в себе коммиты, относящиеся к одной единице функциональности, и, вместо этого становится свалкой коммитов из разных источников. В этом случае новое поведение git merge вам на руку, т.к. больше не прийдется вызывать amend для выяснения причин.
— при вливании ветки с новой функциональностью в интеграционную ветку (или ветку тестирования) причина слияния обычно понятна из контекста — вы вливаете готовую функциональность в проект. В таком случае вы можете передать параметр --no-edit в вызов git-merge и согласиться с подготовленным сообщением без редактирования

(2) У вас есть скрипт, выполняющий git merge и вы оставили стандартный поток ввода и вывода привязанными к терминалу, скрипт спросит у пользователя о причинах merge. Это может быть нежелательно. К примеру, часто подобные скрипты используются для массового слияния большого количества веток в тестовую ветку и должны оставаться неинтерактивными до тех пор, пока подобное автоматическое слияние возможно. Очевидно, что в таком случае вам желательно сохранить старое поведение команды. Совершенно необязательно добавлять --no-edit к каждому вызову git merge. Вместо этого вы можете определить переменную окружения MERGE_AUTOEDIT=no в начале своего скрипта и git merge будет молча делать коммиты до первого конфликта

Попробуйте 1.7.10 до релиза и адаптируйте свои скрипты и привычки :)
Если у вас есть пожелания касательно:
— документации
— ошибок, рекомендаций и прочих сообщений
— логики определения интерактивного запуска
мы всегда рады вас выслушать в списке рассылки git@vger.kernel.org

Сам факт запуска редактора при интерактивном слиянии — не обсуждается. Как сказал Линус — the default matters — и то как вел себя merge раньше было действительно плохим «умолчанием»

Донесите эту новость до пользователей Git в вашем окружении, чтобы они могли начать менять свои привычки.
+51
25 февраля 2012, 17:31
46
xanf 39,0

комментарии (33)

–11
calg0n #
Так что теперь нельзя будет вливать develop в свою ветку без геморроя? Или я чего-то недопонял?
+11
xanf #
Все можно. Просто по умолчанию теперь оно будет просить отредактировать сообщение коммита (как при git commit после разрешения конфликта в merge). Это поведение можно подавить ключом --no-edit или переменной окружения MERGE_AUTOEDIT.

Основная задумка — заставить людей писать почему они делают тот или иной merge (не все направления слияний одинаково полезны)

Почему это важно — новое поведение по-умолчанию может сломать старые скрипты
+1
calg0n #
Ага. Ну в принципе да, полезная фича. Особенно если кодер вливает соседнюю ветку (если вливаемая ветка не develop) в свою — мне как проверяющему коммиты\мержи\и т.п. было бы интересно узнать зачем чел это сделал.
+4
xanf #
Да. А еще может люди задумаются, что иногда сделать, к примеру rebase своей фичи полезнее чем мерджить апстрим в нее
0
xiaose #
а чем полезнее-то? Ну вот делаю я изменения в file1 который зависит от кода в file2 и file3 и тут народ наделал изменений в file2 допустим и замерджил изменения в общую ветку. И я тогда мерджу общую ветку себе и имею измененный file2. Что ж тут неправильно-то? И что мне в этом случае даст rebase?
+1
xanf #
Основная идеология работы с ветками — каждая ветка содержит «чистую» одну единицу функциональности. Т.е. то что кто-то в file2 внес изменения, не нужные для работы вашей функциональности не является поводом для слияния.

В то же время человеку осуществляющему merge вашей ветки с апстримом может быть сложно разрешить конфликты (т.к. он не знает вашего кода). В таком случае полезно делать rebase вашей ветки относительно общей, чтобы сохранялись два принципа:
1. В вашей ветке — только коммиты относящиеся к новой одной единице функциональности
2. Ветка без проблем может быть влита в мастер
0
xiaose #
«каждая ветка содержит «чистую» одну единицу функциональности» — да, да. Тут я понял и со всем могласен. А как быть со случаем когда каждая ветка содержит «чистую» одну единицу функциональности, но только над одной единицей функциональности работает, допустим, 3 человека?
+2
cubuanic #
Ну это же гит, ё-маё! Он же ДЕцентрализованный. И никто не запрещает этим разработчикам обмениваться апдейтами напрямую. И только когда функционал готов — кто-то один из них отсывает (ветку/набор патчей/pull request — подставить нужное) тимлиду или кто там у вас главный.

Горячо рекумендую — посмотрите «Linus Torvalds on Git». Оно есть и с русским переводом если что, но в оригинале есть непередаваяемя экспрессия в речи Линуса. В своё время, лично мне именно это видео дало наибольший толчок в понимании гита.
+2
cubuanic #
Ну как вегда — rebase даёт «красивую» историю коммитов, не более того.
+2
mr_idiot #
Кстати, одно из неудобных вещей в git, это то, что когда из master-ветки вливаются изменения в «функциональную» (как бы это ни было неправильно, это часто необходимо), потом просмотр истории коммитов «фукнциональной» ветки (например, в веб-интерфейсе) теряет смысл. Ибо он, действительно, представляет собой свалку несвязанных между собой коммитов. Я плохо знаю устройство git'а изнутри, поэтому не знаю возможно ли это, но было бы неплохо иметь возможно смотреть «чистую» историю ветки. То есть, чтобы показывались коммиты, совершенные только непосредственно в этой ветке.
0
xanf #
К сожалению так сделать нельзя. «Ветка» в git — это просто именованный указатель на какой-то коммит (голова ветки). Вычислить принадлежность того или иного коммита в истории этой ветки к «самой ветке» архитектурно невозможно.
Поскольку у нас над «функциональными» ветками обычно работает 1 человек, мы просто делаем rebase ветки относительно апстрима
+1
mr_idiot #
Это было бы возможно, если бы в каждом коммите была некая запись, которая сохраняла название «оригинальной» ветки, в которой был совершен коммит. Такая запись влияла бы только на просмотр истории изменений. Интересно, почему разработчики так не сделали.
+2
asm0dey #
Насколько я понимаю, так работают тэги в меркуриале — крутые бранчи )
+3
elw00d #
Не только теги, named branches (то есть обычные неанонимные ветки) работают так же.
+2
mr_idiot #
Сколько пользуюсь гитом, а что такое rebase так и не разобрался :)
+2
edelweard #
Rebase, мне кажется, проще всего понять, если понаблюдать за тем, как именно он происходит, т.е. за сообщениями, которые выдаёт Гит. В двух словах, rebase ветки feature на ветку master — это:
1. Переместить указатель feature на master.
2. Применить все коммиты, которые были в feature и не были в master, один за другим, как патчи. Одновременно двигать указатель feature.

Этим и достигается линейность истории — одна из основных причин использования rebase вместо merge.

Довольно хорошо rebase расписан в популярной книге Pro Git: progit.org/book/ch3-6.html
(Есть и русская онлайн версия книги)
0
Alroniks #
А rebase патчи жестко накладывает на основную ветку или нет? Опишу ситуацию: ветвимся, в масетере в file1 есть код. Мы с воей ветке резвимся и правим этот код. Кто-то раньше нас вливает в мастер изменения и file1 немного поменялся. И тут мы делаем rebase. Файл умело сольется, как это происходит при мердже или будет заменен нашим патчем?

Если 2 вариант, то rebase не такой уж и безобидный.
0
elw00d #
rebase — это тот же merge, просто записывается в историю он так, чтобы история выглядела линейной. Поэтому разрешение конфликтов никто не отменял — если гит не сможет разрулить ваш конфликт автоматически, он попросит вас сделать это вручную.

Ну а вообще я бы рекомендовал перед любыми операциями, которые переписывают историю, делать копию вашего локального репозитория рядом (на всякий случай). Особенно если вы только что познакомились с rebase и еще не знаете, как отменить неудачный rebase, а плюс к этому всему в вашем репозитории много новейших коммитов, бакапа которых нигде нет.
+1
cubuanic #
> делать копию

Зачем копию?
Это ж git!

git br saved_branch

И работайте с вашей текущей веткой как хотите.
+1
elw00d #
Ну, я гитом практически не пользуюсь, чтобы такие тонкости знать, да и скопировать папку целиком как-то надежнее поначалу :)
0
edelweard #
Если репозиторий занимает более гигабайта, это нереально.
А вообще, даже сохранять указатель на бранч не обязательно — есть reflog, в котором все ходы прописаны.
+2
cubuanic #
Что значит «будет заменен нашим патчем»? Вероятно, у вас неверное понимание того, как гит работает.

Во-первых, rebase работает с любыми ветками. Всё зависит только от ваших потребностей и фантазии.

Во-вторых, гит не работает «файлами», и потому ничего не будет «замещаться». Гит работает чендж-сетами — фактически, это набор изменений в наборе файлов. Т.е. такой себе монолитный, неразрывный патч, обо всех изменениях в конкретном коммите.

Для освежения памяти (вы же прочитали man по rebase, верно?...) — позволю себе утащить оттуда картинку:

# было
         A---B---C topic
        /
    D---E---F---G master

# любая из этих команд приведёт к ....
git rebase master
git rebase master topic

# стало
                 A'--B'--C' topic
                /
    D---E---F---G master



Когда делается rebase, то гит последовательно для всех указанных вами коммитов (т.е. фактически — чендж-сетов) — в данном случае: A, B, C — делает cherry-pick на временную ветку (назовём её topic'), и когда процесс закончится — ваша ветка удаляется, а временная переименовывается в используемое ранее имя.

По сути своей, cherry-pick — это тот же merge, но не со всей веткой от указанного коммита, только с одним, явно указанным чендж-сетом. Как и при любом другом слиянии, тут могут возникнуть конфликты. И если вы меняли file1, а кто-то в master'е менял этот же файл в тех же строках — конфликт будет, как и всегда в таких случаях. Если же чендж-сет в master'е для коммита с изменениями в file1 не пересекается с вашим чендж-сетом, то слияние будет беспроблемным.

А по поводу безобидности… Ну вы же слияния делаете, и опасными их не считаете? Так тут по сути то же самое. Хотя как и везде — обязательно найдётся кто-то, кто оправдает поговорку про «и #$% разобъёт, и руки порежет» :)

Надеюсь, это прольёт свет на ваши вопросы.
0
Alroniks #
Спасибо. Общий принцип rebase я знал и знаю, но вот интересовали тонкости. С гитом тоже уже довольно давно знаком и работаю.
+3
edelweard #
Можно сделать git log branch1..branch2 — тогда будут показаны только те коммиты ветки branch2, которые не присутствуют в ветке branch1.
+1
mr_idiot #
Но, вроде бы, ничего не покажется если в branch2 только-что вливали branch1.
+1
edelweard #
Покажутся те коммиты, которых нет в branch1. Не важно, были ли они сделаны до или после «вливания».

Из «man git-log»:
"
git log release..test
Show the commits that are in the «test» branch but not yet in the «release» branch
"
–1
cubuanic #
> Я плохо знаю устройство git'а изнутри…

Символично…
Ваш ник какбэ намекает ;)
+1
zim32 #
git merge options:
-m Set the commit message to be used for the merge commit (in case one is created)

Или это не то?
+1
xanf #
И да и нет. Ключ -m позволяет заранее задать сообщение для merge-коммита. Поведение по-умолчанию — создать стандартное сообщение.
Теперь же если -m (или -no-edit) не указано, поведение по-умолчанию — вывести редактор для создания соощения
+2
zim32 #
Понятно. Спасибо
0
lexich #
Здравая вещь. Мне всегда было странно почему, merge не является причиной для commit. Теперь все по-уму.
0
cubuanic #
> merge не является причиной для commit

Это как?!?! Ещё как является! Просто раньше сообщение к коммиту подставлялось уже готовое, и редактор для него не вызывался — потому коммит проходил тихо и незаметно. Чтоб поменять это сообщение (что за всё время работы с гитом мне понадобилось сделать ровно один раз) — нужно было сразу после git merge явно делать git commit --amend, и менять сообщение вручную. Теперь просто поменяли поведение по умолчанию, и git merge будет подставлять точно такое же сообщение, как и раньше, и сразу, до фиксации коммита, будет вызывать редактор. Вам решать — менять его или нет. И только после этого будет зафиксирован коммит.

> Здравая вещь.
Кому как. Меня текущее поведение устраивало в 99.999% случаев, и этот (теперь) обязательный вызов редактора для меня просто лишняя трата времени. Так что надо будет сконфигурить алиас в ~/.gitconfig для вызова merge --no-edit.
+1
eveel #
Ох и весело щас будет ребятам из git-flow одновременно поддерживать два разных поведения системы контроля версий. :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.