Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fix "warning: symbolic ref is dangling" in git

Tags:

git

repository

Recently I started to notice that on one of my git repositories, any git command I run, I get the following warning:

warning: symbolic ref is dangling: refs/remotes/origin/HEAD

What does this mean and how do I fix it?

like image 572
CJHolowatyj Avatar asked Feb 06 '20 11:02

CJHolowatyj


1 Answers

How to fix it

Because this particular symbolic ref is not useful anyway, I recommend just deleting it:

git remote set-head origin --delete

If you like having this useless symbolic ref, you can have your Git auto-update it instead:

git remote set-head origin --auto

This will have your Git call up the other Git over at origin, ask it what its current branch is now, and update your symbolic-ref origin/HEAD appropriately.

If updating it like this doesn't fix it, don't worry: it's probably not actually broken and will "self heal" eventually. In any case, it is, in my opinion, useless anyway, so even if it is broken, this doesn't hurt anything.

About refs

Let's start by noting what a ref is:

  • A ref, or reference, is a name. There are some restrictions on the name (see the git check-ref-format documentation, but it's mostly just a string of letters and digits and such, with what we can hope spell out a name that means something to you.

    For instance, a branch name is a ref. So is a tag name.

  • Most of these names actually start with refs/. The only ones that don't are not always called refs (Git is not completely consistent about this): they're the special names HEAD, ORIG_HEAD, CHERRY_PICK_HEAD, and so on. (They are all either HEAD itself, which is the most special of all, or end in _HEAD. They should always be spelled out in all uppercase like this.)

  • Refs are divided into namespaces. Two that you use all the time are the branch namespace, which is every ref whose spelling starts with refs/heads/, and the tag namespace, which is every ref whose name starts with refs/tags/. There is a third one you also use all the time: remote-tracking names are refs whose name starts with refs/remotes/. After the word remotes and its trailing slash, Git adds the name of the remote in question, such as origin, and another trailing slash.

Hence ref is simply the general form of any branch or tag or remote-tracking name, or any other name, in Git. There are some less-frequently-used ones, and internal ones, like refs/stash for the git stash command, refs/bisect/ for git bisect, and so on; normally you don't see these at all.

You do see branch, tag, and remote-tracking names, but Git normally strips off the "namespace" part of the name:

  • master is really refs/heads/master, but Git stripped off the refs/heads/ part.
  • v1.2 is really refs/tags/v1.2, but Git stripped off the refs/tags/ part.
  • origin/master is really refs/remotes/origin/master, but Git stripped off the refs/remotes/ part.

Sometimes Git strips only the refs/ part from remote-tracking names. It's not at all clear why, but git branch -a does this. That's why you see remotes/origin/master here. Compare with git branch -r, where you see only origin/master. In any case, this is a valid way to express each name, because there is a six-step rule for resolving any name you give, as shown in the gitrevisions documentation, and three of the steps involve trying to stick refs/remotes or refs/heads or refs/tags in front of any name you give. We'll get back to this in a moment.

Most refs store exactly one thing: a hash ID. For branch names and remote-tracking names, that hash ID must be a commit hash ID. For tags, it's usually either a commit hash ID, or the hash ID of an annotated tag object, which then stores the commit hash ID. This extra step—going through the tag object—allows you to write an annotated tag, or a tag with a message.

But some refs are symbolic refs. What this means is that instead of storing a hash ID, they store the name of some other ref. The most common one—in fact, the only useful one, in my opinion—is the very special HEAD ref.1

HEAD normally contains the name of a branch. That is, if you look at the actual file .git/HEAD, you'll see, e.g.:

$ cat .git/HEAD
ref: refs/heads/master

In other words, the name HEAD simply holds the branch name master. What this means is that master is the current branch. This is how Git implements an attached HEAD. Hence a detached HEAD is just the case where HEAD holds a commit hash ID instead of a branch name. That's really all there is to attached vs detached HEAD. Git just asks itself: Does HEAD hold a branch name? If so, that branch is the current branch and the commit hash ID stored in that branch name is the current commit. If not, HEAD holds a commit hash ID, and that hash ID is the current commit.


1HEAD is very special because, among other things, if the file goes missing, Git will stop believing that the repository is a repository! Never remove .git/HEAD. If, after a computer crash, you need to recover a repository whose HEAD file has gone missing, you can try creating one containing ref: ref/heads/master and see if that allows you to proceed. This happens surprisingly often after a crash because HEAD is such a relatively active file, and one method computer systems use to recover after a crash is simply to remove any damaged file.


What it all means

In general, a ref must resolve to a commit or other hash ID, for some valid, existing, internal Git object.2 For instance, a branch name like master is only allowed to exist if it holds the hash ID of some existing, valid commit.

If a ref holds the name of some other ref, that other ref needs to exist, and resolve to a valid hash ID.3 If the other ref doesn't exist, though, we can say that the symbolic ref is dangling.

That's what this means:

warning: symbolic ref is dangling: refs/remotes/origin/HEAD

Your existing refs/remotes/origin/HEAD is a symbolic ref—all remote-tracking remote/HEAD names are symbolic refs after all—but whatever name it contains, that name doesn't exist.

Note: There is a situation in which your own HEAD is a dangling symbolic reference. This occurs normally in a new, totally-empty repository. HEAD must exist, so it does. It should contain the name of a branch, so it contains ref: refs/heads/master. It is a symbolic ref, saying that you are on branch master. But this is a new, totally-empty repository. There are no valid commits in it! The branch name master cannot exist, because a branch name is required to hold the hash ID of a valid, existing commit, and there are none. So master itself, the branch name, simply doesn't exist. This makes HEAD a dangling symref.

Making the first commit creates the name master, resolving the situation. So it resolves all on its own, and there's nothing wrong with this situation. Hence a dangling symbolic ref is not harmful. This is especially true of HEAD (where Git won't complain about the dangling symbolic ref), but it's true of refs/remotes/origin/HEAD too: these are all harmless. Unlike the special case for HEAD, though, they're not useful.


