Ответ 1
Предполагая, что вы уже понимаете модель "w20" "объекты" (ваши коммиты и файлы и т.д. все являются "объектами в базе данных git", с "свободными" объектами - те, которые не упакованы для экономии пространства в .git/objects/12/34567...
и т.п.)...
Вы правы: git fetch
извлекает объекты "они" (origin
, в этом случае), которые у вас нет, и наклеивает на них метки: origin/master
и тому подобное. Более конкретно, ваш git вызывает их на интернет-телефоне (или любой другой подходящий транспорт) и спрашивает: какие у вас есть ветки и какие идентификаторы совершают? Они имеют master
, а идентификатор 1234567...
, поэтому ваш git запрашивает 1234567...
и любые другие объекты, которых у вас еще нет, и делает ваш origin/master
точкой для фиксации объекта 1234567...
.
Часть git push
, которая является симметричной, такова: ваш git вызывает свои git на одном и том же интернет-телефоне, как обычно, но на этот раз вместо того, чтобы просто спрашивать их об их ветких, ваш git рассказывает о ваших ветвях и объектах репозитория git, а затем говорит: "Как насчет того, чтобы вы установили ваш master
в 56789ab...
?"
Их git рассматривает объекты, которые вы отправили (новый commit 56789ab...
и любые другие объекты, которые у вас есть, что они не сделали, что им нужно будет их взять). Их git затем рассматривает запрос, чтобы установить их master
в 56789ab...
.
Как Крис К уже ответил, здесь не происходит слияния: ваш git просто предлагает, чтобы их git перезаписывали их master
с помощью этого нового коммита -Я БЫ. Это до их git, чтобы решить, разрешать ли это.
Если "они" (кто бы они ни были) не создали каких-либо специальных правил, правило по умолчанию, которое использует здесь git, очень просто: перезапись разрешена, если изменение является "быстрой перемоткой вперед". Он имеет одну дополнительную функцию: перезапись также разрешена, если изменение выполняется с установленным флагом "force". Обычно не рекомендуется устанавливать здесь флаг силы, поскольку правило по умолчанию "только быстрое переключение" обычно является правильным правилом.
Очевидный вопрос здесь: что такое быстрый перемотка вперед? Мы доберемся до этого через мгновение; сначала мне нужно немного расширить метки, или "ссылки" будут более формальными.
Git ссылки
В git веткой или тегом или даже такими вещами, как stash и HEAD
, являются все ссылки. Большинство из них находятся в .git/refs/
, подкаталоге репозитория git. (Несколько ссылок на верхний уровень, включая HEAD
, находятся в самой строке .git
). Все ссылки - это файл 1 содержащий идентификатор SHA-1, например 7452b4b5786778d5d87f5c90a94fab8936502e20
. Идентификаторы SHA-1 громоздки и невозможны для людей, поэтому мы используем имена, такие как v2.1.0
(тег в этом случае, версия 2.1.0 самого git), чтобы сохранить их для нас.
Некоторые ссылки - или, по крайней мере, должны быть полностью статичными. Тег v2.1.0
никогда не должен ссылаться на что-то другое, кроме вышеописанного идентификатора SHA-1. Но некоторые ссылки более динамичны. В частности, ваши собственные локальные ветки, такие как master
, перемещают цели. Один частный случай, HEAD
, даже не является его собственной целью: он обычно содержит имя ветки с движущимся мишенью. Таким образом, существует одно исключение для "косвенных" ссылок: HEAD
обычно содержит строку ref: refs/heads/master
или ref: refs/heads/branch
или что-то в этих строках; и git не позволяет (и не может) применять правило "никогда не менять" для ссылок. Особенно сильно меняются ветки.
Откуда вы знаете, должна ли эта ссылка изменить? Ну, многое из этого просто по соглашению: ветки перемещаются, а тэги - нет. Но вы должны спросить: откуда вы знаете, является ли ссылка веткой или тегом, или что?
Обозначения пробелов: refs/heads/
, refs/tags/
и т.д.
Кроме специальных ссылок верхнего уровня, все ссылки git находятся в refs/
, как мы уже отмечали выше. Однако в каталоге refs/
(или "папке", если вы работаете на Windows или Mac), мы можем иметь целую коллекцию подкаталогов. git имеет на данный момент четыре четко определенных подкаталога: refs/heads/
содержит все ваши ветки, refs/tags/
содержит все ваши теги, refs/remotes/
содержит все ваши ветки удаленного отслеживания, а refs/notes/
содержит git "notes" (который я проигнорирую здесь, поскольку они немного усложняются).
Поскольку все ваши ветки находятся в refs/heads/
, git может сказать, что они должны быть изменены, и поскольку все ваши теги находятся в refs/tags/
, git может сказать, что это не должно.
Автоматическое движение ветвей
Когда вы делаете новый коммит и находитесь на ветке, например master
, git будет автоматически перемещать ссылку. Ваша новая фиксация создается с ее "parent commit", являющейся предыдущей веткой, и после того, как ваша новая фиксация будет безопасно сохранена, git изменяет master
, чтобы содержать идентификатор нового коммита. Другими словами, он гарантирует, что имя ветки, ссылка в подкаталоге heads
, всегда указывает на максимальную фиксацию tip.
(На самом деле ветвь, в смысле набора коммитов, которая является частью фиксированного графа, хранящегося в репозитории, представляет собой структуру данных, выполненную из коммитов в репозитории. Единственное ее соединение с ветвью имя состоит в том, что фиксация наконечника самой ветки хранится в ссылочной метке с этим именем. Это важно позже, если и когда имена ветвей изменяются или стираются по мере того, как репозиторий растет гораздо больше. Теперь это просто что-то, что нужно сохранить ум: существует разница между "концом ветвления", в котором указано "имя ветки", а также дочерняя группа "как-под-под-фиксация-DAG". Немного неудачно, что git имеет тенденцию группировать эти разные понятия под одним именем, "ветвь".)
Что такое ускоренная перемотка вперед?
Обычно вы видите "быструю перемотку вперед" в контексте слияния, часто с объединением, выполненным как второй шаг в git pull
. Но на самом деле "быстрая пересылка" на самом деле является свойством перемещения метки.
Давайте нарисуем немного графика фиксации. Маленькие узлы o
представляют собой коммиты, и каждый из них имеет стрелку, указывающую влево, влево и вверх, или влево или вправо (или в одном случае, две стрелки) на родителя (или родителей). Чтобы иметь возможность ссылаться на три по имени, я дам им заглавные буквы вместо o
. Кроме того, это произведение на основе персонажа не имеет стрелок, поэтому вы должны вообразить их; просто помните, что все они указывают налево или налево, как на три имени.
o - A <-- name1
/
o - o - o - o - B <-- name2
\ /
o - C <-- name3
Если вы попросите git изменить ссылку, вы просто попросите ее вставить новый идентификатор фиксации в метку. В этом случае эти метки живут в refs/heads/
и, следовательно, являются именами ветвей, поэтому они должны иметь возможность принимать новые значения.
Если мы скажем git поставить B
в name1
, мы получим следующее:
o - A
/
o - o - o - o - B <-- name1, name2
\ /
o - C <-- name3
Обратите внимание, что commit A
теперь не имеет имени, а o
слева от него находится только путем нахождения A
..., что трудно, так как A
не имеет имени. Commit A
был оставлен, и эти два коммита имеют право на "сбор мусора". (В git в "reflog" осталось "имя призрака", которое поддерживает ветвь с A
около 30 дней в целом, но это совсем другая тема.)
Как сообщить git поставить B
в name3
? Если мы сделаем следующее, мы получим следующее:
o - A
/
o - o - o - o - B <-- name1, name2, name3
\ /
o - C
Здесь у commit C
все еще есть способ его найти: начинайте с B
и работайте вниз и влево, с другим (вторым) родительским фиксатором, и вы обнаружите commit C
. Так что commit C
не оставлен.
Обновление name1
, как это, не является быстрой перемоткой вперед, но обновление name3
есть.
Более конкретно, смена ссылок является "быстрой перемоткой вперед" тогда и только тогда, когда объект - обычно фиксация - что ссылка, используемая для указания точки, до сих пор доступна, начиная с нового места и работая назад, вдоль всех возможные обратные пути. В терминах графа это быстрый переход, если старый node является предком нового.
Создание push
будет быстрой перемоткой вперед, путем слияния
Имя ветки fast-forward происходит, когда единственное, что вы делаете, это добавить новые коммиты; но также, когда, если вы добавили новые коммиты, вы также объединились - независимо от того, что новое совершает, кто-то добавил. То есть предположим, что у вашего репо есть это в нем, после того, как вы сделали одно новое сообщение:
o <-- master
/
...- o - o <-- origin/master
В этот момент перемещение origin/master
вверх и вправо было бы быстрой перемоткой вперед. Тем не менее, кто-то еще приходит и обновляет другое (origin
) репо, поэтому вы делаете git fetch
и получаете от них новую фиксацию. Ваш git перемещает метку origin/master
(в быстрой перемотке вашего репо, как это бывает):
o <-- master
/
...- o - o - o <-- origin/master
В этот момент перемещение origin/master
в master
не было бы быстрой перемоткой вперед, так как оно отказалось бы от этой новой фиксации.
Однако вы можете выполнить операцию git merge origin/master
, чтобы сделать новую фиксацию на вашем master
, с двумя родительскими идентификаторами фиксации. Пусть обозначается это M
(для слияния):
o - M <-- master
/ /
...- o - o - o <-- origin/master
Теперь вы можете git push
вернуться к origin
и попросить их установить master
, который вы вызываете origin/master
, равным вашему (новому) M
, потому что для них это теперь ускоренная работа!
Обратите внимание, что вы также можете сделать git rebase
, но оставьте это для другой проводки stackoverflow.: -)
1 Фактически, ссылки git всегда начинаются как отдельные файлы в разных подкаталогах, но если ссылка не обновляется долгое время, она, как правило, становится "упакованной" "(вместе со всеми другими в основном статическими ссылками) в один файл, полный упакованных ссылок. Это просто оптимизация сбережений времени, и ключ здесь не зависит от конкретной реализации, а скорее использует команды git rev-parse
и update-ref
для извлечения текущего SHA-1 из ссылки или обновления ссылка на новый SHA-1.