Warner's Useful Mercurial Work Flow

I've been asked by several people to document my current Mercurial setup and work flow. While some people love git, others find it tedious and cumbersome to deal with. If you love git, use git. If not, consider this work flow. If you wish to turn this page into a handbook chapter, please contact the author and remove the folksy, chatty style.

This works well on new development pushed into head. It works less well when I have to MFC. To do that, I still follow the handbook using raw svn directly. I'd love to find a way to not have to do this where mergeinfo is correctly done, but so far I got nothing.

Required Setup

Ideally, this section would be "Install the devel/mercurial port" and then I'd move on. However, this port doesn't have the subversion upstream support baked into it. I use hgsubversion. While there is a FreeBSD port, as of this writing (June 5, 2014), it was too old (1.6.1) to support pulling from the FreeBSD repo due to FreeBSD's repo layout. So this section is a little longer...

Install Required Ports

Install the following ports. While one might be able to do clever things with linking svn to svnlite, I've had mixed results trying that and have always reverted to the actual svn port, since the SWIG bindings work a lot better than the command line bindings, and SWIG works a lot better with the full subversion port. While all this might not be strictly necessary, it is what I know works.

It goes without saying, or should, that you really don't want to try this with really old versions of hg or svn. Use your culturally favored method to update to the latest if you already have some of these installed. Using old version of hg will result in mercury poisoning and leave you as mad as a hatter (who also used hg in their trade). You have been warned.

Grab hgsubversion (not hgsvn)

I put this under $HOME/src like so after installing the above ports:

cd $HOME/src
hg clone http://bitbucket.org/durin42/hgsubversion

The author knows that the following checkout works for him. There are plenty of newer revisions that might also work. Caveat Emptor.

% hg sum
parent: 1146:4cdb0e95604f tip
 svncommands: call util.dump instead of manually writing file
branch: default
commit: (clean)
update: (current)
mq:     (empty queue)

It has also been suggested that you go ahead and install the devel/py-hgsubversion port (to get all the dependencies right) and then pull a second copy as just described so that the next section works (and also so you get the version that's known working). I may have done this on some machines while I was discovering how to set things up, but others I puzzled out errors from running hg with the hgsubversion extention, so on the whole I'd say this is a good suggestion.

Enable hg extentions

You need to enable the mq (mercurial queues) and hgsubversion extentions in your $HOME/.hgrc file:

[ui]
verbose=true
username="Warner Losh <imp@freebsd.org>"

[extensions]
mq=
hgsubversion=~/src/hgsubversion/hgsubversion

Note, I also set my username to my FreeBSD email address, and turn on verbose. While I normally like commands to be silent, I find that verbose tells me just enough about what's going on to know something has happened. But this may be because its verbosity is similar to cvs and svn's default settings and I've used those tools for a decade and a half... The main benefit, apart from familiarity, is that I know what files are being operated on, and can catch cross-threading more easily... Are there too many files on a hg qrefresh, then I likely forgot to do a qnew and I'll have to split the patch (more on that bit of crazy below)... Too few files, I forgot something, etc.

Create a Patch Queue Repo

Create a patch queue repo on freefall. I place mine under my public_html directory so anybody can pull from it, but I'm also careful about what I work on to make sure that it is all public (even if it all isn't going into FreeBSD or needs a lot of love before I commit it to FreeBSD). I store it on freefall in case my local machine's hard drive goes to the great storage hunting grounds... Also, it is a convenient pivot point to allow development on multiple machines...

local% slogin freefall.freebsd.org
freefall% cd public_html
freefall% mkdir public-patch-queue
freefall% cd public-patch=-queue
freefall% hg init
freefall% logout
local%

Pull A hg repo of the FreeBSD tree

While this works equally well on ports as src, all my examples are on src. I've used this technique for the tiny number of ports commits I've done, as well as a hundred or two commits to the repo. It works well for a single commit, or when you have a dozen small chunks that are closely related that you'd like to push upstream at the same time to allow for easy bisection in case there's an issue.

