Amelio Vazquez-Reina Amelio Vazquez-Reina - 4 months ago 27
Git Question

Branch keeps tracking master

Starting recently, my branches are now by default tracking and pushing to the remote master, even if I do

git push -u origin branch_name
and even if I use
matching
as my
push.default
option.

# --------
# STEP 1
# --------
$ git checkout -b my_branch
Branch my_branch set up to track local branch master.
Switched to a new branch 'my_branch'

# --------------------------------------------------------------------------------
# STEP 2: (same behavior BTW with current/upstream/simple)
# --------------------------------------------------------------------------------
$ git config --global push.default matching

# --------
# STEP 3:
# --------
$ git push -u origin my_branch
Counting objects: 8, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 1.16 KiB | 0 bytes/s, done.
Total 8 (delta 7), reused 0 (delta 0)
To XX:YYY/my_repo
62d390c..4e4baa1 my_branch -> master
Branch my_branch set up to track remote branch master from origin.





Also, if I try the following for
STEP 2
, I get an error:

$ git branch --set-upstream-to=origin/my_branch
error: the requested upstream branch 'origin/my_branch' does not exist
hint:
hint: If you are planning on basing your work on an upstream
hint: branch that already exists at the remote, you may need to
hint: run "git fetch" to retrieve it.
hint:
hint: If you are planning to push out a new local branch that
hint: will track its remote counterpart, you may want to use
hint: "git push -u" to set the upstream config as you push.


I know I can edit
.git/config
manually, but WHY doesn't the above work?




$ git remote -v
origin XX:YYY/my_repo (fetch)
origin XX:YYY/my_repo (push)

$ git --version
git version 2.9.2

Answer

This bit is suspicious as the middle line should not occur:

$ git checkout -b my_branch
Branch my_branch set up to track local branch master.
Switched to a new branch 'my_branch'

For instance, when I do git checkout -b foo in a repository:

$ git checkout -b foo
Switched to a new branch 'foo'

Note the lack of an upstream setting. My guess is that your gco alias-or-script has --track inside it somewhere. Edit: This turns out to be because you'd configured branch.autoSetupMerge to always, so new branches created from local branches track their local branch.

This is only half of the problem, but if this were not happening, the full problem would not occur anyway. Hence one fix is to delete that setting (the default seems to be what most people mostly want).


Addressing the other half of the problem:

$ git branch --set-upstream-to=origin/my_branch
error: the requested upstream branch 'origin/my_branch' does not exist

The problem here is literally just what Git is saying: that origin/my_branch does not exist. Well, it doesn't exist yet: you need to convince your Git to have an origin/my_branch. Git being Git, there are a lot of ways to do this, but probably one best way, which we'll get to in a moment.

$ git push -u origin my_branch
Counting objects: 8, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 1.16 KiB | 0 bytes/s, done.
Total 8 (delta 7), reused 0 (delta 0)
To XX:YYY/my_repo
   62d390c..4e4baa1  my_branch -> master
Branch my_branch set up to track remote branch master from origin.

This is where things go wrong: since my_branch already has an upstream setting of master (not origin/master, just master), your Git asks the other Git to use the name master, as if you had run:

git push origin my_branch:master

As there are no other problems with this git push, their side does what your side asks—sets their master to match your my_branch—and then your side does what you asked with -u, which is to change the upstream setting from master to origin/master. That's not what you meant to ask of course.

(And: it seems a little bit evil for your Git to substitute in master when that's a local master, i.e., branch.my_branch.remote is just ., not origin. But assuming we can't change Git itself...)

I see three easy ways to fix this:

  1. Use an explicit remote-side name: git push -u origin my_branch:my_branch. This overrides the current upstream setting, so that your Git asks their Git to write to my_branch. If that succeeds—it should—your Git will now have origin/my_branch and will change your upstream setting for my_branch to origin/my_branch.

    The one drawback here is that if the push fails, your Git won't change the current upstream setting. Of course this is true in general for git push -u (including both of the next two methods, but in those two cases there's no trap left behind for the future).

  2. Explicitly delete the current upstream setting before running the git push: git branch --unset-upstream my_branch (or anything equivalent, including editing the .git/config file). Now that there's no existing upstream, your Git won't ask their Git to use the name master.

    As soon as your Git asks their Git to create my_branch (rather than master) on origin, we're back to what happens with the first method.

  3. Avoid setting your local master as the upstream in the first place. The effect is the same as method 2.

One hard way to do this is to fake out your Git: you can create a remote-tracking branch without actually going to the remote. To do this you will have to use the "plumbing" command git update-ref, rather than git branch. If you do, though, you can then use --set-upstream-to before the push that actually creates the branch on the remote. (The other hard way is the trick you used of editing .git/config directly, or doing the equivalent with git config. This allows you to set the upstream to something that doesn't really exist yet, since all Git is really doing here is storing names.)