Какая разница, когда вы выполняете `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
,