2This is usually a commit, but tags can point to any of Git's four internal object types. They need to be able to point to annotated tag objects, of course, so that you can have annotated tags. Git just takes away all restrictions here though, which lets them point to tree or blob objects too.

3Note that the introduction of symbolic references, which are resolved at reference-look-up-time, also introduces the possibility of loops. The name A might refer to the name B, with the name B referring to C, and then C could refer back to A. How will you resolve this?

Exercise: how does git resolve this? It's relatively easy to detect a self-loop: if the name tumbolia says my answer is obtained by looking up the name "tumbolia", you can immediately see, while looking up "tumbolia", that you'll loop or recurse forever looking yourself up over and over. But what about long chains of names?

Exercise: Your operating system may provide symbolic links or symlinks, which are files whose contents are other files' names. If so, how does your OS prevent or detect symbolic link loops? (Git used to implement symbolic refs via symlinks. This changed when Git added Windows support, since many Windows systems don't offer symlinks.)


Why this isn't useful, unless you think it is

Let's go back to the six-step process Git uses for resolving a name that you type in. For instance, suppose you run:

git rev-parse master

Try this out. Try running git rev-parse with various names, and also with ranges like master..develop, and with names with operators added, like master~2. Then go look at the gitrevisions documentation again, and find this:

  1. If $GIT_DIR/<refname> exists, that is what you mean (this is usually useful only for HEAD, FETCH_HEAD, ORIG_HEAD, MERGE_HEAD and CHERRY_PICK_HEAD);
  2. otherwise, refs/<refname> if it exists;
  3. otherwise, refs/tags/<refname> if it exists;
  4. otherwise, refs/heads/<refname> if it exists;
  5. otherwise, refs/remotes/<refname> if it exists;

Three of these five rules make it possible to just write master or v1.2 or origin/master. Rule #5 takes care of origin/master: since refs/heads/origin/master exists, if you run git rev-parse origin/master and rules 1 through 4 don't work, rule 5 works and that's where git rev-parse stops with a valid commit hash ID. So that's what git rev-parse prints.

Rule #4 handles branch names. Rule #3 handles tag names. Note that rule #3 means that if you create a tag named master, the tag overrides the branch, in most cases.4 If you're in this situation, you can make use of rule #2, by writing heads/master or tags/master.

But that leaves out one last rule. Here it is:

  1. otherwise, refs/remotes/<refname>/HEAD if it exists.

This particular rule means that you can run:

git rev-parse origin

Git will try the other five steps, all of which will fail, and then will try parsing origin as if you'd written refs/remotes/origin/HEAD. If this name exists and can be resolved to a hash ID, that's the hash ID you will get.

What all this boils down to in the end is that you can use the name of a remote as shorthand for the value of the remote-tracking name that my Git has in its symbolic ref under that remote's HEAD. That is, if you have remotes named r1, r2, and r3, you can use the name r2 instead of writing out refs/remotes/r2/HEAD (which works without needing the six-step process) or r2/HEAD (which works by using step #5).

How often do you, or anyone, actually use this? That's how useful these symbolic refs are. No one I know uses it, ever, so as far as I can tell, it's not useful.


4Commands that assume, or first try, a name as a branch name will pick the branch meaning instead. For instance, git checkout master will check out the branch. But git rev-parse master doesn't assume a branch name, so it parses the tag, finding it in step 3 and never moving to step 4.

It's therefore unwise to use the same name as both a branch and tag name. Git has rules that will pick one or the other, but they may not match your personal expectations!

like image 152
torek Avatar answered Nov 15 '22 07:11

torek