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: BasicsSource 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 MercurialHead 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:
This is the name that will be attached to all commits that you make.
Basic commandsWell 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 initThat'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:
#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.cOne very useful command for checking the state of your repository is
hg status:
D:\test>hg status
A hello.cThis 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 commitMercurial 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:
#include <stdio.h>
void main(void)
{
puts("Hello, world!");
puts("Hello again!");
}
If we now check the status,
D:\test>hg status
M hello.cwe 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 1So, in summary, these are the most important commands for working with a local Mercurial repository:
- add new files with hg add
- commit changes with hg commit
- check repository state with hg status
- check 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 operationsUsing .hgignoreLet'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.objMercurial 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:
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
? .hgignoreSo 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 committo get a new revision which now has our
.hgignore file included.
Accessing old revisionsA 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/QuickStarthttp://www.selenic.com/mercurial/wiki/index.cgi/Tutorial
Feel free to ask questions below. I'll bump the thread when new installments are added.