Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change local reference for remote branch

Tags:

git

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:

  • remote origin/master points to local branch master
  • remote 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

  • remote develop/master should point to local branch trunk

How can I do it?

like image 745
pepuch Avatar asked Dec 01 '13 09:12

pepuch


1 Answers

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:

  • a parent commit, or a set of parent commits
  • an author and the "author date" (usually same as committer)
  • a committer and the "commit date" (usually same as author, above)
  • a "tree" (normally taken from the index/staging area), and
  • a commit message (via -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 os) 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:

  1. on the remote named origin, there must be a git repo with a master branch
  2. on the remote named develop, there must be a git repo with a master branch
  3. on your local repo, you want master to be a tracking branch for origin/master
  4. on your local repo, you want trunk to be a tracking branch for develop/master
  5. when you git push origin you'd like to have your master get pushed to origin's master, and probably not have trunk pushed at all
  6. when you git push develop you'd like to have your trunk get pushed to develop's master, and probably not have master pushed at all
  7. when you git 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.

like image 152
torek Avatar answered Oct 15 '22 03:10

torek