В вашем вопросе есть ошибка.
Возможно, что шаг выборки git pull может выбрать более одной удаленной ветви (из-за fetch = +refs/heads/*:refs/remotes/origin/*
).
Простой git fetch
действительно выберет все ветви удаленного устройства (refs/heads/*
). Но git pull
не использует обычную git fetch
. Он использует вариант, в котором git fetch
обновляет только одно имя удаленного отслеживания (при условии, что версии Git> = 1.8.4).
Это не так уж важно для основного вопроса вашего вопроса по другим причинам, но, чтобы быть действительно ясным, я добавил (очень) длинное описание того, как работает git fetch
и как git pull
использует это. А пока давайте рассмотрим команду second , которая запускается git pull
. Помните, git pull
по сути:
- запустить
git fetch
(с различными параметрами), затем
- запустить вторую команду Git, обычно
git merge
(с различными параметрами).
Команда first собирает объекты - в основном, коммиты, а также все необходимое для их завершения - из какого-либо другого хранилища Git. После получения у вашего Git есть (некоторые из / достаточно) их Git коммитов, но они не являются частью любых из ваших ветвей. Вы, вероятно, хотите, чтобы хотя бы некоторые из них были частью хотя бы некоторой ветви, и вот тут появляется вторая команда.
Если вторая команда git merge
, ваш Git запускается:
git merge -m <em>message</em> <em>hash1</em>
[<em>hash2</em> ...
]
То есть, ваш Git указывает сообщение о слиянии - общего вида merge branch '<em>B</em>' of <em>URL</em>
, для некоторых B и URL - и один или несколько, но мы надеемся, что только один - принять хэш-идентификаторы. 1
В любом случае, команда any git merge
влияет только на ветку current . Любое слияние: 2
- ошибок (например, «не то, что мы можем объединить», «уже в курсе») или
- завершается нормально или
- выполняет операцию ускоренной перемотки вместо слияния или
- останавливается посередине из-за конфликта слияния или запроса пользователя (
--no-commit
).
В обычных случаях и в случаях ускоренной перемотки результат состоит в том, что текущее имя ветки - то, к которому прикреплено HEAD
- указывает на какой-то другой коммит: новый коммит слияния для реального слияния или хэш-идентификатор в качестве цели слияния для случая ускоренной перемотки вперед.
Итак, ответ на вопрос:
Слияние также происходит с другими извлеченными удаленными ветвями, которые не соответствуют локальной текущей ветке?
- это нет , но сам вопрос запутан или, по крайней мере, запутывает ing в его формулировке: слияния происходят от до только текущей ветви! Поскольку вы можете только проверить локальные филиалы, только локальные филиалы когда-либо будут объединены. В этом смысле слияния могут происходить только с местными филиалами. Я должен был бы догадаться о вашем значении для фразы "слияние происходит (...) с ... удаленными ветвями". (См. Очень длинный «Длинный» раздел ниже для такого предположения.)
Обратите внимание, что команда git merge
принимает в качестве аргументов один или несколько хеш-идентификаторов фиксации. Эти другие хеш-идентификаторы могут быть указаны через имена удаленного отслеживания, или как необработанные хеш-идентификаторы, или любым другим способом, которым Git принимает хеш-идентификатор, такой как относительное имя, такое как develop~7^2~3
(хотя я никогда не рекомендую это последнее выражение). Команда git pull
всегда использует необработанные хэш-идентификаторы, но скрывает этот факт с помощью аргумента git merge
-m
.
1 Случай, когда в нем указано более одного идентификатора хэша, составляет почти 100% из-за ошибки пользователя. Эта ошибка пользователя, по моему опыту, очень распространена, потому что git pull
- плохо спроектированная команда: думайте о ней как о многолезвийном швейцарском армейском ноже, где несколько лезвий никогда не могут быть закрыты, так что вы всегда порезали себе ладонь каждый раз, когда вы используете его, если вы не держите его очень деликатно. Слияние с несколькими хешами обычно приводит к тому, что Git называет слиянием осьминога , но я не хочу вдаваться в подробности этого здесь. Этот ответ уже слишком длинный.
2 Операция git merge --squash
очень похожа на git merge --no-commit
, так как --squash
включает --no-commit
. Это заставляет вас сделать окончательный коммит самостоятельно. Особенность git merge --squash
в том, что новый коммит, который Git сделает, когда вы делаете коммит самостоятельно, , в конце концов, не является коммитом слияния , а вместо этого является обычным коммитом с одним родителем. В остальном он точно такой же, как git merge --no-commit
.
Long
Некоторые хитрости здесь связаны с тем, как git pull
вызывает git fetch
. Чтобы понять это, мы должны начать с действительно, глубоко понимания git fetch
самого себя.
git fetch
Команда git fetch
сложна, и во многом это связано с тем, как работал действительно старый Git. Древний, изначальный Git не имел имен для удаленного слежения: не было такого понятия, как origin/master
. Это привело к большому уродству, поэтому в конечном итоге люди из Git изобрели имена для удаленного отслеживания, но по причинам обратной совместимости они не могли просто предполагать имена для удаленного отслеживания. Вот откуда происходит много странностей.
Не волнуясь об этом, помните, что git fetch
имеет несколько фаз. Первый этап - весь разговор за git ls-remote
. Я призываю людей, изучающих Git, запускать git ls-remote
хотя бы один раз, чтобы увидеть, что other Git говорит ваш Git в начале любого git fetch
.
Помните, что синтаксис для вызова git fetch
(упрощенный):
git fetch [<remote> [<refspec1> [<refspec2> ...]]]
remote
обычно по умолчанию равен origin
, но, конечно, вы можете быть явным и выписать origin
. Любые другие аргументы после origin
(или другого имени) являются refspecs . На данный момент мы предполагаем, что вы не предоставляете какие-либо refspecs, так что нам не нужно определять, как они работают; и мы предположим, что ваш Git и их Git используют так называемые интеллектуальные протоколы (глупые могут тратить много времени на передачу бесполезных данных, поэтому давайте не будем идти туда). Наконец, мы примем стандартную строку fetch =
, читающую fetch = +refs/heads/*:refs/remotes/origin/*
.
Вот фактический git ls-remote
вывод, немного урезанный (хорошо, lot ), для Git-репозитория для Git:
ecbdaf0899161c067986e9d9d564586d4b045d62 HEAD
0d0ac3826a3bbb9247e39e12623bbcfdd722f24c refs/heads/maint
ecbdaf0899161c067986e9d9d564586d4b045d62 refs/heads/master
2a65cea5f9ce26d22baec1ec541c86d41e2e700a refs/heads/next
855f98be272f19d16564ed44d8e858d8d459d7e5 refs/heads/pu
0596e1ad5143dab167e62bf39387d2b4e06cadb6 refs/heads/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86 refs/tags/gitgui-0.10.0
3d654be48f65545c4d3e35f5d3bbed5489820930 refs/tags/gitgui-0.10.0^{}
[massive snippage]
dcba104ffdcf2f27bc5058d8321e7a6c2fe8f27e refs/tags/v2.9.5
4d4165b80d6b91a255e2847583bd4df98b5d54e1 refs/tags/v2.9.5^{}
Как вы можете прочитать из самого правого столбца, их Git рассказал нам о пяти ветвях, которые у них есть: maint
, master
, next
, pu
и todo
. (Они также дали нам рекомендованную ветку для проверки через HEAD
и список из примерно 20 баджиллионных тегов вместе с коммитами, которые идентифицируют эти теги.)
В любом случае, первая фаза git fetch <em>remote</em>
заключается в том, что ваш Git вызывает какой-то другой Git, по какому-то URL-адресу (предположительно длинный и трудный для ввода) URL-адрес хранится в виде записи конфигурации, используя более короткий, простой и более значимая строка remote
- и делает именно то, что печатает git ls-remote <em>remote</em>
. Это дает вашему Git полный список всех их названий веток и тегов в Git.
Ваш Git затем запускает список через fetch =
строк, , если , вы не предоставили никаких refspecs. Это дает вашему Git список имен для удаленного отслеживания, таких как refs/remotes/origin/master
, для создания или обновления. Он также дает список идентификаторов хеша коммитов, которым ваш Git нужен . Помните, что любое имя, подобное refs/remotes/origin/master
в базе данных name-to-hash-ID, должно содержать допустимый идентификатор хеша, а не просто какой-либо хэш-идентификатор. Поэтому для того, чтобы ваш Git создал или обновил ваш origin/master
, он должен будет иметь локально фиксацию, хэш-идентификатор которой (в данном случае) ecbdaf0899161c067986e9d9d564586d4b045d62
.
На этом этапе ваш Git добавит эти обязательные-хеш-идентификаторы в список коммитов, которые ему необходимы для обеспечения присутствия. Теперь он переходит в фазу разговора «иметь / хочу / не хочу».
Для каждого идентификатора коммита, который отправитель имеет и может отправить, он скажет: У меня есть коммит с хэш-идентификатором H . Для каждого коммита, который ваш Git хочет , ваш Git будет говорить: Да, пожалуйста, пришлите мне этот коммит и расскажите мне также о родителях этого коммита. Если ваш Git уже имеет этот коммит, тем не менее, ваш Git скажет: Нет, мне не нужен этот коммит в конце концов. Тогда их Git скажет вам, что родительские хеши, и ваш Git может сказать да или нет, как раньше , Если ваш Git хочет этих родителей, его Git сообщает вашему Git о родителях родителей и так далее. В конце концов, следуя этим родительским цепочкам в обратном направлении, ваш Git достигает хеш-идентификаторов, которые у вас do уже есть, поэтому ваш Git говорит no этим; как только ваш Git отказался от всех родителей или их Git исчерпал родителей, эти два Git переходят к фактической передаче данных.
Чтобы выполнить передачу данных, их Git теперь упаковывает все выбранные коммиты (если они есть - может быть, у вас уже были все коммиты с ветвлением) и все необходимые подобъекты и отправляет их как то, что называется тонкая упаковка . Это отчасти требует больших вычислительных ресурсов на их конце; Вы можете увидеть сообщения об удаленном подсчете и сжатии объектов. Тем временем ваш Git просто пассивно получает данные тонкого пакета, пока эта фаза не будет завершена. Обычно это самая медленная часть разговора, и здесь вы видите загружаемые сообщения с номером MiB / сек или чем-то еще. Затем ваш Git «исправляет» тонкий пакет, превращая его в обычный (толстый?) Пакет. Это довольно интенсивная вычислительная фаза на вашем конце, и вы можете увидеть больше сообщений о проверке связности и т. П., Но так или иначе, теперь ваш Git имеет все коммиты, а также все другие необходимые объекты чтобы завершить эти коммиты.
Разговор уже завершен, поэтому, опять же, предполагая стандартную строку fetch
, ваш Git переходит к обновлению веток удаленного отслеживания. Теперь, когда ваш Git имеет необходимые коммиты, ваш Git заменяет старое значение вашего refs/remotes/origin/master
и т. Д. Их новыми значениями. Каждая из этих операций обновления:
- может быть перемоткой вперед и / или
- можно принудительно (с ведущим знаком
+
в ссылочных заданиях извлечения).
Принудительное обновление - это то, что будет делать Git, даже если сама операция обновления не является ускоренной перемоткой вперед. Ускоренное обновление - это любое обновление, в котором новый идентификатор хеша оставляет существующий хэш-идентификатор достижимый (полное обсуждение достижимости см. В Думайте как (а) Гит ). Таким образом, этот последний этап пытается обновить каждое сопоставленное имя. Обычно это принудительно, если необходимо, чтобы все эти обновления выполнялись независимо от быстрой перемотки вперед. Ваш Git печатает:
<oldhash>..<newhash> master -> master
, если ваш Git обновил ваш refs/remotes/origin/master
на основе их refs/heads/master
, и обновление было ускоренным. <oldhash>
- это сокращенный старый хэш-идентификатор для вашего refs/remotes/origin/master
, а <newhash>
- сокращенный новый хэш-идентификатор.
Ваш Git печатает:
+ <oldhash>...<newhash> master -> master (forced update)
(обратите внимание на знак плюс и три точки, а также примечание в скобках), если обновление было , а не ускоренной перемоткой вперед, но все равно произошло.
(Ваш Git ничего не печатает и не обновляет ваше имя для удаленного отслеживания, если обновление не является ускоренной пересылкой и не было принудительным. На практике это никогда не происходит, поскольку стандартная строка fetch =
имеет знак плюс.)
Теперь, когда мы знаем, как работает простой git fetch
, давайте посмотрим на него с refspecs
Сначала, конечно, нам нужно определить refspec . И git fetch
, и git push
используют их, хотя они немного различаются в деталях.
Refspecs действительно довольно просты: в основном это пары имен с двоеточием между двумя именами. В передней части есть дополнительный знак плюс, означающий force . То есть мы можем написать:
+refs/heads/master:refs/remotes/origin/master
, что очень похоже на то, что мы видим в строке fetch
.
Слева от двоеточия (после плюса, если есть) есть источник , а справа - пункт назначения . Таким образом, этот refspec означает использование refs/heads/master
в качестве источника, использование refs/remotes/origin/master
в качестве пункта назначения и установка флага принудительной установки . Мы возьмем их master
и сделаем его нашим origin/master
, и мы запустим обновление, если потребуется.
Но вам не нужно выписывать полный refspec, как это. Например, вы можете пропустить refs/heads/
(или для тегов refs/tags/
). В этом случае Git будет догадываться , имели ли вы в виду имя ветви или имя тега, проверяя содержимое своих и / или их имен. Это немного неаккуратно - например, если вы напишите master:master
, что произойдет, если есть не только ответвление с именем master
, но и тег с именем master
? Я не знаю ответа - мне нужно проверить, чтобы быть уверенным - но я думаю Git предпочтет тег здесь, так же как и для большинства локальных поисков имен. Тем не менее, люди делают это все время - опускайте refs/heads/
, я имею в виду, не делайте тег с именем master
. : -)
В конце концов, это означает, что вы будете часто видеть refspecs вроде master:master
. Это особенно полезно с git push
и вряд ли когда-либо с git fetch
: мы не хотим перезаписывать наш master
, вместо этого мы хотим обновить наш origin/master
.
Вы также можете использовать вид half -refspec:
master
или
:delete
Что это означает, является более сложным - в частности, оно отличается для git fetch
и git push
. С git push
, :delete
- это запрос на получение другого Git delete ветки или имени тега. С git fetch
опускать источник вообще не имеет смысла. (Тестирование показывает, что Git интерпретирует отсутствующий источник как HEAD
, но я бы посоветовал не предполагать этого - это нигде не задокументировано.)
Пропуск пункта назначения 1368 *, с другой стороны, говорит git fetch
, что не должен ничего обновлять локально . То есть, если вы запустите:
git fetch origin master
вы говорите Git: Перенесите их master
и не трогайте ни одного из моих названий ветвей. Это более разумно, поскольку ваши имена ветвей ваши , не их перезаписать! Вы могли бы, конечно, сделать:
git fetch origin master:newbranch
, которая создаст новую ветку в вашем хранилище с именем newbranch
, соответствующую origin
'* master
. И на самом деле, это то, что люди делали в старые добрые времена, до того, как существовали удаленные филиалы. Но в современном Git есть имена для удаленного отслеживания, например origin/master
. Поэтому мы хотим git fetch origin master
обновить origin/master
.
(При использовании git push
опускание пункта назначения означает, что использовать то же имя, что и источник , поэтому git push origin master
означает git push origin master:master
. Это самая большая асимметрия fetch-vs-push. Когда вы нажимаете, вы не используете какой-либо вид удаленного отслеживания в другом Git. Нет, вы используете их имена веток Git напрямую!)
В любом случае, начиная с Git версии 1.8.4, ваш Git просто автоматически обновит ваш origin/master
, когда ваш Git перенесет их master
(при условии, что снова будет стандартная строка fetch =
). До этой версии Git, однако, git fetch origin master
действительно извлекал их master
, а затем ... не обновлял вообще ничего .
Ну, это немного ложь, , потому что git fetch
всегда - с незапамятных времен - также создает или добавляет к файлу .git/FETCH_HEAD
. Именно здесь git pull
возвращается в картину, но сначала давайте закончим этот сценарий.
git fetch
без refspecs подчиняется вашей fetch =
настройке
Если вы запускаете git fetch origin
или git fetch
без аргументов вообще и Git выбирает origin
в качестве удаленного, Git будет использовать ваши настройки fetch =
, чтобы решить, что выбрать. Обычная настройка - «перенести все их имена ветвей и переименовать их в имена для удаленного отслеживания», и это обусловлено стандартным refspec с двумя звездочками и начальным знаком плюс.
Другая, несколько менее нормальная, но не сумасшедшая, fetch
строка - это строка, созданная git clone --single-branch
. Вместо +refs/heads/*:refs/remotes/origin/*
читается +refs/heads/somebranch:refs/remotes/origin/somebranch
. Вот что делает ваш клон отдельной веткой: каждый fetch
, который вы запускаете, без аргументов, только обновляет ваше одно имя для удаленного отслеживания из этой одной ветви в их Git. Тогда есть более странные fetch
строки, созданные git clone --mirror
, но они не применяются к обычным, не зеркальным клонам, а зеркальные клоны - это голые клоны, которые не позволяют вам выполнять какую-либо работу, поэтому мы будем просто игнорируй их.
В обоих этих обычных случаях - обычном клоне или клоне с одной ветвью - ваш git fetch
перенесет все или одну ветвь и обновит все или одно имя удаленного отслеживания. Затем, чтобы обеспечить обратную совместимость, ваш Git обновит ваш .git/FETCH_HEAD
, добавив в него все имена, которые принес ваш Git, и их хэш-идентификаторы. Никому не нужно заботиться, потому что в именах удаленного отслеживания есть все это. Содержимое файла .git/FETCH_HEAD
вообще не используется.
git fetch
с refspecs подчиняется вашим refspecs
Предположим, что вы запускаете git fetch origin master
. Это дает refspec , и ваш Git будет подчиняться ему. Ваш Git получит только их master
ветвь. Затем ваш Git обновит ваш origin/master
автоматически, , если ваш Git по крайней мере 1.8.4, потому что это разумная вещь, и ваша строка fetch =
нормальная.
В в каждом случае, хотя - даже на действительно древнем Git - ваш Git также обновит ваш .git/FETCH_HEAD
, и на этот раз это имеет смысл. Если ваш Git древний, то это только место, которое запоминает, какой хэш-идентификатор только что был перенесен, потому что ваш древний, до 1.8.4 Git не обновление origin/master
, Если ваш Git новее этого, ваш Git обновил , обновил origin/master
, но - здесь git pull
снова - возможно, этот git fetch origin master
был запущен git pull
.
git pull
всегда предоставляет refspecs
Как мы теперь знаем, если вы запустите:
git pull
(и при условии, что это сделает что-нибудь полезное), ваш Git запустит для вас git fetch
, а затем выполнит вторую команду для вас. Если вы запускаете git pull
с без аргументов, git fetch
, который запускается git pull
, получается из конфигурации:
$ git config --get-regexp '^branch\.master\.'
branch.master.remote origin
branch.master.merge refs/heads/master
Фактическое git fetch
, которое будет запускаться git pull
, поэтому:
git fetch origin refs/heads/master
Часть origin
получается из branch.master.remote
, а refspec буквально является точным значением branch.master.merge
. Эта конфигурация восходит к тем временам, когда в Git не было имен для удаленного отслеживания.
Этот конкретный git fetch
, теперь мы знаем, свяжется с другим Git в origin
и принесет с собой коммит чаевых ветки master
, из-за части refs/heads/master
. Это, в свою очередь, перенесет другие коммиты master-branch по мере необходимости. Наш Git может обновлять или не обновлять наш собственный refs/remotes/origin/master
, в зависимости от нашего винтажного Git, но во всех случаях наш Git запишет в .git/FETCH_HEAD
идентификатор хеш-функции своего master
-ответвленного коммита.
Сделав это, наш git pull
затем извлечет этот хэш-идентификатор из файла .git/FETCH_HEAD
. Этот - это источник хеш-идентификатора, когда наш git pull
пробеги git merge
для нас! Любое обновление до origin/master
является случайным, если речь идет о git pull
.
Мы также можем запустить:
git pull xyz
В этом случае наш Git будет использовать xyz
в качестве удаленного имени, но все равно будет работать:
git pull origin refs/heads/master
потому что branch.master.merge
установлено на refs/heads/master
. Поэтому на этот раз git fetch
вызовет любой URL, указанный в xyz
, получит их master
и, возможно, обновит наш refs/remotes/xyz/master
в зависимости от нашего винтажного Git. Но еще раз, наш Git запишет в .git/FETCH_HEAD
фактический идентификатор хеша , полученный для их master
, и наш git pull
передаст этот идентификатор хеша в git merge
.
Мы можем запустить:
git pull anyremote foobar
и наш git pull
будет работать:
git fetch anyremote foobar
, который пройдет через все причудливые совпадения, которые мы описали выше, чтобы выяснить, стоит ли перенести их refs/heads/foobar
или refs/tags/foobar
. Но независимо от того, что еще происходит, если этот git fetch
работает вообще, он записывает хэш-идентификатор для anyremote
foobar
в .git/FETCH_HEAD
, а затем наш git pull
вылавливает хэш-идентификатор и запускает git merge
.
Вот где ужасный инструмент git pull
наносит удар пользователям, снова и снова
Учитывая, что пользователи научились бегать:
git pull origin master
они сразу начинают думать: Эй, если я могу вытащить мастера, почему я не могу вытащить мастера и развить оба? Они бегут:
git pull origin master develop
Теперь, по идее, это вполне разумный запрос, но git pull
не делает этого. Вместо этого git pull
выполняет:
git fetch origin master develop
Команда git fetch
покорно вызывает origin
, переносит все коммиты, необходимые для его master
, переносит любые коммиты, необходимые для его develop
, и, возможно, обновляет имена удаленного отслеживания, в зависимости от того, как обычно наш Git винтаж. Затем наши fetch
обновляют .git/FETCH_HEAD
, записывая в него оба хэш-идентификатора .
Теперь наш git pull
работает один и только один git merge
:
git merge -m $message $hash1 $hash2
Это производит слияние осьминога в нашу текущую ветку. Чтобы получить два отдельных слияния, пользователь хотел , git pull
должен сделать:
git checkout master && git merge -m $message1 $hash1 &&
git checkout develop && git merge -m $message2 $hash2
с двумя очевидными сообщениями и двумя хэшами.
Это одна из многих причин, по которой я рекомендую новичкам Git избегать git pull
. Это делает что-то очень удивительное. Многие программисты совершают эту ошибку - я, как и многие коллеги, делали это еще в первые годы существования Git, более десяти лет назад. Документация сегодня намного лучше, так что, возможно, меньше пользователей совершают эту ошибку, но все же требуется много объяснений, прежде чем это станет иметь смысл.