This warning is shown when I do
git branch
I think at some point I deleted the animate2 branch, but may be it had uncommitted changes or something, I don't remember.
You can just delete the reference, though this might be slightly tricky: see example below. Alternatively, you could clone your clone, or if there's nothing in it of value that is not already saved in some other clone, remove the entire clone.
Given that this particular reference is the branch name animate2
,1 you would potentially lose all commits that are only contained in that branch,2 but if the ref is "broken" it's already not protecting commits.
Without having your specific broken ref to test, I can only create my own:
$ echo bad > .git/refs/heads/foo
$ git branch
warning: ignoring broken ref refs/heads/foo
dev
diff-merge-base
* master
Any normal attempt to delete this fails:
$ git branch -d foo
error: branch 'foo' not found.
$ git branch -D foo
error: branch 'foo' not found.
Even the plumbing command git update-ref
is stymied:
$ git update-ref -d refs/heads/foo
error: cannot lock ref 'refs/heads/foo': unable to resolve reference 'refs/heads/foo': reference broken
so I can only resort to manual deletion:
$ rm .git/refs/heads/foo
This last method is not generally recommended, and probably won't work if the broken ref has been "packed" into .git/packed-refs
; in that case you'd need to edit that file directly (and carefully). But it does work for my own broken reference.
1Branches like master
, tags like v1.2
, remote-tracking names like origin/master
, and many more forms that Git uses are all specific kinds of a general purpose mechanism that Git calls a reference. All branch names have full names that start with refs/heads/
. All tag names have full names that start with refs/tags/
. All remote-tracking names really start with refs/remotes/
, and go on to include the name of the remote, plus another slash. This means that Git only needs one internal mechanism for all of these: it just reads through all refs, and if one of them is refs/heads/dev
, that represents the branch name dev
.
2A branch "contains commits" if those commits are reachable from the commit identified by the branch name. All references just identify one particular commit or other object—branch names are constrained and must represent commits, while tag names can represent any object—but Git can start at any given commit and work backwards through the commit graph. This means that a branch name need only indicate the last commit in the branch. All commits reachable from that last commit are part of the branch.
This in turn implies that many commits are on many branches at the same time. This reachability property is pretty central to Git. See Think Like (a) Git.
I think at some point I deleted the animate2 branch, but may be it had uncommitted changes or something ...
That's definitely not how the "broken ref" thing came about. A "broken ref" is one for which the generic reference machinery in Git has set the REF_BROKEN
flag:
/*
* Reference cannot be resolved to an object name: dangling symbolic
* reference (directly or indirectly), corrupt reference file,
* reference exists but name is bad, or symbolic reference refers to
* ill-formatted reference name.
*/
#define REF_ISBROKEN 0x04
(from refs.h, near line 260).
In fact, though, a branch in Git ... well, the word branch is ambiguous (see What exactly do we mean by "branch"?), but the two things that branch should mean to you, in your head, are either:
A name, such as master
, that internally will be a refs/heads/
name or some other reference that you're going to use in "branch-like ways". Remote-tracking names like origin/master
are somewhat branch-like, and some people and documentation, including Git's, sometimes call them remote branches, or at least remote-tracking branch names. (The remote branches term is one that I think is terribly sloppy and I try to avoid it at all times.)
The subset of the commit graph formed by taking the commit indicated by a branch-like name and working backwards. (See both What exactly do we mean by "branch"? and Think Like (a) Git.)
Neither of these can hold uncommitted changes: one is just a name that holds a commit hash ID—a big ugly string like 7c20df84bd21ec0215358381844274fa10515017
, and the other is a chain of commits. So ... where do uncommitted changes exist? The answer is that they are in the index and/or in your work-tree.
Your work-tree holds the set of files you can see and give to other programs to use. You need a work-tree because the file snapshots that are inside each Git commit are stored in a special, read-only, compressed, Git-only form. In the Git-only form, they can be re-used from one commit to another, but they literally cannot be changed. If you can't change them, you can't get any new work done. So you need a place where they're de-Git-ified, turned back into ordinary read/write files, and that's your work-tree.
The index is a special Git data structure—which is actually mostly stored in an ordinary file, .git/index
—that Git uses, that sits between your current commit—the one you have checked out as HEAD
—and your work-tree. This thing, this index, is very central to the process of making new commits. It holds a copy3 of every file taken out of the commit you are working on now, just like the work-tree holds a copy of every file. The copy in the index is in the special frozen Git format, but—unlike the ones in commits—isn't actually frozen. You can replace it with a new copy any time, by using git add
to take your work-tree version and prepare it for Git and overwrite the index version.
The index is so important (and/or the name "index" is so bad) that it has two more names. It's also called the staging area, to reflect its job of holding your proposed next commit, and (rarely these days) the cache, to reflect its job of sitting between the current commit and the work-tree.
When git status
says that some change is staged for commit, what this really means is that the copy of that file that's in the index is different from the copy of the file that's in the HEAD
commit. When git status
says that some change is not staged for commit, what this really means is that the copy of the file that's in the index is different from the copy of the file that's in the work-tree.
So uncommitted changes are those in the index and/or the work-tree. They're not actually in any branch at all. You can be on some branch—as in, git status
will say on branch xyz
—and have those uncommitted changes in the index and your work-tree, but they're not in xyz
at all. If you make a new commit—which snapshots whatever is in the index, which is why you have to git add
all the time to copy from your work-tree to the index—then the new commit becomes the tip commit of the branch. Now they're "in xyz
", but they're not uncommitted any more.
"Deleting a branch" consists of deleting the branch name. That's all that you have to do: just delete the name, and with it, the stored hash ID of the last commit: the commit that you say is the end of the branch. The commit itself is still there. If it's unprotected—unreachable: see Think Like (a) Git again—Git will eventually throw out the commits too.