Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git track a remote branch but push to a different branch?

Tags:

git

bitbucket

Lets assume I have a branch called 'my-local-changes', which is a local branch, that was created from a branch called 'some-remote-branch', which is a remote branch.

Let's also assume there is a third branch called 'develop', which is the remote branch where code is pulled into from multiple branches ('some-remote-branch' is one of them, I also have a local branch called 'develop' that tracks the remote develop branch)

My question is how can i set up 'my-local-changes' to track the 'develop' branch, but push to the branch 'some-remote-branch'?

For those curious, as to why I want to do this, I would like to be able to run git status and see if I am behind 'develop' while not having to switch to that branch, and easily still be able to push to 'some-remote-branch'

My current process is as follows (I'd love any suggestions for improving this as well)

git checkout -b my-local-branch some-remote-branch

(make some changes and add them)

git fetch origin 
git checkout develop
git status

(do this to see if any changes on develop that I need to merge, if not run)

git push origin my-local-branch some-remote-branch
like image 539
Tony Scialo Avatar asked Jun 29 '16 22:06

Tony Scialo


1 Answers

(Not sure why the question is downvoted,1 and you have almost answered it yourself anyway...)

You are nearly there: simply configure push.default to simple or nothing, so that you must specify the push target for this case, and then for your final command, use:

git push origin my-local-branch:some-remote-branch

The way this works is that git push takes, after origin (the name of the remote), a series of refspecs. A refspec is not very complicated: it's just a pair of names, like master:master, or develop:develop, or develop:foo, optionally with a leading plus sign +, and optionally omitting either the first or second name: :foo or develop.

Refspecs get slightly complicated in that they work differently in git fetch and git push, when you leave out one of the two names. If you use both names each time, they stay simple: the name on the left is the name on the source repository, and the name on the right is the name on the destination. For fetch, the source is the remote and the destination is your own repository. For push, the source is your repository and the destination is the remote.

Leaving out the source name only works with push, and in this case, it means delete. Hence git push origin :foo means delete foo on remote origin. (That's not what you want, so avoid that.)

Leaving out the destination name works with both fetch and push but means different things to them. Let's just ignore fetch here. With push, it means use the same name on both local and remote. Since you don't want that here, don't use it here. (Or, for those cases where you do want it, go ahead and use it.)

The leading + sign, if present, means the same as --force. (In fact --force just adds the + on everything.)

If you run git push origin, or git push (without even a remote argument), Git looks up push.default to see what to push. Setting it to nothing means just fail / error-out, so that I have to type in a refspec. Setting it to simple means push just one branch, the current branch, to the current branch's upstream, but also require that the upstream name match. Since my-local-branch and some-remote-branch don't match, this will fail for your case of local branch foo is tracking remote-tracking branch origin/develop: the names foo and develop do not match.

Either way, Git will force you to enter a refspec for this push.

With the nothing configuration, Git will force you to enter a refspec for every push. (I have used this and it works, but it's not terribly convenient.) With the simple configuration, Git will allow you to push your develop to the upstream develop easily, and will allow you to push your foo to an upstream develop (or an upstream jazzy, or any other name but foo) explicitly, but won't push foo to foo since that's not its defined upstream. (I have used this and it works better.)

As of Git version 2.0, simple is the default configuration. So if you have Git version 2.0 or later, you are already good to go. If not, see if you can upgrade your Git version, but push.default is configurable as far back as Git version 1.6. (I'm not sure what values it could take on, then. The current set-of-5 values goes back to 1.7.11 if not earlier, I think.)

For completeness, the other three possible values are: current, upstream, and matching. The current value means use the current branch's name: git push origin $branch:$branch where $branch is the current branch. The upstream value means use the current branch's upstream name: git push origin $branch:$merge, where $merge is from git config --get branch.$branch.merge.

The matching value is the hardest to describe, and was the default before Git version 2.0: it means get a list of every branch on the remote, and match up their names and our local branch names, and then do an en-masse push of all of our local branches to the branch of the same name on the remote, wherever the two names match up. This is not a very safe setting, although as long as you do not use --force, it actually works pretty well in practice, which is why it was usable for so many years.

Side notes: terminology

Git's terminology is kind of a mess.

  • A local branch (or just "a branch" or "a branch name"), like master, is a name in the refs/heads/ name-space. It points to a commit ID. When you make a new commit while on that branch, Git reads the commit ID, makes the new commit with that ID as the new commit's parent, and then writes the new commit's ID into the branch-name, so that the branch now points to the new commit (which in turns points to the previous branch-tip, and so on).

    You can git checkout a local branch, which puts you on "on the branch", so that git status says On branch master for instance. This sets things up so that new commits advance the branch, as I just noted above.

  • A remote-tracking branch like origin/master is a name in the refs/remotes/ name-space. After refs/remotes/ we find the name of the remote itself, origin, then another slash, and finally the name of the branch as seen on that remote (sans refs/heads/ of course). These names are stored locally, in your own repository: they're not actually remote at all. They are just automatically updated when your Git contacts the remote, through git fetch and (to a more limited extent) through git push.

    You can git checkout a remote-tracking branch, but if you do, git checkout puts you in "detached HEAD" mode, so that git status and git branch claim you're not on any branch (or are on "no branch" or similar, sometimes using the "detached HEAD" phrasing). When this happens you are actually on the (single, special) anonymous branch. When you get back on a normal branch, any work you did on the anonymous branch will eventually fade away. (So if you want to keep the work, set up a name, so that it's not anonymous anymore—or use git checkout to get back on a regular branch, before you do that work.)

  • A local branch can track another branch (local or remote). When local branch B is tracking another branch U, Git calls this the "upstream" of the local branch. This upstream is composed of two parts: the name of the remote, like origin, and the name of the branch as seen on the remote, like master. To track a local branch as your upstream for branch B, Git just sets the remote to . instead.

    Hence local branch B is tracking upstream U if it has these two items configured. U is itself normally a remote-tracking branch, which is a local entity (one of those refs/remotes/ name-space names). You must git fetch or git push to get the remote-tracking branch(es) updated, after which git status and git branch -vv will report local branches that are tracking the remote-tracking branches, as being ahead and/or behind their counterparts.

    Local branch B can instead track another local branch as its upstream. In this case, since everything is locally-updated, git status and git branch -vv will be up-to-date without any git fetch needed.

    A local branch B need not track anything, of course. The commands git branch --set-upstream-to upstream and git branch --unset-upstream will set or unset the upstream of the current branch. You can also set, change, and examine the two individual parts of the upstream (the branch.$branch.remote and the branch.$branch.merge parts) using git config, although using git branch is usually nicer and more convenient.


1Perhaps the downvote is because of the Git 2.0 default push.default.

like image 129
torek Avatar answered Sep 19 '22 00:09

torek