Why has Git allowed me to create two branches with the same name?
I'm still relatively new to Git and I have made a bit of a mess of my repository. I'm hoping there is a way to fix it without re-cloning.
I have a repository which I have cloned from Github. The repository has several branches. I worked on the master branch for a while but then needed to switch to one of the other branches.
So, I had:
$ git branch --all
* master
remotes/origin/abc
remotes/origin/def
remotes/origin/HEAD -> origin/master
remotes/origin/ghi
Problem: I wanted to switch to the 'abc' branch but instead of doing git checkout remotes/origin/abc
I accidentally did git branch remotes/origin/abc
which leaves me with the following:
$ git branch --all
* master
remotes/origin/abc
remotes/origin/abc
remotes/origin/def
remotes/origin/HEAD -> origin/master
remotes/origin/ghi
My questions are:
- Why on Earth does Git allow you to create two branches with the same name?
- How do I identify which is the real remotes/origin/abc branch?
- How do I remove the unwanted remotes/origin/abc that I created by accident?
Any help much appreciated.
Solution 1:
You can't create two local branches or two distant branches with the same name.
Here you have a local branch named
remotes/origin/abc
and a distant branch namedabc
on the remoteorigin
. They have not the same name, but it seems to when you use thegit branch --all
command.To identify which branch is which, you can show local branches with
git branch
, or show remote branches withgit branch --remote
. You could also easily differentiate them even while usinggit branch --all
with the branch syntax coloration (git config --global color.branch auto
).To remove the accidentally created local branch
abc
, you have to dogit branch -d abc
(orgit branch -D abc
to force deletion, seeman git-branch
).
Solution 2:
The true story is that Git has a simplification scheme for its "refs" (a Git lingo for "references", which is the term used to refer to branches, tags etc). In fact, references live in their namespaces, which, with the reference Git implementation, are just directories under .git
. For instance, your local branch "master" is really "refs/heads/master" — a file named "master" located in the .git/refs/heads
directory. There are also "refs/tags" namespace and "refs/remotes" namespace — for tags and remote branches (those created by the git fetch
command).
Now when you tell Git to create a branch remotes/origin/abc
it really creates refs/heads/remotes/origin/abc
which does not clash with refs/remotes/origin/abc
because the rules to deal with that simplification scheme make the former trump the latter. At any time you can use the full form of ref naming to remove any disambiguation.
The gory detals of how Git interprets ref names are described in the section "Specifying Revisions" of the git-rev-parse
manual:
<refname>, e.g. master, heads/master, refs/heads/master
A symbolic ref name. E.g. master typically means the commit object referenced by refs/heads/master. If you happen to have both heads/master and tags/master, you can explicitly say heads/master to tell git which one you mean. When ambiguous, a <refname> is disambiguated by taking the first match in the following rules:
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);
otherwise, refs/<refname> if it exists;
otherwise, refs/tags/<refname> if it exists;
otherwise, refs/heads/<refname> if it exists;
otherwise, refs/remotes/<refname> if it exists;
otherwise, refs/remotes/<refname>/HEAD if it exists.
…
Solution 3:
Git places very little restriction on branch names and e.g. slashes in branch names are perfectly fine. Also deleting a branch on the remote is done with e.g.
$ git push origin :abc
while deleting a local branch is e.g.
$ git branch -d remotes/origin/abc
where there is no ambiguity because these two entities live in different namespaces.
Solution 4:
I made a similar mistake (creating a local branch named origin/...
) a couple of times.
To protect against this sort of mistakes (and assuming you will never want a local branch whose name actually starts with origin/
) you can run the following commands in repo/.git/refs/heads
:
mklink /d remotes nul
mklink /d origin nul
mklink /d upstream nul
They create symlinks to nul
that would prevent creating subdirectories under those names. Now accidental mistakes like git branch origin/feature
will give an error:
unable to create directory for .git/refs/heads/origin/feature