Can someone tell me what's wrong with my action? I'm trying to create a new branch off a remote branch, and push the new branch to my repository.
Description:
-Currently on branch "test"
-git pull
-git checkout -b <new_branch_name> origin/test
I confirmed my branch was switched to the new branch. However, when I commit/pushed my changes through visual studio the new branch was NOT created and the changes were pushed to the remote "origin/test".
On the contrary, if I run the checkout command without the second parameter "origin/test" (while still currently on the origin/test branch) and then I execute git push, my new branch is created
Why is this occurring?
It's because the upstream of your new branch is set to origin/test
. Visual Studio apparently (mis)uses this to decide to ask the remote to update its test
branch.
Branches are not created from branches. Branches are created from commits. It's important to know and understand this; if you think that branches mean something in Git, you will eventually hurt yourself. It's like assuming that a 1980s-era automobile has self-driving capability.
That said, each branch, in Git, can have one upstream set. A branch either has an upstream set, or does not have an upstream set. To set a particular upstream, use git branch --set-upstream-to
:
git branch --set-upstream-to=origin/somebranch
for instance. The current branch now has origin/somebranch
set as its upstream (unless the command failed, in which case nothing has changed). If it previously had origin/otherbranch
set as its upstream, that upstream is now removed, because only one upstream can be set: origin/somebranch
is now the upstream.
To unset the upstream, use git branch --unset-upstream
:
git branch --unset-upstream
The current branch now has no upstream. If it had no upstream before, this operation does nothing; otherwise, it removes the upstream setting.
The upstream of a branch is:
origin/somebranch
.In command line Git, the behavior git push
, with no additional arguments, is controlled by the push.default
setting. If this is set to simple
—as it normally is today—this kind of git push
will only push to the upstream of the current branch, and only if the upstream is set to a remote-tracking name that—once the remote part is removed—matches the current branch.
For example, suppose the current branch is named br1
, and both origin/br1
and origin/br2
exist. If we run:
git branch --set-upstream-to=origin/br2
git push
we will get an error from git push
because the name br2
in origin/br2
does not match the name br1
. However, if we then run:
git branch --set-upstream-to=origin/br1
git push
the git push
will this time call up origin
and attempt to have the Git repository at origin
update that other Git repository's branch br1
(the branch on the other Git that we remember, locally, as origin/br1
).
When we choose to create a new branch—with git branch
, git checkout -b
, or git switch -c
—we must give Git two things:
The default hash ID, if we do not give Git one, is the hash ID of the current commit (as found by the current branch name in most cases). That is, if we have run git checkout test
so that the current branch name is test
, and we run:
git rev-parse test
and
git rev-parse HEAD
we will get the same hash ID from both operations. That's because the special name HEAD
is attached to the branch name test
, and the branch name test
points to the commit whose hash ID both git rev-parse
commands print.
We can draw this setup like this:
... <-F <-G <-H <-- test (HEAD)
That is, the name test
points to some commit with some hash ID, drawn here as H
, which stands for "hash". Every commit in Git has both a snapshot and metadata, and the metadata in a commit contain a list of previous commit hash IDs, usually just one entry long. In this case commit H
contains the hash ID of—i.e., "points to"—some earlier commit, whose hash ID we're just calling G
. (The real hash ID, in each case, is some big ugly random-looking string of letters and digits.) Commit G
has a snapshot and metadata, and thereby points to still-earlier commit F
, which has a snapshot and metadata, and so on.
When we ask Git to create a new branch name, with git checkout -b newbr
in this case, Git will:
H
—and thenHEAD
to the new name.We can draw the result like this:
... <-F <-G <-H <-- newbr (HEAD), test
That is, both branch names point to the same commit (whose hash ID is H
).
When we use this form of branch creation, the new branch has no upstream set. So newbr
has no upstream. A command-line git push
will, with the defaults in modern Git, give us an error; we will have to run git push -u origin newbr
or git push -u origin HEAD
to create a name newbr
over on origin
so that our Git will create origin/newbr
locally, so that we have an origin/newbr
to have as an upstream. The -u
option to git push
will then immediately set that as the upstream.
Visual Studio apparently behaves differently. If no upstream is set, VS apparently just runs git push origin newbr:newbr
, rather than giving us an error.
You can, however, tell Git, at the time you create a branch, to set its upstream right then. To do so, you must use the form in which you give Git a specific commit by name rather than by raw hash ID:
git checkout -b newbr origin/test
for instance. This is what you are doing. When you use this form of git checkout -b
or git switch -c
or git branch
, Git will, by default:
origin/test
to find a commit hash ID;newbr
pointing to this commit; andnewbr
to origin/test
.Note this extra step 3, that does not occur when using git checkout -b newbr
without that additional argument.
In command-line Git, this choice—to create a new branch with an upstream set—is actually controllable via multiple options:
-t
or --track
option says do do this.--no-track
options says do not do this.branch.autoSetupMerge
says whether to do this, in which cases, when -t
or --no-track
is not specified.The description I've given above covers the normal behavior when branch.autoSetupMerge
is not set, or is set to true
. You can set it to false
, in which case branch creation options always act as if --no-track
were specified, and you can set it to always
, which makes Git set a local branch as the upstream of a new branch when you provide a local branch name as the starting point.
You probably should not set this option, and simply avoid giving git branch
a starting name, or use the --no-track
option:
git checkout -b newbr $(git rev-parse origin/test)
or:
git checkout -b newbr --no-track origin/test
Both methods will avoid setting any upstream for the new branch.