Resetting Your Github Fork

Let’s say I want to contribute to a project on github. The project repository is at wp-cli/wp-cli. First I fork it, and then clone the resulting repository, scribu/wp-cli:

git clone --recursive git@github.com:scribu/wp-cli.git
cd wp-cli

Now, I make some commits to master, push them to my fork and open a pull request. Piece of cake:

git commit -m "awesome new feature"
git push

But, what happens if my pull request is rejected or only certain commits are accepted? I’m left with a dirty master branch. Oh noes!

There are two ways of solving this:

A. Delete my fork and create it again via the github interface. Can’t get any easier than that.

B. Use git reset:

git remote add upstream git://github.com/wp-cli/wp-cli.git
git fetch upstream
git branch backup
git reset --hard upstream/master
git push --force

If I made a mistake, I can rescue my commits by calling git checkout backup.

Easier way to update submodules in git

I love using git submodules to avoid duplicating common code. Unfortunately, updating a submodule reference to the lastest revision is a chore:

cd framework
git checkout master
git pull
git submodule update --init --recursive
cd ..

However, we can store all of those commands using a git alias:

git config --global alias.up-sub '!f() { cd $1 && git checkout master && git pull && git submodule update --init --recursive; }; f'

Now, to update the ‘framework’ submodule, we can just do:

git up-sub framework

I picked up the trick for running multiple commands in an alias from this article on the Mozilla blog.

Update Use && instead of ; to prevent losing work when you mistype the submodule name.

svn patches from git

A while ago I started using Mark Jaquith’s gitified WordPress for contributing to Core.

The trouble is that the patches generated by git diff aren’t exactly the same as the ones generated by svn. I’ve tried several cooky solutions until I found this one.

So here’s the easies way to create a patch from a git repository, to be aplied to an svn repository:

git diff --no-prefix > ~/some-feature.diff

To avoid typing --no-prefix each time, you can enable it by default:

git config --global diff.noprefix true

And then, to apply it:

patch -p0 < ~/some-feature.diff

The important thing is that you use the exact same command you would use to apply a patch created from svn.

svn tagging is a joke

I keep reading the phrase “Git isn’t better than Subversion, it’s just different”.

I’m sure a few years ago people said “SVN isn’t better than CVS, it’s just different”.

Anyway, here’s one key aspect that shows how primitive svn is:

Subversion doesn’t actually have tags or branches. It only has folders.

The usual branches and tags directories you see in most svn repositories are not special. They’re just a naming convention.

After you “tag” a release (basically copy the trunk folder into a new folder in tags), you can modify it at will and Subversion won’t complain.

Compare with Git or Mercurial, where a tag is a read-only pointer to a specific changeset.

The same with branches: in svn you create an entire copy of the trunk folder, while in git you just create a pointer which advances as you make revisions.

If you’re interested in learning more about git, I recommend reading the Pro Git book. Also, Hg Init is an awesome introduction to Mercurial and to distributed version control systems in general.

Rebasing with svn

So I’ve been accepted as a GSoC student for WordPress. My project involves working on the core WordPress code, but on a separate svn repository. I needed an easy way to keep my fork synchronized with trunk.

Unfortunately, svn doesn’t have the concept of rebasing, so I wrote a little bash script to simulate it:

#!/bin/bash
 
# usage:
# svnrebase [WORKING_COPY_PATH]
 
# set working copy path
WCPATH=${1-'.'}
 
cd $WCPATH
 
# read base URL
BASE_URL=`cat BASE_URL`
if [ "" == "$BASE_URL" ]; then
	echo "can't find base URL"
	exit
fi
 
# read current base revision
OLD_BASE_REV=`cat BASE_REV`
if [ "" == "$OLD_BASE_REV" ]; then
	echo "can't find current base revision"
	exit
fi
 
# fetch latest base revision
NEW_BASE_REV=`svn info $BASE_URL | grep 'Revision:' | cut -d ' ' -f 2`
if [ "" == "$NEW_BASE_REV" ]; then
	echo "can't find base"
	exit
fi
 
if [ "$OLD_BASE_REV" == "$NEW_BASE_REV" ]; then
	echo 'base already at latest revision'
	exit
fi
 
# apply changes to fork
svn merge $BASE_URL@$OLD_BASE_REV $BASE_URL@$NEW_BASE_REV
 
# update current base revision
echo $NEW_BASE_REV > BASE_REV
 
echo "updated base to revision $NEW_BASE_REV"

The above script requires two text files placed in your working copy directory:

BASE_URL – the url of the base repository. In my case, it contains:

http://core.svn.wordpress.org/trunk

BASE_REV – the currently used revision number in your working copy. For example:

14995

The BASE_REV file will be updated automatically when you run the script, but you need to create it manually the first time.