что происходит в конце концов с тем, что Git называет отстраненной головой - PullRequest
0 голосов
/ 11 мая 2018

Я хотел вернуться к более ранней фиксации через SmartGit.Через SmartGit я выбрал более ранний коммит и сделал проверку.Мне было предложено ввести название филиала.Я решил не создавать ветку, потому что казалось ненужным, даже глупым, создавать ветку, когда все, что я хотел сделать, это «вернуться назад во времени» на существующую ветку.Это привело к отстраненной голове.Казалось плохой идеей продолжать разработку на отдельной голове, поэтому я не продолжил.

Я переключился на командную строку и сделал git log, чтобы определить хэш-код для более раннего коммита, в котором я заинтересованЯ сделал git reset --hard 0dfc994b23ea.

Теперь git log, кажется, из более раннего времени, что я и хочу.Аналогично SmartGit выглядит хорошо.Ни в git log, ни в SmartGit (по крайней мере, внешне) нет никаких признаков моей ошибки (отстраненная голова).Что будет с отстраненной головой?

Ответы [ 2 ]

0 голосов
/ 11 мая 2018

Это на самом деле не ошибка , и все здесь в порядке. Ваша ГОЛОВА все еще отсоединена, она просто отсоединена при другом коммите.

Обычно разумнее использовать 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.

0 голосов
/ 11 мая 2018

Позвольте мне кратко объяснить.detached HEAD означает, что ваше рабочее дерево «указывает» на ревизию, которую вы попросили git оформить, и отсюда вы можете работать «как обычно».Вы можете фиксировать вещи, объединять вещи, что угодно.Единственное, что git не будет перемещать никакую «ветку» (иначе указатель ревизии), потому что вы извлекали не «ветку», а ревизию (то же самое может быть достигнуто, если вы попросите извлечь ветку с помощью --detach),Detached HEAD чрезвычайно полезен, если вы хотите сделать что-то быстро и не хотите реального указателя на него (скажем .... сделайте быстрый тест и вернитесь туда, где вы работали раньше. Какой смысл создавать веткупросто вернитесь во времени, затем вернитесь туда, где вы работали раньше, а затем удалите созданную вами ветку «временно», чтобы иметь возможность оформить заказ?).Короче говоря: вы можете переместиться в любое другое место из позиции «отсоединенная голова», и это ничего не изменит.

В качестве дополнительного комментария ... что делает git reset --hard, когдаВы предоставляете диапазон ревизий?Потому что я использовал его только для обеспечения одной ревизии .Только что проверил git help reset и я его не вижу.

...