When pulling the FreeBSD tree, you get to decide how much of a conversion you want to do. hgsubversion converts the FreeBSD history into local hg history. I've never wanted/needed much locally (since I use svnweb for much of it), so I tell hgsubversion to start with the tip. You can substitute your own version, or omit it entirely for all the mainline history if you want. It does seem to slow down the creation process to do this, however. Since I've not done it, I don't know how much it slows down the repo actions, but my experience with hg on other projects suggests the slowdown will be minimal.

% cd FreeBSD
% hg clone --startrev HEAD svn+ssh://svn.freebsd.org/base/head
% cd head

Pulling in a copy of that patch queue from freefall

OK. Now that you've checked out the FreeBSD tree, there's only one more step left. You need to link it to the patch queue repo that you created a couple of steps ago. While not strictly necessary, keeping your patches in a repo means you get versioned history of them, protection from OOPSes and all the other benefits of version control. It can be a bit of a pain to do, but it is well worth it in the long run if it even saves you having to recreate one patch...

% pwd
.../FreeBSD/head
% cd .hg
% hg clone ssh://user@freefall.freebsd.org/public_html/public-patch-queue patches
% cd ..

That should be it. This step always trips me up, so please let me know if I documented it wrong. You'll know that if future steps don't work :)

Auto Properties

In your .subversion/config file, you'll need to put a number of lines to make sure new files get added correctly. Here's what I use. They will be matched in order. Note the last line. I'm forever forgetting to see if new files match all these patterns, so when I go to push I get a subversion failure because there's no $FreeBSD$ in the file, the keywords didn't get set, etc. Most of that can be cleared up using autoprops, but you still have to remember to to put $FreeBSD$ into the file. Also, there are some exceptions to these rules, and those are best handled by adding the file directly to svn with svn. They are rare, but I've found it far easier to drop back to svn when I hit the odd one than to try to work out the weird patterns needed in my subversion config file. This is mostly the one that FreeBSD recommends. Note the last line is a wildcard, and might be considered dangerous. Caution is advised when using it.

*.c     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.h     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.s     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.S     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.cc    = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.cpp   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.cxx   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.in    = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.sh    = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain; svn:executable
*.pl    = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain; svn:executable
*.pm    = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.py    = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.rb    = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.awk   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.sed   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.txt   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.conf  = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
Makefile* = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.1     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.2     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.3     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.4     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.5     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.6     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.7     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.8     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
*.9     = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain
WITH*   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain

*.css   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/css
*.html  = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/html
*.xhtml = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/html+xml
*.xml   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/xml
*.xsd   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/xml
*.xsl   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/xml
*.xslt  = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/xml
*.xul   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/xul
*.sgml   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/sgml
*.docbook = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/sgml

*.pdf   = svn:mime-type=application/pdf
*.ps    = svn:mime-type=application/postscript
*.eps   = svn:mime-type=application/postscript
*.exe   = svn:mime-type=application/octet-stream
*.bin   = svn:mime-type=application/octet-stream

*.jpg   = svn:mime-type=image/jpeg
*.jpeg  = svn:mime-type=image/jpeg
*.gif   = svn:mime-type=image/gif
*.png   = svn:mime-type=image/png
*.tiff  = svn:mime-type=image/tiff

patch*   = svn:eol-style=native; svn:mime-type=text/plain; fbsd:nokeywords=t
*   = svn:eol-style=native; svn:keywords=FreeBSD=%H; svn:mime-type=text/plain

People are telling me this might not be needed, except maybe for the last line, which is too dangerous to have upstream. And maybe the next to the last line when dealing with ports...

How to Do Stuff

This section assumes that you have things setup as described in the previous section. Let's get started. I'll assume some minor familiarity with patch queues. You might want to give http://mercurial.selenic.com/wiki/MqTutorial or http://mercurial.selenic.com/wiki/MqExtension a spin if you have trouble understanding the following. Sadly, the tutorial is slightly dated:

and the reference section is equally dated

