Почему получаю detached head?

:

: 4

  1. HEAD всегда должен указывать только на вершину какой-либо ветки!

    Ветка в git — это плавающий указатель на коммит. у указателя нет и не может быть никакой «вершины».

    Специальный указатель HEAD вполне может указывать и не на ветку, а на конкретный коммит (т.н. detached head). как в вашем случае.

    Это просто «нестандартная» ситуация, которая может понадобиться в особых случаях.

  2. Почему тогда я получаю detached если это моя ветка?

    origin/develop — это ветка не из вашего локального хранилища. Насколько я понимаю из предыдущих ваших вопросов, к этой ветке «привязана» ваша локальная ветка develop. После полной синхронизации между ними (git pull+git push) обе они будут указывать на один и тот же коммит, и, следовательно, распакованные из этого коммита деревья файлов/каталогов будут полностью идентичны.

Потому вам, скорее всего, требуется

$ git checkout develop

а не

$ git checkout origin/develop

1) Если я хочу переключиться на удаленную ветку develop, то почему я получаю сообщение про detached head?

Краткий ответ: потому что так задумано.

Более длинный ответ требует захода очень издалека. Попробую.

Как вы (надеюсь) знаете из толстых руководств, каждый коммит содержит в себе привязку к другому, родительскому коммиту (есть два исключения: самый первый в хранилище коммит не имеет такой привязки, а т.н. коммиты слияния (merge commits) могут содержать два и более родительских коммита). Посмотреть эту информацию можно, например, так:

$ git cat-file -p хэш-коммита
...
parent хэше-родительского-коммита
...

Как программа git «узнаёт», что туда подставить (при вызове git commit)? Она черпает информацию из файла HEAD в корне хранилища (.git/HEAD).

Если этот файл содержит хэш коммита (т.н. «состояние detached head), то этот коммит и есть родительский.

А если этот файл содержит ссылку на ветку («нормальное состояние») — ref: refs/heads/ветка — то хэш родительского коммита берётся из соответствующего файла — .git/refs/heads/ветка.

Саму процедуру создания коммита (её «технический» аспект) я описывать не буду («на полях слишком мало места» © пьер ферма), но упомяну про действие в финале этого процесса:

Если файл HEAD содержит хэш коммита («состояние detached head), то в этот файл записывается новое содержимое — хэш только что созданного коммита.

А если этот файл содержит ссылку на ветку («нормальное состояние»), то хэш вновь полученного коммита записывается в файл, на который указывает эта ссылка — .git/refs/heads/ветка. вот так и реализуется механизм «плавающего указателя на коммит» (которым и является ветка в git).

Возвращаемся к вопросу. Содержимое файла HEAD изменяется командой checkout. Так почему же, если мы укажем команде локальную ветку, она запишет в этот файл ref: refs/heads/ветка, а если укажем ветку удалённого хранилища, то она запишет в файл хэш коммита, на который указывает эта ветка (создав «состояние detached head), вместо, например ref: refs/remotes/хранилище/ветка?

А давайте посмотрим, что выйдет в таком случае.

Запишем руками в файл .git/HEAD, например, ref: refs/remotes/origin/master, внесём изменение в какой-нибудь файл в рабочем каталоге и сделаем git commit.

Хэш этого коммита будет вписан в указанный нами файл — .git/refs/remotes/origin/master. Вроде бы (пока) всё в порядке.

Но. если мы выполним команду fetch (действия, выполняемые ею, совершаются и в начале исполнения команды pull), то содержимое файла .git/refs/remotes/origin/master будет перезаписано информацией из удалённого хранилища! И наш новый коммит «сгинет» где-то в недрах нашего локального хранилища: на него не будет ссылок ни с помощью веток, ни с помощью меток (tags — это тоже указатели на коммит, но, в отличие от веток, «фиксированные», а не «плавающие»), ни с помощью строки parent хэш в каком-либо другом коммите. Да, конечно, этот бесхозный коммит можно будет посмотреть, помня его хэш, но и только.

Вот (в частности) чтобы не создавать такой «бардак», checkout и не запишет в файл HEAD ссылку на удалённую ветку.

Другая причина (по-моему, более веская) — неопределённость действий git при командах pull/push в нашей искусственно созданной ситуации. Да, в текущей реализации git откажется что-либо делать, выдав по простыне сообщений об ошибках. Но если реализовывать какое-то поведение в такой ситуации, то каким оно должно быть? Я лично затрудняюсь с ответом. Вполне вероятно, что и у разработчиков программы такая же проблема. Вот при git checkout удалённая-ветка они и решили записывать в HEAD хэш коммита (создавая «состояние detached head»), а не ссылку на удалённую ветку (создавая неопределённость).

2) Я перешел на локальную develop, сделал пуш в удаленный develop, потом сделал пул, и все равно когда переключаюсь на удаленый develop, он пишет detached from origin/develop - это нормально или нет?

Да, это нормально. см. выше.

3) Почему куда бы я не переключался, мой хед всегда стоит на месте remotes/origin/HEAD -> origin/master?

Эта строка в выдаче команды branch появляется благодаря наличию в хранилище файла refs/remotes/origin/HEAD, содержащего в вашем случае:

$ cat .git/refs/remotes/origin/HEAD
ref: refs/remotes/origin/master

Вы можете абсолютно безболезненно удалить этот файл. тогда эта строчка пропадёт из вывода команды branch.

Файл этот был создан во время клонирования и содержит информацию о том, какая именно ветка была распакована при этом в ваш рабочий каталог. Если бы вы клонировали с опцией --bare (git clone --bare url-хранилища), то этот файл не был бы создан (как и рабочий каталог с распакованными из хранилища файлами).

«Ваш» же файл HEAD, находится непосредственно в корне хранилища: .git/HEAD.