Neuron's

Main | Blend on x86 Solaris »
Středa VIII 23, 2006

First look @ Mercurial

Goal

This entry is here to describe my first experience with Mercurial version control system.

I use mutt as my mail agent of choice. Mutt works great for me, just the lack of direct SMTP support is a limitation for me. Luckily not just me, there are already patches adding this functionality. My choice was patch from Brendan Cully located at http://mutt.kublai.com. This patch requires libsasl for authentication. I felt that adding another library just for plain authentication is not necessary, so I modified the original patch to support plain authentication directly. Plus I do have fix (let's call it mark-old) for another issue in my workspace. I'm sorry, this will be a lot of text, and only few (ugly) pictures.

How I did it in the past

Mutt itself uses CVS as it's revision control system. I pulled actual mutt version, applied Brendan's patch and modified it to my needs. I ran cvs up from time to time to keep my repository up-to-date. It was simple to find out what is the difference between my workspace and official cvs - just run cvs diff. However it was much harder to find out what exactly is my addition to Brendan's patch. Also I can't keep revisions of my modifications, since I don't have the commit privilege to mutt CVS.

The "official" Mercurial way

So one not-so-sunny day I asked Mercurial for help. After few moments I got the impression, that Mercurial's answer (for most of questions, really) is to "create many repositories, we're distributed !". Right. So let's have incoming repository which will hold plain cvs tree. Then create two child repositories - the SMTP child containing plain Brendan's patch and mark-old child containing my fix. Now let's take the SMTP child, and make another repository his child - this will contain my changes to Brendan's patch.

Now that looks like good solution at the first glance. I can commit my changes, as every clone has own repository. It's also easy to find what exactly is the difference I did against Brendan's patch - just compare the two repositories. But wait, we don't have command for comparing two repositories. hg diff is not able to compare repositories, only working directory with repository or two revisions of the repository. Moreover if we pull changes from the official tree, the changes gets mixed with my changes, and it will be hard to find my actual work.

(Well, there would be anther possibility. When you update your incoming repository, clone it to new workspace. Then from this new workspace pull all changes from your old working workspace. That way would your changes stay on top all the time. I guess it would work for me, but the "Patch Queues" extension described later does more than this and you don't have to juggle with repositories)

We do have hg export, which picks all the local commits (changesets) and displays them as diffs. That's slightly better, but not there yet. For example, if you commit two changes to the same file, hg export treats it as two changesets, and thus you have two diffs for one file.

Now I got a bit stuck in here. Obviously there would be possibility to use Mercurial in the same way as I did with CVS (do not commit my changes at all), but what would be the advantage then ? I still have the feeling that I overlooked something so obvious ... if that is the case, please enlighten me.

Mercurial "Patch Queues"

While going through Mercurial wiki, I found an extension called 'mq'. It basically let's you define point in history, where ends the official tree, and from where you start adding your changes. Every your changeset is called 'patch'. Moreover it let's you rollback all your 'patches' (so that your working directory returns to the clean state after cloning) and apply them again later. That's very convenient. If you want to pull changes from the parent, just un-apply all your patches, update your workspace from parent and re-apply your changes back.

Seeing is believing

Enough of theory, let's create some repositories. 'mq' extension is shipped with Mercurial, so the only thing left is to enable it (if you haven't done so yet).
$ echo -e "[extensions]\nhgext.mq=\n" >> ~/.hgrc
Create mercurial repository from cvs checkout.
$ cvs -q -d:pserver:anonymous@dev.mutt.org:/cvsroots co mutt
$ mv mutt mutt-incoming
$ cd mutt-incoming
$ hg init .
Ignore CVS directories, and commit everything else to our new repository
$ echo -e "syntax: regexp\n(^|/)CVS($|/)\n" > .hgignore
$ hg status | grep CVS
$ hg add .hgignore
$ hg ci -m "* .hgignore: Ignore CVS directories"
$ hg add
$ hg ci -m "* initial cvs checkout"
The incoming tree is ready, create our working repository
$ cd ..
$ hg clone mutt-incoming mutt-patches
$ cd mutt-patches
Initialize 'mq' extension. '-c' lets us version our patches later.
$ hg qinit -c
Notify Mercurial that my work starts here.
$ hg qnew -m "* http://mutt.kublai.com/patches/patch-cvs20060528.bc.smtp" patch.bc.smtp
First patch will be unmodified Brendan's work. So apply his patch.
$ wget http://mutt.kublai.com/patches/patch-cvs20060528.bc.smtp
$ patch -p1 < patch-cvs20060528.bc.smtp
$ rm patch-cvs20060528.bc.smtp
$ hg status
$ rm *~
$ hg add smtp.c
Tell mercurial to take current changes into account. Put the changes into patch.bc.smtp.
$ hg qrefresh
Second patch will be my own work on the top of Brendan's patch.
$ hg qnew -m "* Plain SMTP authentication" patch.vm.plain_auth
$ cp ~/Dokumenty/mutt/smtp.c smtp.c # copy from my previous CVS repository
$ hg qrefresh
And third patch will be my mark_old fix
$ hg qnew -m "* Implement mark-old for imap folder" patch.vm.mark_old
$ cp ~/Dokumenty/mutt/imap/command.c imap/command.c
$ hg qrefresh

