Какая разница, когда вы выполняете `git fetch upstream master:master` против`git pull upstream master:master`

Я знаю разницу между git fetch а также git pull, git pull в основном git fetch + git merge в одной команде.

Тем не менее, я изучал, как обновить мой ветвь (ветвь master) с помощью апстрима, не проверяя ветку master. Я сталкивался с таким SO-ответом - объединяйте, обновляйте и тяните ветки Git без проверок

Но когда я использовал git fetch upstream master:master после того как я уже проверил на мастере, я столкнулся с этой ошибкой -

fatal: Refusing to fetch into current branch refs/heads/master of non-bare repository

Итак, я попробовал git pull upstream master:master и это сработало. Что интересно, это делать git pull upstream master:master обновляет мой форк с апстримом независимо от того, нахожусь ли я на мастере или нет. В то время как git fetch upstream master:master работает только тогда, когда я НЕ на главной ветке.

Будет очень интересно почитать объяснения по этому поводу у знающих людей здесь.

1 ответ

git pull в основном git fetch + git merge в одной команде

Да, но, как вы и подозревали, в этом есть нечто большее.

Комментарий Беннетта МакЭлви, в ответе на который вы ссылаетесь, на самом деле содержит один из ключевых пунктов. Он упоминает, что вы можете:

использование fetch origin branchB:branchB, который потерпит неудачу безопасно, если слияние не ускоренное.

Другое не очень хорошо задокументировано: это -u ака --update-head-ok вариант в git fetch, который git pull наборы. Документация определяет, что она делает, но немного таинственно и страшно:

По умолчанию git fetch отказывается обновлять заголовок, соответствующий текущей ветке. Этот флаг отключает проверку. Это чисто для внутреннего использования git pull для связи с git fetch, и если вы не используете собственный фарфор, вы не должны его использовать.

Это приводит нас к вашему наблюдению:

Итак, я попробовал мерзавец pull upstream master:master и это сработало. Что интересно, это делать git pull upstream master:master обновляет мой форк с апстримом независимо от того, нахожусь ли я на мастере или нет. В то время как git fetch upstream master:master работает только тогда, когда я НЕ на главной ветке.

Это связано с тем, что -u флаг. Если ты побежал git fetch upstream master:master, это будет работать, для некоторого смысла смысла работы, но оставит вас с другой проблемой. Предупреждение там по причине. Давайте посмотрим, что это за причина, и посмотрим, является ли предупреждение слишком резким. Внимание: здесь много всего! Большая часть сложностей, приведенных ниже, состоит в том, чтобы компенсировать исторические ошибки при сохранении обратной совместимости.

Названия веток, ссылки и перемотка вперед

Сначала поговорим о ссылках и операциях быстрой перемотки.

В Git ссылка - это просто модный способ говорить о названии ветки, например master или имя тега типа v1.2 или имя удаленного отслеживания, например origin/master или, ну, любое количество других имен, все в одном общем и разумном виде: мы группируем каждый конкретный тип имени в пространство имен или как одно слово, пространство имен. Имена филиалов живут под refs/heads/, имена тегов живут под refs/tags/ и так далее, чтобы master действительно просто refs/heads/master,

Каждое из этих имен, каждое из которых начинается с refs/, это ссылка. Есть несколько дополнительных ссылок, которые не начинаются с refs также, хотя Git немного ошибочен внутри себя, решая, как имена HEAD а также ORIG_HEAD а также MERGE_HEAD на самом деле ссылки. 1 В конце, тем не менее, ссылка в основном служит способом получения полезного имени для хеш-идентификатора объекта Git. В частности, у имен веток есть забавное свойство: они переходят от одного коммита к другому, обычно способом, который Git называет ускоренным движением вперед.

То есть, учитывая ветвь с некоторыми коммитами, представленными здесь заглавными буквами, и вторую ветвь с большим количеством коммитов, которые включают все коммиты в первой ветке:

...--E--F--G   <-- branch1
            \
             H--I   <-- branch2

Git разрешено скользить имя branch1 вперед, чтобы он указывал на любой из коммитов, которые до этого были доступны только через имя branch2, 2 Сравните это, чтобы сказать:

...--E--F--G------J   <-- branch1
            \
             H--I   <-- branch2

Если бы мы должны были переместить имя branch1 указать, чтобы совершить I вместо фиксации J что случится совершить J сам? 3 Этот тип движения, который оставляет за собой коммит, является операцией без ускоренной перемотки имени ветви.

Эти имена могут быть сокращены, если оставить refs/ частично или часто даже refs/heads/ часть или refs/tags/ часть или что-то еще. Git будет искать в своей базе данных эталонных имен 4 первое совпадение, используя правила шести шагов, описанные в документации gitrevisions. Если у тебя есть refs/tags/master и refs/heads/master например, и сказать master Git будет соответствовать refs/tags/master сначала и используйте тег. 5


