Здесь следует помнить, что «ветви» - точнее, ветви names - на самом деле ничего не значат для Git. Они означают что-то для вас , но для Git каждое имя - это просто метод нахождения одного указанного c коммита. Что действительно волнует Git, так это коммитов в репозитории.
Каждый коммит имеет свой уникальный большой уродливый идентификатор ha sh. Например, вы видите эти идентификаторы в выводе git log
. Истинные маги c из Git заключаются в том, что каждый Git репозиторий везде согласится с тем, что, если один конкретный c коммит в вашем репозитории - или один в их - имеет некоторое ха sh ID H , нет других коммитов где-либо может когда-либо иметь , что га sh ID. 1 Так что когда Два Git хранилища "встречаются", по git fetch
или git push
, им нужно только сравнить га sh ID . В вашем GitHub-репозитории на GitHub есть некоторые коммиты с некоторыми ha sh ID H , перечисленными под их name master
. Ваш Git вызывает их Git. Их Git говорит: Мое имя master
соответствует га sh ID H. Ваш Git чек: Есть ли у меня H? Если ваш Git имеет H уже сделано. Если нет, ваш Git просит их Git отправить коммит H .
У ваших собственных Git есть ваши собственные имена. Один из них может быть master
. У него есть га sh ID. Неважно, какой у вас sh идентификатор вашего master
, единственное, что имеет значение для вашего Git для операции fetch
, это: У меня вообще есть коммит H? Ваш Git может всегда искать все свои внутренние Git объекты напрямую по необработанному идентификатору ha sh. У вас уже есть H , или у вас его нет. Поскольку ни один Git нигде не может когда-либо использовать га sh ID H для чего-либо, кроме этого коммита, все, что ваш Git должен сделать, это проверить эту вещь.
Если у вас нет , в конце концов, H , у вашего Git будет Git коммит для отправки H . Теперь еще одна вещь о каждом коммите: каждый коммит записывает некоторый набор parent commit ha sh ID. Родителем или родителями коммита является коммит (или для слияния, двух или более коммитов), который приходит «непосредственно перед» этим коммитом. То есть, учитывая длинную строку коммитов, сделанных по одному, каждый из них хранит предыдущий идентификатор комита ha sh в обратной цепочке:
... <-F <-G <-H
Так что, если вы собираетесь получить H , их Git теперь предложит вам G . Ваш Git спрашивает себя: У меня уже есть G? Если нет, ваш Git говорит ОК, дайте мне это тоже. Если у вас есть G , ваш Git говорит Нет, спасибо, он у меня уже есть. Это повторяется для каждого их коммита, который у вас нет. В конце концов, git fetch
имеет список всех коммитов, которые он должен отправить, и все ваши коммиты, которые этот список коммитов расширит.
В этот момент их Git упаковывает желаемое подмножество из их коммитов - и связанных снимков и т. д. - зная, какие коммиты у вас есть, потому что ваш Git сказал им У меня уже есть этот . Их Git может сжимать все файлы и все такое, что внутри коммитов, используя эту информацию. Таким образом, вы получаете гораздо меньший набор данных по сети, чем если бы они просто отправили вам все.
1 Технически, два разных коммита могут иметь одинаковое значение ha sh ID, но только если они никогда не "встречаются". То есть, если вы подключаете ваш Git к какому-либо другому хранилищу, и он имеет некоторую фиксацию с ha sh ID H , а ваш Git имеет другой H , оба Gits поверит, что это тот же коммит, и ни один не отправит его другому. Пока ваши Git и их Git никогда не встречаются и не пытаются обмениваться коммитами, это не вызывает проблем. На практике, такого рода столкновения ха sh не станет даже удаленно вероятным, пока у вас не будет более 10 17 объектов. В этот момент у вас появляется такой же шанс, как если бы система хранения вашего компьютера случайно вышла из строя на вас, что также пагубно. Это может быть проблемой, если кто-то тщательно спроектирует столкновение ха sh. Подробнее см. Как недавно обнаруженное столкновение SHA-1 влияет на Git?
Git использует имена для поиска last commits
Мы нарисовали простую цепочку коммитов выше, заканчивающуюся коммитом H
:
...--F--G--H
(где буквы обозначают реальные идентификаторы ha sh, которые выглядят совершенно случайными, но фактически полностью определены c). Учитывая идентификатор последнего коммита H
, мы просто Git ищем H
. Внутри H
, Git находит га sh ID G
, что позволяет ему искать G
. Внутри G
, Git находит га sh ID F
, что позволяет ему искать F
и так далее. Это позволяет Git go от последнего фиксировать весь процесс до самого первого в истории коммита.
Это работает даже при наличии ветвящихся структур фиксации:
I--J
/
...--G--H
\
K--L
Теперь два последних коммитов. J
- последний коммит в одной структуре, а L
- последний коммит в другой структуре. Две структуры - должны ли мы называть их ответвления ? - встречаются, когда они возвращаются к H
, и затем они разделяют коммиты вплоть до начала времен (предположительно, коммит A
).
В реальном хранилище мы можем иметь тысячи или миллионы коммитов. Есть большая старая путаница с sh идентификаторами. Как вы, или Git, быстро найдете последний коммит? Вы можете - и в командах обслуживания Git делает - перечислить каждый коммит и выяснить, какие из них являются "последними". Это занимает некоторое время: несколько секунд или в действительно больших репозиториях, иногда минут . Это явно слишком медленно. Кроме того, кто хочет работать с идентификаторами ha sh? Люди, конечно же, этого не делают.
Итак, Git предлагает нам возможность использовать имя для запоминания одного (1) га sh ID. Мы можем выбрать имя branch1
, чтобы запомнить га sh ID J
, и имя branch2
, чтобы запомнить га sh ID L
:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
Мы можем, если мы хотите, используйте имя master
, чтобы запомнить ha sh ID H
:
I--J <-- branch1
/
...--G--H <-- master
\
K--L <-- branch2
Неважно, что после H
есть коммиты. H
является последним коммитом в master
. Это полное определение ветви. Вот и все: имя ветки - это просто указатель в Git; это просто способ хранить один идентификатор ha sh, и по определению, какой бы идентификатор ha sh не имел имя, это последний коммит в этой ветви. 2
Таким образом, ветвь branch1
заканчивается при коммите J
, и автоматически включает каждый коммит, к которому вы можете добраться, начиная с J
и работая в обратном направлении. Ветвь branch2
заканчивается при коммите L
и включает все коммиты до L
, с Git снова работающим в обратном направлении. Git всегда работает в обратном направлении. Если по какой-то причине ему нужно работать вперед, он делает это, сначала работая в обратном направлении и запоминая ha sh идентификаторов, а затем продвигаясь вперед по запомненному списку. И коммиты могут быть, и очень часто, на более чем одной ветви .
Когда ваш Git получает новые коммиты от их Git, ваш Git должен установить несколько имен, чтобы помнить идентификаторы ha sh, которые их Git имели в их именах ветвей. Но их Git рассказывает вам об этом прямо в начале git fetch
. Вы запускаете git fetch origin
, а Git в начале - по URL-адресу, указанному под именем origin
, - говорит: My master
содержит H, мой develop
содержит L, ... . Ваш Git только что получил весь этот список.
Затем, когда запускается выборка, ваш Git выбирает любые коммиты, которые у вас есть, а ваши Git отправляют их. Это добавляет новые коммиты в ваш репозиторий, без удаления каких-либо коммитов - буквально физически не может удалить любые коммиты, поскольку они только отправляют вам новые (новые для вас) вещи. Когда все это будет сделано, вы определенно получите эти коммиты.
Так что теперь ваш Git берет все их ответвления имен и переименовывает этих имен. Ваши Git превращают их master
в ваши origin/master
. Ваши Git превращают их develop
в ваши origin/develop
. Это продолжается для всех их названий филиалов. Это Git имена для удаленного слежения , потому что они запоминают ("отслеживают") имена ветвей и идентификаторы ha sh, которые ваш Git видел в их Git под вашим remote name origin
.
Итак, допустим, у вас есть это до запуска нового git fetch
:
...--G--H <-- master, origin/master
У вас обоих есть только одна ветвь, с именем master
, и оба эти имени идентифицируют коммит H
. Затем вы запускаете git fetch
. Их master
теперь указывает на новый коммит J
, и у них есть имя ветки develop
, которое указывает на коммит L
:
I--J <-- origin/master
/
...--G--H <-- master
\
K--L <-- origin/develop
Вы не должны ничего делать , но если хотите, вы можете Git переместить ваше имя master
, чтобы указать J
. Есть много способов сделать это, но часто самый простой - это сначала git checkout master
, если это необходимо. 3 Это прикрепляет специальное имя HEAD
к имени master
, так что Git знает какую имя ветви использовать для операций, которые записывают новые идентификаторы ha sh в текущую ветку:
I--J <-- origin/master
/
...--G--H <-- master (HEAD)
\
K--L <-- origin/develop
Операция git checkout
также упорядочивает вашу index (он же область подготовки ) и рабочее дерево (он же рабочее дерево ), чтобы вы могли просматривать и / или работать с указанным коммитом по названию филиала. Таким образом, H
теперь является вашим текущим коммитом , а master
является вашим текущим ответвлением . Мы не будем go подробно описывать здесь индекс и рабочее дерево, но они очень важны: именно там вы создаете свой следующий коммит и как вы работаете с файлами, которые Git хранит внутри коммитов в специальном, только для чтения, Git -только формате.
В любом случае, теперь, когда вы находитесь в этой конкретной ситуации, вы можете Git сказать Операция «слияния» с ускоренной перемоткой вперед-не-слиянием , чтобы ваши master
догнали их origin/master
:
git merge --ff-only origin/master
Это займет ваше текущее имя ветви - master
из кассы, которую мы только что сделали, если нужно, и выполняем операцию ускоренной перемотки, если это возможно. Если он не может, он не не выполняет слияние, он просто говорит, что не может перемотать вперед, и завершает работу. Так как здесь он может перематывать вперед вместо слияния, он делает это:
I--J <-- master (HEAD), origin/master
/
...--G--H
\
K--L <-- origin/develop
Теперь у вас есть коммит J
, и вы можете видеть (и работать с) его файлы в вашем рабочем дереве. Ваше имя master
теперь идентифицирует тот же коммит, что и их имя origin/master
, и у вас все еще есть все коммиты, которые у них есть, а у вашего Git, для его имен для удаленного отслеживания, есть имена их ветвей.
2 К добавить коммит в ветку, вы делаете это с помощью Git:
- Выберите название этой ветви и его последний коммит, например,
git checkout branch1
. - Выполните все, что нужно, чтобы сообщить Git о создании нового коммита.
- Ваш Git записывает новый коммит, который получает новый уникальный идентификатор ha sh. родительский этого нового коммита - это коммит, выбранный вами на шаге 1. Тогда ваш Git просто записывает идентификатор ha sh, созданный здесь, на шаге 3, в имя Вы выбрали на шаге 1.
Теперь имя ветви идентифицирует последний коммит в ветви, как это было до того, как вы сделали новый коммит. Новый коммит является последним коммитом в ветви!
Наглядно:
...--G--H <-- branch (HEAD)
становится:
...--G--H <-- branch (HEAD)
\
I
на мгновение, но затем Git немедленно записывает I
ха sh ID в имя branch
. Git знает, что branch
является правильным именем, потому что специальное имя HEAD
является , прикрепленным к имени branch
. Итак, теперь у нас есть:
...--G--H
\
I <-- branch (HEAD)
, и нет причин не просто рисовать их всех по прямой линии.
3 В Git 2.23 и более поздних , вы можете использовать git switch
вместо git checkout
. Причина для этого заключается в том, что git checkout
, как команда, выполняет слишком много разных заданий. Таким образом, в Git 2.23 он был разделен на две отдельные команды: git switch
выполняет половину своей работы, а git restore
выполняет другую половину. Если у вас более старый Git или вы привыкли к старому способу действий, старая команда git checkout
по-прежнему работает так же, как и всегда.
Сокращение
Примечание что если они удаляют имя ветви, ваш Git все равно сохраняет в памяти их имя. То есть, предположим, что они решают, что коммиты K-L
ничего не стоят и просто полностью sh их develop
называют. У вас есть это в вашем хранилище:
...--G--H--I--J <-- master (HEAD), origin/master
\
K--L <-- origin/develop
, и вы запускаете git fetch
, и ваш Git вызывает Git в origin
. Они перечисляют тот факт, что их master
идентифицирует коммит J
, и это все для их ветвей. Ваш Git говорит ах, у меня есть коммит J
уже , и они не отправляют вам коммитов, а два Gits отключаются. Ваш Git обновит ваш origin/master
, изменив его с указанием на J
на указание на J
, что не изменит его, поэтому здесь ничего не происходит. И тогда ваш Git завершен, и ваш origin/develop
все еще помнит коммит L
, даже если у них больше нет develop
.
Если вы этого не хотите - если Вы хотите избавиться от вашего origin/develop
- вы просто говорите своему Git до обрезке , когда оно доставается. Поскольку ваш Git получает полный список всех своих ветвей, ваш Git может видеть, что у него больше нет develop
. Таким образом, ваш Git теперь будет удалять ваш origin/develop
, оставляя вам:
...--G--H--I--J <-- master (HEAD), origin/master
\
K--L [abandoned]
Чтобы выполнить это сокращение, запустите git fetch --prune
. Чтобы все операции git fetch
автоматически сокращались, когда это возможно, настройте fetch.prune
на true
:
git config --global fetch.prune true
, например.
Обратите внимание, что фиксирует все еще там, по крайней мере, на некоторое время. Без имени , чтобы найти их, ваш Git в конечном итоге удалит их. 4 Процесс удаления оставленных коммитов фактически выполняется командой обслуживания, git gc
, которую вы может работать, но это занимает много времени: несколько секунд или даже минут. Git запускает его для вас автоматически, в фоновом режиме, когда это кажется Git вероятным прибыльным предприятием, поэтому вряд ли когда-либо есть причина запускать его самостоятельно.
4 Когда вы отказываетесь от своих собственных коммитов, ваши Git имеют тенденцию запоминать свои идентификаторы ha sh как минимум еще 30 дней в одной или нескольких записях reflog . Это поддерживает оставленные коммиты живыми не менее 30 дней, на случай, если вы захотите их вернуть. В этом случае, однако, больше нет записей reflog, так что это «возможное», как только запускается следующий git gc
. Хотя трудно предсказать, когда это произойдет.
Все это приводит к последнему правилу: не беспокойтесь о названиях ветвей, пока не захотите одно
Посмотрите на нашу диаграмму где у них было два имени ветви, а у нас было одно:
...--G--H--I--J <-- master (HEAD), origin/master
\
K--L <-- origin/develop
Нам не нужно наше собственное имя ветви develop
здесь. Нам нужен только один, если мы хотим добавить коммиты в конец . Мы можем сделать один:
...--G--H--I--J <-- master, origin/master
\
K--L <-- develop (HEAD), origin/develop
и затем сделать новые коммиты:
...--G--H--I--J <-- master, origin/master
\
K--L <-- origin/develop
\
M--N <-- develop (HEAD)
Теперь нам нужно отправить наши новые коммиты на их, для которых мы используем git push
. Это работает так же, как git fetch
: мы предлагаем им коммиты, которые у нас есть, а не sh ID. Но это заканчивается по-другому. Отправив им наши коммиты M-N
, мы просим их установить имя ветви develop
, чтобы указать на коммит N
. Если они принимают, мы обновляем наш собственный origin/develop
:
...--G--H--I--J <-- master, origin/master
\
K--L
\
M--N <-- develop (HEAD), origin/develop
Commit L
больше не имеет никакого имени, указывающего на него, поэтому мы можем выправить излом на чертеже. Но мы можем также вернуться к нашему имени master
и delete our develop
:
...--G--H--I--J <-- master (HEAD), origin/master
\
K--L--M--N <-- origin/develop
Коммиты все еще там. Мы находим их, используя имя origin/develop
. Больше нет причин искать их по имени develop
. Поэтому, как только мы закончим с этим, мы просто перестанем его использовать и удалим. Затем, если они добавляют больше коммитов и мы git fetch
, единственное имя, которое у нас есть, автоматически обновляется:
...--G--H--I--J <-- master (HEAD), origin/master
\
K--L--M--N--O <-- origin/develop
Если мы обнаружим, что нам нужно добавить больше коммитов, мы git checkout develop
снова создайте наше имя develop
из нашего origin/develop
:
...--G--H--I--J <-- master, origin/master
\
K--L--M--N--O <-- develop (HEAD), origin/develop
, и мы готовы добавить новые коммиты, а затем git push
, как обычно.
Нам нужно только наше имя , если мы собираемся добавить новые коммиты . В противном случае их имен - наших имен для удаленного отслеживания - достаточно. Мы просто используем их, и все готово.
Мы можем даже посмотреть на их коммиты, используя Git ' detached HEAD mode. Предположим, что мы нажали O
и удалили наш develop
, чтобы у нас было:
...--G--H--I--J <-- master (HEAD), origin/master
\
K--L--M--N--O <-- origin/develop
Теперь они добавляют новый коммит P
. Мы используем git fetch
, чтобы получить его:
...--G--H--I--J <-- master (HEAD), origin/master
\
K--L--M--N--O--P <-- origin/develop
Мы можем git checkout origin/develop
сейчас. Поскольку origin/develop
не является именем ветви - это имя удаленного отслеживания - наш Git будет использовать режим detached HEAD . В этом режиме специальное имя HEAD
просто содержит необработанный га sh ID коммита, который мы просматриваем:
...--G--H--I--J <-- master, origin/master
\
K--L--M--N--O--P <-- HEAD, origin/develop
Если мы сделаем новый коммит Q
здесь, имя HEAD
продвигается, чтобы указать на наш новый коммит:
...--G--H--I--J <-- master, origin/master
\
K--L--M--N--O--P <-- origin/develop
\
Q <-- HEAD
, и теперь мы действительно должны сделать имя ветви, чтобы запомнить ha sh ID Q
, потому что если мы переключитесь с этого коммита (скажем, на P
или J
), мы забудем идентификатор ha sh. Кто может помнить эти вещи? Ну, Git может их запомнить. Нам просто нужно создать имя . Вот для чего нужны имена ветвей: запомнить последний коммит . Если Q
будет последним коммитом, мы создадим для него новое имя. Мы можем называть его как угодно:
git checkout -b feature
и теперь имеем:
...--G--H--I--J <-- master, origin/master
\
K--L--M--N--O--P <-- origin/develop
\
Q <-- feature (HEAD)
Операция git checkout -b
создает выбранное нами имя и присоединяет HEAD
к имени , Коммит, который мы выбрали, был коммитом, который мы использовали: тот, на который HEAD
указывал напрямую. Теперь HEAD
присоединено к новому имени, feature
, а имя - имя ветви - указывает на коммит.
Обычно вы создаете имя, указывающее сначала P
, затем обязуется сделать Q
. Но если вы забудете, вот как вы восстанавливаетесь: git status
говорит отсоединенная ГОЛОВА и вы говорите себе: упс, я должен создать имя ветви сейчас. Вы запускаете checkout -b
или в Git 2.23 и более поздних версиях git switch -c
, чтобы сделать это.
Заключение
Ваши имена ветвей предназначены для запоминания last-commit ha sh Идентификаторы. Создайте их, когда вы этого хотите. В противном случае, не связывайтесь с именами. Используйте опцию prune , чтобы убрать мертвые origin/*
имена.
Ваш Git хочет использовать хотя бы одно имя, так что вы можете позволить этому сделать это: пусть он использует master
, например. Затем сделайте перемотку вперед после git fetch
. Если вы на самом деле никогда не выполняете свою собственную работу в репозитории, вы просто используете master
и позволяете git merge --ff-only origin/master
перенести вас на их обновление.
Или вы можете даже использовать режим detached-HEAD: git checkout origin/master
, затем удалите имя master
. Тебе это не нужно. Отдельное имя HEAD
и имя удаленного отслеживания будут использоваться. После того, как git fetch
обновит ваш origin/master
, вы можете просто git checkout origin/master
снова переместить отсоединенную ГОЛОВКУ. Это может удивить некоторых Git пользователей, поэтому, если вы используете этот подход, и кто-то другой захочет взять на себя этот Git репозиторий, вы можете предупредить их, но ваш Git репозиторий предназначен для вас не для них.