Git head detached at что значит
Перейти к содержимому

Git head detached at что значит

  • автор:

Как победить detached HEAD state?

Описание проблемы: Два прогера (Петя и Вася) начали делать один и тот же проект (project2018wm) каждый своим способом. Каждый делает свои коммиты в свой репозиторий на Github. Когда сделали по 10 коммитов, посовещались и решили что вариант Васи предпочтительнее, за исключением последних двух коммитов.

Вопрос: Как Пете сделать чтобы он своим следующий коммитом (11-м) сделал состояние своего репозитория на гитхабе идентичным состоянию проекта Васи после его 8 коммита?

Я (Петя ) пробовал так: 1. Удалил свой проект локально усебя на компе. 2. Клонировал проект Васи с гитхаба себе на комп. 3. git checkout хэш_8_го_коммита_Васи 4. Теперь у меня все файлы проекта как у Васи после 8 коммита 5. git remote set-url origin url_на_репозиторий_Пети.git

Ну и дальше в IDEA при попытке: VCS —> Commit. —> Commit and push. получаю сообщение «The Git repository C:\project2018wm is in the detached HEAD state»

Как сделать? ( Не заливая проект заново)

  • Вопрос задан более трёх лет назад
  • 3039 просмотров

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

Ответы на дополнительные вопросы, заданные в комментарии:

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 .

Git: наглядная справка

Если вы не видите иллюстраций, попробуйте переключиться на версию со стандартными картинками (без SVG).

SVG изображения были отключены. (Включить SVG изображения)

На этой странице представлена краткая наглядная справка для наиболее часто используемых команд git. Если у вас уже есть небольшой опыт работы с git, эта страница поможет вам закрепить ваши знания. Если вам интересно, как был создан этот сайт, загляните на мой репозиторий на GitHub.

Основные команды

Следующие четыре команды предназначены для копирования файлов между рабочей директорией, сценой, также известной как «индекс», и историей, представленной в форме коммитов.

  • git add файлы копирует файлы в их текущем состоянии на сцену.
  • git commit сохраняет снимок сцены в виде коммита.
  • git reset — файлы восстанавливает файлы на сцене, а именно копирует файлы из последнего коммита на сцену. Используйте эту команду для отмены изменений, внесённых командой git add файлы . Вы также можете выполнить git reset , чтобы восстановить все файлы на сцене.
  • git checkout — файлы копирует файлы со сцены в рабочую директорию. Эту команду удобно использовать, чтобы сбросить нежелательные изменения в рабочей директории.

Вы можете использовать git reset -p , git checkout -p , и git add -p вместо имён файлов или вместе с ними, чтобы в интерактивном режиме выбирать, какие именно изменения будут скопированы.

Также можно перепрыгнуть через сцену и сразу же получить файлы из истории прямо в рабочую директорию или сделать коммит, минуя сцену.

  • git commit -a аналогичен запуску двух команд: git add для всех файлов, которые существовали в предыдущем коммите, и git commit.
  • git commit файлы создаёт новый коммит, в основе которого лежат уже существующие файлы, добавляя изменения только для указанных файлов. Одновременно, указанные файлы будут скопированы на сцену.
  • git checkout HEAD — файлы копирует файлы из текущего коммита и на сцену, и в рабочую директорию.

Соглашения

Иллюстрации в этой справке выдержаны в единой цветовой схеме.

Коммиты раскрашены зелёным цветом и подписаны 5-ти буквенными идентификаторами. Каждый коммит указывает на своего родителя зелёной стрелочкой. Ветки раскрашены оранжевым цветом; ветки указывают на коммиты. Специальная ссылка HEAD указывает на текущую ветку. На иллюстрации вы можете увидеть последние пять коммитов. Самый последний коммит имеет хеш ed489. main (текущая ветка) указывает на этот коммит, stable (другая ветка) указывает на предка main-ового коммита.

Подробно о командах

Diff

Есть много способов посмотреть изменения между коммитами. Ниже вы увидите несколько простых примеров. К каждой из этих команд можно добавить имена файлов в качестве дополнительного аргумента. Так мы выведем информацию об изменениях только для перечисленных файлов.

Commit

Когда вы делаете коммит, git создаёт новый объект коммита, используя файлы со сцены, а текущей коммит становится родителем для нового. После этого указатель текущей ветки перемещается на новый коммит. Вы это видите на картинке, где main — это текущая ветка. До совершения коммита main указывал на коммит ed489. После добавления нового коммита f0cec, родителем которого стал ed489, указатель ветки main был перемещён на новый коммит.

