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