Можно ли сказать Github, что моя ветка была объединена с ведущим ведущим сервером?

Я использовал свою локальную ветку feature, чтобы создать PR для репозитория github (у меня нет доступа на запись к нему). Позже я решил, что хочу выделить его последнюю фиксацию в отдельный PR, поэтому я переместил feature одну фиксацию назад:

git checkout feature
git branch feature2
git reset --hard @~
git push -f

Первый PR объединен вверх по течению, поэтому теперь я хочу создать второй PR:

git checkout master
git pull upstream master
git push origin
git checkout feature2
git rebase master

К сожалению, оказывается, что git не хватает информации о том, что feature был объединен с master. Следовательно, он не понимает, что ближайшая общая база feature2 и master очень близка: это просто feature. Вместо этого rebase возвращается к общей базе feature и master, как если бы они никогда не были объединены. В результате git rebase master становится излишне беспорядочным.

Почему Github потеряла информацию, которая feature была объединена в master через предварительный PR? Есть ли способ предоставить Github эту информацию?

В итоге пришлось прибегнуть к:

git checkout master
git checkout -b feature2_new
git cherry-pick feature2

К счастью, мне нужно было позаботиться только об одном коммите. И даже с одной фиксацией, я думаю, что слияние с истинной базой (если бы git знал об этом) было бы лучше, чем cherry-pick, потому что git сможет использовать свои знания истории для автоматического разрешения большего количества конфликтов.

Обратите внимание, что если бы я слил feature с master локально, а не делал PR на github, никакая информация не была бы потеряна. Конечно, тогда мой master не будет синхронизироваться с вышестоящим репо, поэтому это будет бессмысленно.


person max    schedule 21.04.2017    source источник
comment
Вы уверены, что для upstream remote задан правильный URL-адрес? Проверить с git remote -v   -  person jakub.g    schedule 21.04.2017
comment
@ jakub.g Да. Я вижу фиксацию слияния в моем локальном master. Несмотря на то, что этот коммит соответствует (сжатой) версии моих локальных коммитов в feature, их история не связывает.   -  person max    schedule 21.04.2017
comment
Ну, может быть, разработчики Github не объединили ваш исходный PR в том виде, в каком он был, а использовали недавно представленную функцию Github, которая выполняет сжатие и перебазирование; тогда коммиты, которые попадают в репо, имеют SHA1, отличные от ваших исходных коммитов, поэтому rebase должен повторно применять каждую фиксацию.   -  person jakub.g    schedule 21.04.2017
comment
@ jakub.g да, в восходящем потоке master коммит, созданный PR, имеет только одного родителя.   -  person max    schedule 21.04.2017


Ответы (4)


Чтобы ответить на заданные вопросы,

Можно ли сказать Github, что моя ветка была объединена с ведущим ведущим сервером? [...] Почему Github потерял информацию о том, что функция была объединена в master через предварительный PR?

Да, безусловно, можно записать слияние. Как обычно.

Кто-то решил сказать git (и github) не записывать это, поэтому эффекты появились в основной ветке восходящего потока без следа того, откуда они пришли.

То, что вы видите, - это результат того, что кто-то решил отделить основную историю от истории, которую вы предложили. Почему они решили это сделать, вам нужно будет узнать у них. Это распространенный и широко используемый вариант, по которым люди будут рады высказать свое мнение. Линеаризация истории выглядит неплохо, но предполагает компромиссы, в любом случае есть свои недостатки, и разные люди и разные ситуации по-своему склонят чашу весов.

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

Что с этим делать?

Выбранный вами вариант, который также может быть более гибким (он обрабатывает истории нескольких фиксаций feature1..feature2), может быть полностью записан как

git rebase --onto master feature1 feature2

как правило, самый чистый: апстрим отказался от вашей истории feature1, поэтому вы отказываетесь от нее, переустанавливая функцию feature2 на контент, который у них есть сейчас.

Если по какой-то причине вы действительно не хотите отказываться от истории feature1 в своем собственном репозитории - возможно, у вас есть больше, чем просто feature2, основанная на старой подсказке feature1, и перебазирование станет утомительным - вы также можете добавить локальная запись слияния.

echo $(git rev-parse origin/master origin/master~ feature1) >>.git/info/grafts