То же самое происходит, если одна ветка является предком другой ветки. Ниже показан пример нового коммита 1800b в ветке stable, которая является предком ветки main. После этого ветка stable уже больше не является предком ветки main. И в случае необходимости объединения работы, проделанной в этих разделённых ветках, вам следует воспользоваться командой merge (что более предпочтительно) или rebase.

Если вы сделали ошибку в последнем коммите, её легко исправить с помощью команды git commit —amend . Эта команда создаёт новый коммит, родителем которого будет родитель ошибочного коммита. Старый ошибочный коммит будет отброшен, конечно же если только на него не будет ещё каких-либо других ссылок, что маловероятно.

Четвертый случай коммита из состояния «detached HEAD» будет рассмотрен далее.

Checkout

Команда checkout используется для копирования файлов из истории или сцены в рабочую директорию. Также она может использоваться для переключения между ветками.

Когда вы указываете имя файла (и/или ключ -p ), git копирует эти файлы из указанного коммита на сцену и в рабочую директорию. Например, git checkout HEAD~ foo.c копирует файл foo.c из коммита HEAD~ (предка текущего коммита) в рабочую директорию и на сцену. Если имя коммита не указано, то файл будет скопирован со сцены в рабочую директорию. Обратите внимание на то, что при выполнении команды checkout позиция указателя текущей ветки (HEAD) остаётся прежней, указатель никуда не перемещается.

В том случае, если мы не указываем имя файла, но указываем имя локальной ветки, то указатель HEAD будет перемещён на эту ветку, то есть мы переключимся на эту ветку. При этом сцена и рабочая директория будут приведены в соответствие с этим коммитом. Любой файл, который присутствует в новом коммите (a47c3 ниже), будет скопирован из истории; любой файл, который был в старом коммите (ed489), но отсутствует в новом, будет удалён; любой файл, который не записан ни в одном коммите, будет проигнорирован.

В том случае, если мы не указываем имя файла, и не указываем имя локальной ветки, а указываем тег, дистанционную (remote) ветку, SHA-1 хеш коммита или что-то вроде main~3, то мы получаем безымянную ветку, называемую «Detached HEAD» (оторванная голова). Это очень полезная штука, если нам надо осмотреться в истории коммитов. К примеру, вам захочется скомпилировать git версии 1.6.6.1. Вы можете набрать git checkout v1.6.6.1 (это тег, не ветка), скомпилировать, установить, а затем вернуться в другую ветку, скажем git checkout main . Тем не менее, коммиты из состояния «Detached HEAD» происходят по своим особым важным правилам, и мы рассмотрим их ниже.

Коммит из состояния «Detached HEAD»

Когда мы находимся в состоянии оторванной головы (Detached HEAD), коммит совершается по тем же правилам, что и обычно, за исключением одной маленькой особенности: ни один указатель ветки не будет изменён или добавлен к новому коммиту. Вы можете представить эту ситуацию как работу с анонимной веткой.

Если после такого коммита вы переключитесь в ветку main, то коммит 2eecb, совершённый из состояния «Detached HEAD», потеряется и попросту будет уничтожен очередной сборкой мусора только потому, что нет ни одного объекта, который бы на него ссылался: ни ветки, ни тега.

В том случае, если вы хотите сохранить этот коммит на будущее, вы можете создать на основе него новую ветку командой git checkout -b new .

Reset

Команда reset перемещает указатель текущей ветки в другую позицию и дополнительно может обновить сцену и рабочую директорию. Эту команду можно также использовать для того, чтобы скопировать файл из истории на сцену, не задевая рабочую директорию.

Если коммит указан без имён файлов, указатель ветки будет перемещён на этот коммит, а затем сцена приведётся в соответствие с этим коммитом. Если мы используем ключ —soft , то сцена не будет изменена. Если мы используем ключ —hard , то будет обновлена и сцена, и рабочая директория.

Если имя коммита не будет указано, по умолчанию оно будет HEAD. В этом случае указатель ветки не будет перемещён, но сцена (а также и рабочая директория, если был использован ключ —hard ) будет приведена к состоянию последнего коммита.

Если в команде указано имя файла (и/или ключ -p ), то команда работает так же, как checkout с именем файла, за исключением того, что только сцена (но не рабочая директория) будет изменена. Если вы подставите имя коммита на место двойной черты, вы сможете получить состояние файла из этого коммита, тогда как в случае с двойной чертой вы получите состояние файла из коммита, на который указывает HEAD.

