I've got two repositories binded to my working directory.
git remote show origin
* remote origin
Fetch URL: ssh://project.git/
Push URL: ssh://project.git/
HEAD branch: master
Remote branch:
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)
git remote show develop
* remote develop
Fetch URL: ssh://projecttest.git/
Push URL: ssh://projecttest.git/
HEAD branch: master
Remote branch:
master new (next fetch will store in remotes/develop)
Local ref configured for 'git push':
master pushes to master (local out of date)
As far as I know local branches are referenced to remote branches in that way:
origin/master
points to local branch master
develop/master
points to local branch master
What I want to achieve is to change this reference for develop/master
remote branch. I want it to point to trunk
local branch
develop/master
should point to local branch trunk
How can I do it?
Before I answer, I want to change the terms here, as the way you are using some words do not match the words and ways used in most git documentation.
First, let's talk about the graph generated by commits in a git repository.
Any time you create a commit in a repository, you must specify the following items. Many of them are implicit; in fact, git commit
and git merge
(which are the two principle commit-creators) will look all of them up for you:
-m
argument, or git commit
fires up your editor, etc)Git combines all of these and makes a new unique SHA-1 value—those unwieldy strings of 40 characters like 9c4ea50db79d3ce6fe3abccf20f1af27abae45b2
—that is the commit (it stores this commit-object in the repo, by its SHA-1 ID). If the commit is an ordinary commit, it has one parent, which git commit
finds via HEAD
(which git stores in the .git
directory) shortly before git commit
goes to create the new commit. If it is a "root commit" like the first commit in a new repo, it has no parents. If it is a "merge commit" (created by git merge
, for instance), it has at least two parent IDs (these come from files git stores in the .git
directory as well). The parents stored in any given commit are the raw SHA-1 IDs.
Thus, given any one commit SHA-1 ID, you can read the commit out of the repository and get its parent commits. Reading those gets you their parents. Continue until you reach a root commit and you have something you can draw, with possible branch-points and merge-points in it:
o-o-o-o-o-o-o
\ /
o-o-o
This is a commit graph with ten commit "nodes" (the o
s) that has some of them branching off and then merging back in. I've drawn lines between the nodes with -
\
and /
characters.
When we use git we often call these lines "branches", and that's a reasonable name, but there's another meaning for "branch".
Next, let's define a "branch" (or a "local branch") this way, which is how git really makes it work: A (local) branch is a name, like master
or develop
, that stores an SHA-1 ID and that has one other special property. The special property is that the stored ID changes when we make a new commit, so that the branch-name always identifies the "tip" of the branch.
Remember that each commit has its parent IDs, but not any child IDs. When you make a new commit, the new commit "points back" (via these stored IDs) to the old one(s), but the old ones are never changed. It's difficult to find child IDs given only parent IDs, but it's easy to find parent IDs given only child IDs. So as long as the branch-name "points to" the tip of the branch, we can find the "rest of the branch" easily. So git simply guarantees that the local branch name always identifies the tip of the branch.
(It's a bit unfortunate that "the branch" means both "the local name holding the ID of the tip of the branch" and "the chain of commits formed by starting at the tip and working backwards". It's almost always obvious which one is to be used when, but only "almost".)
Now let's define "remote": A remote is a name you make up and put in your local git repository that will be used to remember things about some other git repository, often on another machine and belonging to someone else. A remote has a url
(or sometimes several: remote.origin.url
and optionally remote.origin.pushurl
for instance), which is how git will fetch and push from and to the other repo.
Of course, that other repo has (local) branches—branch names pointing to tip commits—and it turns out that we want, very often, to know what the other repo's local branches—names and tip commits—are—or more precisely, what they were the last time we checked. (They get out of date, so we refresh them from time to time.) These are what become "remote branches". So now we can define "remote branch" a bit loosely by example: The remote branch remotes/origin/master
is a copy of what the local branch master
was, on remote origin
, the last time we got a chance to connect to origin
and ask it "what's in your master
?"
More precisely: Each remote branch remotes/R/B
is a copy of the local branch B
on remote R
the last time we checked and saved it.
Now we just need one more definition, for a "tracking branch". A tracking branch is a local branch where we have told git that there is a corresponding remote, and on that remote, a corresponding branch name. The low-level way we tell git these two things is to configure two strings with git config
. For instance, let's say we want the local branch greyhound
to track the branch on remote racetrack
that is named hare
. To do this at the lowest level, we do this:
git config branch.greyhound.remote racetrack
git config branch.greyhound.merge hare
(To complicate things—this seems to be a historical accident—the actual tracking is mapped through the fetch
configuration for the remote. But remote.racetrack.fetch
is almost always set up to read +refs/heads/*:refs/remotes/racetrack/*
. For the most part we can just assume the refs/remotes/
part is constant.) So, again, at the bottom level, when git wants to see if greyhound
has caught up with hare
, what it really does is look at remotes/racetrack/hare
. The hare
part comes from the branch.greyhound.merge
line, and the racetrack
part comes from branch.greyhound.remote
.
Git will supply the remotes/
part if you leave it out. Thus, instead of remotes/origin/master
you can normally just write origin/master
. (There are some exceptions if you create ambiguities, e.g., if you create a local branch named origin
it gets tricky. But as long as you don't do that, you can leave out the remotes/
, and people—and git—often do leave it out.)
One last side note here, since I have all the other definitions above. The special reference HEAD
is most often a "symbolic reference". A symbolic ref contains the name of another ref, usually a local branch name. This is how git knows to adjust the local branch to the new branch-tip: If you're "on branch master", HEAD
contains ref: refs/heads/master
, so git commit
adds a new commit and also updates master
. If HEAD
contains instead a raw SHA-1 value, git commit
adds the new commit as usual, then changes HEAD
to have the new SHA-1, but no local branch name points to the new commit. This is the "detached HEAD" condition.
Whew! OK, now back to the question at hand. You're about to stumble over an annoyance that is currently being fixed in git.
You want or need:
origin
, there must be a git repo with a master
branchdevelop
, there must be a git repo with a master
branchmaster
to be a tracking branch for origin/master
trunk
to be a tracking branch for develop/master
git push origin
you'd like to have your master
get pushed to origin's master
, and probably not have trunk
pushed at allgit push develop
you'd like to have your trunk
get pushed to develop's master
, and probably not have master
pushed at allgit push
(with no additional arguments), you want ... well, I'm not really sure what you want, but I'll take a guess below.Steps 1 through 4 need only be done once. If branch master
is not yet created on one or both, you can do:
git push -u origin master:master
git push -u develop trunk:master
(if your git push is new enough to have -u
) to do everything all at once. Or, you can use --set-upstream
if you have that, or do the tracking completely manually with git config
if you like. But one way or another, create the branches on the remote repos if needed, then pick them up (as "remote branches" in your local repo) and set up your local branches to "track" the remote branches.
That leaves the remaining steps. These get a bit confusing.
In recent versions of git (at least 1.7 onward), there is a configuration setting, push.default
, that affects how git push
works when you omit a refspec, i.e., the push
commands in items 5 through 7. You need this to be set to upstream
. This is never the default setting, so you must run:
git config push.default upstream
to set it.
If you run a bare git push
(as in #7), git uses the same tracking branch info to choose the remote. So here, now that you have push.default
changed, if you're on branch master
and run git push
, you will in effect be running git push origin master:master
, and if you're on branch trunk
and run git push
, you will in effect be running git push develop trunk:master
.
If you just let the push.default upstream
setting do its thing, though, and run git push develop
while you are on master
, well, you didn't tell it to push your local branch named trunk
. So, to make 5 and 6 work, you need two different git configuration items.
If you run git push remote
(with no refspec argument), git push
looks up remote.remote.push
, and uses that if it's set. Thus, you can set remote.origin.push master:master
and remote.develop.push trunk:master
. Now git push develop
has a setting and does not need to use push.default
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With