Почему git checkout с явными ссылками/заголовками/ветками дает отдельную HEAD?

Если я извлекаю ветку, используя только имя ветки, HEAD обновляется, чтобы указывать на эту ветку.

$ git checkout branch
Switched to branch 'branch'

Если я извлекаю ветку с помощью refs/heads/branch или heads/branch, HEAD отсоединяется.

$ git checkout refs/heads/branch
Note: checking out 'refs/heads/branch'.

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 performing another checkout.

$ git checkout "refs/heads/branch"
Same result

$ git checkout heads/branch
Same result

Почему? Если его версия зависит, у меня есть git 1.7.9.5 на Ubuntu 12.04.3.

Ответы

Ответ 1

Команда checkout различает два случая (ну, на самом деле "много", но пусть начнутся только с двумя:-)):

  • "Я хочу, чтобы я получил ветку & rsquo;; здесь имя ветки": например, git checkout branch.
  • "Я хочу посмотреть на какую-то конкретную ревизию и выйти из любой ветки, вот идентификатор ветки, который не является именем ветки": например, git checkout 6240c5c.

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

Теперь скажите, что вы хотите первого. Это проще всего написать, просто набрав имя ветки, без части refs/heads/.

Если вы хотите последний, вы можете указать ревизию любым из методов, перечисленных в gitrevisions, за исключением любого метода, который приводит к в "попадание на ветку".

По какой-либо причине выбранный здесь алгоритм - он документально подтвержден на странице в разделе <branch> - это: Если вы написали имя, которое при добавлении refs/heads/ к нему называет ветку, git checkout поместит вас в эту ветку. Если вы укажете @{-N} или -, он будет искать N-ю старшую ветвь в рефлекторе HEAD- значением @{-1}). В противном случае он выбирает второй метод, предоставляя вам "отключенную HEAD". Это верно, даже если имя указано в gitrevisions для избежания двусмысленности, т.е. heads/xyz, когда есть другой xyz. (Но: вы можете добавить --detach, чтобы избежать случая "получить на ветке", даже если он в противном случае попал бы в ветвь.)

Это также противоречит разрешающим правилам, перечисленным в документе gitrevisions. Чтобы продемонстрировать это (хотя это трудно увидеть), я сделал тег и ветку с тем же именем, derp2:

$ git checkout derp2
warning: refname 'derp2' is ambiguous.
Previous HEAD position was ...
Switched to branch 'derp2'

Это помещало меня в ветку, а не отделяя и переходила к помеченной ревизии.

$ git show derp2
warning: refname 'derp2' is ambiguous.
...

Это показало мне помеченную версию, как говорит gitrevisions.


Односторонняя заметка: "получение на ветке" действительно означает "размещение символической ссылки на имя ветки в файл с именем HEAD в каталоге git". Символическая ссылка - это буквальный текст ref: (с конечным пространством), за которым следует полное имя ветки, например refs/heads/derp2. Кажется некорректным, что git checkout требует имя без части refs/heads/, чтобы добавить часть ref: refs/heads/, но git для вас.:-) Для этого может быть какая-то историческая причина: изначально, чтобы быть символической ссылкой, файл HEAD был фактически символической ссылкой на файл ветки, который всегда был файлом. В наши дни, частично из-за Windows и частично только благодаря эволюции кода, у нее есть эта буквальная строка ref:, и ссылки могут стать "упакованы" и, следовательно, в любом случае недоступны как отдельный файл.

В противоположном случае "отдельная HEAD" на самом деле означает "помещение необработанного SHA-1 в файл HEAD". Помимо наличия числового значения в этом файле, git продолжает вести себя так же, как когда "на ветке" : добавление нового коммита все еще работает, а новый фиксатор - текущий коммит. Слияния все равно могут быть выполнены, а родители слияния являются текущими и подлежащими объединению фиксациями. Файл HEAD обновляется каждым новым фиксатором, как это происходит. 1 В любой момент вы можете создать новую метку ветки или метки, указывающую на текущую фиксацию, чтобы вызвать новую цепочку коммитов сохраняются в будущем сборке мусора даже после отключения "отсоединенной головки"; или вы можете просто отключиться и позволить новым коммитам, если таковые имеются, вытащиться с обычной сборкой мусора. (Обратите внимание, что HEAD reflog предотвратит это в течение некоторого времени, по умолчанию, по умолчанию, 30 дней.)

[ 1 Если вы "на ветке" , происходит такое же автоматическое обновление, это происходит только с веткой, на которую ссылается HEAD. То есть, если вы находитесь в ветке B, и вы добавляете новую фиксацию, HEAD все еще говорит ref: refs/heads/B, но теперь код фиксации, который вы получаете с помощью git rev-parse B, - это новый комманд, который вы только что добавили. Вот как ветки "растут": новые коммиты добавляются, а "на ветке" заставляют ссылку на ветку автоматически двигаться вперед. Аналогично, когда в этом состоянии "отсоединенного HEAD" новые коммиты добавили причину HEAD для автоматического перемещения вперед.]


Для полноты, здесь может быть список других вещей git checkout, которые я мог бы добавить в различные отдельные команды, если бы у меня были такие полномочия:

  • проверьте конкретную версию некоторого пути (путей), записывая через индекс: git checkout revspec -- path ...
  • создать новую ветку: git checkout -b newbranch (плюс опции для git branch)
  • создайте новую ветку, которая, если и когда вы сделаете фиксацию на ней, будет корневой фиксацией: git checkout --orphan (это помещает вас в "ветвь", которая еще не существует, т.е. записывает ref: refs/heads/branch-name в HEAD, но не создает ветвь branch-name, это также означает, что master - неродившаяся ветвь в новом репозитории)
  • создать или воссоздать конфликт слияния или слияния: git checkout -m ...
  • разрешить конфликт слияния, выбирая одну или другую "сторону" слияния: git checkout --ours, git checkout --theirs
  • интерактивно выбирает патчи между объектами репозитория и файлами дерева работ, аналогичными git add --patch: git checkout --patch

Ответ 2

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