Note the description of qtip and qbase. These are tags that describe the top of the tree and the branching point. Below I use 'mq()' in quotes for the entire queue. You could also use qbase:qtip most of the time, but I forget when it fails so I always use the shorter to type variant. Also, if there's only one revision and one change pushed on you can use '.' instead of 'mq()' for qfinish if you are a lazy typist with a cluttered mind of special cases to feed that laziness. I find it useful, but to each their own.

Creating your first patch

In this example, we're going to edit UPDATING to add a new note. We'll create a patch on the queue, look at its status, edit its commit message, commit the patch to your repo, push it out, etc. Not all these steps are always necessary, but it makes a good example to do them all, even if you iterate over only some of the steps.

% hg diff
# should be no output here
% vi UPDATING
% hg diff
diff -r 9fa9e4914606 UPDATING
--- a/UPDATING
+++ b/UPDATING
@@ -9,7 +9,8 @@ handbook:
     http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/makeworld.html
 
 Items affecting the ports and packages system can be found in
-/usr/ports/UPDATING.  Please read that file before running portupgrade.
+/usr/ports/UPDATING.  Please read that file before running portupgrade or
+portmaster.
 
 NOTE: FreeBSD has switched from gcc to clang. If you have trouble bootstrapping
 from older versions of FreeBSD, try WITHOUT_CLANG and WITH_GCC to bootstrap to
% hg qnew -e update
% hg diff
% hg qdiff
diff -r 9fa9e4914606 UPDATING
--- a/UPDATING
+++ b/UPDATING
@@ -9,7 +9,8 @@ handbook:
     http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/makeworld.html
 
 Items affecting the ports and packages system can be found in
-/usr/ports/UPDATING.  Please read that file before running portupgrade.
+/usr/ports/UPDATING.  Please read that file before running portupgrade or
+portmaster.
 
 NOTE: FreeBSD has switched from gcc to clang. If you have trouble bootstrapping
 from older versions of FreeBSD, try WITHOUT_CLANG and WITH_GCC to bootstrap to
% hg qhead
Tell users about portmaster too.
%

The above sequence will check to make sure you have a pure tree, then edit UPDATING, and then create a new patch named update. The -e on the qnew command kicks you into the editor for your commit message. hg diff tells you what is different and uncommitted. hg qdiff combines the most recently saved patch with any local changes, as shown above. So, in this example, we've made a change to UPDATING, and have created a commit message "Tell users about portmaster too."

Now, before we push this upstream, let's say we've thought about the change and now want to reword the UPDATING things a bit. We also don't like the commit message. So we go in and edit things. Here's a typical sequence of events.

% vi UPDATING
% hg diff
diff -r 2a6b5ffa63c9 UPDATING
--- a/UPDATING
+++ b/UPDATING
@@ -9,8 +9,8 @@ handbook:
     http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/makeworld.html
 
 Items affecting the ports and packages system can be found in
-/usr/ports/UPDATING.  Please read that file before running portupgrade or
-portmaster.
+/usr/ports/UPDATING.  Please read that file before running portupgrade,
+portmaster or other port updating utility.
 
 NOTE: FreeBSD has switched from gcc to clang. If you have trouble bootstrapping
 from older versions of FreeBSD, try WITHOUT_CLANG and WITH_GCC to bootstrap to
% hg qdiff
diff -r 9fa9e4914606 UPDATING
--- a/UPDATING
+++ b/UPDATING
@@ -9,7 +9,8 @@ handbook:
     http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/makeworld.html
 
 Items affecting the ports and packages system can be found in
-/usr/ports/UPDATING.  Please read that file before running portupgrade.
+/usr/ports/UPDATING.  Please read that file before running portupgrade,
+portmaster or other port updating utility.
 
 NOTE: FreeBSD has switched from gcc to clang. If you have trouble bootstrapping
 from older versions of FreeBSD, try WITHOUT_CLANG and WITH_GCC to bootstrap to
% hg qrefresh
% hg diff
% hg qrefresh -e
% hg qhead
Wordsmith advise on port upgrading.
% hg qseries
0 A updating
% 

