Это на самом деле не ошибка , и все здесь в порядке. Ваша ГОЛОВА все еще отсоединена, она просто отсоединена при другом коммите.
Обычно разумнее использовать git checkout
для переключения на коммит, который вы хотите. Чтобы понять почему, читайте дальше.
Длинное описание
Ключ к пониманию этого состоит из нескольких частей. Во-первых, Git в основном касается commits . Комитеты, как вы видели, имеют большие уродливые хэш-идентификаторы, 0dfc...
и тому подобное, которые трудно использовать людям, но прекрасно работают для Git. Таким образом, эти хэш-идентификаторы являются «истинными именами» для каждого коммита.
Кроме того, каждый коммит записывает родительский коммит по его хэш-идентификатору. Этот родительский коммит - это коммит, который был до него. Некоторые коммиты - слияния - записывают более одного родителя, и по крайней мере один коммит в репозитории имеет нет родителя, потому что это был самый первый коммит из когда-либо сделанных, поэтому он не может записать любой более ранний идентификатор коммита: там не было каких-либо более ранних коммитов.
Что все это означает, что мы можем нарисовать график коммитов, используя их хэш-идентификаторы или используя одиночные заглавные буквы вместо больших уродливых хеш-идентификаторов, пока мы не только после 26 коммитов:
A <-B <-C
Это крошечный репозиторий с тремя коммитами. последний коммит, C
, имеет большой уродливый хэш-идентификатор. Commit C
записывает хэш-идентификатор своего родителя B
, а B
записывает хэш-идентификатор A
. A
- это первый коммит, поэтому у него нет родителя - Git называет его root commit - и он заканчивает цепочку. Мы говорим, что C
является потомком B
и указывает на B
, а B
указывает на A
. Git начинается с C
и работает в обратном направлении, следуя этим указателям. Но как Git знает, что нужно начинать с C
?
Имена ветвей указывают на коммиты
Git нужен какой-то способ, чтобы найти хеш-идентификатор коммита C
, и именно здесь приходят имена веток. Мы выбираем удобочитаемое имя, например master
, и используем его для хранения фактического хеш-идентификатора коммита C
, давая:
A--B--C <--master
Мы можем прекратить рисовать внутренние стрелки, потому что (1) ни один коммит после его выполнения не может измениться, и (2) все они обязательно в обратном направлении, так как дочерний коммит не существует, когда создается родитель, но родитель существует, когда ребенок становится Нам по-прежнему нужна стрелка master
, потому что теперь мы можем видеть, как Git добавляет новый коммит в репозиторий.
Добавление коммитов изменяет имена веток
Если мы извлечем master
и выполним некоторую работу и запустим git add
, а затем git commit
, Git создаст новый коммит - назовем его D
- и дадим D
идентификатор хеша C
как родитель D
:
A--B--C <-- master
\
D
Теперь, когда D
существует и получил новый уникальный идентификатор хэша, Git просто записывает D
ID в имя master
, так что master
теперь указывает на D
вместо C
:
A--B--C
\
D <-- master
и теперь у нас есть новый коммит. Давайте выпрямим цепочку и добавим еще одно имя ветки, указывающее на коммит D
:
A--B--C--D <-- develop, master
Теперь давайте сделаем новый коммит E
. Это работает так же, как и раньше:
A--B--C--D <-- develop, master
\
E
Git теперь нужно записать хеш-код E
в одно из двух имен веток, чтобы обновить его. Но какой? Это где HEAD
входит.
HEAD обычно присоединяется к имени ветки
Давайте нарисуем это снова, но прикрепим HEAD к develop
:
A--B--C--D <-- develop (HEAD), master
\
E
Теперь Git знает , какое имя нужно обновить: это то, к которому HEAD
прикреплено, то есть develop
. Итак, Git записывает новый идентификатор в develop
, давая нам:
A--B--C--D <-- master
\
E <-- develop (HEAD)
и теперь Git знает, что для работы с develop
он должен использовать commit E
, но для работы с master
он должен использовать commit D
.
Обратите внимание, что с таким типом присоединенной HEAD HEAD
действительно содержит имя ветви . Мы вернемся к отдельному чехлу HEAD позже.
git reset
мОвес твоя ГОЛОВА
Если мы все еще на develop
и запускаем git reset <hash-of-C>
, вот что происходит:
A--B--C <-- develop (HEAD)
\
D <-- master
\
E <-- ???
То есть, после git reset
текущее имя ветви develop
теперь относится к фиксации C
, а не к фиксации E
. Имя master
(к которому не прикреплено HEAD
) не перемещается. Коммит E
теперь довольно потерян, поскольку у нас нет имени для него, и найти его может быть сложно.
Команда git reset
может делать больше, чем просто перемещать вашу ГОЛОВУ, и имеет режимы работы, в которых не перемещает вашу ГОЛОВУ, так что это может быть довольно запутанным, но когда используется как git reset --hard
it всегда перемещает HEAD, даже если место, в которое он перемещается, является текущим коммитом. Например, если мы вернем develop
к указанию на коммит E
:
A--B--C--D <-- master
\
E <-- develop (HEAD)
и запустив git reset --hard HEAD
, мы перемещаем нашу ГОЛОВУ, т. Е. Имя develop
, с E
на E
, что оставляет его на месте. Применяются другие эффекты git reset
, и, вероятно, именно поэтому мы сделали это git reset
, поскольку движение нашей ГОЛОВКИ было без движения.
Теперь вы готовы понять отдельную ГОЛОВУ
A detached HEAD просто означает, что Git изменил HEAD, так что вместо того, чтобы содержать имя ветви , он непосредственно содержит необработанный хэш-идентификатор фиксации. Мы можем нарисовать это так:
A--B--C--D <-- master
\
E <-- develop, HEAD
Теперь мы можем git reset --hard
перейти к фиксации C
(вместе с другими действиями, которые может git reset
), который перемещается HEAD
без перемещения master
или develop
, чтобы дать нам это :
A--B--C <-- HEAD
\
D <-- master
\
E <-- develop
Любые другие git reset --hard
, которые мы делаем, пока HEAD отсоединен, просто перемещаются HEAD
(плюс делают все остальное, что мы хотим с помощью git reset --hard
).
Команда git checkout
может сделать большую часть этого тоже
Когда вы запускаете git checkout master
или git checkout develop
, то, что вы просите, чтобы Git сделал в два раза:
Переключение коммитов: используйте имя, master
или develop
, чтобы найти коммит для проверки и извлечь этот коммит, чтобы мы могли посмотреть на него и / или работать на нем.
Измените имя , к которому присоединен наш HEAD: используйте имя master
или develop
, чтобы выбрать текущую ветвь. Наш HEAD теперь присоединен к этой ветке, поэтому дополнительные коммиты автоматически перемещают ветку, как мы видели выше.
Когда вы даете git checkout
то, что не имя ветви, но идентифицирует коммит, вы просите Git:
Переключиться на данный коммит. Это работает так же, как и для именованной ветви.
Снять ГОЛОВКА, если она была прикреплена. Запишите хеш-идентификатор целевого коммита непосредственно в имя HEAD
. Если ваш HEAD уже отсоединен, он остается отсоединенным, и вы просто переключаете коммиты.
Помимо того факта, что git reset
изменит имя , если присоединен HEAD, существуют некоторые другие различия между использованием git checkout
и git reset
для перехода от одного коммита к другому. В частности, git checkout
пытается удостовериться, что никакая текущая работа никогда не будет уничтожена, но git reset --hard
сообщает Git: любая текущая работа ничего не стоит; если переключение коммитов требует его уничтожения, продолжайте и уничтожьте эту работу.
В этом смысле git checkout
намного безопаснее, чем git reset --hard
.