-->

Difference between “(no branch)” and “(detached at

2019-05-11 23:13发布

问题:

Normally when you run something like this inside of a git repository:

git checkout abc1234

You end up in a detached HEAD state. If you run git branch, the output will look something like this:

* (detached from abc1234)
  master

This is fine and expected behaviour.

I've been playing around with pygit2 recently, and have come across something I haven't seen before. Let's say I do the following:

repo = pygit2.discover_repository("/path/to/repo")
repo.head = "abc1234"

I would expect the repository to be in a detached HEAD state. For all intents and purposes, I believe it is after doing this. However, the output from git branch looks a bit different:

* (no branch)
  master

Does anyone know what the difference is, why there is a difference, and what it means?

EDIT:

Below is the reflog after cloning a repository using pygit2, assigning a commit SHA1 hash to repo.head, then running git checkout master, then running git checkout myhash:

69df316 HEAD@{0}: checkout: moving from master to 69df3161f315e9b13ba4bd811635c11f67616598
d6ece61 HEAD@{1}: checkout: moving from 69df3161f315e9b13ba4bd811635c11f67616598 to master
69df316 HEAD@{2}:
d6ece61 HEAD@{3}: clone: from file:///path/to/repo

回答1:

When in a "detached HEAD" state, git will show "(no branch)" or "(detached from abc)" depending on the contents of the reflog entry.

In your code, you simply overwrite the value without providing any message and thus no message is written to the reflog (as you can see in the HEAD@{2} entry from your reflog). If there were a similar "checkout" message, the detached text would appear.

pygit2 provides Reference.log_append() to append entries to the log, so you can create such an entry, though currently it would still create the empty one). The solution would be to use the update method once it supports the newer reflog handling which was introduced in libgit2 0.21.

The reference-setting methods Reference.set_target() and Repository.set_head() provide a place to put your own identity and message for the reflog, which you can use to provide a message equivalent to the one git's checkout command would create.

You can try doing the update as you currently are and writing by hand the entry in the reflog (it's under .git/logs/HEAD and it's a text file) to mimic what git would write and you should see the "(detached from abc)" message appear.



回答2:

Git is indicating in the first example that you have checked out a commit (not a branch) and thus your head is detached.

In the second example, Git is indicating that you have checked out something invalid. You are not in a detached HEAD state, your HEAD is pointing to an invalid branch, in particular, it's pointing at a branch with the name of your commit ID.

You specified a string argument to repo.head, as if it were a branch name . (And pygit dutifully set HEAD to the branch name, which does not exist.) Instead, you need to specify an Oid, to indicate that the head should be detached:

repo.head = Oid(hex="abc1234...")


回答3:

Check if the version of git was the same in both cases.
The article "Checking the current branch programatically" from Junio C Hamano states:

in fact, since release 1.8.3, the output when you are not on any branch, has become something like this:

$ git checkout v1.8.3
$ git branch
* (detached from v1.8.3)
  master
  next

That means the second output "(no branch)" is consistent with using a git pre-1.8.3, or with the fact that pygit2 uses an older version of libgit2, as commented by Jean Hominal


If this is produced by the same git, then check the content of HEAD: see "user manual"

The HEAD then refers to the SHA-1 of the commit instead of to a branch, and git branch shows that you are no longer on a branch:

$ cat .git/HEAD
427abfa28afedffadfca9dd8b067eb6d36bac53f
$ git branch
* (detached from v2.6.17)
master

However, in the second case, if pygit2 doesn't set HEAD at all, the wt_status.c would display no branch.