As you can see, we've tweaked the UPDATING file. You can also see the difference between hg diff and hg qdiff here, now that we've been working on a change for a while. The first hg qrefresh regenerates the patch we're working on. The second one (with the -e) allows us to edit the commit message, which you can see we've completely rewritten. We could keep up this all day, but let's say we're happy with this.

We could go and directly commit it. However, to instill good habits, let's save a copy of the patch and push that copy to freefall first.

% hg commit --mq -m"Save updating patch"
updating
series
committed changeset 70:2e92a0e95b31
% hg push --mq
pushing to ssh://freefall.freebsd.org/public_html/patch-queue
running ssh freefall.freebsd.org 'hg -R public_html/patch-queue serve --stdio'
searching for changes
1 changesets found
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 2 changes to 2 files
%

Notice that there's changes to two files, and this weird file 'series' gets pushed as well. series is where hg keeps track of the order of the patches in this directory. There's a second file that you'll find if you look in the patches directory too called status. It tells the order the patches are applied right now. This file is never saved. These files are 'special' and so you can't name any of your patches series or status. Sorry. Otherwise you can name them anything. If you include / in the names, it will put them in subdirectories. I never thought this too useful, but some people might.

Now it is time to get serious and push these changes in. Since this is an example I don't want to commit, I've pasted an actual commit below.

% hg qfinish 'mq()'
% hg out
comparing with svn+ssh://svn.freebsd.org/base/head
changeset:   1475:d239c74b6b6a
user:        "Warner Losh <imp@freebsd.org>"
date:        Thu Jun 05 16:36:35 2014 -0600
files:       sys/amd64/conf/GENERIC sys/arm/conf/ZEDBOARD sys/i386/conf/GENERIC sys/i386/conf/XEN sys/ia64/conf/GENERIC sys/sparc64/conf/GENERIC
description:
Restore comments accidentally removed.

MFC after: 3 days


changeset:   1476:f50eda80d522
tag:         tip
user:        "Warner Losh <imp@freebsd.org>"
date:        Thu Jun 05 22:07:35 2014 -0600
files:       bin/ed/Makefile lib/libtelnet/Makefile libexec/telnetd/Makefile release/picobsd/bridge/crunch.conf release/picobsd/qemu/crunch.conf usr.bin/telnet/Makefile usr.sbin/ntp/ntp-keygen/Makefile usr.sbin/ntp/ntpd/Makefile usr.sbin/ppp/Makefile usr.sbin/sendmail/Makefile usr.sbin/tcpdump/tcpdump/Makefile
description:
When building picobsd, define WITHOUT_OPENSSL and WITHOUT_KERBEROS and
remove the now-redundant checks for RELEASE_CRUNCH. This originally
was defined for building smaller sysinstall images, but was later also
used by picobsd builds for a similar purpose. Now that we've moved
away from sysinstall, picobsd is the only remaining consumer of this
interface. Adding these two options reduces the RELEASE_CRUNCH
special cases in the tree by half.


% hg push
pushing to svn+ssh://svn.freebsd.org/base/head
searching for changes
committing d239c74b6b6a
[r267134] gjb: Document r266463, newsyslog.conf(5) includes in conf.d.
M head/release/doc/en_US.ISO8859-1/relnotes/article.xml
release/doc/en_US.ISO8859-1/relnotes/article.xml
 committed to "default" as 5e47f969d8bb
[r267141] allanjude: Style cleanups on ifconfig.8
M head/sbin/ifconfig/ifconfig.8
sbin/ifconfig/ifconfig.8
 committed to "default" as 63a749057781
[r267142] zont: Use mtx_lock_spin/mtx_unlock_spin primitives on spin lock
M head/sys/dev/netmap/netmap.c
M head/sys/dev/netmap/netmap_mbq.c
sys/dev/netmap/netmap.c
sys/dev/netmap/netmap_mbq.c
 committed to "default" as a6acb044c8fd
[r267145] brd: - Fix the keyfile being cleared prematurely after r259428
M head/sys/geom/eli/g_eli.c
sys/geom/eli/g_eli.c
 committed to "default" as f011200641fe
