One small annoyance is that "git pull" doesn't leave my clone in the state I want. Say I have the master branch checked out. "git pull" will update all of my remote tracking branches, but it will only update the local branch that I currently have checked out. This is annoying, first of all because if I later type "git log REL9_0_STABLE" I'll only get the commits since the last time I checked out and pulled that branch, rather than as I intended the latest state of the upstream, and secondly because it leads to spurious griping when I later do "git push": it complains that the old branches can't be pushed because it wouldn't be a fast-forward merge. This is of course a little silly: since my branch tip is an ancestor of the tracking branch, it would be more reasonable to conclude that I haven't updated it than to imagine I meant to clobber the origin.
My first response to this problem was to write a little script that checked out each back-branch in turn and did git pull in it. This is kind of annoying, though. The older branches aren't updated all that often, and checking out a new branch takes several seconds, and we've got five of them (we used to have eight, but we're down to five). So after playing around with it some more, I came up with this:
#!/bin/sh
git fetch || exit 1
for b in REL8_{2,3,4}_STABLE REL9_0_STABLE master; do
revs=`git rev-list $b...origin/$b | wc -l`
echo branch $b has $revs revisions different from origin
if [ $revs -gt 0 ]; then
git checkout $b || exit 1
git rebase origin/$b || exit 1
fi
done
git checkout master || exit 1
git fetch || exit 1
for b in REL8_{2,3,4}_STABLE REL9_0_STABLE master; do
revs=`git rev-list $b...origin/$b | wc -l`
echo branch $b has $revs revisions different from origin
if [ $revs -gt 0 ]; then
git checkout $b || exit 1
git rebase origin/$b || exit 1
fi
done
git checkout master || exit 1
This is a lot faster because (1) it only runs "git fetch" once, whereas repeatedly running "git pull" rechecks the origin server every time and (2) it only checks out and rebases the branches where there's some difference between the local tracking branch and the origin.
I think it would be nicer still if there were a way to do this without actually checking out the back-branches that need to be updated. If the given local branch tip is an ancestor of the remote tracking branch, I'd like "git pull" to move the local branch head up to the remote branch head automatically, without requiring a checkout. I suppose I could write a script to manually update the files in .git/refs/heads, but a less magical approach would be nicer.
I use a script with an outcome quite similar to yours, although more flexible (it does not have to list the branches you are following). It has, of course, its subtleties (i.e. it would break horribly if remote and local branches are named differently, or if you use something other than origin/), but it does the work fine – I do use merge. So, hoping the formatting will not be mangled beyond recognition:
ReplyDelete#!/usr/bin/ruby
def current_branch
`git branch` =~ /\* (.+)$/
current = $1
if current == '(no branch)'
raise RuntimeError, 'Not currently following a branch - You\'d lose your location!'
end
end
def origin_branches
`git show-branch -a --topics`.map { |br|
br =~ /\[([^\[]+)\]/ && $1
}.map { |br|
br =~ /^origin\/(.+)/ ? $1 : nil
}.select {|br| br}
end
def merge_branch(branch)
system('git checkout %s' % branch)
system('git merge origin/%s' % branch)
end
system('git fetch')
current = current_branch
branches = origin_branches
branches.each { |br| merge_branch(br) }
system('git checkout %s' % current)
PS: For the next three days, you can get it with the proper format at:
http://paste.debian.net/108082/