Using Mercurial patch queues

Date: Wed Sep 20 2017 Mercurial

The 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"