Merge

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

Во всех других случаях выполняется «настоящее» слияние. Вы можете изменить стратегию слияния, но по умолчанию будет выполнено «рекурсивное» слияние, для которого будет взят текущий коммит (ed489 ниже на схеме), другой коммит (33104) и их общий предок (b325c); и для этих трех коммитов будет выполнено трёхстороннее слияние. Результат этого слияния будет записан в рабочую директорию и на сцену, и будет добавлен результирующий коммит со вторым родителем (33104).

Cherry Pick

Команда cherry-pick («вишенка в тортике») создаёт новый коммит на основе только одного сладкого «коммита-вишенки», применив все его изменения и сообщение.

Rebase

Перебазирование (rebase) — это альтернатива слиянию для задач объединения нескольких веток. Если слияние создаёт новый коммит с двумя родителями, оставляя нелинейную историю, то перебазирование применяет все коммиты один за одним из одной ветки в другую, оставляя за собой линейную историю коммитов. По сути это автоматическое выполнение нескольких команд cherry-pick подряд.

На схеме выше вы видите как команда берёт все коммиты, которые есть в ветке topic, но отсутствуют в ветке main (коммиты 169a6 and 2c33a), и воспроизводит их в ветке main. Затем указатель ветки перемещается на новое место. Следует заметить, что старые коммиты будут уничтожены сборщиком мусора, если на них уже ничего не будет ссылаться.

Используйте ключ —onto чтобы ограничить глубину захвата объединяемой ветки. На следующей схеме вы можете увидеть как в ветку main приходят лишь последние коммиты из текущей ветки, а именно коммиты после (но не включая) 169a6, т. е. 2c33a.

Технические заметки

Содержание файлов не хранится в индексе (.git/index) или в объектах коммитов. Правильнее было бы сказать, что каждый файл хранится в базе данных объектов (.git/objects) в двоичном представлении; найти этот файл можно по его SHA-1 хешу. В файле индекса записаны имена файлов, их хеши и дополнительная информация. В информации о коммитах вы встретите тип данных дерево, для идентификации которого также используется SHA-1 хеш. Деревья описывают директории в рабочей директории, а также содержат информацию о других деревьях и файлах, принадлежащих обозначенному дереву. Каждый коммит хранит идентификатор своего верхнего дерева, которое содержит все файлы и другие деревья, изменённые в этом коммите.

Если вы делаете коммит из состояния «оторванной головы» (detached HEAD), то на этот коммит будет ссылаться ссылка истории HEAD. Но рано или поздно время хранения этой ссылки истечёт, и этот коммит будет уничтожен сборщиком мусора точно так же, как это делается при выполнении команд git commit —amend и git rebase .

Copyright © 2010, Mark Lodato. Russian translation © 2012, Alex Sychev.

Это произведение доступно по лицензии Creative Commons Attribution-NonCommercial-ShareAlike (Атрибуция — Некоммерческое использование — С сохранением условий) 3.0 США.

Понимание Detached HEAD в Git

Нередко можно столкнуться с загадочным состоянием при работе с git. Тем не менее, в один прекрасный день, скорее всего, вы увидите «отдельную ГОЛОВУ».

В этом уроке мы обсудим, что такое отсоединенный HEAD и как он работает. Мы рассмотрим, как переходить в отдельный HEAD в Git и выходить из него.

2. Что такое HEAD в Git​

Git сохраняет запись о состоянии всех файлов в репозитории, когда мы создаем коммит. HEAD — еще один важный тип ссылки. Целью HEAD является отслеживание текущей точки в репозитории Git . Другими словами, HEAD отвечает на вопрос «Где я сейчас?»:

 $git log --oneline  a795255 (HEAD -> master) create 2nd file  5282c7c appending more info b0e1887 creating first file 

Например, когда мы используем команду log, как Git узнает, с какого коммита он должен начать отображать результаты? ГОЛОВА дает ответ. Когда мы создаем новый коммит, его родитель указывается тем, на что в данный момент указывает HEAD.

Поскольку Git имеет такие расширенные функции отслеживания версий, мы можем вернуться к любому моменту времени в нашем репозитории, чтобы просмотреть его содержимое.

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

3. Пример отдельной ГОЛОВКИ​

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

