I was looking for a convenient way to customize a few community-developed projects and to keep these modifications up to date with the latest stable version of said projects. For example, let say I want to create a custom variation of WordPress version 2.6, (beyond just writing a theme), update it automatically when version 2.6.1 comes out, and be warned if there are conflicts between my customizations and the new code in WordPress 2.6.1. Using two version control systems concurrently (namely git and subversion) eases this situation and prevents losing modifications by accident during the updates.
This customization scenario is probably pretty common among webapps, and some even accommodate it explicitly. Gallery, for example, lets you put your own version of any file in a
local sub-directory and those custom versions are preferred to the stock ones. When a new version of Gallery comes out, unpacking it over the previous one retains the
local directories, hence the modifications, but updates the core. However, the files in
local slowly drift away from the main code as their stock equivalent gets updated. So even this system has its flaws.
The key really is to merge the personal changes into the code of each new version, hence to maintain a set of patches and to apply them on the project every time. For git repositories, projects such as StGIT seems to provide this kind of functionality. However, this is yet another layer to get used to, so I looked for a simpler solution. Plus, most of the projects I am interested in still use subversion for their main repository and I figured that git itself could provide the additional layer atop the svn working copy, through the use of
In subversion and git’s terminology what I set out to do was:
- follow the development of the project in subversion
- only use stable versions, i.e. svn tags, of the project
- keep the history of my modifications using commits in git
- update the base code each time a new version is tagged and have git automatically merge my changes into the new code
Here is how.
First, get the svn history into git, using
git svn clone -s http://svn.automattic.com/wordpress/ wordpress
This is a lengthy process because it fetches the entire history of the project, including the svn tags and branches. But we are interested in tags precisely. As opposed to a git tag (which is just a label assigned to a particular commit), it is possible to commit additional modifications to an svn tag, making it effectively a branch. Therefore,
git svn imports svn tags as branches
cd wordpress git branch -a * master ⋮ tags/1.5 tags/1.5.1 ⋮ trunk
We’ll create our own branch as a customization of a specific tag, let say 2.6, the latest stable version
git checkout -b mine tags/2.6 Switched to a new branch "mine"
Then we can proceed to some personal modifications and commit them locally (i.e. not to the svn repository), using git
vi whatever.php git commit whatever.php -m "Added a custom function"
Now the log looks like
commit 4460d7efc9603ae02d82293ba0dda0d519ae8779 Author: me <email@example.com> Date: Tue Jul 25 04:26:14 2008 +0000 Added a custom function commit 6c4a7cba12b6a131b6e597d025486f5472182955 Author: ryan <ryan@1a063a9b-81f0-0310-95a4-ce76da25c4cd> Date: Tue Jul 15 03:11:16 2008 +0000 Tag 2.6 git svn-id: http://svn.automattic.com/wordpress/tags/2.6@8337 ⋯ commit 3a9762ecd40d989daeb353011f322ff580d4f546 Author: ryan <ryan@1a063a9b-81f0-0310-95a4-ce76da25c4cd> Date: Tue Jul 15 03:05:51 2008 +0000 2.6 branch ⋮
This custom version can be uploaded on the web, leaving out the
.git directory. Now let say version 2.6.1 comes out and is tagged. First we need to get the svn tag in our git repository
git svn fetch Found possible branch point: ⋯ ⋮ git branch -a ⋮ tags/2.6 tags/2.6.1 trunk
What we want at this stage it to move the modifications we added over 2.6 to 2.6.1. We want to base our personal branch on 2.6.1 rather than on 2.6, hence we use the well named
git rebase --onto <newbase> <oldbase> <our branch>
git rebase --onto tags/2.6.1 tags/2.6 mine
Git takes care of creating patches with our modifications relative to 2.6, move the code from 2.6 to 2.6.1, and apply the patches. If there is a conflict, i.e. that something we modified was also modified between 2.6 and 2.6.1,
git rebase will signal it and the rebase operation can be finished after manually resolving the conflict.
The only difficulty here is to remember which tag your personal branch is currently based upon, which can usually be found out in the svn log. I guess, there is a git command to see what the ancestor of your personal branch and would welcome pointers in the comments.
This way, the history of the main code is in svn, accessible in git through
git svn; the history of personal modifications are in git and always appears on top of the svn history in the repository and in the logs. Nevertheless, you may want to filter out the svn history completely and one way to do it is to look at your commits only
git log --author me
Finally, the new version can be mirrored on the web, over the old one.