Maintaining a patched version with Bazaar
There are times where the vanilla FusionForge doesn't quite fit your own particular needs; while you can of course do the changes locally on the installed forge, you'd still like to keep track of the upstream FusionForge code and take advantage of the evolutions there. And at some point, you may even want to contribute some of your local changes, because if they're not 100% specific to you there are good chances that they'll be useful to someone out there. But you're worried about getting lost in a maze of branches, merges and so on.
This is a possible workflow, using Bazaar as the DVCS. Lolando has used it successfully to manage several sets of branches for clients who require slightly patched versions of FusionForge.
Let's assume that Acme Corp. wants to deploy their AcmeForge, based on the latest stable series of FusionForge (5.1.x at the time of writing, coming from the branch called Branch_5_1 in the fusionforge.org Subversion repository), with a customized theme that's only a minor deviation from the standard theme (adding a few links, changing the colours in the CSS, etc.).
We'll first create a local repository to save disk space, and copy the upstream branch there:
$ bzr init-repo acmeforge Shared repository with trees (format: 2a) Location: shared repository: acmeforge $ cd acmeforge ~/acmeforge $ bzr branch http://fusionforge.fusionforge.org/bzr/branches/Branch_5_1 Branched 10435 revision(s). ~/acmeforge $
Then we'll create our local branch, called "patched":
~/acmeforge $ bzr branch Branch_5_1 patched Branched 10435 revision(s). ~/acmeforge $
Time to do some local work!
~/acmeforge $ cd patched/ ~/acmeforge/patched $ emacs src/www/themes/gforge/Theme.class.php […] ~/acmeforge/patched $ bzr commit -m"Patched default theme to match Acme Corp's colours" Committing to: /home/guest/acmeforge/patched/ modified src/www/themes/gforge/Theme.class.php Committed revision 10436. ~/acmeforge/patched $ emacs src/www/themes/gforge/Theme.class.php […] ~/acmeforge/patched $ bzr commit -m"Fixed a syntax error introduced with the previous commit" Committing to: /home/guest/acmeforge/patched/ modified src/www/themes/gforge/Theme.class.php Committed revision 10437. ~/acmeforge/patched $
We can now deploy the code based on this "patched" branch, either by building the *.deb or *.rpm packages or installing from source. If further rounds of the local patch are needed, just edit and commit, and deploy again from that branch.
Now what happens when the upstream 5.1.x branch evolves? We want to integrate these changes into our patched forge, and not lose our changes. Here's how:
~/acmeforge $ cd Branch_5_1/ ~/acmeforge/Branch_5_1 $ bzr pull Using saved parent location: http://fusionforge.fusionforge.org/bzr/branches/Branch_5_1/ M src/common/include/FusionForge.class.php All changes applied successfully. Now on revision 10436. ~/acmeforge/Branch_5_1 $ cd ../patched/ ~/acmeforge/patched $ bzr merge ~/acmeforge/Branch_5_1/ M src/common/include/FusionForge.class.php All changes applied successfully. ~/acmeforge/patched $ bzr commit -m"Merged from upstream 5.1 branch" Committing to: /home/guest/acmeforge/patched/ modified src/common/include/FusionForge.class.php Committed revision 10438. ~/acmeforge/patched $
Our "patched" branch now has both the latest changes from upstream and the local changes to the theme, and it can be deployed as previously.
Several independent patches
The previous section works nice when there's only a single conceptual change applied on top of FusionForge. When there are several of them, having each in its own branch makes it easier for developing (each is isolated during development), debugging (bisection becomes more valuable if only one feature is worked on in a branch) and contributing (you don't need to manually separate what can be contributed back from what's really local). The "downside" is that it requires one extra branch to integrate all the changesets into the deployed version, but that can also be seen as an advantage, since you only pull into that integration branch when the feature branches are stable enough, and you can have feature branches in various states of development at any time. Since we're likely to have several branches as time passes, we'll also try to keep them organised.
It starts similarly:
$ bzr init-repo acmeforge Shared repository with trees (format: 2a) Location: shared repository: acmeforge $ cd acmeforge ~/acmeforge $ mkdir upstream patches patches/5.1 integration ~/acmeforge $ cd upstream ~/acmeforge/upstream $ bzr branch http://fusionforge.fusionforge.org/bzr/branches/Branch_5_1 Branched 10435 revision(s). ~/acmeforge/upstream $
Then we'll start two local branches:
~/acmeforge/upstream $ cd ../patches/5.1 ~/acmeforge/patches/5.1 $ bzr branch ~/acmeforge/upstream/Branch_5_1/ theme-acmecorp Branched 10435 revision(s). ~/acmeforge/patches/5.1 $ bzr branch ~/acmeforge/upstream/Branch_5_1/ plugin-acmezilla Branched 10435 revision(s). ~/acmeforge/patches/5.1 $
These two branches work similarly to the "patched" branch mentioned in the simple workflow. Commits concerning the Acme Corp. theme go to the theme-acmecorp branch, and the addition of a plugin interfacing FusionForge with the AcmeZilla tool goes to plugin-acmezilla. Both can merge from ~/acmeforge/upstream/Branch_5_1 when commits happen upstream (after a pull in that branch, as previously). Note that each of those branches will only have changes related to one feature: either the theme, or the plugin. This allows easier comparison with upstream, easier debugging, and so on. But we can't deploy either of them to the production site, since each one lacks the feature developed in the other branch.
So we'll do an integration branch:
~/acmeforge/patches/5.1 $ cd ../../integration/ ~/acmeforge/integration $ bzr branch ~/acmeforge/upstream/Branch_5_1/ acmeforge-5.1 Branched 10435 revision(s). ~/acmeforge/integration $
Assuming the plugin-acmezilla and theme-acmecorp feature branches have seen some commits, assembling all that into the integration branch is simply a matter of merging, in turn, from each feature branch into the integration branch:
~/acmeforge/integration $ cd acmeforge-5.1/ ~/acmeforge/integration/acmeforge-5.1 $ bzr merge ~/acmeforge/patches/5.1/theme-acmecorp/ M src/www/themes/gforge/Theme.class.php All changes applied successfully. ~/acmeforge/integration/acmeforge-5.1 $ bzr commit -m"Merged from theme-acmecorp" Committing to: /home/guest/acmeforge/integration/acmeforge-5.1/ modified src/www/themes/gforge/Theme.class.php Committed revision 10436. ~/acmeforge/integration/acmeforge-5.1 $ bzr merge ~/acmeforge/patches/5.1/plugin-acmezilla/ +N src/plugins/acmezilla/ […] +N src/plugins/acmezilla/www/index.php All changes applied successfully. ~/acmeforge/integration/acmeforge-5.1 $ bzr commit -m"Merged from plugin-acmezilla" Committing to: /home/guest/acmeforge/integration/acmeforge-5.1/ added src/plugins/acmezilla […] added src/plugins/acmezilla/www/index.php Committed revision 10437. ~/acmeforge/integration/acmeforge-5.1 $
The acmeforge-5.1 branch now contains both the theme and the plugin, and it can be deployed to production; note that even if each feature branch works by itself, it makes sense to test the integration branch too, because there may be some unforeseen interaction between the apparently independent changes.
When new commits happen on the upstream 5.1 branch, it's usually better to merge them into the integration branch before merging them into the feature branches, so that a merge from a feature branch only brings changes that happened on this branch, instead of including changes from upstream too.