Version control with Mercurial

(1/5) > >>

muku:
There seems to be some interest in a tutorial on version control, so I thought I'd write something on my current favorite SCM, Mercurial. Since I'm a bit starved on time, I'll do this in installments. The first one is just on installation and basic operations, but I'll hopefully get to the more interesting stuff soon.

Part 1: Basics

Source Code Management (SCM) systems give you a way to handle source code repositories (or any other set of preferrably text-based files) more efficiently. In recent years, Distributed SCMs (or DSCM) have gained wide-spread acceptance for their light-weight approach and excellent scalability. Some notable DSCMs are Git, Darcs, Bazaar and Mercurial.

If you have already used classic SCMs like CVS or Subversion, the major difference to keep in mind is this: in distributed systems, there is no central repository. All repositories are created equal and carry the full history of all revisions within them.

For this tutorial, we will use Mercurial because it is more portable than Git and faster than Bazaar.

Installing Mercurial

Head on over to http://www.selenic.com/mercurial/wiki/index.cgi/BinaryPackages and download and install the binary package for your platform. If you are on Windows, you probably also have to change the PATH environment variable in order to include the Mercurial directory since we will be using it from the command line. There are GUIs available like TortoiseHg for Windows, but I don't personally use them. Anyway, if you understand the basics covered here, using a GUI should come naturally.

One last thing you might want to do before we start is setting a username. For this you have to find your Mercurial configuration file (at ~/.hgrc on UNIX-like systems, at %USERPROFILE%\Mercurial.ini on Windows) and add a line like the following to the UI section:

Code:

[ui]
username = John Doe <[email protected]>


This is the name that will be attached to all commits that you make.

Basic commands

Well now, let's get started. Drop into a terminal window/command prompt/whatever your OS chooses to call it, create a new directory (I'll call it test) and cd to it. All Mercurial commands start with hg. To create a new repository, simply type

hg init

That's it. If you got no response, everything worked fine and our directory now contains a repository. There is no remote server involved, no background service to handle your files, just a self-contained repository right there in our directory.

Of course, our repository doesn't have any files yet. To remedy that, create a file, say hello.c, by whatever means you feel comfortable with and add some code to it, let's say the classic hello world example:

Code:

#include <stdio.h>

void main(void)
{
puts("Hello, world!");
}


We still have to tell Mercurial that we want it to track this file, so we issue the command

hg add hello.c

One very useful command for checking the state of your repository is hg status:

D:\test>hg status
A hello.c

This tells us that the repository contains one file, hello.c, and the A in front of it informs us that the file will be added with the next commit. So let's do that. Whenever you have finished work on a new feature or have fixed a bug in your software, you should commit your changes to the repository with an appropriate commit message. To commit, type

hg commit

Mercurial will bring up an appropriate editor for you to type your commit message in. You can ignore the additional lines that your editor will show; just type in the first line and you'll be fine. In this case I'll just type "Initial commit.", hit save and quit the editor. Mercurial now takes all uncommitted changes from the working directory and stuffs them into a new changeset. (Note: If you ever mess up and want to abort a commit, simply quit the editor without saving.)

If we now again check the status of our repository,

D:\test>hg status


we see that there are no longer any pending changes to be committed. Also, if we request a log of all changes so far with hg log, we get


D:\test>hg log
changeset:   0:293ec2344b39
tag:         tip
user:        Clemens <...>
date:        Fri Oct 10 02:29:02 2008 +0200
summary:     Initial commit.


So we see that there has been one commit, we get the user who did the commit as well as its date and comment, and we see that the corresponding revision has the number 0 (the bit before the : in the changeset number).

Now, let's change our code a little. Open up hello.c in your editor and make it read like this:

Code:

#include <stdio.h>

void main(void)
{
puts("Hello, world!");
puts("Hello again!");
}

If we now check the status,

D:\test>hg status
M hello.c

we see that Mercurial recognizes that our file has been modified (M). If you want to see the changes you have made in detail, you can use the diff command:


D:\test>hg diff
diff -r 293ec2344b39 hello.c
--- a/hello.c   Fri Oct 10 02:29:02 2008 +0200
+++ b/hello.c   Fri Oct 10 02:40:45 2008 +0200
@@ -3,4 +3,5 @@
 void main(void)
 {
        puts("Hello, world!");
+       puts("Hello again!");
 }

Finally, let's commit these new changes by issuing hg commit. After that is done, we can again check the log of our repository:


D:\test>hg log
changeset:   1:6467ebe7dd54
tag:         tip
user:        Clemens <...>
date:        Fri Oct 10 02:41:55 2008 +0200
summary:     Added a second hello message.

changeset:   0:293ec2344b39
user:        Clemens <...>
date:        Fri Oct 10 02:29:02 2008 +0200
summary:     Initial commit.


Summary of part 1

So, in summary, these are the most important commands for working with a local Mercurial repository:
add new files with hg addcommit changes with hg commitcheck repository state with hg statuscheck detailed changes with hg diff
Play around with them for a bit to get comfortable with them. You might also need hg remove for deleting files and hg rename for moving/renaming files. Simply typing hg will give you a list of the most important commands.

