Tags: 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"