Когда вы запускаете интерактивную перебазировку, Git переводит вас в режим «отсоединенная голова».Когда вы проверяете имя ветки по имени, Git переводит вас в режим «присоединенного заголовка», то есть обратно к ветке.Это довольно сильно нарушает текущую перебазировку, потому что любые новые сделанные вами коммиты теперь трудно найти.
Ответ Лемуэля Набонга содержит ключ (но неверный): вы должны перепроверитьиз соответствующего коммита detached-HEAD, который вы можете найти, используя git reflog
.Не делайте этого с git reset
, делайте это с git checkout <em>hash</em>
или git checkout HEAD@{<em>number</em>}
, после нахождения правильного коммита в reflog.После этого вы сможете продолжить перебазирование.
Длинное описание
Что detached HEAD означает, что специальный файл .git/HEAD
(который всегда существует) больше не существуетсодержит название ветви .Обычно HEAD
, или .git/HEAD
, содержит строку типа ref: refs/heads/master
, указывающую, что текущая ветвь имеет имя master
.Затем текущая ветвь определяет текущий коммит.
Однако для выполнения определенных видов работы - включая интерактивное перебазирование - Git изменяет .git/HEAD
, так что вместо него он содержит необработанный хэш-идентификатор коммита.Интересно, что в этом режиме вы можете делать новые коммиты, которые получают новые хеш-идентификаторы, которые отличаются от каждого существующего коммита.Когда вы делаете это, идентификаторы этих новых коммитов могут только , найденные путем чтения самого .git/HEAD
.
Изображение, я думаю, делает это намного яснее.Если мы начнем с небольшого репозитория, содержащего всего три коммита, мы можем нарисовать их так, используя одиночные заглавные буквы, чтобы заменить эти ужасные строки хеш-идентификаторов, такие как ccdcbd54c4475c2238b310f7113ab3075b5abc9c
.Мы будем называть наш первый коммит A
, наш второй B
и наш третий C
:
A <-B <-C <--master
Commit C
, наш последний коммит, его хеш-идентификатор хранится под именемmaster
.Мы говорим, что имя master
указывает на C
.Коммит C
сам сохраняет хэш-идентификатор коммита B
в качестве родителя , поэтому мы говорим, что C
указывает на B
.Коммит B
хранит хэш-идентификатор A
по очереди, поэтому B
указывает на A
.Коммит A
- это самый первый коммит из когда-либо сделанных, поэтому у него вообще нет родителя.Git называет это root commit , и именно там действие останавливается, если мы запустим, например, git log
, потому что нет более ранних коммитов, на которые можно посмотреть.
Следовательно, Git всегда работает в обратном направлении: имя ветви указывает на последний коммит в ветви.Сам коммит запоминает предыдущий коммит и так далее.Если мы добавим новый коммит к master
, мы запустим:
git checkout master # if needed
... do things to modify files ...
git add file1 file2 ...
git commit
Шаг фиксации упаковывает последний снимок (из index aka). промежуточная область , где git add
скопировал их, но мы оставим это для другой темы), затем записывает новый коммит D
, чьим родителем является текущий коммит C
:
A <-B <-C <--master
\
D
Наконец, выписав новый коммит, git commit
записывает хэш-идентификатор нового коммита - каким бы он ни был;это нелегко предсказать - на имя master
, так что master
теперь указывает на D
:
A <-B <-C
\
D <--master
и фиксация выполнена.
Способ, которым Git знает которое имя ветки для обновления, если у вас более одного имени ветки, - это присоединение HEAD
к нему.Предположим, что вместо фиксации D
на master
мы делаем это:
git checkout master
git checkout -b develop # create new develop branch
Теперь чертеж выглядит следующим образом (я опускаю внутренние стрелки, мы знаем, что они всегда указывают назад и получаюттрудно рисовать):
A--B--C <-- master, develop (HEAD)
Мы делаем свою работу, git add
и git commit
, и, поскольку HEAD
присоединен к develop
, а не master
, Git пишет новый коммит *Хэш-идентификатор 1104 * в develop
вместо master
, давая:
A--B--C <-- master
\
D <-- develop (HEAD)
A detached HEAD просто означает, что вместо HEAD
, прикрепленного к какому-либо имени ветвиHEAD
указывает непосредственно на некоторый коммит.Если бы мы отсоединились HEAD
сейчас и имели точку для фиксации D
, мы могли бы нарисовать это как:
A--B--C <-- master
\
D <-- develop, HEAD
Если мы сейчас сделаем new commit E
, мыВы получите это:
A--B--C <-- master
\
D <-- develop
\
E <-- HEAD
Еслитеперь мы говорим git checkout master
, вот что происходит:
A--B--C <-- master (HEAD)
\
D <-- develop
\
E <-- ???
Способ вернуться туда, где мы были, - это найти какое-то имя для коммита E
(помните, его настоящее имя - какой-то большой уродливый идентификатор хеша).
И rebase, и git commit --amend
работают, делая new commits. Особая вещь, которую делает --amend
, состоит в том, чтобы сделать новый коммит с его родителем, являющимся parent текущего коммита. Если мы начнем с:
A--B--C <-- master
\
D <-- develop (HEAD)
и запустите git commit --amend
, Git делает новый коммит E
, чьим родителем является родитель D
C
, а не D
. Затем Git пишет это в соответствующее имя - в данном случае develop
, давая:
E <-- develop (HEAD)
/
A--B--C <-- master
\
D <-- ??? [abandoned?]
Это где reflogs приходят
У каждого имени ветви есть журнал регистрации, в котором записываются идентификаторы коммитов, которые использовало имя ветви, на которые указывает . То есть, если master
указывал на A
за один раз (что должно быть), тогда в reflog для master
включается хэш-идентификатор для коммита A
. Этот reflog также включает хеш-код для коммита B
. Как только master
больше не указывает прямо на C
, рефлог master
будет содержать также хеш-идентификатор C
и т. Д.
Существует также рефлог для самого HEAD
, в котором записываются хэш-идентификаторы, на которые HEAD
указал, прямо (отсоединен) или косвенно (будучи прикрепленным к имени ветки). Так что git reflog HEAD
показывает вам те записи reflog, которые позволяют вам найти фактический идентификатор хеша для коммита, который вы ищете.
Один недостаток записей reflog состоит в том, что срок их действия истекает: через 30-90 дней, Git предполагает, что вам уже все равно. Этот недостаток здесь не применим, так как коммит, который вы ищете, свежий. Другим (другим?) Недостатком является то, что коммиты, найденные в reflog, как правило, выглядят одинаково, и их может быть очень много, поэтому может быть трудно найти их в шуме. Помогает также отметить, что они сохранены в порядке: запись @{1}
- это старое значение, которое было несколько минут назад, запись @{2}
- та, что была до этого, и так далее. Так что, если вы только недавно перешли, тот, который вы хотите, будет в числе первых.