ответ Phd правильный.Разбейте команду git pull
на две составляющие:
git fetch origin branchB:branchC
.Запустите его в той же настройке, т. Е. С branchC
, установленным для указания коммита, на который он указывал перед вашей командой git pull
.
git merge <hash-id>
.Фактический идентификатор хеша берется из .git/FETCH_HEAD
, где git fetch
оставляет его.Запустите его в той же настройке, установив branchA
для указания коммита, на который он указывал перед вашей командой git pull
.
Обратите внимание, что на шаге 2 git merge
,не влияет на ссылку branchC
.Это оказывает некоторое влияние на текущее имя ветви, то есть refs/heads/branchA
.Так как он запускает git merge
, он может выполнять слияние в прямом направлении, или истинное слияние, или вообще ничего.
Давайте углубимся в шаг fetch
, который на самом деле более интересен, илипо крайней мере, один вызов.
git ls-remote
Перед запуском git fetch origin branchB:branchC
, запустите git ls-remote origin
.Вот что я запускаю в Git-репозитории для Git (с большим количеством битов):
$ git ls-remote origin
e144d126d74f5d2702870ca9423743102eec6fcd HEAD
468165c1d8a442994a825f3684528361727cd8c0 refs/heads/maint
e144d126d74f5d2702870ca9423743102eec6fcd refs/heads/master
093e983b058373aa293997e097afdae7373d7d53 refs/heads/next
005c16f6a19af11b7251a538cd47037bd1500664 refs/heads/pu
7a516be37f6880caa6a4ed8fe2fe4e8ed51e8cd0 refs/heads/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86 refs/tags/gitgui-0.10.0
3d654be48f65545c4d3e35f5d3bbed5489820930 refs/tags/gitgui-0.10.0^{}
...
dcba104ffdcf2f27bc5058d8321e7a6c2fe8f27e refs/tags/v2.9.5
4d4165b80d6b91a255e2847583bd4df98b5d54e1 refs/tags/v2.9.5^{}
Вы можете видеть, что их Git предлагает моему Git длинный список имен ссылок и хэшейИдентификаторы.
Мой Git может выбрать их и выбрать, какие имена и / или идентификаторы ему нравятся, а затем перейти к следующему этапу git fetch
: спросите их, какие хеш-идентификаторы они могут использовать.дайте мне это, например, с коммитом e144d126d74f5d2702870ca9423743102eec6fcd
(идентификатор хеша для их master
).Мой Git сделал бы это, если бы я сказал ему перенести их master
или refs/heads/master
в качестве левой стороны refspec, поскольку эти строки имен совпадают с refs/heads/master
.
(безrefspecs, мой Git будет запрашивать все ответвления. Теги хитрее: --tags
мой Git принимает все, --no-tags
мой Git не принимает ни одного, но между ними есть какой-то ужасно извилистый код внутри git fetch
.)
В любом случае, они предлагают некоторые хэши, мой Git говорит, хочет ли он или имеет какие-то другие хэши, а их Git использует их git rev-list
для создания набора хеш-идентификаторов для коммитов, деревьев, больших двоичных объектов и / илианнотированные теговые объекты для помещения в так называемую тонкую упаковку .На этом этапе git fetch
вы видите сообщения об удаленном подсчете и сжатии объектов.
git fetch origin
Позвольте мне запустить фактический git fetch
сейчас:
$ git fetch origin
remote: Counting objects: 2146, done.
remote: Compressing objects: 100% (774/774), done.
remote: Total 2146 (delta 1850), reused 1649 (delta 1372)
В конце концов, их Git заканчивает упаковывать все отправляемые объекты и отправляет эти объекты.Мой Git получает их:
Receiving objects: 100% (2146/2146), 691.50 KiB | 3.88 MiB/s, done.
Мой Git исправляет тонкий пакет (git index-pack --fix-thin
), чтобы сделать его жизнеспособным обычным пакетом, который может находиться в моем каталоге .git/objects/pack
:
Resolving deltas: 100% (1850/1850), completed with 339 local objects.
Наконец, происходят самые интересные для нас части извлечения:
From [url]
ccdcbd54c..e144d126d master -> origin/master
1526ddbba..093e983b0 next -> origin/next
+ 8b97ca562...005c16f6a pu -> origin/pu (forced update)
7ae8ee0ce..7a516be37 todo -> origin/todo
Имена слева от стрелок ->
являются их именами;имена справа my Git.Так как я запускал только git fetch origin
(без refspecs), мой Git использовал мои default refspecs:
$ git config --get remote.origin.fetch
+refs/heads/*:refs/remotes/origin/*
, поэтому я как бы писал:
$ git fetch origin '+refs/heads/*:refs/remotes/origin/*'
который использует полностью определенные refspecs, а не частичные имена, такие как branchB:branchC
.Этот конкретный синтаксис также использует символы типа * glob-pattern *
.Технически это не совсем глобусы, так как это просто строки, а не имена файлов, и справа есть *
, но принцип аналогичен: я прошу мой Git соответствовать каждому имени, начинающемуся с refs/heads/
, ископируйте их в мой собственный репозиторий под именами, начинающимися с refs/remotes/origin/
.
В пространстве имен refs/heads/
находятся все имена веток моего Git.Пространство имен refs/remotes/
- это место, где находятся все имена для удаленного отслеживания моего Git, а refs/remotes/origin/
- это место, где мой Git и я поместили имена удаленного отслеживания, которые соответствуют именам ветвей, которые мы нашли в Git на origin
,Ведущий знак плюс +
впереди устанавливает флаг force , как если бы я выполнил git fetch --force
.
Обновления имени ссылки
Следующий шаг требует, чтобы мы посмотрели на граф коммитов - Направленный ациклический граф или DAG всех коммитов, найденных в моем Git-репозитории.В этом случае, поскольку новый файл пакета был интегрирован, он включает в себя все новые объекты, которые я только что добавил через git fetch
, так что у меня есть новые коммиты (и любые деревья и сгустки, необходимые для них), полученные из ихGit.
Каждый объект имеет уникальный хэш-идентификатор, но они слишком громоздки для непосредственного использования.Мне нравится рисовать мои графики слева направо в тексте в StackOverflow и использовать круглые o
s или одинарные заглавные буквы (или обе) для обозначения определенных коммитов.Предыдущие коммиты идут влево, позже коммиты вправо, а имя ветки указывает на коммит tip этой ветки:
...--o--o--A <-- master
\
o--B <-- develop
Обратите внимание, что в этом представлении GitВ объектной базе данных мы вообще не обращаем внимания на index / staging-area и вообще не обращаем внимания на work-tree .Нас интересуют только коммиты и их метки.
Поскольку я фактически получил свои коммиты от Git в origin
, у моего Git также есть origin/*
имена, поэтому давайте нарисуем их в:
...--o--o--A <-- master, origin/master
\
o--B <-- develop, origin/develop
Теперь предположим, что я запускаю git fetch
, и он вводит два новых коммита, которые я буду обозначать C
и D
.Родитель C
- A
, а D
- это узел непосредственно перед B
:
C
/
...--o--o--A <-- master
\
o--B <-- develop
\
D
Для моего Git сохранить эти коммиты, мойУ Git должно быть какое-то имя или имена , с помощью которых он может достичь этих коммитов.Имя, которое достигает C
, будет origin/master
, а имя, которое достигает D
, будет origin/develop
.Эти имена , используемые для , указывают на коммиты A
и B
соответственно, но git fetch origin +refs/heads/*:refs/remotes/origin/*
говорит моему Git заменить их, давая:
C <-- origin/master
/
...--o--o--A <-- master
\
o--B <-- develop
\
D <-- origin/develop
Вывод этого git fetch
отобразит это как:
aaaaaaa..ccccccc master -> origin/master
+ bbbbbbb...ddddddd develop -> origin/develop (forced update)
Обратите внимание на +
и три точки в выводе здесь.Это потому, что при перемещении origin/master
из коммита A
(хэш-идентификатор aaaaaaa
) в коммит C
была операция fast-forward , при перемещении origin/develop
из коммита B
в коммит D
было не .Для этого требуется флаг force .
Этот же процесс работает, даже если вы используете локальные имена ветвей
Если вы запускаете git fetch origin br1:br2
, вы инструктируете свой Git:
- вызвать Git на
origin
(на самом деле remote.origin.url
) - получить их список имен ветвей
- использовать их
br1
(вероятно refs/heads/br1
)обновить ваш br2
- скорее всего, ваш refs/heads/br2
, перенеся все объекты, необходимые для этого.
Эта фаза обновления, обновляющая ваш br2
на основена их br1
, не установлен флаг силы.Это означает, что ваш Git разрешит изменение в том и только в том случае, если операция выполняется в ускоренном режиме .
(Между тем, ваш Git также обновит ваш origin/br1
, потому что Git делает такое оппортунистическое обновление на основе remote.origin.fetch
. Обратите внимание, что это обновление * * имеет флаг принудительной установки, предполагая стандартную конфигурацию remote.origin.fetch
.)
Быстрая перемотка вперед - это свойство перемещения метки
Мы (и Git) говорим о выполнении ускоренного слияния , но это неверно по двум причинам,Первое и самое важное, что fast-forward является свойством движения метки.Учитывая некоторую существующую ссылочную метку (ветвь, тег или что-то еще) R , которая указывает на некоторый коммит C1
, мы говорим Git: переместить R в точку, чтобы зафиксировать C2
вместо .Предполагая, что оба хэш-идентификатора являются действительными и указывают на коммиты, при рассмотрении DAG для фиксации мы обнаружим, что:
C1
является предком C2
.Это изменение на R является ускоренным. - Или
C1
является , а не предком C2
.Это изменение R не является перемоткой вперед.
Особое свойство операции ускоренной пересылки заключается в том, что теперь, когда R указывает на C2
, если мы начнем с C2
и будем работать в обратном направлении, как всегда делает Git, мы в конечном итоге встретим C1
.Таким образом, C1
остается защищенным именем, и если R является именем ветви, commit C1
все еще находится в ветви R .Если операция не с ускоренной перемоткой вперед, C1
является , а не , достижимой из C2
, и C1
может больше не защищаться и может - в зависимости от того, что-либо ещезащищает его и его относительный возраст - собирать мусор в будущем.
Из-за вышеизложенного, обновление ссылки style style - имя ветви в refs/heads/
илиимя для удаленного отслеживания в refs/remotes/
- часто требуется использовать флаг принудительной установки, если обновление не является перемоткой вперед.Различные части Git реализуют это по-разному: git fetch
и git push
оба имеют --force
и начальный-плюс-знак, в то время как другие команды Git (не имеющие refspecs) просто имеют --force
или, как в случае git reset
, просто предположите, что вы - пользователь - знаете, что вы делаете.
(Очень старые версии Git, 1.8.2 и старше, случайно применили эту быструю перемотку впередправила для имен тегов, а также для имен ветвей.)
Команда git merge
знает об индексе и рабочем дереве
Что отличает операцию слияния git merge
ускоренной перемотки вперед - хорошоПо крайней мере, немного отличается от этого вида быстрой пересылки меток то, что git merge
знает и работает с вашим индексом / промежуточной областью и вашим рабочим деревом.Когда вы запускаете:
git merge <commit-specifier>
Git вычисляет базу слияния текущего коммита HEAD и данного другого коммита.Если эта база слияния является текущим коммитом, операция может быть выполнена как ускоренное перемещение метки вперед, если Git также приносит индекс и рабочее дерево вместе с ним.
Если база слияния являетсяпредок текущего коммита, или если вы используете флаг --no-ff
, git merge
должен выполнить настоящее слияние и сделать новый коммит слияния.(Конечно, есть также флаги для подавления коммита и для создания нового коммита как обычного коммита без слияния, поэтому в этом представлении git merge
также пропускаются некоторые важные детали.)