FusionForge

Maintaining a patched version with Bazaar

From FusionForge Wiki
Jump to: navigation, search

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.

One patch

Let's assume that Acme Corp. wants to deploy their AcmeForge, based on the latest stable series of FusionForge (5.2.x at the time of writing, coming from the branch called Branch_5_2 in the fusionforge.org Git 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 https://fusionforge.org/anonscm/bzr/deb-packaging/upstream/5.2
Branched 12475 revision(s).                                                     
~/acmeforge $ 

Then we'll create our local branch, called "patched":

~/acmeforge $ bzr branch 5.2 patched
Branched 12475 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 12476.
~/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 12477.                                                                 
~/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.2.x branch evolves? We want to integrate these changes into our patched forge, and not lose our changes. Here's how:

~/acmeforge $ cd 5.2
~/acmeforge/5.2 $ bzr pull
Using saved parent location: https://fusionforge.org/anonscm/bzr/deb-packaging/upstream/5.2
 M  src/common/include/FusionForge.class.php
All changes applied successfully.
Now on revision 12476.
~/acmeforge/5.2 $ cd ../patched/
~/acmeforge/patched $ bzr merge ~/acmeforge/5.2/
 M  src/common/include/FusionForge.class.php                                                                      
All changes applied successfully.                                                                  
~/acmeforge/patched $ bzr commit -m"Merged from upstream 5.2 branch"
Committing to: /home/guest/acmeforge/patched/                                                 
modified src/common/include/FusionForge.class.php
Committed revision 12478.                                                                 
~/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.2 integration
~/acmeforge $ cd upstream
~/acmeforge/upstream $ bzr branch https://fusionforge.org/anonscm/bzr/deb-packaging/upstream/5.2
Branched 12475 revision(s).
~/acmeforge/upstream $

Then we'll start two local branches:

~/acmeforge/upstream $ cd ../patches/5.2
~/acmeforge/patches/5.2 $ bzr branch ~/acmeforge/upstream/5.2/ theme-acmecorp
Branched 12475 revision(s).
~/acmeforge/patches/5.2 $ bzr branch ~/acmeforge/upstream/5.2/ plugin-acmezilla
Branched 12475 revision(s).
~/acmeforge/patches/5.2 $

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/5.2 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.2 $ cd ../../integration/
~/acmeforge/integration $ bzr branch ~/acmeforge/upstream/5.2/ acmeforge-5.2
Branched 12475 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.2/
~/acmeforge/integration/acmeforge-5.2 $ bzr merge ~/acmeforge/patches/5.2/theme-acmecorp/
 M  src/www/themes/gforge/Theme.class.php
All changes applied successfully.
~/acmeforge/integration/acmeforge-5.2 $ bzr commit -m"Merged from theme-acmecorp"
Committing to: /home/guest/acmeforge/integration/acmeforge-5.2/
modified src/www/themes/gforge/Theme.class.php
Committed revision 1476.
~/acmeforge/integration/acmeforge-5.2 $ bzr merge ~/acmeforge/patches/5.2/plugin-acmezilla/
+N  src/plugins/acmezilla/
[…]
+N  src/plugins/acmezilla/www/index.php
All changes applied successfully.
~/acmeforge/integration/acmeforge-5.2 $ bzr commit -m"Merged from plugin-acmezilla"
Committing to: /home/guest/acmeforge/integration/acmeforge-5.2/
added src/plugins/acmezilla
[…]
added src/plugins/acmezilla/www/index.php
Committed revision 12477.
~/acmeforge/integration/acmeforge-5.2 $

The acmeforge-5.2 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.2 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. Note that Bazaar will warn about "criss-cross merges", which is just a way of saying that it's trying to merge changes that have already been merged; that's not a problem unless it causes conflicts.

~/acmeforge/integration/acmeforge-5.2 $ cd ~/acmeforge/upstream/5.2
~/acmeforge/upstream/5.2 $ bzr pull
Using saved parent location: https://fusionforge.org/anonscm/bzr/deb-packaging/upstream/5.2
 M  src/common/include/FusionForge.class.php
All changes applied successfully.
Now on revision 12476.
~/acmeforge/upstream/5.2 $ cd ~/acmeforge/integration/acmeforge-5.2
~/acmeforge/integration/acmeforge-5.2 $ bzr merge ~/acmeforge/upstream/5.2
 M  src/common/include/FusionForge.class.php
