Почему получаю 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 .
Как исправить «Отдельный HEAD» в репозитории Git?
Одним из регулярных сообщений об ошибках, с которыми вы, вероятно, столкнетесь в Git, является предупреждение о том, что «вы находитесь в состоянии ‘detached HEAD’». Возможно, вы наткнулись на это случайно, но не волнуйтесь — это обычная проблема, и ее можно легко исправить, как только вы поймете, что это значит.
Что такое Git HEAD?
«HEAD» — это просто псевдоним для вашего текущего рабочего коммита, очень похожий на ваш текущий каталог в командной строке. В каком бы состоянии ни находился ваш репозиторий Git, HEAD всегда указывает на что-то, и новые коммиты будут добавлены перед HEAD.
Обычно HEAD напрямую не ссылается на один коммит. Вместо этого он указывает на ветку и косвенно ссылается на последнюю фиксацию в этой ветке. Всякий раз, когда вы «выписываете» новую ветку, Git переключает HEAD на эту ветку. Это позволяет вам делать новые коммиты, которые будут добавлены в конец этой ветки.
Однако вы также можете проверить отдельные коммиты. Что, если вы хотите изучить репозиторий в определенный момент времени? Это одно из основных преимуществ Git, но оно создает небольшую проблему в том, как Git структурирует вещи. Поскольку новые коммиты не будут обновлять ветку, любые коммиты, которые вы сделаете после удаления HEAD, станут отсоединенными от всех ссылок на ветки, следовательно, «отсоединенные HEAD».
Это действительно может быть довольно полезно. Скажем, вы переместили HEAD назад на несколько коммитов, а затем сделали несколько экспериментальных обновлений. По сути, вы создаете новую ветку, которая позже может быть объединена с master .
Вам нужно будет официально интегрировать его в репозиторий Git, если вы решите сохранить изменения, но в качестве инструмента для внесения изменений в прошлое наличие отдельного HEAD не так страшно, как кажется.
Если вы попали сюда случайно
Очень легко оказаться здесь случайно и запутаться в сообщении об ошибке, на которое жалуется Git, несмотря на то, что он не сделал ничего плохого. К счастью, это невероятно легко исправить:
Если вы только что извлекли коммит и не трогали репозиторий в противном случае (никаких новых коммитов не было), вы можете просто переместить HEAD туда, где он должен быть, с помощью git checkout :
git checkout master
В основном это похоже на запуск cd в Linux, за исключением того, что Git по-прежнему связывает кэшированные коммиты и изменения со старым HEAD, поэтому вам не нужно проверять, если у вас есть коммиты или изменения, которые останутся висящими.
Если у вас были какие-то изменения, и вы хотите их просто выбросить, вы можете полностью сбросить настройки до master :
git reset --hard origin/master git clean -d --force
Однако, если вы хотите сохранить свои коммиты, вам нужно будет официально объединить их с временной шкалой Git.
Если вы хотите сохранить изменения
Первое, что вам нужно сделать, если вы хотите сохранить изменения, сделанные вами в отсоединенном состоянии HEAD, — это создать новую ветку. Это связано с тем, что сборщик мусора Git автоматически очищает отсоединенные коммиты, так что вы никогда не захотите их потерять.
git branch detached-branch
Затем вы можете проверить эту ветку, чтобы переместить HEAD, чтобы он больше не был отсоединен, и вместо этого указывал на эту официальную новую ветку:
git checkout detached-branch
После того, как изменения записаны, у вас есть один из двух вариантов. Эта новая ветка представляет собой обычную функциональную ветку, поэтому вы можете использовать git merge или git rebase .
Слияние является простым; мастер проверки и объединить отсоединенную ветку:
git checkout master git merge detached-branch
Это хорошо работает, если вы интегрируетесь в ветку, которая требует одобрения, как в случае с запросами на включение и проверками кода. Возможно, вам придется пройти процесс утверждения вашей командой, а не запускать эти команды самостоятельно.
Если у вас есть доступ, например, если вы выполняете слияние с функциональной веткой, над которой работаете, лучший способ — выбрать отдельные коммиты. Выбор вишни в основном копирует коммит и вставляет его в другую ветку. Обычно он используется для извлечения определенных коммитов из функциональной ветки до того, как все это будет готово, но в этом случае выбор вишни может согласовать отсоединенную ветку без слияния.
В этом примере фиксация выше отсоединена. Вместо того, чтобы объединять его, он выбирается и перемещается в ветку feature . Это перемещает ветвь HEAD и feature так, чтобы она указывала на последнюю фиксацию без слияния. Затем исходный отсоединенный коммит можно удалить.
Во-первых, вам нужно сделать отдельную ветку, а затем проверить ветку feature, чтобы переместить туда HEAD:
git branch detached-branch git checkout feature
Затем запустите Git log, чтобы получить список коммитов:
git log --pretty=format:"%h %s" --graph
Затем вы можете выбрать коммит по его идентификатору:
git cherry-pick 1da76d3
Затем, как только вы убедились, что выбранные коммиты применяются правильно, вы можете удалить отсоединенную ветку, так как коммиты были скопированы и она больше не используется.
git branch -D detached-branch
Перебазирование вместо слияния
Вы также можете выполнить перебазирование. Ребазы отличаются от слияний тем, что они переписывают историю ветки, поднимая отсоединенные коммиты и перемещая их в начало ветки.
Однако проблема в том, что у вас по-прежнему остаются две ветки, и вам все равно нужно будет объединить feature и detached-branch вместе, в результате чего у вас останется слияние совершить в любом случае. Но это оставляет репозиторий с более линейной историей Git, что предпочтительнее, чем запуск git merge , если у вас есть на это полномочия.
Все права защищены. © Linux-Console.net • 2019-2023
Понимание 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 :
Что нам делать, если мы хотим сохранить эти изменения или вернуться к предыдущему? Посмотрим в следующем пункте.
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 — это просто менее обычное состояние, в котором может находиться наш репозиторий. Помимо того, что это не ошибка, оно на самом деле может быть весьма полезным, позволяя нам проводить эксперименты, которые мы затем можем сохранить или отменить.
- 1. Обзор
- 2. Что такое HEAD в Git
- 3. Пример отдельной ГОЛОВКИ
- 4. Преимущества Git Detached HEAD
- 5. Сценарии
- 5.1. Случайно
- 5.2. Внесены экспериментальные изменения, но их нужно отменить
- 5.3. Внесены экспериментальные изменения, но необходимо их сохранить
What’s a «detached HEAD» in Git?
It might very well be that you’ll never come across this «mysterious» state in your Git career. However, if you do one day, you’d probably like to know what a «detached HEAD» is — and how you might have arrived at that state.
The Git Cheat Sheet
No need to remember all those commands and parameters: get our popular «Git Cheat Sheet» — for free!
Understanding how «checkout» works
With the «git checkout» command, you determine which revision of your project you want to work on. Git then places all of that revision’s files in your working copy folder.
Normally, you use a branch name to communicate with «git checkout»:
$ git checkout development
However, you can also provide the SHA1 hash of a specific commit instead:
$ git checkout 56a4e5c08 Note: checking out '56a4e5c08'. You are in 'detached HEAD' state.
This exact state — when a specific commit is checked out instead of a branch — is what’s called a «detached HEAD».
The problem with a detached HEAD
The HEAD pointer in Git determines your current working revision (and thereby the files that are placed in your project’s working directory). Normally, when checking out a proper branch name, Git automatically moves the HEAD pointer along when you create a new commit. You are automatically on the newest commit of the chosen branch.
When you instead choose to check out a commit hash, Git won’t do this for you. The consequence is that when you make changes and commit them, these changes do NOT belong to any branch.
This means they can easily get lost once you check out a different revision or branch: not being recorded in the context of a branch, you lack the possibility to access that state easily (unless you have a brilliant memory and can remember the commit hash of that new commit. ).Tip
Detached HEAD handling in Tower
In case you are using the Tower Git client, the app will prominently inform you when you’re in a detached HEAD state. More importantly, Tower will also explicitly warn you in case you’re trying to commit in such a state.
When a detached HEAD shows up
There are a handful of situations where detached HEAD states are common:
- Submodules are indeed checked out at specific commits instead of branches.
- Rebase works by creating a temporary detached HEAD state while it runs.
Where a detached HEAD should not show up
Additionally, another situation might spring to mind: what about going back in time to try out an older version of your project? For example in the context of a bug, you want to see how things worked in an older revision.
This is a perfectly valid and common use case. However, you don’t have to maneuver yourself into a detached HEAD state to deal with it. Instead, remember how simple and cheap the whole concept of branching is in Git: you can simply create a (temporary) branch and delete it once you’re done.
$ git checkout -b test-branch 56a4e5c08 . do your thing. $ git checkout master $ git branch -d test-branch
Learn More
- Check out the chapters Checking Out a Local Branch and Submodules in our free online book
- More frequently asked questions about Git & version control
Get our popular Git Cheat Sheet for free!
You’ll find the most important commands on the front and helpful best practice tips on the back. Over 100,000 developers have downloaded it to make Git a little bit easier.
About Us
As the makers of Tower, the best Git client for Mac and Windows, we help over 100,000 users in companies like Apple, Google, Amazon, Twitter, and Ebay get the most out of Git.
Just like with Tower, our mission with this platform is to help people become better professionals.
That’s why we provide our guides, videos, and cheat sheets (about version control with Git and lots of other topics) for free.
© 2010-2023 Tower — Mentioned product names and logos are property of their respective owners.