Using Mercurial patch queues

; Date: Thu Nov 20 2008

Tags: Mercurial

The (www.selenic.com) Mercurial Queues Extension (mq) is really cool, and now that I've gotten my head around how to use it I'm never looking back. It is a completely awesome tool. However it didn't make sense the first couple times I tried to figure it out, so it might be good to offer my explanation.

The problem mq tries to solve is better management of local changes to a local instance of a master repository. It's derived from earlier work known as 'quilt' and there are links to several papers on the Mercurial site. I'm going to ignore those papers and focus on the Mercurial implementation.

With mq you can create multiple patches which are automagically applied to the repository. With a few commands you can easily back out the patches, apply specific patches, delete patches, or convert a patch into a changeset. It does that and automatically manipulates your files to match whichever patches are in effect.

The mq extension is bundled with Mercurial and is enabled by putting this in your hgrc

[extensions]
hgext.mq =

To enable using mq with a given repository use the qinit command

[tang]$ hg qinit
abort: patch queue directory already exists

Of course if the repository is already enabled with mq this will fail. The "patch queue" directory this message refers to is inside the ".hg" directory

[tang]$ ls .hg
00changelog.i  branch.cache  hgrc     requires	strip-backup  undo.dirstate
branch	       dirstate      patches  store	undo.branch

The .hg/patches directory is itself a mercurial repository, and that repository is managed by the "q" commands. To create a patch do the following:-

[tang]$ hg qnew -m 'sample to demonstrate patches' installMods
[tang]$ vi INSTALL.txt 
[tang]$ hg diff
diff -r 831051f667dd INSTALL.txt
--- a/INSTALL.txt	Thu Nov 20 20:53:49 2008 -0800
+++ b/INSTALL.txt	Thu Nov 20 20:54:05 2008 -0800
@@ -1,3 +1,5 @@
+This is a modification
+
 // $Id: INSTALL.txt,v 1.61.2.4 2008/07/09 19:15:59 goba Exp $
 
 CONTENTS OF THIS FILE
[tang]$ hg qrefresh
[tang]$ hg diff
[tang]$ cat .hg/patches/installMods 
sample to demonstrate patches
 
diff -r a423db267b88 INSTALL.txt
--- a/INSTALL.txt	Thu Nov 20 10:10:29 2008 -0800
+++ b/INSTALL.txt	Thu Nov 20 20:54:11 2008 -0800
@@ -1,3 +1,5 @@
+This is a modification
+
 // $Id: INSTALL.txt,v 1.61.2.4 2008/07/09 19:15:59 goba Exp $
 
 CONTENTS OF THIS FILE

The steps were: a) qnew to initialize a new patch, b) edit one or more files to create change, c) qrefresh to add that change into the patch. You'll note that the second "hg diff" execution said there were no changes, and that instead the patch file had the change.

You can look at the set of changes this way:-

[tang]$ hg qseries
installMods
htaccess

The list of patches maintained by mq are treated like a stack. You push and pop changes onto the top of the stack.

[tang]$ hg qpop
Patch queue now empty
[tang]$ head INSTALL.txt 
// $Id: INSTALL.txt,v 1.61.2.4 2008/07/09 19:15:59 goba Exp $
 
CONTENTS OF THIS FILE
---------------------
 
 * Requirements
 * Optional requirements
 * Installation
 * Drupal administration
 * Customizing your theme(s)
[tang]$ hg qpush
applying installMods
Now at: installMods
[tang]$ head INSTALL.txt 
This is a modification
 
// $Id: INSTALL.txt,v 1.61.2.4 2008/07/09 19:15:59 goba Exp $
 
CONTENTS OF THIS FILE
---------------------
 
 * Requirements
 * Optional requirements
 * Installation

hg qrefresh can only add changes to whatever patch is at the top of the stack. If you desire to change a patch in the middle of the stack you can qpop until the desired change is at the top of the stack, then make your change, then do qrefresh. That would be something like this:

$ hg qpop desiredPatchToChange
$ vi file(s) and make changes
$ hg qrefresh
$ hg qpush -a

The qrefresh command can be run multiple times to iteratively build up a set of changes into a patch. In the above commands, the desiredPatchToChange already existed but we wanted to add more change to it. The qrefresh command makes this possible, again, by appending the new changes to whatever change is already in the patch.

It is most convenient if you follow the given order of commands: a) qnew, b) make changes, c) qrefresh

If you make your changes before using qnew then the following error is printed

[tang]$ vi UPGRADE.txt 
[tang]$ hg qnew upgradeMods
abort: local changes found, refresh first

The error message isn't very clear, but what it's saying is there are already existing changes. It refuses to create a new patch if there are existing changes. However...

[tang]$ hg qnew -f upgradeMods
[tang]$ hg qrefresh
[tang]$ hg qseries
installMods
upgradeMods
htaccess

You can always force it.

It's turned out that one of the trickiest things to handle is bringing in changesets from the master repository while there are patches applied. If an upstream change conflicts with one of the patches, then the update of changesets from the master will go screwy. I've found it best to do the following to ensure that when you pull in upstream changesets it's to a clean repository.

$ hg qpop -a
$ hg pull
$ hg update
$ hg qpush -a

It's possible that one of the patches will conflict with an upstream change. If so when you "hg qpush -a" the patching process will bomb out at that moment and you'll be left with a ".rej" file containing the chunk that failed. At that point one thing which might be useful is to delete the patch using the "qdelete" command. The exact route to follow depends a lot on the upstream change, the local change, and your own preferences.

If you have a patch you wish to send as a changeset, the "qdelete" command is used again. This is a little confusing as "delete" has a different connotation from "commit". In any case what you do is "hg qdelete -r patchNameToConvert". The changeset comment will be derived from whatever message was given on the "qnew" command... therefore it is useful to give a decent explanation when doing "hg qnew -m 'explanation of patch' patchName"

About the Author(s)

(davidherron.com) David Herron : David Herron is a writer and software engineer focusing on the wise use of technology. He is especially interested in clean energy technologies like solar power, wind power, and electric cars. David worked for nearly 30 years in Silicon Valley on software ranging from electronic mail systems, to video streaming, to the Java programming language, and has published several books on Node.js programming and electric vehicles.