Git тянуть с refspec - PullRequest
0 голосов
/ 26 мая 2018

Я прочитал вопрос , и теперь у меня есть сомнения относительно того, как git pull работает с refpec:

Step 1 : I am on branchA.

Step 2 : I do `git pull origin branchB:branchC` .

Step 3: I notice : 

a) commits from branchB on remote comes and update `remotes/origin/branchC`

b) Then a merge happened. `branchC` was updated with `remotes/origin/branchC`

c) The `branchC` was merged into `branchA`.

Теперь я запутался, поскольку git pull = git fetch+ git merge, тогда как здесь произошло 2 слияния?Шаг b) и шаг c) объединены.

Ответы [ 3 ]

0 голосов
/ 26 мая 2018

ответ Phd правильный.Разбейте команду git pull на две составляющие:

  1. git fetch origin branchB:branchC.Запустите его в той же настройке, т. Е. С branchC, установленным для указания коммита, на который он указывал перед вашей командой git pull.

  2. 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 также пропускаются некоторые важные детали.)

0 голосов
/ 02 июня 2018

Ну, после прочтения @ torek-ans-1 и @ torek-ans-2 [Это нужно прочитать, чтобы понять работу git fetch / pull], я чувствуюотправить полный ответ на мой вопрос для тех, кто хочет получить его быстро.

Во-первых, шаги в вопросе неверны.Это правильные шаги:

Step 1 : I am on branchA.

Step 2 : I do `git pull origin branchB:branchC` .

Step 3: I notice : 

a) commits from branchB on remote comes and update `refs/heads/branchC`

b) Then based on `remote.origin.fetch` was used to try to update `remotes/origin/branchB` on our local.

[ Notice that no attempts will be made to update `remotes/origin/branchC`]

c) The `branchC` was merged into `branchA`.

[Порядок может варьироваться от одной версии git к другой]

На шаге a) + шаг b) объединения не происходит.Это называется ускоренным обновлением.Существует также нечто, называемое ускоренным слиянием, которое ведет себя так, но мы говорим ускоренное слияние, когда git merge ведет себя как ускоренное обновление.

Здесь на шаге а) + шаг б) не вызывается git merge.Следовательно, мы называем это ускоренным обновлением вперед, а не ускоренным слиянием.

В шаге c) будет вызываться git merge.

Короче: git pull origin branchB:branchC= git fetch origin branchB:branchC ((a) + (b))+ git merge branchC (c)

Сейчасмой вопрос был, почему 2 слияния называется?

нет 2 слияния.На шаге в) есть только 1 слияние.Да, есть 2 быстрых обновления и git fetch делает их.

0 голосов
/ 26 мая 2018

Шаг 2 - это не настоящее слияние, это ускоренное слияние .Перемотка вперед - единственный вид слияния, возможный для нетоковой (т. Е. Еще не извлеченной) ветви.Если быстрая пересылка невозможна, git прервет fetch/pull;в этом случае вы можете либо выполнить слияние (извлеките BranchC и запустить git pull origin branchB), либо принудительно обновите (git fetch origin +branchB:branchC), потеряв при этом локальные коммиты во главе BranchC.

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