Это сообщает локальному git, что текущий исходный / главный коммит имеет в качестве родителей как его записанный первый родительский коммит, так и локальный коммит feature1. Поскольку апстрим отказался от фиксации feature1, а вы этого не сделали, все механизмы git теперь работают правильно как здесь, так и вверх по течению.

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

Но здесь важно то, что rebase-before-pushing - это ваша последняя возможность убедиться, что то, что вы нажимаете, в точности правильное. Это отличная привычка, и трансплантаты прекрасно работают в этом рабочем процессе.

person jthill    schedule 11.05.2017
comment
Круто, я думал, что родительская информация жестко закодирована в (неизменяемых) коммитах, поэтому изменить ее невозможно. Но я предполагаю, что графты представляют собой отдельный слой стрелок git DAG, над слоем обычных стрелок в коммитах? И я предполагаю, что этот слой трансплантатов не используется в git push, поэтому он остается ограниченным моим локальным репо? Я видел некоторые комментарии о том, что трансплантаты являются битами хакерский, но кажется, что это идеальный (и единственный) способ добавить дополнительную информацию о родителях. - person max; 15.05.2017

Github теперь поддерживает 3 метода для объединения запросов на вытягивание:

  • Слияние: создает коммит слияния (без перемотки вперед) + извлекает все исходные коммиты из ветви PR.
  • Сжатие и слияние: создает одну фиксацию.
  • Rebase and merge: создается столько коммитов, сколько PR-ветка, но они перемещаются на главную.

Только регулярное слияние сохраняет информацию о том, что мои локальные коммиты были частью PR, объединенного с master. Если бы он использовался, я бы не столкнулся с проблемой, описанной в вопросе.

Два других метода теряют это знание - и я ничего не могу сделать, чтобы создать его задним числом (без изменения ведущего мастера). Это цена, которую нужно заплатить за более простую историю.

Интуитивно понятно, что для git, чтобы знать, что основная фиксация восходящего потока U связана с моей локальной фиксацией L, должна быть стрелка, указывающая от U к L.

Концептуально есть два способа добиться этого.

Во-первых, U может иметь двух родителей: один соединяет его с L, другой - со всеми предыдущими коммитами в восходящем направлении master. Именно это и делает техника Github merge.

Во-вторых, U может иметь L в качестве единственного родителя, если L уже указывает на все предыдущие коммиты в восходящем направлении master. Github мог бы поддержать это, разрешив ускоренную перемотку вперед с помощью техники слияния, но он решил этого не делать.

Если PR Github объединяется либо с сжатием и слиянием, либо с перебазированием и слиянием, все коммиты, созданные на главном сервере восходящего потока, имеют только одного родителя; стрелок между ними и моими локальными коммитами нет.

Редактировать:

Кроме того, теперь я считаю, что потеря истории, о которой я спрашивал, не имела большого значения. IIUC, конфликты, с которыми я мог бы столкнуться с git cherry-pick, на самом деле такие же, как конфликты с git rebase, если master был подключен к feature2 через обычную фиксацию слияния. И если бы у меня было более одной фиксации, разделенной на отдельный PR, вишневый справился бы с этим легко тоже.

person max    schedule 21.04.2017
comment
Приветствуются любые разъяснения относительно того, почему мой ответ неверен; если вы дадите отдельный ответ, чтобы дать такое разъяснение, даже лучше. - person max; 11.05.2017
comment
Я думаю, проблема в том, что ответ - да, конечно, можно сказать git, что ваши коммиты были объединены, но тот, кто выполнил слияние, предпринял явные шаги, чтобы скрыть его, стереть акт из истории git и полностью отобразить изменения содержимого не связанные. - person jthill; 11.05.2017
comment
@jthill but whoever did the merge took explicit steps to hide it: владельцы вышестоящего репо следовали одному из стандартных вариантов рабочего процесса github для слияния (в частности, сквоша и слияния). Я полагаю, вы можете считать это явными шагами, чтобы скрыть это, но именно об этом и заключается мой вопрос: как можно избежать его сокрытия (либо благодаря моим усилиям в качестве участника, либо благодаря усилиям владельцев вышестоящего репо). Я обновил вопрос, чтобы сказать github вместо git, чтобы подчеркнуть, что речь не идет о произвольном репозитории git. - person max; 11.05.2017
comment
@DeepMistry пожалуйста; Надеюсь, мой ответ правильный (изначально я волновался, что он может быть неправильным, потому что у него было 2 отрицательных голоса и нет положительных). - person max; 13.05.2017

