Конфликт Git Rebase: кто такой HEAD?

У меня есть этот проект, в котором удаленное репо имеет основную ветку разработки, и у меня есть форк, содержащий экспериментальную ветку. Мне необходимо rebase изменить ветку разработки на экспериментальную, прежде чем я отправлю ее на свой форк. Итак, это выглядит так:

git checkout experimentalbranch
git fetch remoterepo
git rebase remoterepo/developmentbranch

К этому времени я столкнулся с конфликтами. Однако я не знаком ни с одним из этих изменений (я перебазирую изменения, внесенные за неделю, потому что они не сразу объединили мои изменения). Кроме того, я впервые делаю rebase. Я больше привык к merge.

В сочетании это обычно похоже на <<LOCAL||REMOTE>> вместо merge, что звучит очень интуитивно. Но в rebase это <<HEAD||COMMIT MESSAGE>>. Кто такой HEAD? Это HEAD ветки разработки? Это последний код в ветке разработки или где-то еще?


person Joseph    schedule 21.04.2014    source источник


Ответы (2)


TL;DR (добавлено в мае 2018 г.)

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

Обратите внимание, что случаи, которые нас здесь интересуют, происходят, когда вы запускаете:

git checkout somebranch; git rebase origin/their-branch

или похожие. Перебазирование временно приостановлено, чтобы заставить вас разрешить конфликт слияния, после чего вы должны git add разрешить конфликт и запустить git rebase --continue. (Если вы используете какой-либо инструмент слияния с git mergetool или причудливый графический интерфейс, этот интерфейс может сделать часть или все это для вас каким-то другим способом, но внутри он git add обрабатывает разрешенные файлы и запускает git rebase --continue.)

В самом начале коммит HEAD является их ветвью, поэтому, если вы используете git checkout --ours или git checkout --theirs, --ours означает их — последний коммит origin/their-branch — а --theirs означает ваш, первый коммит, который вы перемещаете. Это обычная повседневная путаница в Git (см. Каково точное значение нашего и их в git?) и не то, что привело к первоначальному вопросу.

Позже, однако, коммит HEAD фактически представляет собой своего рода смесь. Это результат копирования некоторого количества ваших коммитов поверх последнего коммита. Теперь вы получаете конфликт между вашей собственной частично созданной серией новых коммитов и вашими собственными исходными коммитами. Источником этого конфликта является обычно что-то, что "они" сделали (что-то, что изменилось в origin/their-branch). Вам еще предстоит разрешить этот конфликт. Когда вы это сделаете, вы можете увидеть повторение того же самого конфликта в последующих коммитах.

Опять же, HEAD или local или --ours — это фиксация, созданная rebase путем объединения ваших изменений и их изменений, а другая фиксация (remote или >>>>>>> или --theirs) — это ваша собственная фиксация, которую rebase пытается скопировать поверх HEAD.

дольше

При слиянии (включая перебазирование, которое является частным случаем многократного внутреннего «слияния») задействованы две «головки» (два конкретных ответвления). Назовем их your-branch и origin/their-branch:

              G - H --------      <-- HEAD=your-branch
            /               \
... - E - F                   M   <-- desired merge commit [requires manual merge]
            \               /
              I - J - K - L       <-- origin/their-branch

Этот момент обычно (и неудивительно) сбивает с толку, хотя при такой маркировке он достаточно ясен.

Однако, что еще хуже, git использует --ours и --theirs для обозначения двух головных коммитов во время слияния, причем «наш» — это тот, на котором вы были (коммит H), когда вы запускали git merge, а «их» — это, ну, их. (совершить L). Но когда вы выполняете перебазирование, две головки меняются местами, так что «наша» — это головка, на которую вы перемещаете, т. е. их обновленный код, а «их» — это коммит, который вы в данный момент перемещаете, т. е. ваш собственный код.

Это связано с тем, что rebase фактически использует серию операций выбора вишни. Вы начинаете с почти той же картины:

              G - H           <-- HEAD=your-branch
            /
... - E - F
            \
              I - J - K - L   <-- origin/their-branch

Здесь git нужно скопировать результат коммитов G и H, т. е. git cherry-pick коммит G, а затем повторить это снова с коммитом H. Но для этого git должен сначала переключиться на коммит L внутренне (используя режим "detached HEAD"):

              G - H           <-- your-branch
            /
... - E - F
            \
              I - J - K - L   <-- HEAD, origin/their-branch

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

              G - H           <-- your-branch
            /
... - E - F                   G'   <-- HEAD
            \               /
              I - J - K - L   <-- origin/their-branch

Если слияние прошло неудачно, HEAD все еще остается в фиксации L (поскольку фиксация G' еще не существует). Таким образом, да, HEAD является главой их отдела разработки — по крайней мере, прямо сейчас.

Однако, как только копия G существует, HEAD перемещается в G', и git пытается скопировать изменения из H таким же образом (diff G против H, затем diff F против G' и объединяет результаты):

              G - H           <-- your-branch
            /
