GrabDuck

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

:

  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.