Как мы видим, HEAD указывает на ветку master, которая указывает на последний коммит. Все выглядит идеально. Однако после выполнения приведенной ниже команды репо находится в отдельном HEAD:

 $ git checkout 5282c7c Note: switching to '5282c7c'.   You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch.  If you want to create a new branch to retain commits you create, you may  do so (now or later) by using -c with the switch command. Example:   git switch -c new-branch-name>   Or undo this operation with:   git switch -  HEAD is now at 5282c7c appending more info  users@ubuntu01: MINGW64 ~/git/detached-head-demo ((5282c7c. )) 

Ниже приведено графическое представление текущего git HEAD. Поскольку мы проверили предыдущий коммит, теперь HEAD указывает на коммит 5282c7c , а ветка master по-прежнему ссылается на то же самое:

4. Преимущества Git Detached HEAD​

После отсоединения HEAD путем проверки определенного коммита ( 5282c7c) мы можем перейти к предыдущему моменту в истории проекта.

Допустим, мы хотим проверить, существовала ли уже данная ошибка в прошлый вторник. Мы можем использовать команду журнала, фильтруя по дате, чтобы запустить соответствующий хэш коммита. Затем мы можем проверить фиксацию и протестировать приложение либо вручную, либо запустив наш автоматический набор тестов.

Что, если бы мы могли не только взглянуть на прошлое, но и изменить его? Вот что позволяет нам сделать отдельный HEAD. Давайте рассмотрим, как это сделать, используя следующие команды:

 echo "understanding git detached head scenarios" > sample-file.txt  git add .   git commit -m "Create new sample file"   echo "Another line" >> sample-file.txt  git commit -a -m "Add a new line to the file" 

Теперь у нас есть два дополнительных коммита, которые происходят от нашего второго коммита. Запустим git log –oneline и посмотрим на результат:

 $ git log --oneline  7a367ef (HEAD) Add a new line to the file  d423c8c create new sample file  5282c7c appending more info b0e1887 creating first file 

Прежде чем HEAD указывал на коммит 5282c7c , мы добавили еще два коммита, d423c8c и 7a367ef . Ниже представлено графическое представление коммитов, сделанных поверх HEAD. Это показывает, что теперь HEAD указывает на последний коммит 7a367ef :

./e7181ffa51458dd2421de03685ab1159.png

Что нам делать, если мы хотим сохранить эти изменения или вернуться к предыдущему? Посмотрим в следующем пункте.

5. Сценарии​

5.1. Случайно

Если мы достигли состояния detached HEAD случайно, то есть мы не собирались проверять фиксацию, вернуться назад несложно. Просто проверьте ветку, в которой мы были, прежде чем использовать одну из приведенных ниже команд:

 git switch branch-name> 
 git checkout branch-name> 

5.2. Внесены экспериментальные изменения, но их нужно отменить

В некоторых сценариях, если мы внесли изменения после отсоединения HEAD для проверки некоторых функций или выявления ошибок, но мы не хотим объединять эти изменения с исходной веткой, мы можем просто отменить ее, используя те же команды, что и в предыдущем сценарии, и перейти чтобы поддержать нашу исходную ветку.

5.3. Внесены экспериментальные изменения, но необходимо их сохранить

Если мы хотим сохранить изменения, сделанные с помощью отдельного HEAD, мы просто создаем новую ветку и переключаемся на нее. Мы можем создать его сразу после получения отдельного HEAD или после создания одного или нескольких коммитов . Результат тот же. Единственное ограничение состоит в том, что мы должны сделать это до того, как вернемся к нашей обычной ветке. Давайте сделаем это в нашем демонстрационном репозитории, используя приведенные ниже команды после создания одного или нескольких коммитов:

 git branch experimental  git checkout experimental 

Мы можем заметить, что результат git log –oneline точно такой же, как и раньше, с той лишь разницей, что имя ветки указано в последнем коммите:

 $ git log --oneline  7a367ef (HEAD -> experimental) Add a new line to the file  d423c8c create new sample file  5282c7c appending more info b0e1887 creating first file 

6. Заключение​

Как мы видели в этой статье, отсоединенный HEAD не означает, что с нашим репозиторием что-то не так. Detached HEAD — это просто менее обычное состояние, в котором может находиться наш репозиторий. Помимо того, что это не ошибка, оно на самом деле может быть весьма полезным, позволяя нам проводить эксперименты, которые мы затем можем сохранить или отменить.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *