detached head git что это

Машина времени в git

В последнее время мои коллеги начинают знакомство с git’ом. И один из интересующих их вопросов — как откатиться до определённой ревизии. В интернете можно найти набор команд, но хочется, чтобы было понимание каждой из них. Баловство с комадами git’а без понимания может привести к потере истории разработки.

Здесь кружочками обозначены коммиты. Чем правее коммит, тем он новее. Коммит с хэшем 6e04e..-это самый первый коммит. Одно из основных понятий, которое стоит уяснить себе новичку, — это указатели на коммиты, а точнее некоторое «прозвище» того или иного коммита. Их тьма тьмущая, например: HEAD, master, FETCH_HEAD, ORIG_HEAD и т.д. Это я перечислил крупицу стандартных прозвищ. Их можно создавать и самим, но об этом впереди.

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

Перенос указателя HEAD ( git checkout )

Откат по истории коммитов:

Неужели мы потеряли всю историю? Как узнать самый «новый» коммит? Это не проблема — есть выход, и их несколько:

Для прояснения механизма git checkout создадим новую ветку devel:

Заметим, что указатель HEAD указывает на вершину ветки devel.

Породим в новой ветке несколько коммитов. История репозитория будет выглядеть следующим образом:

Возвращение в ветку master происходит также безболезненно:

2 :

2 :

ORIG_HEAD полезен для редактирования неверных коммитов на локальной машине (!). Предположим, что мы хотим объединить два последних коммита в единый. Для этого, сохраняя текущее состояние файлов, переводим указатель master на два коммита назад:

Посмотрим на изменения:

Ну а теперь сделаем трюк — объединяем коммиты

Вводим сообщение, сохраняемся. Теперь наша история выглядит вот так:

Важное замечание — ORIG_HEAD по-прежнему указывает на коммит d79fb… Если мы сейчас выполним команду git checkout ORIG_HEAD, то мы получим так называемое состояние detach HEAD. Оно характеризуется тем, что HEAD указывает не на вершину ветки, а просто на коммит. HEAD всегда должен указывать только на вершину какой-либо ветки!

Удачных вам путешествий по истории своего репозитория!

При подготовке материала использовались следующие источники:
Самый лучший manual — книга: ProGit
Наглядная справка по git: A Visual Git Reference (Русская версия)