Основная основная причина ваших проблем заключается в том, что после завершения запроса на вытягивание для feature (функциональная ветка с одной откатной фиксацией) возникает фиксация слияния, переходящая в master. Вот схема, показывающая, как выглядят master и feature2 после завершения feature запроса на перенос в master:

master:   ... A -- B -- C -- M
                         \
feature:                  D
                           \
feature2:                   E

Здесь мы видим, что feature отделился от master при фиксации C, а feature2 является просто продолжением feature с одной дополнительной фиксацией. Фиксация слияния M находится в верхней части master и представляет всю дополнительную работу, проделанную в feature. Обратите внимание, что это фиксация слияния и, следовательно, не имеет ничего общего с историей feature2.

Затем вы выполнили следующую перебазировку feature2 на master:

git checkout feature2
git rebase master

После этого перебазирования feature2 будет выглядеть так:

feature2: ... A -- B -- C -- M -- D' -- E'

Обратите внимание, что фиксация слияния остается частью истории. Хотя с функциональной точки зрения это может показаться ненужным, потому что фиксация D содержит все необходимое для совершения этой фиксации слияния, эта фиксация все равно появляется.

Если вам интересно, что вы можете сделать, чтобы этого избежать, можно было бы сохранить историю master linear. Недостатком был запрос на перенос, который завершился фиксацией слияния. Если бы вместо этого вы играли коммиты из feature непосредственно поверх master, у вас бы не было этой проблемы. Рассмотрим следующие команды:

git checkout feature
git rebase master

Затем выполните быстрое слияние master с feature:

git checkout master
git merge feature

В результате master и feature2 выглядели бы так:

master:   ... A -- B -- C -- D
feature2: ... A -- B -- C -- D -- E

Теперь, если вы объедините feature2 в master, Git просто воспроизведет E фиксацию, вместо того, чтобы возвращаться к исходной точке, откуда master и feature расходились.

person Tim Biegeleisen    schedule 21.04.2017
comment
Проблема здесь в том, что коммит слияния на самом деле представляет собой сжатие feature, поэтому git не знает, как связать N коммитов с одиночным сжатым коммитом. git правильно обрабатывает коммиты слияния, но OP описывает слияние, которое фактически переписывает историю feature (тонким образом). - person Anthony Sottile; 21.04.2017
comment
@AnthonySottile Именно об этом и говорит мой ответ. OP обманывают, думая, что rebase может распутать фиксацию слияния, хотя на самом деле это невозможно. Обработка коммитов слияния здесь не является реальной проблемой. - person Tim Biegeleisen; 21.04.2017
comment
Итак, из три варианта объединения PR, которые предоставляет git. Какие из них сохраняют историческую информацию? Я предполагаю, что если история будет сохранена, git rebase master применит только одну фиксацию. - person max; 21.04.2017
comment
Коммиты слияния кажутся, по крайней мере, из того, что я вижу, как основная причина вашей проблемы. Коммиты слияния и перебазирование похожи на нефть и воду или, может быть, на демократов и республиканцев. Их можно смешивать вместе, но обычно они не дружат друг с другом. Итак, если вы можете позволить GitHub перемотать вперед master во время запроса на вытягивание, если это возможно, тогда это сохранит линейность истории, и ваша первоначальная перебазировка приведет к тому, что только одна фиксация будет воспроизводиться поверх master . - person Tim Biegeleisen; 21.04.2017
comment
@TimBiegeleisen Коммит слияния означает в git очень специфическую вещь, OP описывает не коммит слияния, а сквош. - person Anthony Sottile; 21.04.2017
comment
@AnthonySottile I want to separate its last commit into a standalone PR ... есть ли у вас какие-либо основания полагать, что этот или последующие PR не привели к фиксации слияния? Имейте в виду, что по умолчанию GitHub выполняет фиксацию слияния во время запроса на перенос. - person Tim Biegeleisen; 21.04.2017
comment
Комментарий @TimBiegeleisen OP выше. Несмотря на то, что этот коммит соответствует (сжатой) версии моих локальных коммитов в функции, их история не связывает. - person Anthony Sottile; 21.04.2017
comment
У коммита, созданного ведущим ведущим сервером в результате моего PR, есть только один родительский элемент. На самом деле, насколько я могу судить сейчас, моя проблема не возникла бы при правильных коммитах слияния. - person max; 21.04.2017
comment
@max Если этот коммит не имеет того же хэша SHA-1, что и исходный коммит в feature, у вас все равно будет проблема, описанная в моем ответе. Если у вас было несколько коммитов в feature, идущих в одну фиксацию в master, то вы обязательно получите то поведение, которое наблюдаете. - person Tim Biegeleisen; 21.04.2017
comment
Конечно, он не может иметь один и тот же SHA-1 (созданный в разное время и с другим отцом); но если бы у него был SHA-1 моего коммита в качестве родительского, я думаю, я бы не столкнулся с этой проблемой. - person max; 21.04.2017
comment
+1 - спасибо, ваш ответ и обсуждение в комментариях прояснили для меня некоторые вещи, но после исследования того, что делает github, я не согласен с вами в одном пункте. Единственный способ линеаризовать основную ветвь в репозитории восходящего потока github - это использовать перебазирование и слияние, и это не избавит от описанной мной проблемы. OTOH, простое слияние избавит от него, даже если оно не приведет к линейной истории. Я резюмирую свое понимание в своем собственном ответе. - person max; 21.04.2017