[r267146] imp: Restore comments accidentally removed.
M head/sys/amd64/conf/GENERIC
M head/sys/arm/conf/ZEDBOARD
M head/sys/i386/conf/GENERIC
M head/sys/i386/conf/XEN
M head/sys/ia64/conf/GENERIC
M head/sys/sparc64/conf/GENERIC
sys/amd64/conf/GENERIC
sys/arm/conf/ZEDBOARD
sys/i386/conf/GENERIC
sys/i386/conf/XEN
sys/ia64/conf/GENERIC
sys/sparc64/conf/GENERIC
 committed to "default" as 74a80bdb8e36
pulled 5 revisions
committing f50eda80d522
[r267147] imp: When building picobsd, define WITHOUT_OPENSSL and WITHOUT_KERBEROS and
M head/bin/ed/Makefile
M head/lib/libtelnet/Makefile
M head/libexec/telnetd/Makefile
M head/release/picobsd/bridge/crunch.conf
M head/release/picobsd/qemu/crunch.conf
M head/usr.bin/telnet/Makefile
M head/usr.sbin/ntp/ntp-keygen/Makefile
M head/usr.sbin/ntp/ntpd/Makefile
M head/usr.sbin/ppp/Makefile
M head/usr.sbin/sendmail/Makefile
M head/usr.sbin/tcpdump/tcpdump/Makefile
bin/ed/Makefile
lib/libtelnet/Makefile
libexec/telnetd/Makefile
release/picobsd/bridge/crunch.conf
release/picobsd/qemu/crunch.conf
usr.bin/telnet/Makefile
usr.sbin/ntp/ntp-keygen/Makefile
usr.sbin/ntp/ntpd/Makefile
usr.sbin/ppp/Makefile
usr.sbin/sendmail/Makefile
usr.sbin/tcpdump/tcpdump/Makefile
 committed to "default" as 0b26ea42a4e8
pulled 1 revisions
resolving manifests
getting release/doc/en_US.ISO8859-1/relnotes/article.xml
getting sbin/ifconfig/ifconfig.8
getting sys/dev/netmap/netmap.c
getting sys/dev/netmap/netmap_mbq.c
getting sys/geom/eli/g_eli.c
5 files updated, 0 files merged, 0 files removed, 0 files unresolved
2 changesets found
saved backup bundle to /usr/home/imp/FreeBSD/head/.hg/strip-backup/d239c74b6b6a-backup.hg
6 changesets found
adding branch
adding changesets
adding manifests
adding file changes
added 6 changesets with 22 changes to 22 files

So hg qfinish 'mq()' takes the commit from the patch queue and finishes it up and places it in the hg default branch. The 'mq()' bit says do all pushed changes in your queue. In this case, there were two. "hg out" gives you a list of what will be pushed upstream. In this case, there's two patches. One that touches some kernel config files, and one that does some build stuff. You'll also see a bunch of other stuff. When using hg with subversion, the hg push command is also doing a bit of a rebase. It pulls in all the changes from upstream that happened while you were editing, and then pushes your changes out on top of that. It never does the usual hg thing of creating multiple heads. I consider this a feature, but others disagree. Such is life. In this case, it pulled down 4 changes, added my 2 and gave a summary at the end. I was lucky that these 4 commits didn't touch anything that I'd messed with, or I'd get a merge conflict, which would need to be resolved.

So those are the raw basics. I'll add more about multiple patches, moving patches around, and resolving conflicts. I'll also talk about multiple machines and resolving conflicts in the actual patch queue. My patch queues typically have a dozen entries, 2-3 are approaching ready to commit, and a bunch of experimental stuff, some of which is just debug crap, others are diamonds in the rough, and others are lumps of coal in the rough that will never amount to anything.

Adding in code reviews with Phabric

Do all the above stuff, but before the commit install devel/arcanist port.

% arc diff

I haven't found a way to have arc do the commit, so I do the commit as above. I then mark it close with

% arc close-revision d1234