1 Если ссылка - это имя, которое имеет или может иметь рефлог, то HEAD это ссылка в то время как ORIG_HEAD и другие *_HEAD имен нет. Здесь все немного размыто по краям.

2 Эти коммиты могут быть доступны через несколько имен. Важно то, что они не были доступны через branch1 до перемотки вперед и после.

3 Непосредственный ответ: на самом деле ничего не происходит, но в конечном итоге, если совершить I недоступно через какое-то имя, Git будет собирать коммит с помощью мусора.

4 Эта "база данных" действительно просто комбинация каталога .git/refs плюс файл .git/packed-refs По крайней мере, на данный момент. Если Git находит и запись файла, и путь, хэш пути переопределяет тот, который находится в packed-refs файл.

5 Исключение: git checkout сначала пытается аргумент в качестве имени ветви, и если это работает, обрабатывает master как название филиала. Все остальное в Git обрабатывает его как имя тега, так как префикс с refs/tags это шаг 3 против шага 4 для имени ветви.


Refspecs

Теперь, когда мы знаем, что ссылка - это просто имя, указывающее на коммит, а имя ветви - это особый вид ссылки, для которого быстрые перемотки являются обычными повседневными вещами, давайте посмотрим на refspec. Давайте начнем с самой распространенной и объяснимой формы, которая является просто двумя ссылочными именами, разделенными двоеточием, такими как master:master или же HEAD:branch,

Git использует refspecs всякий раз, когда вы соединяете два Git друг с другом, например, во время git fetch и во время git push, Имя слева - это источник, а имя справа - место назначения. Если вы делаете git fetch источником является другой Git-репозиторий, а местом назначения является ваше собственное. Если вы делаете git push Источник - ваш репозиторий, а пункт назначения - их. (В особом случае использования ., что означает, что этот репозиторий, и источник, и место назначения, - вы сами, но все по-прежнему работает так же, как если бы ваш Git говорил с другим, отдельным Git.)

Если вы используете полные имена (начиная с refs/), вы точно знаете, какой из них вы получите: ветку, тег или что-то еще. Если вы используете неполные или неквалифицированные имена, Git обычно все равно поймет, что вы имеете в виду. Иногда вы сталкиваетесь с тем, что Git не может понять, что вы имеете в виду; в этом случае используйте полное имя.

Вы можете еще больше упростить refspec, пропустив одно из двух имен. Git знает, какое имя вы опускаете, по какой стороне двоеточия нет: :dst не имеет имени источника, а src: не имеет имени назначения. Если ты пишешь name Git рассматривает это как name:: источник без пункта назначения.

Что это значит, меняется. Пустой источник для git push значит удалить: git push origin :branch Ваш Git попросит, чтобы их Git полностью удалил имя. Пустой пункт назначения для git push означает использовать значение по умолчанию, которое обычно совпадает с именем ветви: git push origin branch толкает ваш branch попросив их Git установить свою ветку с именем branch, 6 Обратите внимание, что это нормально git push непосредственно в их ветку: вы отправляете им свои коммиты, затем просите их установить их refs/heads/branch, Это сильно отличается от нормального fetch!

За git fetch, пустой пункт назначения означает, что не обновлять ни одну из моих ссылок. Непустое назначение означает обновить ссылку, которую я предоставляю. В отличие от git push тем не менее, обычным местом назначения, которое вы можете использовать здесь, является имя для удаленного отслеживания: вы должны получить их refs/heads/master в свой refs/remotes/origin/master, Таким образом, название вашей ветви master -ваш refs/heads/master - остался нетронутым.

Однако по историческим причинам обычная форма git fetch просто написано как git fetch remote branch, опуская пункт назначения. В этом случае Git делает что-то внешне противоречивое:

  • Пишет обновление названия ветки никуда. Отсутствие пункта назначения означает, что ни одна (локальная) ветвь не обновляется.
  • Он записывает хэш-идентификатор в .git/FETCH_HEAD, Все git fetch выборки всегда идут сюда. Это где и как git pull узнает что git fetch сделал.
  • Обновляет имя удаленного отслеживания, например refs/remotes/origin/master, даже думал, что это не было сказано, чтобы сделать это. Git называет это оппортунистическим обновлением.

(Многое из этого на самом деле контролируется через refspec по умолчанию, который вы найдете в вашем .git/config файл.)

Вы также можете усложнить refspec, добавив начальный знак плюс +, Это устанавливает флаг "force", который переопределяет стандартную проверку "fast forward" для движения имени ветви. Это обычный случай для имен удаленного отслеживания: вы хотите, чтобы ваш Git обновлял ваши refs/remotes/origin/master чтобы соответствовать их Git's refs/heads/master даже если это не перемотка вперед, так что ваш Git всегда помнит, где их master в последний раз ваш Git говорил со своим Git.