... - E - F                   G' - H'   <-- HEAD
            \               /
              I - J - K - L   <-- origin/their-branch

Опять же, если слияние не удается и требуется помощь, у вас остается HEAD, указывающий на G' вместо H', поскольку H' еще не существует.

Как только все слияния завершаются успешно и коммиты G' и H' действуют существуют, git удаляет метку your-branch из коммита H и вместо этого указывает на коммит H':

              G - H
            /
... - E - F                   G' - H'   <-- HEAD=your-branch
            \               /
              I - J - K - L   <-- origin/their-branch

и теперь вы перебазированы, и HEAD снова соответствует вашим ожиданиям. Но во время перебазирования HEAD является либо их концом ветки (коммит L), либо одним из новых коммитов, скопированным и добавленным за их концом ветки; и --ours означает, что ветка растет в конце L, а --theirs означает, что фиксация копируется из (G или H выше).

(Это в основном git, раскрывающий сырой механизм того, как он делает то, что он делает, что довольно часто происходит в git.)

person torek    schedule 22.04.2014
comment
Итак, в основном HEAD, в самой первой операции - это HEAD их ветки, а в последующих операциях HEAD теперь является недавно добавленными коммитами от меня? - person Joseph; 22.04.2014
comment
Да; но это может быть довольно запутанным, потому что они являются недавно добавленными коммитами в ветке, которую вы развиваете, у которой еще нет имени. На рисунках выше они находятся в среднем ряду, а не в верхнем или нижнем ряду. - person torek; 22.04.2014
comment
Спасибо за объяснение. мой босс бросил на меня такой взгляд, когда я объяснил, что запутался в том, что такое HEAD. я думаю, что он живет во вселенной перебазирования, а я жил во вселенной слияния. - person dtc; 15.10.2014
comment
Читая это, я все еще не уверен, когда встречаю <<<<<<< HEAD в конфликтующем файле, принадлежит ли этот бит мне или их? Означает ли сложность ответа, что его можно перефразировать как «это зависит»? - person dumbledad; 28.04.2016
comment
@dumbledad: да, это зависит от того, делаете ли вы git merge или git rebase (или, если уж на то пошло, git cherry-pick или git revert). Во всех случаях HEAD — это то, что было HEAD при выполнении фактической команды, поэтому наиболее запутанным является то, что git rebase запускает git cherry-pick (для интерактивной перебазировки) или git am (для неинтерактивной) из отдельного HEAD, который увеличивает новые коммиты на цели. - person torek; 28.04.2016
comment
Если, пока я остановлен в конфликте перебазирования, я использую HEAD в команде git, это тот же HEAD, что и HEAD в текущих маркерах конфликта? - person nafg; 09.01.2018
comment
Думаю, это не мой настоящий вопрос. Что я действительно хочу знать, так это то, какая фиксация внесла изменения в сторону HEAD конфликта. - person nafg; 09.01.2018
comment
@nafg: это сложно. Предположим, например, что rebase уже успешно скопировала первые три (всего четыре) ваших коммитов: тогда HEAD — это копия вашего третьего коммита. Конфликт слияния является результатом копирования вашего четвертого коммита. Но очевидно, что ваша четвертая фиксация прекрасно применялась в исходной серии из четырех. Откуда взялся конфликт? Это должен быть какой-то более ранний (предок-HEAD) коммит: не один из ваших трех, которые до сих пор скопированы, а что-то до этого момента. Но это может быть где угодно. - person torek; 09.01.2018
comment
Правильно, поэтому я изменил свой вопрос. Но git знает (или может); не мог ли он сказать мне? - person nafg; 09.01.2018
comment
@nafg: Это не совсем так: Git не знает, какая фиксация это ввела; он просто просматривает необработанные снимки (следовательно, конфликт указывает на HEAD). Вы можете запустить git blame, чтобы Git нашел более раннюю фиксацию, которая касалась рассматриваемой строки (строк). Обратите внимание, что это просто находит последний коммит, который касается этих строк; более ранние коммиты все еще могут быть основным источником конфликта (например, возможно, коммит Джо касается строк, слегка их переформатируя, но коммит Индиры до этого вносит серьезные изменения, и это тот, на который вам следует обратить внимание). - person torek; 09.01.2018
comment
@torek технически это верно, но это более широкий вопрос о git; точно так же, возможно, Индира на самом деле перемещала код, созданный Майком, в какой-то другой файл. Или репо. Git не работает на этом уровне. Это более низкий уровень, чем это. Но что есть, то есть. С точки зрения git, я бы сказал, что последний коммит, касающийся этих строк, — это все, что имеет значение. Поэтому я был бы очень признателен, если бы git показывал в маркере конфликта последнюю фиксацию, которая относится к blame для этой версии. - person nafg; 29.01.2018
comment
Возможно, еще одно измерение заключается в том, что сторона конфликта имеет несколько строк, которые могут иметь разные коммиты вины, и, возможно, не всегда легко определить, какая из них является последней (если они из разных родителей слияния)? Затем снова вы можете выбрать фиксацию слияния... - person nafg; 29.01.2018
comment
@nafg: В общем, способ узнать это - запустить git blame в зависимости от того, какой коммит внес интересующие вас данные. Но даже в этом случае git blame несовершенен: он должен сделать линейный обход истории, в то время как история не обязательно линейна. Он использует эвристику для коммитов слияния, чтобы выбрать ту сторону, которая кажется более перспективной для этого файла. Обратите внимание, что git blame ищет объединенный и скопированный код; это довольно хорошо при поиске вещей в большинстве реальных случаев. - person torek; 29.01.2018
comment
Этот ответ хорош, но мог бы быть лучше с записью TL; DR вверху. - person Panzercrisis; 17.05.2018
comment
@torek лол! :) - person Panzercrisis; 17.05.2018
comment
@torek Самое ясное объяснение перебазирования! Другие источники пропускают часть слияния, создавая впечатление, что rebase вообще не сливается. Все эти вещи с быстрой перемоткой... - person Tim; 14.02.2019
comment
Могу ли я получить TLDR TLDR? - person David van Dugteren; 06.07.2020
comment
@DavidvanDugteren: Это сбивает с толку? :-) (Возможно, даже сбивает с толку...) - person torek; 06.07.2020
comment
Диаграмма, сделанная другим автором в другой ветке Stackoverflow, — это TLDR, который мне был нужен. - person David van Dugteren; 07.07.2020