Note that all commands can be abbreviated as long as they are unique, so you can e.g. say hg st instead of hg status.


Part 2: Advanced operations

Using .hgignore

Let's say we work with the repository from the end of part 1 and have compiled our Hello World application. This might have resulted in an object file hello.obj and an executable hello.exe (mentally adjust for whatever extensions your platform uses). Let's do another hg status:

D:\test>hg stat
? hello.exe
? hello.obj

Mercurial realizes there are two new files, but it's not sure what to do about them. If you simply leave it alone, it will not version those files, which is what you typically want for your binaries. On the other hand, if you issue a hg add command without further arguments, Mercurial will add all files marked with ? to the repository. This can be helpful when you want to add a large batch of files to the repository all at once, but we certainly want to exclude binaries from that process. To do that, let's add a file with the magic name .hgignore (note the leading period) and the following contents to the repository's root directory:

Code:

syntax: glob

*.obj
*.exe
bin/*


The first line gives the type of syntax we want to use. glob is basically what you would use at a shell, and regexp for regular expressions is also supported. You can even mix them in the same file if you want.

The rest of the file is just a straightforward list of file name patterns we want Mercurial to ignore. As you can see from the last file, we can also easily tell it to exclude entire directories from versioning. (If your platform doesn't use a specific extension for executables, and you don't keep the executable in some specific subdirectory, you might have to add your executable's file name in full to .hgignore.)

That should do the trick. If we now issue another status command, we get

D:\test>hg stat
? .hgignore

So Mercurial now rightfully ignores our binaries; it's however still unsure what to do about the .hgignore file itself. It's not a bad idea to version that file, so simply do

D:\test>hg add
adding .hgignore

D:\test>hg commit

to get a new revision which now has our .hgignore file included.

Accessing old revisions

A version control system would hardly deserve its name if it wouldn't allow you to go "back in time," so to speak, in order to view older revisions of your code. The simplest way to do this with Mercurial is the hg update command.

Let's again continue from the repository we built up over the previous sections. Our source file hello.c currently looks as follows:

D:\test>type hello.c
#include <stdio.h>

void main(void)
{
        puts("Hello, world!");
        puts("Hello again!");
}

Assume that we want to go back to the original version of our repository, identified by revision number 0 (you can get these from the output of hg log, as described before). That's easy enough:

D:\test>hg update 0
1 files updated, 0 files merged, 1 files removed, 0 files unresolved

D:\test>type hello.c
#include <stdio.h>

void main(void)
{
        puts("Hello, world!");
}

It's magic! The source file has gone back to the state it was in the original commit. You will also notice that the .hgignore file has vanished from the directory since it wasn't yet a part of the repository in revision 0. Of course these changes aren't lost. In order to go back to the latest revision (also called the "tip"), we simply do:

D:\test>hg update
2 files updated, 0 files merged, 0 files removed, 0 files unresolved

D:\test>type hello.c
#include <stdio.h>

void main(void)
{
        puts("Hello, world!");
        puts("Hello again!");
}

We are back where we started; also, the .hgignore file has magically reappeared. There is an important observation to be made here: Mercurial never assigns revision numbers to individual files (as e.g. CVS does), but always to the state of the repository as a whole.

When you start making changes to your repository, always make sure that you are at the tip, unless you indeed want to branch off from an earlier revision (which can be very useful and is in fact one of the strengths of distributed SCMs).

[... to be continued ...]


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


Feel free to ask questions below. I'll bump the thread when new installments are added.

increpare:
That was...painless :)  Next step: copying to and from a 'central' repository?

Actually: here's a question.  I have many many projects.  Should I start right away creating reps for each of them, or is there some smart way to go about dealing with having many different ones?

muku:
Quote from: increpare on October 10, 2008, 03:59:54 PM

That was...painless :)  Next step: copying to and from a 'central' repository?

Yes, I guess I'll do that next. Or... some more advanced repo operations? Ah, I hope I'll get to it all in due time.

Quote

Actually: here's a question.  I have many many projects.  Should I start right away creating reps for each of them, or is there some smart way to go about dealing with having many different ones?


I'm not quite sure what you mean... are you thinking of automating the process of checking your files into a repository? It's very easy: a hg init in the relevant directory to set up a repo, a hg add (without further arguments) to add all files, and a hg commit to commit them. The only catch here is that hg add will by default add all files, even stuff you don't want versioned like binaries and such, but once we get to .hgignore files (which I wanted to cover next) that will be resolved too.

Or, if that was your question: you should definitely have one repo per project, and not one huge über-repo containing all your projects. And it also doesn't matter where they are in your file system, they are all self-contained.

increpare:
Advanced repo operations would also be good.  I'll let you judge!

I meant that I have like, loads of project directories, and .... .hgignore sounds like what I should learn about before I start manually putting them into their respective archives.  Might save me some work?

Quote from: muku on October 10, 2008, 04:36:11 PM

Or, if that was your question: you should definitely have one repo per project, and not one huge über-repo containing all your projects.

That question wasn't actively on my mind, but I have wondered about it before ;)  So...thanks for the response...

muku:
Added a part about .hgignore.

Navigation

[0] Message Index

[#] Next page