Разве не должно быть копий кода на ветку?
Нет.Это не то, как работают имена веток в Git.
То, что хранит Git, в некотором смысле не является ветвями вообще.Git магазины коммитов .Коммиты - это почти все в Git.Куда приходят ветви, гораздо позже, где они играют довольно незначительную роль.Конечно, они важны для людей , потому что настоящее имя любого коммита - это большой уродливый идентификатор хеша, который бесполезен для людей.Имена ветвей позволяют использовать имена вместо хеш-идентификаторов.Но Git в основном заботится о коммитах, с их хэш-идентификаторами.
У меня есть только один репозиторий версий на моем локальном жестком диске, но несколько веток на Github.
Обычнохранилище на вашем локальном диске - это клон хранилища на GitHub.Или мы можем также сказать, что хранилище на GitHub является клоном хранилища на вашем локальном диске.Ни один репозиторий не "лучше", чем другой, у них просто есть разные компьютеры для их хранения, и, возможно, немного разные наборы коммитов, и, скорее всего, разные ветки names в них (но поскольку Git больше заботится о фиксирует , для Git это не имеет большого значения).
Чтобы понять, как это работает, начните с фиксации.Каждый коммит, называемый каким-то большим уродливым хеш-идентификатором, таким как 8a0ba68f6dab2c8b1f297a0d46b710bb9af3237a
, сохраняет полный снимок некоторого набора файлов.Наряду с этим снимком, каждый коммит имеет некоторые метаданные , некоторые данные о коммите.Например, если вы сделали коммит самостоятельно, в коммите сохраняются ваше имя и адрес электронной почты, а также отметка времени для , когда вы сделали коммит.И каждый коммит обычно хранит хэш-идентификатор своего предыдущего или parent commit.(Родитель 8a0ba68f6dab2c8b1f297a0d46b710bb9af3237a
выше, например, 15cc2da0b5aaf5350f180951450e0a5318f7d34d
.)
Git использует эти parent ссылки для поиска коммитов.Давайте рассмотрим крошечный репозиторий, который вы создали недавно, в котором вы сделали всего три коммита.У каждого коммита есть какой-то большой некрасивый хеш-идентификатор, но вместо этого давайте использовать заглавные буквы.Поэтому первый коммит, который вы когда-либо сделали, это коммит A
.Поскольку является первым коммитом, у него есть нет родителя.
Затем, с коммитом A
, вы сделали коммит B
.Он имеет A
в качестве родителя.Аналогично, вы использовали C
для создания B
, поэтому C
хранит хэш-идентификатор B
.
Всякий раз, когда у нас есть хеш-идентификатор некоторого коммита, мы говорим, что можем указать , что совершить.Так C
указывает на B
, а B
указывает на A
.Если мы нарисуем это, мы получим:
A <-B <-C
Теперь, с простыми заглавными буквами и этим рисунком, очевидно, что нам нужно начать с C
и работать в обратном направлении.Это то, что делает Git, но способ , который Git находит в коммите C
, заключается в использовании имени ветви .Поскольку мы только что запустили этот репозиторий, мы, вероятно, все еще используем master
, поэтому давайте нарисуем это в:
A <-B <-C <--master
Имя master
содержит идентификатор хеша commit C
, так что Git может найтиC
.Оттуда Git может работать в обратном направлении, до B
и затем A
.
. Если мы хотим добавить new commit, мы запускаем git checkout master
, что дает нам копиюиз C
, над которым мы можем работать, и помнит, что мы сейчас находимся на master
, который является коммитом C
.Мы делаем нашу работу, git add
файлы по мере необходимости и запускаем git commit
.Git делает новый коммит D
(из того, что есть в index , который я не собираюсь здесь определять) и устанавливает D
, чтобы указать C
:
A--B--C <--master
\
D
Теперь происходит сложная часть: поскольку мы только что создали D
, и мы находимся на master
, Git сейчас обновляет master
с любым реальным идентификатором хэша для нового коммитаD
.Так что теперь master
указывает на D
, а не C
:
A--B--C
\
D <--master
, и мы можем выправить излом на чертеже (и мне тоже нравится немного ослаблять стрелку):
A--B--C--D <-- master
Теперь предположим, что мы решили добавить новую ветку .Мы бежим, например, git checkout -b develop
.Для этого просто добавьте новое имя , но сохраните все то же commitits .Новое имя develop
указывает на выбранный нами коммит, который по умолчанию соответствует тому, который мы используем сейчас, т. Е. Commit D
:
A--B--C--D <-- master, develop
Теперь нам нужен способ рисованиякакое название ветви мы используем.Для этого мы приложим слово HEAD
(заглавными буквами) к одной из двух ветвей.Поскольку мы сделали git checkout
, чтобы присоединить HEAD
к новому develop
, давайте нарисуем это:
A--B--C--D <-- master, develop (HEAD)
Теперь пришло время сделать еще один новый коммит.Не делая ничего другого, мы модифицируем некоторые файлы, используем git add
, чтобы скопировать обновленные файлы в индекс, и запускаем git commit
, чтобы сделать новый коммит, который мы назовем E
(но который получит, как обычно,какой-то непонятный хеш-идентификатор).Когда Git обновляет ветку name , она обновляет имя, к которому прикреплено HEAD
, поэтому график теперь выглядит так:
A--B--C--D <-- master
\
E <-- develop (HEAD)
На этом этапепредположим, я клонирую ваш репозиторий (напрямую с вашего компьютера или из точной копии, которую вы отправляете на GitHub, с теми же коммитами и теми же именами веток).Мой Git сначала сделает это:
A--B--C--D <-- origin/master
\
E <-- origin/develop
Здесь у меня нет каких-либо веток.У меня все те же коммиты , но что запоминает их концы, это не имена ветвей , а скорее имена для удаленного слежения .Вместо master
у меня есть origin/master
для запоминания хеш-идентификатора коммита D
.Вместо develop
у меня есть origin/develop
, чтобы запомнить хэш-идентификатор E
.
В качестве последнего шага моего клона мой собственный Git пытается git checkout master
.Вместо неудачи, потому что у меня нет a master
, на самом деле создает мой master
, используя мой origin/master
, который мой Git скопировал с вашего Git'а master
.Итак, теперь у меня есть:
A--B--C--D <-- origin/master, master (HEAD)
\
E <-- origin/develop
Если я теперь запусту git checkout develop
, мой Git просмотрит мой репозиторий и не найдет develop
, но найдет найдет origin/develop
.Мой Git затем создаст новое имя , develop
, указывающее на фиксацию E
, и присоединит HEAD
к этому имени вместо master
:
A--B--C--D <-- origin/master, master
\
E <-- origin/develop, develop (HEAD)
Обратите внимание, что коммиты не были скопированы .Git просто добавил новое имя , указывающее на существующий коммит.Существующий коммит уже был там, в моем локальном репозитории.
Вы подключаете два репозитория Git с fetch
и push
Так вы получаете обновления из репозитория GitHub.Если у них есть новые коммиты, которых у вас нет, вы можете запустить git fetch
в своем репозитории Git.Ваш Git использует имя origin
, которое ваш Git создал во время исходной операции git clone
, чтобы найти URL для GitHub.Затем ваш Git вызывает GitHub, а ваш Git и их Git немного беседуют.
С git fetch
ваш Git спрашивает у Git свои имена веток и окончательные коммиты для каждого из этих имен.Их Git может сказать: my master
указывает на какой-то коммит с большим уродливым хешем - давайте просто назовем это H
, вместо того, чтобы пытаться угадать фактический хеш-идентификатор.Ваш Git смотрит на ваш репозиторий и говорит себе: У меня нет H
, я бы лучше попросил его. Ваш Git спрашивает у своего Git о H
;они говорят, что родитель H
- G
, чей родитель F
, чей родитель D
.У вас обоих D
, так что эта часть разговора завершена.Их Git может также сказать: my develop
указывает на коммит E
. Ваш Git уже имеет E
, так что эта часть разговора также выполнена.Больше не нужно беспокоиться о других именах, так что теперь ваш Git отправляет свои Git по коммитам F
, G
и H
, которые ваш Git сохраняет в своем хранилище:
F--G--H <-- origin/master
/
A--B--C--D <-- master
\
E <-- origin/develop, develop (HEAD)
Обратите внимание, что кроме добавления new коммитов в ваш собственный репозиторий, единственное, что сделал ваш Git, это обновил все ваши origin/*
имена так, чтобы они совпадали с именами других Git.То есть ваш origin/master
переместился из общего коммита D
в общий коммит H
.
Выборка всегда безопасна, но git push
отличается
Всегда безопасно запускать git fetch
: это соединяет ваш Git с каким-то другим Git, получает любые новые коммиты от них,и обновляет ваши имена для удаленного отслеживания .Поскольку эти имена просто помнят их работу, а не вашу работу, это безопасно.Если вы сделали новые коммиты, ваши новые коммиты все еще находятся в ваших ветвях , которые не являются их ветвями, кроме тех, которые имеют общие коммиты у вас обоих.
Когда вы используете git push
, ваш Git вызывает свой Git, и два Git ведут очень похожий разговор.Ваш Git предлагает своим Git несколько новых коммитов, если у вас есть новые.Но затем вместо того, чтобы ваш Git обновил ваши имена для удаленного слежения , ваш Git предлагает Git вежливый запрос: Пожалуйста, если все в порядке, обновите ваш master
, чтобы он соответствовал моим master
, или пожалуйста, если все в порядке, обновите ваш develop
, чтобы он соответствовал моему develop
- или, возможно, даже обоим.(Вы можете нажать столько названий веток, сколько захотите за один раз.)
Предположим, что после более раннего git fetch
у вас было следующее:
F--G--H <-- origin/master
/
A--B--C--D <-- master
\
E <-- origin/develop, develop (HEAD)
Вы остались на вашем develop
и сделал несколько новых коммитов, которые мы назовем I
и J
, так что теперь у вас есть это:
F--G--H <-- origin/master
/
A--B--C--D <-- master
\
E <-- origin/develop
\
I--J <-- develop (HEAD)
Но, без вашего ведома, кто-то еще добавилновый коммит их develop
.Этот коммит имеет хеш-идентификатор, которого у вас нет нигде.Давайте назовем это K
в их Git, так что они имеют это:
F--G--H <-- master
/
A--B--C--D
\
E--K <-- develop
Вы отправляете их I
и J
, так чтотеперь у них есть это:
F--G--H <-- master
/
A--B--C--D
\
E--K <-- develop
\
I--J <-- (polite request to make develop go here)
Если они примут ваш запрос и переместят их develop
, что произойдет с их коммитом K
?В итоге они получают следующее:
F--G--H <-- master
/
A--B--C--D
\
E--K ???
\
I--J <-- develop
Как мы уже говорили выше, Git работает с коммитами и именами веток так, что он использует имя ветки для поиска last commit, а затемработает в обратном направлении.Работая в обратном направлении с J
, их Git перейдет на J
С J
они вернутся к I
, затем E
, затем D
, C
, B
и A
.Это означает, что коммит K
потерян!
В результате их Git скажет no на ваш вежливый запрос: Нет, еслиЯ перенес свой develop
, я бы потерял некоторые коммиты. (Ваш Git сообщает об этом как "отклоненный" и "не ускоренный переход".)
В этом случае, что вынужно git fetch
взять их новый коммит K
, а затем сделать что-то в своем собственном хранилище, чтобы приспособить его.Однако это еще один набор вопросов (на все они уже даны ответы в StackOverflow).