Moving patches around and rebasing

Sometimes as you develop a series of patches, you find that some trivial patches you did second are ready to push upstream before other changes you might have started first. It is a simple matter to "hg qpop -a" the entire queue, and then move these fixes to the front of the queue with "hg qpush --move patch-name". If all the patches are popped off, you can also edit the .hg/patches/series file directly. After moving patches around, each new hg qpush will reapply the patch in the patch queue. This may result in some fuzz or in some cases conflicts. To resolve these, you'll need to do "hg qrefresh" to regenerate the patch.

Speaking of "hg qrefresh" you sometimes need to use this command when you rebase. If it has been a while since you've pulled sources from upstream, you'll need to go through a rebasing operation. There's an hg rebase command, but I never use it. It is useful when you are using hg's branching stuff, which isn't used in this workflow. Instead, a rebasing operation is simple a replaying of the patches. Since the history of the development of the patches is usually uninteresting to the upstream repo, if the patches are constructed right and have good commit messages, this works out well. I usually hg qpop all my changes off. I then hg commit --mq and hg push --mq to save the current state (using the current upstream revision of your working tree in the commit message lets you get back more easily). Then I do the hg pull -u to update the upstream sources. I then hg qpush the patches one at a time, doing hg qrefresh for those that didn't apply 100% cleanly (after checking the diffs and maybe cleaning up any mismerges). Once I've rebased, I'll often save a copy with the same hg commit --mq, this time with the revision of the newly pulled tree. It is a little bit of overkill, but when it saves your butt, it suddenly becomes well worth it.

Sometimes you've pushed all your changes and type make and boom! a build error. Usually these are annoyingly trivial to fix, so I fix them. However, if you have a dozen patches pushed, say, chances are quite good it isn't the top most patch that needs fixing. You could solve this by always doing a full build for every change. Or, if you don't have that kind of time, you can solve this by doing "hg qnew fold-me" and then popping back to the change that had a problem. Once back to that change "hg qfold fold-me" will apply the fix to the current patch, refresh the current patch and delete fold-me from the queue.

When Chaos attacks: fixing a bad push

Like with most things that conflict, your best bet is to avoid pushing something that will fail. However, things will fail. You'll forget a $FreeBSD$ keyword, or maybe the keywords aren't right, or maybe somebody else just committed to the same file, causing merge conflicts. Whatever the reason, sometimes the state can get all messed up after you hg qfinish your changes. Here's some advise for how to proceed when a hg push fails.

First, determine which of the change sets you were attempting to push made it in. You'll need to know this information later. When hgsubversion hits an error, especially after one or more changes have been pushed up stream, it doesn't clean up well. Here's the steps that I've used to unwind. For each of the changes you wanted to push, you'll need to "hg qimport -r XXXX" (where XXXX is the revision # or hash from "hg log" or "hg out") in reverse order (for example, do revision 10, then 9, then 8, etc). This import will import them back into your queue with names of XXXX.diff. Unless it is going to take a lot of effort to resolve the issue, I usually don't bother to rename them. Once they are back in your queue, "hg qrm" the ones that made it upstream (see notes above about backups, etc). Once you have those sorted out, I've found that the metadata can be out of sync. Fortunately, it is easy to resync this

hg svn rebuildmeta svn+ssh://svn.freebsd.org/base/head