Обратите внимание, что начальный плюс имеет смысл, только если есть пункт назначения для обновления. Здесь есть три варианта:

  • Вы создаете новое имя. Это вообще нормально. 7
  • Вы не вносите никаких изменений в имя: оно использовалось для сопоставления, чтобы зафиксировать хеш-код H, и в запросе указывается, что оно должно отображаться для фиксации хэша H. Это явно нормально.
  • Вы меняете имя. Этот разделен на три дополнительные возможности:
    • Это совсем не похожее на ветвь имя, например, это тег, который не должен двигаться. Вам понадобится флаг принудительной установки, чтобы переопределить отклонение по умолчанию. 8
    • Это название, похожее на ветвь, а движение ветки - это ускоренная перемотка вперед. Вам не понадобится флаг силы.
    • Это название, похожее на ветку, но движение не ускоренное. Вам понадобится флаг силы.

Это охватывает все правила для обновления ссылок, за исключением одного последнего правила, для которого нам нужно еще больше фона.


6 Вы можете усложнить это, установив push.default в upstream, В этом случае, если ваша ветка fred вверх по течению установлен в origin/barney, git push origin fred просит их Git установить их ветку с именем barney,

7 Для различных случаев обновлений вы можете написать хуки, которые делают все, что вам нравится, для проверки имен и / или обновлений.

8 В версиях Git до 1.8.3 Git случайно использовал правила веток для обновления тегов. Так что это относится только к 1.8.3 и позже.


ГОЛОВА очень особенная

Помните, что название ветви, как master просто определяет какой-то конкретный хеш коммита:

$ git rev-parse master
468165c1d8a442994a825f3684528361727cd8c0

Вы также видели, что git checkout branchname ведет себя так, и git checkout --detach branchname или же git checkout hash ведет себя по-другому, давая страшное предупреждение о "отрешенной голове". В то время как HEAD действует как ссылка во многих отношениях, в некоторых это очень особенное. Особенно, HEAD обычно является символьной ссылкой, в которой он содержит полное имя имени ветви. То есть:

$ git checkout master
Switched to branch 'master'
$ cat .git/HEAD
ref: refs/heads/master

говорит нам, что текущее имя ветви master: тот HEAD прикреплен к master, Но:

$ git checkout --detach master
HEAD is now at 468165c1d... Git 2.17
$ cat .git/HEAD
468165c1d8a442994a825f3684528361727cd8c0

после которого git checkout master возвращает нас обратно master по-прежнему.

Это означает, что когда у нас есть отдельный HEAD, Git знает, какой коммит мы извлекли, потому что правильный идентификатор хеша прямо там, в имени HEAD, Если бы мы должны были сделать какое-то произвольное изменение значения, хранящегося в refs/heads/master Git все равно будет знать, какой коммит мы проверили.

Но если HEAD просто содержит имя master единственный способ, которым Git знает, что текущий коммит, скажем, 468165c1d8a442994a825f3684528361727cd8c0, в том, что refs/heads/master карты для 468165c1d8a442994a825f3684528361727cd8c0, Если мы сделали что-то, что изменилось refs/heads/master с каким-то другим идентификатором хэша, Git подумал бы, что у нас есть другой коммит.

Имеет ли это значение? Да, это так! Посмотрим почему:

$ git status
... nothing to commit, working tree clean
$ git rev-parse master^
1614dd0fbc8a14f488016b7855de9f0566706244
$ echo 1614dd0fbc8a14f488016b7855de9f0566706244 > .git/refs/heads/master
$ git status
...
Changes to be committed:
...
        modified:   GIT-VERSION-GEN
$ echo 468165c1d8a442994a825f3684528361727cd8c0 > .git/refs/heads/master
$ git status
...
nothing to commit, working tree clean

Изменение идентификатора хеша, хранящегося в master изменил представление Git о статусе!

Статус включает в себя HEAD против индекса плюс индекс против рабочего дерева

git status команда запускает два git diff набухать, git diff --name-status эс, внутренне):

  • сравнить ГОЛОВУ с индексом
  • сравнить индекс с рабочим деревом

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

Если HEAD содержит необработанный хэш-идентификатор для текущего коммита, а затем сравнивает HEAD vs index остается неизменным независимо от того, что мы делаем с нашими именами ветвей. Но если HEAD содержит одно конкретное имя ветви, и мы меняем значение этого конкретного имени ветви, а затем проводим сравнение, мы сравним другой коммит с нашим индексом. Индекс и рабочее дерево останутся неизменными, но представление Git об относительной разнице между (различным) текущим коммитом и индексом изменится.