It's not all black and white

While I have to admint that I like the mq way of managing files there are few things which have to be watched on.
  • You have to call qrefresh instead of commit. If you make mistake, you have to fix it (not very hard) before going on.
  • Repository not directly clone able, hg clone does not understand mq (! this is improved in devel version of mq, look for hg qclone)

Comments:

a very intersting and useful entry! one question remains: how do you handle the $id$ very often used in cvs?

Posted by 198.240.213.26 on září 14, 2006 at 05:53 odp. MEST #

Hi,

Well, the answer is, I don't. Mercurial here is just a tool which helps me keep the patches in sync with main (cvs) branch. There won't be any official mutt build from my repository, so I don't care about it.

But you have risen very good point here. The source could have been maid so that it just won't build without proper support for cvs properties ( http://ximbiot.com/cvs/manual/cvs-1.11.22/cvs_12.html#SEC97 ) like $Revision$ or $Id$. In that case I would just have to add another patch on the top of the patch list which fixes this.

Posted by Neuron on září 14, 2006 at 07:12 odp. MEST #

For the 'The "official" Mercurial way' case: You can use 'hg incoming' and 'hg outgoing' to find out about changesets that are in one repository which are not in the other. With the additional option '-M' you can hide merge changesets, which often are less interesting. To get a plain diff between two repositories, you can use: hg clone one common cd common hg pull ../two common hg diff -r tip Or use 'hg clone -U' to not create a working directory (not needed), but then you manually have to specify a second -r option to 'hg diff' to say which is the other head to compare to.

Posted by Thomas Arendsen Hein on březen 11, 2007 at 02:37 odp. MET #

Hi Thomas,

You are right, there are other ways to achieve what I wanted to do. I learned a bit since I wrote this blog and also mercurial got some few amazing power-ups (especially if you use tip of the development branch). Still creating clone to get summary of your work seems to be a bit overhead. I personally started to use mercurial queues (which had few problems fixed since I tried them for the first time). I want to publish few scripts which are helping me to synchronize my patches with development made in other revision systems. Stay tuned :)

Thanks for your comment

--
Neuron

Posted by Neuron on březen 12, 2007 at 01:36 odp. MET #

Hi How do you create a new repository from scratch ? Which cvs clientes are compatible with Mercurial? Is tortoise compatible? thanks paul

Posted by psanz on srpen 02, 2007 at 01:27 odp. MEST #

psanz: 1. just do "hg init foo" to create a repository in the subdirectory "foo". 2. CVS clients are not compatible with Mercurial. 3. Of course TortoiseCVS and TortoiseSVN are not compatible with Mercurial, but you can one of the alternatives, e.g. http://tortoisehg.sourceforge.net/ or some other tools mentioned on the mailing list (as I don't use such GUIs I don't know which one is recommended to use) Generally the project wiki or the mailing list will give you better answers than mine.

Posted by Thomas Arendsen Hein on srpen 04, 2007 at 08:50 dop. MEST #

Hi,

Just to add to Tom's comment, there is nice quickstart guide:

http://www.selenic.com/mercurial/wiki/index.cgi/QuickStart

You will find more helpful documents on the same wiki. The main difference is that Hg does not have separate repository and working copy, but they are always coupled in one directory.

Hth

--
Neuron

Posted by Neuron on srpen 06, 2007 at 10:45 dop. MEST #

Thomas , Tom Firstly thanks for replying. It seems so dam difficult to understand and use .. I`m trying to use mercurial as a control version server on linux (fedora 7) and use a client so that the programmers can use it from windows XP. 1. How do I create the modules ,submodules for that repository? and where on the server end on the clients end buff so confused its so different than cvsnt . Repo 1 module x submodule x1 submodule x2 submodule x3 submodule x4 module y ... ... normally i created the repository at the servers end and the modules from the clients end. (tortoise) and then just forgot about the servers end.

Posted by psanz on srpen 06, 2007 at 11:36 dop. MEST #

Post a Comment:
  • HTML Syntax: NOT allowed