maybe substituting a different upstream repo you used originally. After that, I do an "hg update -C default" to make sure I have a clean checkout (note that -C will clobber local changes. But it is safe in this case because if you are pushing, you have no local changes. I then pull form the upstream repo (hg pull -u) to make sure that I have everything.

OK. At this point you are finally ready to fix the issue that lead you here. If it is just a $FreeBSD$ on a new file or a merge conflict, then that's easy. If it is a property thing, please see above for info on how I cope.

Splitting patches -- The easy case

So let's say you are working on src/sys/dev/pci/pci.c. You have a patch cooking and life is good.

Squirrel.

Now you're editing src/sys/kern/subr_bus.c and find a typo in the comments. Not wanting to let it continue to exist, you fix it and save.

Squirrel.

Back to pci.c. You find the bug, implement the thing, or whatever and type hg qrefresh. Without verbose, you notice nothing out of the ordinary (unless you habitually develop hg qdiff as an ocd affectation). With verbose, you see the extra file and want to undo it. Well, you can't. Too bad. But you can almost undo it generally, and in this case you can undo it completely.

"hg qrefresh src/sys/kernel/pci/pci.c" will refresh the patch (you can list as many files as you want)... And it will leave subr_bus.c in the tree modified. "hg qnew fix-typo" will create a new patch with subr_bus.c...

Splitting patches -- The Hard Case

Sometimes the squirrel is in the same file. Or multiple files. In that case, sometimes it is easier to edit the patch directly... There's a couple of caveats.

% hg qpop the-patch (or hg qpop -a)
% cd .hg/patches
% cp the-patch the-other
% hg add the-other
% cd ../..
% hg commit --mq -m"Snap to pickup new files."
% vi .hg/patches/series
--- add the-other after the-patch

So now you have two copies of the patch. We added the-other and committed to ensure that it gets into your queue (normally qnew does the add). We've added the-other to the series file so when you push the patch back on the queue, it will apply.

Next, well this is a "then a miracle occurs" step. Edit the two patch files to split them in half. emacs diff mode can help. Others swear by other tools, but I've never used anything other than emacs. However you do it, make sure that you get all the bits you want to keep in the right places. Then you can, if you didn't totally mess it up (a very real possibility: this is why I had you commit above before starting: save early, save often, bank what you can as often as you can), hg qpush the-other and get on with your work.

There are all-singing, all-dancing extensions to hg try to cope with this problem. And modes in emacs. However, in the ironic words of the Tom Baker Dr Who "Never trust gimmicky gadgets." I've never found any that save me time over editing the raw patches. Telling you how to do this is beyond the scope of this article, just the mechanics of interacting with hg (which is more scope than I want by a long shot, but here we are).

Fixing merge conflicts in series file

When using multiple machines, and perhaps when the blood level in your caffeine stream is too high, you'll find yourself pulling to a machine that has changes you've not pushed. In the simplest, and most common, version of this you'll just have conflicts in the series file. Your best bet is to edit this file by hand (I use emacs, you can use vi) to resolve the conflict. I've never found a good way to make hg resolve --mark --mq to work, so I go down and dirty and cd to .hg/patchtes and "hg resolve --mark series" directly. Maybe this was bugs in old hg that are fixed in a more latter-day version. Maybe it isn't supposed to work. Once bitten twice shy on some things, eh?

Fixing merge conflicts in queues -- when patches conflict

First off, these are best avoided. Three way merge for patch files is at best confusing and at worst a nightmare.

Second off, I'm not sure I want to write this section.

Still reading? Try http://mercurial.selenic.com/wiki/MqMergePatches and see if it helps. I still have PTSD from my attempts many years ago, but I'm sure they've replaced the robotic brain behind this extension that liked to rip people limb from limb with one that doesn't do that by now. But a quick read suggests more the former than the latter remains. This might help: http://www.ibiblio.org/Dave/Dr-Fun/df9311/df931117.jpg .

Non-goals

Sometimes I get suggestions or requests for info. This section documents them.

"But I want to use real hg branches for my development"

Good for you. But that's not my workflow. Figure out what works and write it up. I had huge issues with rebasing my changes, and with creating clean patches to go upstream when I did that. But if you can make it work, or the horrible bugs in hg have been fixed that prevented this: awesome. Send me a note...

"But I want to use git"

Then use git. The work flow is totally different. Feel free to write up what you find. I can't help you, since I tried git and gave up because I couldn't properly translate all my hg idioms into git-ese. It was easier for me to go with what I knew. But for me, this http://www.ibiblio.org/Dave/Dr-Fun/df9311/df931116.jpg describes my experiences and expected outcomes :)

WarnerLosh/MercurialSetup (last edited 2018-07-18T08:21:37+0000 by KubilayKocak)