All changes applied successfully.
~/acmeforge/integration/acmeforge-5.2 $ bzr commit -m"Merged from upstream 5.2"
Committing to: /home/guest/acmeforge/integration/acmeforge-5.2/
modified src/common/include/FusionForge.class.php
Committed revision 12478.
~/acmeforge/integration/acmeforge-5.2 $ cd ~/acmeforge/patches/5.2/theme-acmecorp/
~/acmeforge/patches/5.2/theme-acmecorp $ bzr merge ~/acmeforge/upstream/5.2 && bzr commit -m"Merged from upstream 5.2"
 M  src/common/include/FusionForge.class.php
All changes applied successfully.
Committing to: /home/guest/acmeforge/patches/5.2/theme-acmecorp/
modified src/common/include/FusionForge.class.php
Committed revision 12478.
~/acmeforge/patches/5.2/theme-acmecorp $ cd ~/acmeforge/patches/5.2/plugin-acmezilla/
~/acmeforge/patches/5.2/plugin-acmezilla $ bzr merge ~/acmeforge/upstream/5.2/ && bzr commit -m"Merged from upstream 5.2"
 M  src/common/include/FusionForge.class.php
All changes applied successfully.
Committing to: /home/guest/acmeforge/patches/5.2/plugin-acmezilla/
modified src/common/include/FusionForge.class.php
Committed revision 12477.
~/acmeforge/patches/5.2/plugin-acmezilla $ cd ~/acmeforge/integration/acmeforge-5.2
~/acmeforge/integration/acmeforge-5.2 $ bzr merge ~/acmeforge/patches/5.2/plugin-acmezilla/
Warning: criss-cross merge encountered.  See bzr help criss-cross.
All changes applied successfully.
~/acmeforge/integration/acmeforge-5.2 $ bzr commit -m"Merged from plugin-acmezilla"
Committing to: /home/guest/acmeforge/integration/acmeforge-5.2/
Committed revision 12479.
~/acmeforge/integration/acmeforge-5.2 $

Optimisations for the multi-patch workflow

If you don't want to keep one full working copy of each branch, you may choose to work with a separate repository and lightweight checkouts.

~ $ mkdir acmeforge
~ $ cd acmeforge
~/acmeforge $ bzr init-repo --no-trees repository
Shared repository (format: 2a)
Location:
  shared repository: repository
~/acmeforge $ cd repository/
~/acmeforge/repository $ mkdir upstream patches patches/5.2 integration
~/acmeforge/repository $ cd upstream
~/acmeforge/repository/upstream $ bzr branch https://fusionforge.org/anonscm/bzr/deb-packaging/upstream/5.2
Branched 12477 revision(s).
~/acmeforge/repository/upstream $ cd ../patches/5.2
~/acmeforge/repository/patches/5.2 $ bzr branch ~/acmeforge/repository/upstream/5.2 theme-acmecorp
Branched 12475 revision(s).
~/acmeforge/repository/patches/5.2 $ bzr branch ~/acmeforge/repository/upstream/5.2/ plugin-acmezilla
Branched 12475 revision(s).
~/acmeforge/repository/patches/5.2 $ cd ../../integration/
~/acmeforge/repository/integration $ bzr branch ~/acmeforge/repository/upstream/5.2/ acmeforge-5.2
Branched 12475 revision(s).
~/acmeforge/repository/integration $ cd ../../
~/acmeforge $ bzr checkout --lightweight ~/acmeforge/repository/integration/acmeforge-5.2/ integration
~/acmeforge $ bzr checkout --lightweight ~/acmeforge/repository/patches/5.2/theme-acmecorp/ switched-branch
~/acmeforge $

Then you have only two working copies: one for the integration branch, and one that you switch from feature branch to feature branch:

~/acmeforge $ cd switched-branch/
~/acmeforge/switched-branch $ bzr nick
theme-acmecorp
~/acmeforge/switched-branch $ bzr switch plugin-acmezilla
Tree is up to date at revision 12475.
Switched to branch: /home/guest/acmeforge/repository/patches/5.2/plugin-acmezilla/
~/acmeforge/switched-branch $ bzr nick
plugin-acmezilla
~/acmeforge/switched-branch $ 

The repository can also be remote if several people need to work on the same set of branches.

Good practices

  • Try to keep each feature in its own branch; this makes it easier to push changes upstream, and also to upgrade to new upstream versions since the potential conflicts will be smaller.
  • If possible, run the testsuite on each feature branch from time to time, or at least before merging into the integration; also run the testsuite on the integration branch. Yes, this means you need to update the testsuite as you evolve the actual code.
  • Avoid committing generated files to feature branches; if several branches touch the "source" files for them, conflicts are almost guaranteed at integration time.