Да нормально. Но git отвечает на этот вопрос, глядя на историю, а не на изменения. Если вы используете сквош Github (то есть уничтожаете историю), вы теряете возможность использовать эту историю; а именно способность git определять, существует ли уже часть этой истории в восходящем потоке.

Основываясь на диаграмме, созданной Тимом Бигелейзеном:

master:   ... A -- B -- C -- M
                         \  /
feature:                  D
                           \
feature2:                   E

Когда вы переходите к переустановке feature2 на master, вы должны на самом деле увидеть эту историю:

master:   ... A -- B -- C -- M
                         \  /  \
feature:                  D     \
                                 \
feature2:                         E'

Потому что rebase никогда не воссоздает фиксацию, которая уже существует в месте назначения. Он будет знать, что D уже в истории магистра.

Когда вы перебазировали feature2 на master, вы увидели коммиты, которые логически уже присутствовали в master. Это может произойти только в следующем сценарии.

Перед слиянием вы переписываете некоторые коммиты в функции:

                 D'   feature
               / 
... A -- B -- C 
               \  
                D 
                 \
                  E  feature2

выполнить слияние:

                 D'   feature
               /   \
... A -- B -- C --- M   master
               \  
                D 
                 \
                  E  feature2

затем попробовал перебазировать feature2 поверх master:

                 D'   feature
               /   \
... A -- B -- C --- M   master
                     \  
                      D'' 
                       \
                        E'  feature2
person cdosborn    schedule 11.05.2017
comment
Prior to the merge, you rewrite some of the commits in feature - нет, не делал. Единственное, что я сделал, это запустил команды git, которые я разместил в своем вопросе. Я думаю, что ответ @TimBiegeleisen хорошо объясняет, почему возникла проблема. - person max; 11.05.2017
comment
Если функция была сжата, а затем слита, тогда мастер содержит переписанную историю функции, то есть общая история между функцией и функцией2 никогда не объединяется с мастером (хотя изменения есть), поэтому, когда вы перебазируете, git не знает, что история feature2 уже находится в мастере, и вы видите коммиты. - person cdosborn; 11.05.2017
comment
Да, и мой собственный ответ, и @TimBiegeleisen утверждают это. Часть, в которой эти два ответа расходятся, заключается в том, при каких условиях эта общая история между feature и feature2 объединяется в master. IIUC TimBiegeleisen предложил линеаризовать главную ветвь; в моем ответе утверждается, что это не сработает, и что только обычное слияние github может достичь этой цели. Вы предлагаете другое решение? - person max; 11.05.2017
comment
Есть еще один интересный момент. Если вы видите коммиты типа D'', они будут включены (в перебазирование) только в том случае, если включение D'' имеет какой-либо эффект (или имеет изменения). Мне любопытно, почему вы видите эти коммиты. Если вам нужен совет: Git дает вам полный контроль над структурой вашей истории, перепишите ее, сделайте полезной для себя за несколько месяцев; редко сжимаются, редко объединяются, часто перемещаются, объединяются только тогда, когда вы хотите показать логическое объединение. - person cdosborn; 11.05.2017