Besides VonC's advice (which is good: use git switch
to avoid some of the confusion), it's worth noting that remotes/origin/feature
and remotes/origin/origin/feature
are not actually branch names. Git's terminology here is awful: it is confusing and not always consistent.
To make sense of this, you need to know several things about Git:
It's actually the commits that matter. A name—whether it is a branch name, or a tag name, or some other kind of name entirely—exists so that you and Git can find the hash ID of the commit. The hash ID, that big ugly string of letters and digits like d420dda0576340909c3faff364cfbd1485f70376
, is the true name of the commit, but humans are bad at hash IDs. We like simple names like main
or next
or v2.37.0
or whatever.
So, Git provides names. These come in a long list of flavors. Branch names are just one of those flavors.
You can have a branch xyzzy
and a tag xyzzy
. You probably shouldn't—it's very confusing when you do this—but you can do it. So Git needs to know how to tell a branch name from a tag name, for instance.
The method Git uses to know this is to give everything a "full name" (like a person's full name: "J Robert Oppenheimer"), and to use a shortened name for more familiar usage (e.g., "Bob").
To categorize the names, Git sticks them in a namespace: all branch names start with refs/heads/
, and then the rest of the string is the branch name, so master
is actually short for refs/heads/master
. All tag names start with refs/tags/
, so if you were to create a tag named master
, it would be refs/tags/master
. Don't do this: Git can keep them straight internally by sticking the prefixes back on, but you probably won't.
Now, one particular, and slightly weird, name-flavor is the remote-tracking name.1 A remote-tracking name is a name (of course) in your own Git repository, and it actually starts with refs/remotes/
. Git leaves out the refs/
, and sometimes also the remotes/
part as well:
$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/feature
remotes/origin/master
remotes/origin/origin/feature
Had you run git branch -r
, Git would not have listed * master
at all, and would have taken remotes/
off the remaining names, leaving:
origin/HEAD -> origin/master
origin/feature
origin/master
origin/origin/feature
Again, these are all remote-tracking names. They really are spelled "refs/remotes/origin/whatever". Git is just shortening them by leaving off "refs/" or "refs/remotes/", and you can do that too.
1Git has, over the years, not been entirely consistent in what Git calls these, but modern Git tries to use the phrase "remote-tracking branch name" pretty consistently now. I don't like the insertion of "branch" here: I think it's actually less confusing to leave it out. So that's what I do, but know that Git sticks it in. Pay particular attention to the "remote-tracking" part, as that's the real clue.
Remote-tracking names and where they come from
Every Git repository is independent of all other Git repositories, even if they're linked and yours is a clone of some other existing repository. So every Git repository has its own branch names. You have your master
, Joe has Joe's master
, Sally has Sally's master
, Ramesh has Ramesh's master
, and so on.
When you cloned the repository you're using, they had a branch named master
too. Your Git refers to that repository as origin
. So your Git took their name master
—technically, refs/heads/master
—and changed it to origin/master
, or technically refs/remotes/origin/master
. So their master
, a branch name, became your origin/master
, a remote-tracking name.
Your Git software did that for every branch name that their Git software said that they have in their repository. So that tells us that, given the list of origin/*
names that git branch -a
showed, they have HEAD
, feature
, master
, and—here's the weird one—origin/feature
. That's a branch name in their Git repository, whose full name is refs/heads/origin/master
. Their Git software and your Git software always use the full name internally so that they don't get confused about this. But your Git software shows you your abbreviated names. Your Git created a refs/remotes/origin/origin/feature
to match their branch named origin/feature
, and your Git now shows that as either origin/origin/feature
(after taking off refs/remotes/
) or remotes/origin/origin/feature
(after taking off only refs/
).
Checkout vs switch
The git checkout
command lets you use both branch names and remote-tracking names. If you put something in that's ambiguous, git checkout
has rules by which it figures out what you mean. Confusingly, both git checkout
and git switch
have modes that will create a new branch name, and this interacts with the "figuring out" rules in weird and wonderful (or terrible) ways. Because git switch
has simpler rules, the interactions are not as wacky. Unfortunately for you, someone else goofed up and set up a situation where you get to experience at least a little bit of wackiness no matter what you do.
The rules for git checkout
go roughly like this (although they're actually considerably more complicated):
- Try something as a branch name: do we have a branch with that name? If so, use that as our existing branch name.
- Try something as, in order, a tag name, a remote-tracking name, or a general-purpose name. Do we have that name as that kind of name? If so, use that as a non-branch name.
- If none of those worked and
--guess
mode is in effect, proceed into the "guess and create new branch if possible" code. (This used to be called "DWIM mode", and still is in older Git documentation.)
The middle rule here, step 2, allows you to check out any specific commit as a detached HEAD. The git switch
command won't do that unless you add --detach
to the arguments.
Because origin/feature
is a remote-tracking name in your repository, your git checkout
takes it up if and when it hits step 2 (but not if it finds origin/feature
as a branch name in your repository in step 1). But origin/origin/feature
is also a remote-tracking name in your repository, so you could git checkout origin/origin/feature
and perhaps get that in step 2.
Once you create a branch named origin/feature
, git checkout
takes that up in step 1.
Because this kind of thing does happen, it's a bad idea for someone to create a branch named origin/whatever
. It's also a bad idea to have a remote-tracking name refs/remotes/origin/origin/feature
, but you don't control this directly: you can only ask or command the Git repository over at origin
to use a different name, such as refs/heads/feature2
.
Fixing the blame and/or the problem
Whoever created the name origin/feature
as a branch name over on origin
is the person at fault. To fix the problem, though, you just need to rename the branch over on origin
. This may be easy—perhaps there's a web interface to do that, for instance—or it may be a bit tricky.
Once the branch on origin
has been renamed (to feature2
or some more meaningful name), run:
git fetch --prune
to have your Git software call up their Git software, see their new list of branch names, and delete your refs/remotes/origin/origin/feature
name while also creating the new remote-tracking name based on the new branch name.