(Примечание: я начал писать сегодня рано утром и закончил поздно вечером; на вопрос был дан промежуточный ответ, но, выполнив всю эту работу, я все еще собираюсь опубликовать ответ. :-))
TL; DR
Команда git fetch
никогда ничего не объединяет.Он может обновлять ссылки и очень хочет быстро обновлять ссылки, подобные ветвям.Более неохотно обновлять такие ссылки без ускоренной перемотки;чтобы сделать это, вы должны принудительно обновить.
Быстрая перемотка вперед, если она должным образом отделена от идеи слияния, является свойством изменения ссылки, которая относится ккоммит .В частности, нас обычно интересует, изменяется ли значение имени ветви или значения имени для удаленного отслеживания в ускоренном режиме.Это означает, что мы должны посмотреть на граф фиксации , поскольку это новое место в графе фиксации в сочетании с коммитом , выбранным в данный момент ссылкой, чтоопределяет, является ли обновление этой ссылки быстрой перемоткой вперед.
Long
Первоначальное утверждение здесь неверно, по крайней мере, одним важным способом:
Iзнайте, что git fetch
всегда выполняет быстрое слияние между ветвью и удаленным отслеживанием после получения коммитов с пульта.
Давайте разберем это немного, чтобы у нас были правильные словаи фразы для использования.Нам нужно знать:
- что такое ссылка ;
- что такое refspec ;и что наиболее важно,
- что означает сделать обновление с ускоренной пересылкой по сравнению с без ускоренной пересылки обновление с ссылкой.
Этот последний бит также включает флаг force: каждое обновление ссылки может быть принудительным или не принудительным.Возможно, вы знакомы с git push --force
, который устанавливает флаг форсировки для каждый указатель, который Git нажимает.Команда git fetch
имеет тот же флаг, с тем же эффектом, но, как правило, «все или ничего» слишком широкое, поэтому в Git есть способ установить флаг принудительного применения на более индивидуальной основе.(Команда git push
имеет здесь еще больше уточнений, но мы упомянем их только мимоходом.)
Определение reference и refspec
A ссылка в Git - это просто имя - в идеале, имя, которое имеет смысл для человека - для какого-то конкретного коммита или другого объекта Git. 1 Ссылки всегда 2 начинаются с refs/
и в большинстве случаев имеют второй компонент, разделенный косой чертой, который объявляет, к какому типу ссылок они относятся, например, refs/heads/
является именем ветви , refs/tags/
является именем тега , а refs/remotes/
является именем удаленного отслеживания . 3
(ссылки, о которых мы здесь заботимся, длярешение о том, является ли какое-то обновление быстрым или нет, - это те, которые мы хотели бы вести «по-ветвистому»: те, что в refs/heads/
, и те, в refs/remotes/
. Правила, которые мы обсудим чуть позже может применяться к любой ссылке, но определенно применяется к этим ссылкам "branch-y".)
Если вы используете неквалифицированное имя, такое как master
, где Git либо требует, либо может использовать ссылку, Git найдет полную ссылку, используя шестиэтапную процедуру, описанную в начале в документации gitrevisions чтобы преобразовать сокращенное имя в полное имя. 4
A refspec в Git - это в основном пара ссылок, разделенных двоеточием (:
), с необязательным начальным знаком плюс +
.Ссылка слева - это источник , а ссылка справа - пункт назначения .Мы используем refspecs с git fetch
и git push
, которые соединяют два разных репозитория Git.Ссылка на источник предназначена для использования любым Git, отправляющим коммиты и другие объекты Git, а пункт назначения предназначен для использования получающего Git.В частности, для git fetch
источником является other Git, а местом назначения являются мы сами.
Если ссылка в refspec не полностью определена (не начинается с refs/
), Git может использовать описанный выше процесс для его определения.Если обе ссылки в одной refspec не определены, Git содержит некоторый код, чтобы попытаться поместить их обе в подходящее пространство имен, но я никогда не доверял этому коду.Мне, например, неясно, кто действительно определяет источник и назначение во время выборки: участвуют два Git, но другой Git обычно высылает нам полный список всех своих ссылок, поэтому наш Git может выполнить разрешение, используяэтот списокОчевидно, что здесь разумнее использовать полностью квалифицированные ссылки, однако, в случае, если их набор ссылок не соответствует вашим собственным ожиданиям: если у них есть только refs/tags/xyz
и вы ожидаете, что xyz
расширится доrefs/heads/xyz
, вы можете быть удивлены, когда это не так.
В любой refspec, вы можете опустить либо исходную, либо целевую часть.Чтобы опустить пункт назначения, вы пишете refspec без двоеточия, например, refs/heads/br
.Чтобы опустить исходный код, вы пишете refspec с двоеточием, но без указания места исходной части, например, :refs/heads/br
.Что это означает , когда вы делаете, эти вещи меняются: git fetch
относится к ним совсем иначе, чем git push
.А пока, просто обратите внимание, что есть исходная и целевая части с возможностью их опускания.
Ведущий плюс, если вы решите его использовать, всегда идет впереди.Следовательно, git push origin +:refs/heads/br
- это толчок с установленным флагом силы из пустого источника в пункт назначения refs/heads/br
, который полностью квалифицирован.Так как это push, источник представляет имя нашего Git (нет), а назначение представляет имя их Git (ветвь с именем br
).Подобная строка +refs/heads/br
имеет установленный флаг силы, имеет полностью определенный источник и не имеет места назначения.Если бы мы имели дело с git push
, мы могли бы взглянуть на значения этих двух refspecs для push, но давайте перейдем сейчас.
1 Любая ссылка, подобная ветви должен указывать на коммит.Имена тегов могут указывать на любой объект.У других имен ссылок могут быть другие ограничения.
2 Внутри самого Git есть некоторое внутреннее разногласие относительно того, должна ли каждая ссылка записываться в форме полного имени как нечтосоответствие refs/*
.Если бы это было так, HEAD
никогда не был бы ссылкой.На самом деле специальные имена, такие как HEAD
и ORIG_HEAD
и MERGE_HEAD
, иногда действуют как обычные ссылки, а иногда нет.Для себя я в основном исключаю их из понятия ссылки, за исключением случаев, когда их удобно включать.Каждая команда Git решает, как и нужно ли обновлять эти *_HEAD
имена, поэтому нет формального системного подхода, как есть - или, в основном, есть, учитывая другие странные особые случаи, которые возникаютв некоторых командах - для ссылок на стиль refs/
.
3 Существуют более известные подпространства: например, refs/replace
зарезервировано для git replace
.Идея здесь, однако, достаточно проста: за refs/
следует еще одна читаемая человеком строка, которая говорит нам, что это за ссылка.В зависимости от типа, мы можем потребовать еще одно подпространство, как в случае refs/remotes/
, где мы в следующий раз хотим знать: какой удаленный?
4 Некоторые команды Git знают или предполагают, что сокращенная ссылка должна быть именем ветви или именем тега.Например, git branch
не позволит вам разобрать refs/heads/
в некоторых местах: он просто грубо запихивает refs/heads/
в себя, так как он только работает с именами ветвей.Шестиступенчатая процедура обычно используется, когда нет четкого должно быть именем ветви или должно быть именем тега rule.
Graph commit
Прежде чем мы сможем определить, что значит сделать ускоренное обновление , нам нужно взглянуть на график commit .Ускоренная перемотка вперед и без ускоренной перемотки имеет смысл только в контексте коммитов и графа коммитов.В результате это имеет смысл только для ссылок, которые относятся конкретно к commitits .Подобные ветке имена - те, что в refs/heads/
и те в refs/remotes/
- всегда указывают на коммиты, и это те, о которых мы заботимся здесь.
Коммиты однозначно идентифицируются по их хэш-идентификатору. 5 Каждый коммит также хранит некоторый набор parent хеш-идентификаторов коммитов.Большинство коммитов хранят один родительский идентификатор;мы говорим, что такой коммит указывает на его родительский коммит.Эти указатели образуют обратную цепочку, от самого последнего коммита до самого старого: например,
A <-B <-C
в крошечном репозитории всего с тремя коммитами.Коммит C
имеет коммит B
в качестве непосредственного родителя, поэтому C
указывает на B
.Коммит B
имеет коммит A
в качестве непосредственного родителя, поэтому B
указывает на A
.A
это самый первый сделанный коммит, поэтому он не имеет родителя: это корневой коммит , и он нигде не указывает.
Эти указатели образуют отношения предок / потомок.Мы знаем, что эти указатели всегда смотрят назад, поэтому нам не нужно рисовать внутренние стрелки.Нам do нужно что-то, чтобы идентифицировать tip commit структуры данных, однако, чтобы Git мог найти end этих цепочек:
o--o--C--o--o--o--G <-- master
\
o--o--J <-- develop
Здесь master
указывает на некоторый коммит G
, а develop
указывает на J
.Следование J
в обратном направлении или G
в обратном направлении в конечном итоге приводит к фиксации C
.Коммит C
, следовательно, является предком коммитов G
и J
.
Обратите внимание, что G
и J
не имеют родительских / дочерних отношений друг с другом!Ни один не является потомком другого, и ни один не является родителем другого;они просто имеют некоторого общего предка, как только мы уходим достаточно далеко назад во времени / истории.
5 Фактически, каждый объект Git уникально идентифицируется егоидентификатор хешаЭто, например, то, как Git хранит только одну копию содержимого некоторого файла, даже когда эта конкретная версия этого файла сохраняется в десятках или тысячах коммитов: коммиты, которые не изменяют содержимое файла, могут повторно использовать существующий BLOB-объектobject.
Определение fast-forward
Fast-forward-ness - это свойство перемещения метки .Мы можем перемещать существующие имена (master
и develop
), но давайте на минуту не будем этого делать.Предположим, вместо этого мы добавляем новое имя и указываем на фиксацию C
.Давайте добавим однобуквенные хэш-идентификаторы и для остальных коммитов:
............ <-- temp
.
A--B--C--D--E--F--G <-- master
\
H--I--J <-- develop
Теперь мы можем попросить Git переместить новое имя из коммита C
в любой другой коммит.
Когда мы это сделаем, мы можем задать еще один вопрос об этом шаге.В частности, temp
в настоящее время указывает на фиксацию C
.Мы выбираем другой идентификатор из вселенной A
-through- J
возможных коммитов и сообщаем Git move temp
, чтобы он указывал на этот вновь выбранный коммит.Наш вопрос прост: Является ли новый коммит потомком коммита, на который указывает метка прямо сейчас?
Если это перемещение метки приводит к имени temp
, указывающему на коммит, который является потомком C
, этот ход является ускоренной перемоткой вперед.Если нет - если мы выберем коммит B
или A
- это движение , а не ускоренная перемотка вперед.
Вот и все - это все быстроевперед есть.Это ответ на вопрос, приведет ли это обновление к эта метка , которую мы собираемся сделать прямо сейчас , приводит ли метка к движению вперед вдоль некоторой цепочки наших обращенных назад коммитов.
Причина, по которой это особенно интересно для ответвлений имен - имен в пространстве refs/heads/
- в том, что git commit
создает новый коммит, чьим родителем является текущий коммит, и добавляет этот новый коммит в граф, а затем обновляет текущее имя ветки , чтобы указать на новый коммиттолько что сделал.Поэтому повторяющаяся серия операций git commit
приводит к прямому перемещению метки ветвления на один шаг за раз.Например, если мы извлечем develop
и сделаем два новых коммита, мы получим:
A--B--C--D--E--F--G <-- master
\
H--I--J--K--L <-- develop
с именем develop
, указывающим на второй из этих новых коммитов.
Если, манипулируя с temp
, мы указали нашему имени ветви temp
точку для фиксации J
, мы могли бы теперь fast-forward temp
указать на фиксацию L
.Поскольку L
указывает на K
, что указывает на J
, все операции Git, которые следуют за этими цепочками, будут обрабатывать коммит K
как все еще находящийся "на" ветви temp
.Так что быстрая перемотка вперед интересна, потому что это означает, что мы не «теряем» коммиты.
С другой стороны, если мы поставили temp
вместо E
, переместив temp
теперь, чтобы указать наK
"потеряет" коммиты D
и E
из ветви temp
.Эти коммиты все еще в безопасности на master
, поэтому они все еще защищены здесь.Если они по какой-то причине больше не были master
- например, если мы сделали что-то странное или необычное для master
, такое как удаление имени ветви - , тогда фиксирует D
и E
будет защищен именем temp
до тех пор, пока мы не потянем temp
без ускоренной перемотки вперед.Если temp
является только именем, защищающим эти коммиты от сборщика мусора, они становятся уязвимыми.
Сравнение быстрой пересылки с тем, что означает слияние , означает, чтоглагол
У Git есть то, что он называет ускоренным слиянием .Мне не нравится фраза «ускоренное слияние», поскольку на самом деле это вообще не слияние - это больше похоже на выполнение git checkout
, за исключением того факта, что имя ветви перемещается.Но в документации git merge
используется эта фраза, после более формального высказывания, что некоторое слияние разрешается как ускоренная перемотка , поэтому мы должны иметь возможность интерпретировать ее.
A ускоренное слияние в Git является результатом выполнения git merge <em>other</em>
, где other
- это коммит, который строго опережает (т.е. является потомком) текущийили HEAD
коммит в графе.Это означает, что ветка name , к которой прикреплен HEAD
, может быть перемещена в ускоренном режиме.Например, с именем ветки temp
, указывающим на коммит C
, мы можем выполнить:
git checkout temp
git merge <hash-of-commit-E>
Git поймет, что перемещение метки temp
из коммита C
в коммит E
ускоренная перемотка вперед на этом ярлыке.Главное, что позволяет нам использовать глагол merge , это тот факт, что мы просто использовали git merge
для его достижения: поэтому команда git merge
обновляет наш индекс и рабочее дерево а также выполнение операции ускоренной перемотки вперед.
Но этопросто git merge
позаимствовал концепцию быстрой пересылки.Перемотка вперед сама по себе не является концепцией «слияния».Если вы запускаете другой git merge <em>other</em>
, где other
является , а не потомком текущего коммита, но является потомком некоторого общего предка текущего коммита, т. Е. Базы слияния, тогда в этом случае git merge
выполняет истинное слияние, используя ваш индекс и рабочее дерево в качестве областей, в которых выполняется слияние. То, что - это слияние, операция, которая действительно заполняет глагол фразы , чтобы объединить .
(У нас нет такого коммита в нашем графе - мы быдолжен сделать потомком A
или B
, после чего коммит A
или коммит B
станет базой слияния.)
Ни git fetch
, ни git push
никогда не сливаются
Как мы только что отметили, реальное слияние требует - по крайней мере потенциально - использования индекса и рабочего дерева.Команда git fetch
не не касается индекса и рабочего дерева.git push
часто делается в --bare
хранилище, которое даже не имеет рабочего дерева!
A git fetch
или git push
операция может сделать ускоренную перемотку.Поскольку ускоренная пересылка не слияния, это не противоречит нашему требованию "никогда не объединять".Операция git fetch
или git push
может также выполнять операции без быстрой перемотки с именами ссылок, включая имена ветвей, но для этого необходимо включить флаг force эта конкретная операция.
(Команда git push
предлагает не только «обычный» и «принудительный», но и «принудительный с арендой», что аналогично команде сравнения и замены или инструкции CAS вмногопоточное программирование. Команда fetch не имеет этой опции CAS, она имеет только простой или принудительный тип.)
Как git fetch
использует refspecs для обновления ссылок
Команда git fetch
имеет(по крайней мере, в зависимости от того, как вы считаете) две части:
- передача коммитов (и других объектов Git) из другого Git в наш Git, увеличивая наш граф коммитов;
- опционально, обновите некоторые ссылки в нашем репозитории.
У него есть побочный эффект записи всего , который он знает о новых коммитах в .git/FETCH_HEAD
, который является специальным файлом, который определенно не является справочнымrence - в этом нет никакой двусмысленности, в отличие от HEAD
, - но он содержит хеш-идентификаторы (плюс дополнительная информация о том, что наш Git видел от другого Git).Остальная часть Git может использовать данные, оставленные в этом файле, даже если git fetch
не обновляет никаких ссылок.
Теперь, помните, что refspec может перечислять как исходную ссылку, так и целевую ссылку, или простоисточник или просто пункт назначения.Он также может иметь начальный знак +
, обозначающий «силу в случае необходимости».
Если обратить особое внимание на git fetch
, то при рассмотрении того, что должно произойти во второй половине, мы имеем три возможныхслучаи:
- refspec с источником и адресатом: используйте источник, чтобы найти имя в другом репозитории Git;используйте место назначения, чтобы выбрать имя для обновления в нашем собственном хранилище.
- refspec с источником, но без места назначения: используйте источник, чтобы найти имя в другом хранилище Git, но не обновляйте локальное имя (носм. ниже).
- refspec с адресатом, но без источника: ошибка.
В оченьстарые версии Git - до Git версии 1.8.4 - операция git fetch
просто подчиняется любым ссылочным спецификациям, указанным в командной строке.Если вы не укажете ему refspecs, он будет использовать и подчиняться директивам remote.<em>remote</em>.fetch
в конфигурации.То есть в этих старых версиях Git запуск git fetch origin xyz
извлекает любую ссылку, соответствующую xyz
, и, поскольку существует пункт назначения no , это обновляет ссылку no в нашем собственном репозитории!(Команда по-прежнему записывает информацию в .git/FETCH_HEAD
, как и всегда.) Обратите внимание, что xyz
может быть тегом: другой Git может найти refs/tags/xyz
, а не refs/heads/xyz
.Мы не указали;если мы хотим быть уверенными в получении ветки , нам нужно указать refs/heads/
.
Если ваш Git по крайней мере версии 1.8.4, то когда git fetch
приводит к ответвление имя, Git делает случайное обновление , используя ваши remote.<em>remote</em>.fetch
настройки выборки.Итак, предполагая нормальную настройку remote.origin.fetch
, git fetch origin refs/heads/xyz
:
- ничего не обновляет из-за пустой части назначения;
- , но затем обновляет
refs/remotes/origin/xyz
из-за fetch
setting.
Как только git fetch
приступает к выполнению всех своих обновлений, каждое обновление:
- может быть успешным, поскольку правила для такого рода ссылок позволяютобновление или
- может завершиться ошибкой, поскольку правила не допускают этого, а флаг принудительного вызова не установлен;или
- может быть успешным, потому что, хотя правила не допускают этого, флаг силы установлен.
Предположим, что мы запустим:
git fetch origin refs/heads/xyz:refs/heads/abc
и что - это a refs/heads/xyz
на другом Git в origin
.Предположим далее, что наш Git по крайней мере 1.8.4 и у нас есть обычный refspec в remote.origin.fetch
.Тогда наш Git:
- Приносит коммиты, которые идут вместе с их Git's
refs/heads/xyz
, если необходимо. - Пытается обновить наш
refs/heads/abc
.Это обновление не принудительное.Это обновление связано с тем, что мы сказали нашему Git в командной строке. - Попытки обновить наш
refs/remotes/origin/xyz
.Это обновление является принудительным.Это обновление связано с тем, что мы сказали нашему Git через remote.origin.fetch
.
Поскольку refs/heads/
и refs/remotes/
являются пространствами имен в стиле ветвей, наш Git, который, как мы знаем, равен по крайней мере 1,8.4 - следует правилам обновления ветки здесь. 6 Они сообщают Git, что обновление автоматически разрешено , если это ускоренная перемотка вперед .
Для пункта 2 здесь обновляемое имя - refs/heads/abc
(потому что это справа от refspec в командной строке).Опять же, fast-forward здесь не имеет ничего общего со слиянием: Git просто проверяет, является ли текущее значение refs/heads/abc
предшественником предложенного нового значения refs/heads/abc
.Если это так, это обновление разрешено.Если нет, то это не так.
Для элемента 3 обновляемым именем будет refs/remotes/origin/xyz
(потому что слева было выбрано refs/heads/xyz
, а по умолчанию refspec читается +refs/heads/*:refs/remotes/origin/*
).У этого refspec установлен флаг принудительной установки , поэтому обновление до refs/remotes/origin/xyz
произойдет .Это будет обычное ускоренное обновление без принудительного обновления, если изменение является ускоренным.Это будет принудительное обновление без ускоренной перемотки вперед, если изменение не является ускоренной пересылкой.
6 В Git 1.8.2 и более ранних версиях Git случайно применяетОбновление ветки "должно быть ускоренной пересылкой" правил также для имен тегов.В Git 1.8.4 это было исправлено.Однако новая ошибка была введена в какой-то момент .Код внутри Git для обновления ссылок во время git fetch
ужасен и извращен, и я думаю, что, вероятно, его следует выбросить и перекодировать с нуля, но на самом деле это - настоящий кошмар.
Есть еще одно особое ограничение в git fetch
Мымимоходом отмечалось, что специальное имя HEAD
, которое, вероятно, не является ссылкой, обычно присоединяется к некоторому имени ветви.Когда ваш HEAD присоединен к какой-либо ветви, эта ветвь является вашей текущей веткой .Это внутреннее определение того, что имеет эту ветвь в качестве текущей ветви: имя ветки должно быть внутри файла .git/HEAD
.
По умолчанию git fetch
отказывается обновлять название этой ветки .То есть, если HEAD
присоединен к master
, git fetch
просто не обновится refs/heads/master
.Запуск git fetch origin refs/heads/master:refs/heads/master
не сможет обновить ваш refs/heads/master
.После того, как вы git checkout
какая-то другая ветвь, например, прикрепив HEAD
к develop
, , тогда git fetch
захочет обновить master
и сейчас вы можете запустить git fetch origin master:master
(при условии, что вы предпочитаете более короткое, немного рискованное, неквалифицированное написание), если хотите. 7
Причина для этого специального ограниченияимеет отношение к разнице, отмеченной выше, о том, как git merge
выполняет слияние, которое разрешается в ускоренной перемотке вперед: git merge
обновляет индекс и рабочее дерево , как если бы вы запустили git checkout
.Команда git fetch
никогда обновляет индекс и рабочее дерево.Если git fetch
позволяет вам быстро переслать master
на новый коммит, ваш индекс и рабочее дерево могут получить изумительного .
Проблема в том, что ваш индекси work-tree предназначены для соответствия вашему текущему коммиту, за исключением любой работы, которую вы проделали с тех пор, как вы запустили git checkout
, чтобы изменить свой индекс и рабочее дерево.Если git fetch
обновляет имя ветви refs/heads/
, к которому присоединен ваш HEAD
, ваш индекс и рабочее дерево больше не будут соответствовать вашему текущему коммиту, потому что ваш текущий коммит - тот, чей хеш-идентификатор хранится в этой ветке-название.(Если вам do удастся войти в это состояние, исправить это будет досадно, хотя возможно . См. Почему Git позволяет переходить на извлеченную ветку вдобавленное рабочее дерево? Как мне восстановить? )
Команда git fetch
имеет флаг --update-head-ok
, который специально отменяет эту проверку.Вы не должны использовать это.Код git pull
использует , потому что git pull
немедленно запускает вторую команду Git, которая исправит индекс и рабочее дерево даже в этих особых случаях.Более того, git pull
делает некоторые предварительные fetch
проверки, чтобы убедиться, что эта вторая команда не разрушит все.Однако, если вы точно не знаете, что делаете, вам не следует его использовать.
7 Если вы делаете , делайте этоВы просто делаете дополнительную умственную работу для себя, в общем.Я рекомендую не делать это как повседневную практику.Вместо этого используйте git fetch origin && git checkout master && git merge --ff-only
.Я определил псевдоним git mff
, который запускает git merge --ff-only
, который я использую для этих вещей.