Вот почему git fetch отказывается обновлять текущее имя ветки по умолчанию. Вот почему вы не можете перенести в текущую ветвь не-пустого хранилища: у этого не-пустого хранилища есть индекс и рабочее дерево, содержимое которого, вероятно, предназначено для соответствия текущей фиксации. Если вы измените представление Git о текущем коммите, изменив хэш, хранящийся в имени ветви, индекс и рабочее дерево, скорее всего, перестанут совпадать с коммитом.

Это не смертельно - на самом деле совсем нет. Это именно то, что git reset --soft делает: это изменяет название ветви, к которой HEAD прикреплен, не касаясь содержимого в индексе и рабочем дереве. между тем git reset --mixed изменяет имя ветви и индекс, но оставляет рабочее дерево без изменений, и git reset --hard изменяет имя ветви, индекс и рабочее дерево за один раз.

Ускоренное "слияние" в основном git reset --hard

Когда вы используете git pull бежать git fetch а потом git merge, git merge Шаг очень часто может делать то, что Git называет слиянием в ускоренном режиме. Однако это вовсе не слияние: это операция ускоренной перемотки текущего имени ветви с немедленным обновлением содержимого индекса и рабочего дерева до нового коммита, таким же образом. git reset --hard было бы. Главное отличие в том, что git pull проверки - ну, должен проверить 9 - что никакая текущая работа не будет уничтожена этим git reset --hard, в то время как git reset --hard Сам по себе намеренно не проверяет, чтобы позволить вам выбросить незавершенную работу, которая вам больше не нужна.


9 Исторически, git pull продолжает ошибаться, и это исправляется после того, как кто-то теряет кучу работы. избежать git pull!


Собираем все это вместе

Когда ты бежишь git pull upstream master:master Git сначала запускается:

git fetch --update-head-ok upstream master:master

где ваш Git вызывает другой Git по URL, указанному для upstream и собирать коммиты от них, как найдено через их имя master - левая сторона master:master refspec. Затем ваш Git обновляет свой собственный master предположительно refs/heads/master, используя правую часть refspec. fetch шаг, как правило, потерпит неудачу, если master ваша текущая ветвь - если ваша .git/HEAD содержит ref: refs/heads/master -но -u или же --update-head-ok флаг предотвращает сбой.

(Если все идет хорошо, ваш git pull побежит второй, git merge, шаг:

git merge -m <message> <hash ID extracted from .git/FETCH_HEAD>

но давайте сначала закончим с первым шагом.)

Правила ускоренной перемотки убедитесь, что ваш master обновление - это операция перемотки вперед. Если нет, выборка не удалась и ваш master без изменений, и pull останавливается здесь Так что пока все в порядке: ваш master быстро продвигается тогда и только тогда, когда это возможно, учитывая новые коммиты, если таковые имеются, полученные из upstream,

На данный момент, если ваш master был изменен, и это ваша текущая ветка, ваш репозиторий теперь не синхронизирован: ваш индекс и рабочее дерево больше не соответствуют вашим master, Тем не мение, git fetch оставил правильный хэш-идентификатор в .git/FETCH_HEAD как и твои git pull Теперь переходим к обновлению, подобному сбросу. Это на самом деле использует эквивалент git read-tree скорее, чем git reset, но пока это удается - учитывая pull проверяет, должно ли это получиться - конечный эффект тот же: ваш индекс и рабочее дерево будут соответствовать новому коммиту.

В качестве альтернативы, возможно master это не ваша текущая ветка. Возможно твой .git/HEAD содержит вместо ref: refs/heads/branch, В этом случае ваш refs/heads/master безопасно проложен путь git fetch сделал бы даже без --update-head-ok, Ваш .git/FETCH_HEAD содержит тот же идентификатор хеша, что и ваш обновленный master, и ваш git pull работает git merge попытаться выполнить слияние, которое может быть или не быть операцией ускоренной пересылки, в зависимости от фиксации, к которой относится имя вашей ветви. branch очки прямо сейчас. Если слияние завершается успешно, Git либо выполняет фиксацию (реальное слияние), либо корректирует индекс и рабочее дерево, как и раньше (ускоренное "слияние"), и записывает соответствующий хэш-идентификатор в .git/refs/heads/branch, Если слияние не удается, Git останавливается с конфликтом слияния и заставляет вас убирать беспорядок как обычно.

Последний возможный случай заключается в том, что ваш HEAD отсоединен, но это работает так же, как для ref: refs/heads/branch дело. Разница лишь в том, что новый хэш-идентификатор, когда все сказано и сделано, переходит прямо в .git/HEAD а не в .git/refs/heads/branch,

Другие вопросы по тегам