Определения

В этом разделе мы увидим ответы, которые нам задают в ответ:

Кто такой ГЛАВА?

HEAD: текущая фиксация вашего репозитория. В большинстве случаев HEAD указывает на последний коммит в вашей ветке, но это не обязательно. HEAD на самом деле просто означает «на что сейчас указывает мой репо».

Если фиксация HEAD не является вершиной какой-либо ветки, это называется "detached head".

Это НАЧАЛЬНИК ветки разработки?

В момент выполнения слияния или перебазирования HEAD немедленно переходит к указателю на созданный или реорганизованный коммит и поэтому будет указывать на ветку разработки.

В git bash мы видим ситуацию HEAD, перечисляя коммит:

# Normal commit list
git log
# List of commit in a single line
git log --oneline 
# All commits graphically-linear (Recommended as alias)
git log --all --graph --decorate --oneline

Упражняться

В этом разделе мы увидим, _как_ работают некоторые действия, выполняемые пользователем

Когда пользователь переходит к:

# Command to change from the branch to the current one to experimentalbranch
git checkout experimentalbranch
# Command that traverses the typical workflow to synchronize its local repository with the main branch of the central repository (remoterepo)
git fetch remoterepo
# git fetch origin
# git fetch origin branch:branch
# With the command git rebase, you can take all the changes confirmed in one branch (remoterepo), and reapply them over another developmentbranch
git rebase remoterepo/developmentbranch

К этому времени я столкнулся с конфликтами. Однако я не знаком ни с одним из этих изменений (я переустанавливаю изменения за несколько недель, потому что они не сразу объединили мои изменения). Кроме того, я впервые делаю перебазирование. Я больше привык к слиянию.

Объединение ветвей осуществляется двумя способами:

  • git слияние

  • git перебазировать.

Примечание .

Для примеров мы будем использовать следующее дерево :

* a122f6d (HEAD -> remoterepo) Commit END
* 9667bfb Commit MASTER
| * b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
| * 110b2fb Commit 2
| * e597c60 Commit 1
|/
* 0e834f4 (origin/remoterepo) First commit

git слияние

Самая известная форма — git merge, которая выполняет слияние трех полос между двумя последними снимками каждой ветки и общим предком для обеих, создавая новую фиксацию со смешанными изменениями.

Например :

git checkout remoterepo
git merge experimentalbranch

Это произвело бы нас:

*   003e576 (HEAD -> remoterepo) Merge branch 'experimentalbranch' in remoterepo
|\
| * b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
| * 110b2fb Commit 2
| * e597c60 Commit 1
* | a122f6d Commit END
* | 9667bfb Commit MASTER
|/
* 0e834f4 (origin/remoterepo) First commit

git перебазировать

git rebase в основном он собирает одно за другим изменения, подтвержденные в одной ветке, и повторно применяет их в другой.

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

Например :

git checkout remoterepo
git rebase experimentalbranch


* f8a74be (HEAD -> remoterepo) Commit END
* 4293e9d Commit MASTER
* b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
* 110b2fb Commit 2
* e597c60 Commit 1
* 0e834f4 (origin/remoterepo) First commit

Что такое происхождение?

origin : имя по умолчанию, которое git дает вашему основному удаленному репозиторию. У вашего ящика есть собственное репо, и вы, скорее всего, отправляете его в какое-то удаленное репо, куда вы и все ваши коллеги отправляете. Это удаленное репо почти всегда называется источником, но это не обязательно.

person Nicolás Alarcón Rapela    schedule 14.06.2018