UPD:
В комментариях посоветовали ещё один полезный ресурс по git`у: githowto

Источник

Pro Git

Страницы

четверг, 19 марта 2015 г.

Про отрубленную голову Git (detached HEAD state)

В этой статье пойдет речь про отрубленное отсоединенное состояние указателя HEAD. Надо понимать что названия веток в Git это указатели на коммиты. Имя ветки, допустим master, указывает на последний коммит в ветке master.

Существует так же множество других указателей и один из них HEAD, но это очень важный указатель. И вот о нем мы и поговорим.

И так у нас есть история коммитов

И сейчас указатель ветки master и указатель HEAD указывают на последний коммит С4 в ветке master.

То же самое можно увидеть в Git

Оба указателя master и HEAD указывают на коммит С4 (efaaf18).

Теперь передвинем указатель HEAD на коммит C2 командой

$ git checkout 7bbbd68

Вот мы и получили отрубленную отсоединеннуюый голову указатель HEAD. И Git нам любезно сообщил что снег башка попал…

На диаграмме это можно изобразить так

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

Все это можно увидеть в Git

Посмотреть историю перемещения головы можно командой git reflog

На скрине мы так же посмотрели состояние Git и он нам (аж красным) сообщил, что башка отсоединена на коммит 7bbbd68.

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

Давайте сделаем коммит и посмотрим что будет. Изменим файлик и посмотрим статус

Ну и делаем коммит

Из лога коммитов видно что сейчас HEAD указывает на коммит С5 (84b361c), но на этот коммит не указывает ни какая ветка, верней сказать ни какой указатель ветки.

Графически это можно изобразить так

Мы по прежнему находимся в состоянии detached HEAD, о чем нам все время напоминает Git. Еще раз напомню что это означает что HEAD указывает не на вершину какой-либо ветки, а просто на коммит. В нашем случае на коммит С5 (84b361c).

Выйти из состояния detached HEAD очень легко, для этого надо переключится на какую-либо ветку или создать новую ветку.

Давайте переключимся на ветку master командой git checkout master

При переключении на ветку master, Git заботливо нас предупредил, что мы оставляем 1 commit, который не присоединен ни к какой ветке. Сообщил нам имя этого коммита – С5 и его хэш – 84b361c. И посоветовал, что возможно уже самое время создать новую ветку командой

git branch new_branch_name 84b361c

Поскольку если мы сейчас не создадим ветку (указатель) на этом коммите, то мы можем его потерять.

Если сейчас дать команду просмотра лога коммитов, то мы коммит С5 в логе не увидим

Читайте также:  какой мультик нам посмотреть сегодня

Таким образом мы можем “потерять” коммит С5, если забудем его хэш. Конечно, как уже говорилось, что в Git какой-либо сделанный коммит сложно потерять (но можно). И в данном случае мы можем посмотреть историю перемещения HEAD

и в ней мы можем увидеть наш потерянный коммит, хотя мы его и не видели в истории коммитов (логе коммитов).

Теперь дадим команду

$ git branch lost_branch 84b361c

Теперь, все хорошо. На коммит С5 указывает ветка lost_branch

Графически это выглядит так

Теперь мы легко можем переключится на ветку lost_branch и состояния HEAD deatached уже не будет, поскольку HEAD уже будет указывать на вершину ветки lost_branch.

Сейчас переключение произошло безболезненно, поскольку это вполне штатная работа в Git.

Ну и покажу это состояние графически

На этом, с отсоединенной головой пока все. Хотя есть еще варианты по отделению головы в Git, но об этом как-нибудь в другой раз.

Источник

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

после того как переключаюсь на ветку develop в репазитории origin :

Так вот я тут почитал, что состояние detached получаем вот так

Оно характеризуется тем, что HEAD указывает не на вершину ветки, а просто на коммит. HEAD всегда должен указывать только на вершину какой-либо ветки!

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

При переключении на удаленную ветку develop получаю такое сообщение

1 ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

detached HEAD explained

Git has something called a ‘detached HEAD’ that bears some explanation. You are told ‘do not commit on a detached HEAD’, and it is not always clear what or why this is.

This document is here for historical reasons, and because it provides a nice analogy. However, git concepts simplified should be a much nicer read for most people. It has a lot of nice pictures, and it also touches upon many other issues, not just on detached HEADs.

1 the analogy

Forget git for a bit and think about linked lists from your data structures course. Specifically, consider a singly linked list, where each node is created as a “child” of some existing node, and contains a link to that “parent” node. The words “child” and “parent” are chosen to be similar to git’s nomenclature for the relationship between a commit and its predecessor commit(s).

As with any list, you have a pointer to the node at the top of the list (we’ll call this pointer list_top ). This would be the most recently created node, and so there’s a chain of “parent” links that can take you all the way to the first node created.

And you know how to add a new node to this linked list. In pseudo-code:

Let’s also assume that all list operations are done with some temporary variable (that is, they never directly use list_top etc.).

So far, no harm done; your list_top variable still points to the top of the list, and you can always do temp = list_top to go back to where you were for any further operations.

Now suppose you add a new node to the list on top of the current node:

This is the same code as before, except temp has taken the place of list_top this time.

But ‘temp’ is a temporary variable, and unless you somehow save its value into some ‘global’ (or ‘static’) variable, you risk losing that commit you just made, oops, I mean the new node you added to the linked list 🙂

Читайте также:  при какой температуре лучше всего хранить розы

2 reality check

A previous version of this article took the analogy too far into git territory, and I did not realise how many facts about git I had passed over/simplified until an extended review on irc by doener and jsquared.

So we’ll git real, if you’ll pardon the weak pun, and get on with understanding a ‘detached HEAD’.

In git, commits always go on top of HEAD. In terms of our linked list analogy, they ‘insert a new commit at HEAD’.

However, when you checkout anything that is not a proper, local, branch name, then HEAD is no longer a symbolic reference to anything. Instead, it actually contains the SHA-1 hash (the commit id) of the commit you are switching to.

This is called a detached HEAD. Example commands that will cause your HEAD to become detached (ouch!) are [1] :

2.1 re-attaching the HEAD

If you made the classic error of checking out a remote branch (like git checkout origin/master ) and making a few commits on it before realising something was wrong, you can recover quite easily:

To go back to the linked list analogy way up there, you just created a new ‘global’ variable to save off the value of ‘temp’ before some other operation overwrote it.

2.2 Auld Lang Syne

What if you didn’t realise this, and – after making those commits – blithely switched to another branch:

HEAD is now a symbolic reference to ‘someoldbranch’, and its previous value (the SHA-1 representing the top commit you made on the detached HEAD) has now been overwritten.

So is it gone for good now, never to be seen again? Fortunately, no! The commit object itself is still safe out there somewhere, but you don’t know where! You have to find it…

Think back to these 2 lines in the analogy above, where the ‘current node’ was updated:

What if you had some sort of wrapper around that line, such that, every time this happened, the new value being assigned is also saved away somewhere, including information on what command called it and when. And let’s say this information is kept for 30 days. So, even if you lost all your pointers, you could check this saved list and the caller/time information to jog your memory of which one it was, and actually use that pointer value to recover the node you ‘lost’.

That’s the ‘reflog’ in git:

Now you look at this, decide which one you want, and grab it:

2.3 reality check – conclusion

There are a few other points that don’t fit the simple ‘linked list’ analogy, or present some additional information that may be useful:

a commit ‘tree’ is really more of a graph. A ‘directed acyclic graph’, to be precise.

a branch can point to any node in the graph, not just the top one. For example, when you fork a branch called ‘new’ from ‘master’, and make 2 commits on ‘new’, then ‘new’ is pointing to the top node in that portion of the graph, but ‘master’ is still pointing two commits down from the top node. This is because the ‘master’ branch represents the same set of commits it did before; we have done nothing to change that.

while most commits have only one parent, a ‘merge’ commit has more than one parent. Most merges have 2 parents, but an octopus merge can have any number of parents.

apart from the reflog for HEAD, git also stores a reflog for each local branch (recording commits, rebases, merges, and so on) and each remote branch (recording pulls/fetches mainly). The HEAD reflog is effectively a superset of the reflogs of all the local branches, with some extra detail (like individual steps within a rebase, for instance).

Источник

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

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

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

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

Содержание

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

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

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

Соглашения

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

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

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

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

Commit

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

Читайте также:  проспект энгельса какое метро рядом

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

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

Checkout

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

foo.c копирует файл foo.c из коммита HEAD

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

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

В том случае, если мы не указываем имя файла, и не указываем имя локальной ветки, а указываем тег, дистанционную (remote) ветку, SHA-1 хеш коммита или что-то вроде main

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

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

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

Reset

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

Merge

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

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

Cherry Pick

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

Rebase

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

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

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

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

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

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

Источник

Сказочный портал