git branch -a
shows me
* master
remotes/origin/HEAD -> origin/master
remotes/origin/feature
remotes/origin/master
remotes/origin/origin/feature
I want to checkout the last branch remotes/origin/origin/feature
.
I don't really know why there is remotes/origin/feature
and remotes/origin/origin/feature
they were created by someone else.
The structure looks like this
--> F --> G origin/feature
/
A --> B --> C --> D --> E master
Now I want to merge remotes/origin/origin/feature
into master.
Strangley, If I want to checkout the feature branch using
git checkout 'origin/feature'
all files are at the state from point B
and not at point G
. I triggered the merge via the gitlab GUI and it told me I have to use
git checkout -b 'origin/feature' 'origin/feature'
This worked, now all files are at the stage of G
.
Why is this so? Why do I have to use the -b
option even if the branch already exists and why do I have to enter it twice?
CodePudding user response:
First, do not use git checkout
, which is obsolete and replaced with git switch
.
Or at least use git checkout origin/feature --
to force git checkout to consider origin/feature
as a branch name, not a file, as I mentioned here.
(Hence git switch
, which only deals with branches, not branches or files, like git checkout
does)
Second:
Why do I have to use the
-b
option even if the branch already exists and why do I have to enter it twice
Because the branch exists only as a remote tracking branch (a memento of what was last fetched from the remote named origin
)
It is best to not include origin/xxx
in the name of the branch, because it is confusing since the default remote repository is itself named origin
.
A simple git switch origin/feature
is enough, because it has a guess mode:
If
<branch>
is not found but there does exist a tracking branch in exactly one remote (call it<remote>
) with a matching name, treat as equivalent to$ git switch -c <branch> --track <remote>/<branch>
CodePudding user response:
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 likemain
ornext
orv2.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 tagxyzzy
. 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.