Происходит ли этап слияния git pull с выбранными удаленными ветвями, не соответствующими локальной текущей ветке? - PullRequest
0 голосов
/ 10 января 2019

Контроль версий с Git говорит о шаге слияния git pull:

Но как Git узнал, как объединить эти конкретные ветви? Ответ исходит из файла конфигурации:

[branch "master"]
        remote = origin
        merge = refs/heads/master

Перефразируя, это дает Git два ключевых элемента информации: Когда мастер текущая , извлеченная ветвь, использование источника в качестве удаленного по умолчанию из которого можно получить обновления во время выборки (или извлечения). Далее, на этапе слияния git pull, используйте refs /heads / master из удаленного в качестве ветви по умолчанию, чтобы слить в это, мастер филиал.

Возможно, что шаг выборки git pull может выбрать более одной удаленной ветви (из-за fetch = +refs/heads/*:refs/remotes/origin/*).

Но шаг объединения говорит только о том, что слияние происходит с выбранной удаленной ветвью, соответствующей локальной текущей ветке . Происходит ли слияние также с другими извлеченными удаленными ветвями, которые не соответствуют локальной текущей ветке ? Если да, когда это произойдет?

Ответы [ 2 ]

0 голосов
/ 10 января 2019

В вашем вопросе есть ошибка.

Возможно, что шаг выборки 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, более десяти лет назад. Документация сегодня намного лучше, так что, возможно, меньше пользователей совершают эту ошибку, но все же требуется много объяснений, прежде чем это станет иметь смысл.

0 голосов
/ 10 января 2019

Другие местные ветви не подвержены влиянию git pull. (Но соответствующие указатели ваших веток на origin обновлены ).

Если вы переключаетесь на другую ветвь, которая также была обновлена, вы можете использовать git merge или git rebase для обновления вашей локальной ветки на удаленную ветку. git pull на самом деле не делает ничего больше, чем git fetch && git merge (git rebase, если ваше значение конфигурации pull.rebase установлено на true).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...