Давайте начнем с краткого обзора уже известных вам вещей:
Базовая c единица хранения в любом репозитории Git - это commit, Внутри коммитов есть более мелкие единицы - например, коммиты содержат файлы, в некоторой степени напоминающие то, как атомы содержат протоны, нейтроны и электроны, - но сам коммит - это контейнер, который вы должны использовать. (В этой аналогии мы хотим заниматься химией, а не ядерной физикой. ?)
Каждый коммит имеет свой уникальный идентификатор ha sh. Эти идентификаторы ha sh большие и уродливые и трудные для работы с людьми, поэтому Git иногда сокращает их для отображения: у вас есть, например, 548af67
(что коротко для чего-то гораздо более длинного), be89a73
(снова сокращение от 40 символов) и так далее. Я получил это из вашего git log --all --decorate --oneline --graph
вывода. Каждый репозиторий Git везде согласен с тем, что эти идентификаторы ha sh зарезервированы для этих конкретных коммитов, даже если в этом репозитории нет этих коммитов.
Вы можете всегда использовать необработанный идентификатор ha sh для обозначения любого коммита, который у вас есть в вашем собственном репозитории.
Сам коммит содержит:
data: снимок всех ваших файлов. Это не отличается от предыдущего коммита. Это полная копия каждого файла. (Эти файлы хранятся в сжатом, замороженном, только для чтения, Git -только формате. Поскольку они только для чтения, они могут быть общими . Например, если большинство ваших коммитов имеют файл README
, и существует только три версии из README
за 90 коммитов, с одним изменением каждые 30 коммитов, затем одна внутренняя замороженная копия README в формате Git. обслуживает первые 30 коммитов, другие серверы следующие 30 и т. д.)
метаданные: информация о коммит, например: кто это сделал (имя и адрес электронной почты), когда (отметки даты и времени) и почему (сообщение в журнале фиксации). В этих метаданных каждый коммит может перечислять необработанный идентификатор ha sh некоторых предыдущих коммитов.
В большинстве коммитов указан ровно один предыдущий коммит ha sh ID. Указанный коммит является коммитом parent , т. Е. Коммитом, который приходит непосредственно перед коммитом. Некоторые коммиты содержат более одного предыдущего коммита, т. Е. Имеют более одного родителя. Один коммит в каждом непустом репозитории был первым коммитом в этом репозитории, и поэтому перечисляет нет parent.
Всякий раз Git имеет доступ к одному коммиту, Git может посмотреть на родителя этого коммита (или родителей) и, следовательно, вернуться к предыдущему коммиту. Это дает Git доступ к родительскому коммиту. Так что теперь Git может найти другого родителя - родителя этого родителя, то есть деда коммита, который у нас был минуту назад, и, конечно, у этого коммита есть родитель. Таким образом, Git может найти всю историю , просто начиная с last commit и работая в обратном направлении.
Имена ветвей находят конкретный коммит
Но совершите га sh идентификаторы выглядят случайными и непредсказуемыми. Как вы - и Git - быстро и легко узнать, какой коммит - последний ? Это где имена ветвей входят. Имя ветви как master
или detached-head-after-gitlab-crush
хранит one commit ha sh ID. Этот идентификатор ha sh по определению является последним коммитом в этой ветви.
Давайте использовать заглавные буквы для обозначения фактических коммитов ha sh ID. Мы закончим довольно быстро, и это одна из причин, по которой Git не использует простые заглавные буквы, но это будет нормально для нашего рисунка. Давайте предположим, что наш репозиторий действительно новый и содержит только три коммита. Самым первым является коммит A
, и поскольку он является первым, у него нет родителя:
A
Мы назовем второй коммит B
. Он помнит ha sh ID первого коммита как его родителя. Итак, мы скажем, что commit B
указывает на commit A
, и нарисуем вот так:
A <-B
И, конечно, commit C
содержит га sh Идентификатор коммита B
, поэтому C
указывает назад на B
:
A <-B <-C
Чтобы быстро найти C
, Git сохраняет свой идентификатор ha sh в name master
:
A--B--C <-- master
(На данный момент мы немного устали и становимся ленивыми и рисуем соединения из commit для коммитов в виде линий, а не стрелок. Просто помните, что они ' все еще стрелки, и они выходят из потомка и указывают на родителя, а не от родителя к потомку. Все части каждого коммита замораживаются навсегда, включая стрелки, выходящие из него, поэтому мы не можем go вернуться назад и добавить стрелку, указывающую вперед, позже: мы делаем коммит, у него есть одна или две стрелки, указывающие назад для его родителей, и с тех пор мы застряли с этим. Дети знают кто их родители, но родители никогда не знают, кто их дети.)
Теперь что у нас есть, давайте добавим еще одно название ветви к этой картинке. Вместо того, чтобы произносить crash
как crush
, я просто назову это develop
:
A--B--C <-- master, develop
Теперь давайте добавим новый коммит в нашу коллекцию. Мы делаем это, используя обычный процесс Git. Мы назовем новый коммит D
, независимо от того, что придет sh ID Git. Новый коммит D
укажет на существующий коммит C
, потому что мы начнем работать с make D
, проверив коммит C
. Таким образом, после создания D
это будет выглядеть следующим образом:
A--B--C
\
D
с D
, направленным вверх и влево к C
, C
, направленным назад к B
, и так
Здесь
*1143*
У нас сейчас проблема. У нас есть два имени филиала. Какой должен запомнить новый коммит D
?
Чтобы сообщить Git, какой из них, мы добавим специальное имя HEAD
во всех заглавных буквах буквы, к одному из двух существующих названий ветвей. Скажем, у нас есть это как договоренность, прежде чем мы сделаем новый коммит D
:
A--B--C <-- master (HEAD), develop
Тогда мы получим это потом:
A--B--C <-- develop
\
D <-- master (HEAD)
Но если это не то, что мы хотим мы должны git checkout develop
в первую очередь. Тогда у нас будет:
A--B--C <-- master, develop (HEAD)
и когда мы сделаем новый коммит D
, мы получим:
A--B--C <-- master
\
D <-- develop (HEAD)
Мы получим тот же набор коммитов в любом случае. Разница в том, что когда Git делает новый коммит, он записывает ha sh ID из новый коммит для любого имени ветви, к которому присоединено имя HEAD
к. Затем это имя ветки автоматически указывает на новый коммит.
Фактически, родительский нового коммита является тем, на которое указывалось имя ветки HEAD
, на которое указывал ранее. Это, по определению, коммит , который у нас был. Мы использовали git checkout master
или git checkout develop
, но в любом случае мы выбрали существующий коммит C
.
A detached HEAD происходит, когда HEAD
не присоединен к имени ветви
Теперь, когда у нас есть:
A--B--C <-- master
\
D <-- develop (HEAD)
, мы можем go включить и сделать больше новых коммитов:
A--B--C <-- master
\
D--E--F <-- develop (HEAD)
например. Но мы могли бы, если захотим, снять нашу ГОЛОВУ. Git имеет режим, в котором мы можем HEAD
указать непосредственно для любого существующего коммита. Скажем, например, что по какой-то причине мы хотим, чтобы наша точка HEAD
указывала непосредственно на фиксацию E
:
A--B--C <-- master
\
D--E <-- HEAD
\
F <-- develop
Теперь мы можем сделать новый коммит - мы назовем это G
- это укажет на существующий коммит E
. Git запишет идентификатор нового комита ha sh, каким бы он ни был, в отдельную ГОЛОВУ, давая нам:
A--B--C <-- master
\
D--E--G <-- HEAD
\
F <-- develop
В этом режиме нет ничего присущего неправильно, но потом все усложняется. Допустим, мы хотим go посмотреть на commit C
снова. Мы можем запустить git checkout master
. Это добавит имя HEAD
к имени master
снова:
A--B--C <-- master (HEAD)
\
D--E--G
\
F <-- develop
Как вы найдете коммит G
? Мы можем легко найти C
: это наш текущий коммит и имена HEAD
и master
оба найдут его. Мы можем найти B
из C
, вернувшись назад. Мы не можем найти D
из C
, но мы можем найти F
из имени develop
. С F
мы можем вернуться к E
, а оттуда к D
. Но мы не можем шагнуть вперед . Все стрелки Git указывают назад. Больше нет простого способа найти коммит G
.
Решение состоит в том, чтобы добавить новое имя ветки до того, как мы переключимся с G
. Это то, что вы сделали ранее, когда создали имя detached-head-after-gitlab-crush
. Мы можем сделать то же самое по-другому, если мы знаем идентификатор ha sh, равный G
(например, если он все еще находится на экране):
git branch save-it <hash-of-G>
добьется цели:
A--B--C <-- master (HEAD)
\
D--E--G <-- save-it
\
F <-- develop
и теперь мы можем некоторое время поработать с коммитом C
и, возможно, даже сделать новый коммит H
, который изменит master
, указав H
:
A--B--C--H <-- master (HEAD)
\
D--E--G <-- save-it
\
F <-- develop
Все, что нам нужно сделать, чтобы вернуться к G
, это git checkout save-it
, который присоединяет HEAD
к имени save-it
(которое по-прежнему указывает на G
):
A--B--C--H <-- master
\
D--E--G <-- save-it (HEAD)
\
F <-- develop
Что вам нужно сделать, это выяснить, почему вы продолжаете отключать HEAD
Хотя в режиме отключенного HEAD в Git нет ничего принципиально неправильного, с ним трудно работать. Вы должны вручную создать и / или обновить имена ветвей, чтобы запомнить ваши коммиты.
Git будет входить в этот режим отключенного HEAD всякий раз, когда вы сообщаете ему:
git checkout --detach master
, например, говорит " Я хочу использовать коммит, обозначенный master
, но я хочу сделать это в режиме отсоединенного HEAD ".
Git будет также отсоединять HEAD всякий раз, когда вы просите его проверить (или переключитесь на новую Git 2.23 и более позднюю git switch
) фиксацию по необработанному идентификатору ha sh или по любому имени, которое не имя ветви. Сюда входят имена для удаленного отслеживания , такие как origin/master
, и имена тегов, такие как v1.2
, если вы создали теги.
Некоторые команды, включая, в частности, git rebase
, будут временно отсоединить ГОЛОВУ, пока они бегут. Если они не могут fini sh, так что вы находитесь в середине перебазирования, они остановятся и оставят вас в этом отсоединенном режиме HEAD. Затем вы должны выбрать, закончить ли sh перебазирование или полностью завершить его с git rebase --abort
. (Если вы не хотите делать ни то, ни другое, вы немного застряли: вам действительно нужно сделать одно из них.)
Итак: выясните, почему вы продолжаете переходить в режим отсоединенного HEAD. Что ты делаешь, что вызывает это? Вы можете создавать новые имена веток для коммитов с git branch
, или с git checkout -b
(или в Git 2.23 и позже, git switch -c
, с c
, обозначающим создание), чтобы исправить положение, когда вы находитесь в отсоединенном HEAD режим, или если вам не нужно помнить, где вы сейчас находитесь - если вы намеренно просматриваете исторический коммит, который вы можете, и, вероятно, нашли, например, используя git log
- просто используйте git checkout
или git switch
повторно присоединить вашу ГОЛОВУ к существующему названию ветки. Но за исключением тех особых случаев, когда вы do хотите отсоединить HEAD (чтобы использовать коммит с тегами или посмотреть на исторический коммит), или случаи, когда вы работаете с ребазой, когда вы находитесь в режиме отсоединенного HEAD, пока вы fini sh, вы, вероятно, не хотите, чтобы работал в режиме отсоединенного HEAD. Так что не делай этого!