Per Prof. Downing’s suggestions, here is a quick overview of Mercurial:
Installing
Mercurial is easy to install on most platforms:
Binary Packages are available from the Mercurial website for Windows, an array of Linuxes, and Mac.
TortoiseHg: Mercurial integrated with the Windows Explorer (does not provide command-line access). If you prefer working from the Windows GUI, you may prefer this. If you have ever used TortoiseSVN, TortoiseHg is very similar. As of the time of writing the latest version is http://bitbucket.org/tortoisehg/stable/downloads/TortoiseHg-0.8.2-hg-1.3.1-1444a42f6052.exe
Ubuntu
Installing Mercurial is easy via apt-get:
sudo apt-get install mercurial
UTCS Machines
The UTCS machines already have Mercurial 1.3.1 installed.
Basic Usage
Let’s walk through project 1: Collatz, using Mercurial. All of the examples I will give are on the command line. All actions work the same from TortoiseHg. Quick note on formatting:
Commands will be monospace, flushed to the left. Output from a command will also be monospace, but indented
- First, lets create a new Mercurial repository for our project:
mkdir project1 cd project1 hg init
If we list the directory contents, we will see a newly created .hg folder.ls -al drwxr-xr-x 3 jdbernard jdbernard 4096 2009-09-22 10:31 . drwxr-xr-x 4 jdbernard jdbernard 4096 2009-09-22 10:31 .. drwxr-xr-x 3 jdbernard jdbernard 4096 2009-09-22 10:31 .hg
If you looked at an SVN working copy, you would have noticed a similar .svn folder. There are a few important differences between what the .svn and .hg folders contain and mean:- When you did a checkout from SVN, you were not getting the full repository. When you create a Mercurial repository, everything is present. The working copy of a Mercurial repository is the entire repository, including its full history.
- The .svn folder is the SVN administration area. You will see one .svn folder in every directory of your project. The .svn folder is used to track changes and properties of the files in the current folder relative to the central repository maintained somewhere else (Google Projects for example).
- The .hg folder is not just an admin area, it IS the repository. You will also notice that there is only one .hg folder in the root directory.
- Now we’ll grab the initial sources from the links on the Collatz blog post. No hg new commands here, just wget to download stuff.
COLLATZ=http://www.cs.utexas.edu/users/downing/projects/c++/collatz wget $COLLATZ/Collatz.txt wget $COLLATZ/main.c++ wget $COLLATZ/TestCollatz.h wget $COLLATZ/Collatz.in wget $COLLATZ/Collatz.out wget $COLLATZ/TestCollatz.out
- Before we go any further, let’s look at two important commands: hg status and hg log.
- hg status lists all the changes in the working directory which have not been committed.
- hg log is similar to looking at the changelog from SVN. It lists the history of commits, the committer, and the commit message for that commit.
hg status ? Collatz.in ? Collatz.out ? Collatz.txt ? TestCollatz.h ? TestCollatz.out ? main.c++ hg log (nothing here yet)
This illustrates another important point regarding Mercurial. Think of the .hg directory as the repository and the current directory as the working copy. hg status tells us that there are files in the working copy that are not under version control. hg log tells us that the repository has no history at this point. - So let’s add our initial files to the repository. We can either add them one at a time:
hg add Collatz.txt main.c++ TestCollatz.h Collatz.in Collatz.out TestCollatz.out
or we can add the current directory, which recursively adds its contents:hg add . adding Collatz.in adding Collatz.out adding Collatz.txt adding TestCollatz.h adding TestCollatz.out adding main.c++
You can see that if we do not explicitly tell Mercurial which files to add, it will tell us which files it is adding. Let’s see what our working directory looks like now:hg status A Collatz.in A Collatz.out A Collatz.txt A TestCollatz.h A TestCollatz.out A main.c++ hg log (still nothing here)
We see that the ‘?’ changed to ‘A’ for the files we added. In general, the first character from the hg status command indicates the status of the particular file.- ‘A’ means it is present in the working copy but not in the last revision and it will be added once we commit.
- ‘R’ means it is present in the last revision but not in the working copy and it will be removed once we commit.
- ‘M’ means that the file is modified in the working copy relative to the last revision and the modifications will be applied once we commit.
- ‘?’ means that the file is unknown to Mercurial and will be ignored if we do a plain commit.
- So lets start committing:
hg commit -m "Initial commit" hg status (nothing here) hg log changeset: 0:2740894f4023 tag: tip user: Jonathan Bernard date: Tue Sep 22 11:05:57 2009 -0500 summary: Initial commit
Now we see an entry in the log.- changeset: each revision has two names in a Mercurial repository. It has a revision number, left of the colon (’0′ in this case) and a digest value right of the colon. The digest is a hash computed over the changes committed for this revision. This is getting a little ahead of ourselves, but it is fairly common for revision numbers to change. If two people both have their own copy of this repository and they both do a commit before merging, then they will both have different revsions named ’1′. So we cannot always use the revision number as a unique identifier for a revision. The hash, however, does uniquely identify the revision.
- tag: tags in Mercurial operate differently than tags in SVN. I will not go into detail here, but Google provides answers easily.
- user: is the username of the user performing the commit. This will default to your unix username.
- date: time stamp of when the commit was performed.
- summary: is the beginning of the commit message you provided. The example I gave provides a short commit message on the command line. If you do not provide a message Mercurial will open an editor (looks at either the EDITOR or HGEDITOR evironment variables) for you to write a commit message. The summary shows the commit message up to the first period. So a good practice when writing longer commit messages is to provide a summary of the commit in one sentence before writing out the details.
- Let’s make some more changes and commit them. Edit main.c++ and add the following just before the definition of eval:
/** * cycleLength * Computes the cycle length for the given number */ int cycleLength(unsigned long n) { assert(n > 0 && n <= 1000000); int temp = n; int cl = 1; while(n != 1){ if(n%2 == 0){ /* even */ cl++; n = n >> 1; } else{ /* odd */ n = n + (n >> 1) + 1; cl = cl + 2; } } assert(cl > 0); return cl; }Before we commit, let’s check the status:hg status M main.c++
As expected, main is modified. Let’s commit:hg commit -m "Added cycleLength function." hg log changeset: 1:95f4400ad38a tag: tip user: Jonathan Bernard date: Wed Sep 23 08:06:01 2009 -0500 summary: Added cycleLength function. changeset: 0:2740894f4023 user: Jonathan Bernard date: Tue Sep 22 11:05:57 2009 -0500 summary: Initial commit
Notice that the revision number for the latest commit is revision 1 and the ‘tip’ tag automatically moved to the latest revision. One other command I want to show you before moving on is the hg diff command.hg diff (nothing here) hg diff -r 0 -r 1 diff -r 2740894f4023 -r 95f4400ad38a main.c++ --- a/main.c++ Tue Sep 22 11:05:57 2009 -0500 +++ b/main.c++ Wed Sep 23 08:06:01 2009 -0500 @@ -68,6 +68,32 @@ in >> j; return true;} +/** + * cycleLength + * Computes the cycle length for the given number + */ +int cycleLength(unsigned long n) { + + assert(n > 0 && n <= 1000000); + int temp = n; + int cl = 1; + + while(n != 1){ + if(n%2 == 0){ /* even */ + cl++; + n = n >> 1; + } + else{ /* odd */ + n = n + (n >> 1) + 1; + cl = cl + 2; + } + } + + assert(cl > 0); + + return cl; +} + // ---- // eval // ----Two things to note: the first call to hg diff printed nothing. By default, hg diff shows the differences between the working directory and the latest revision. Since we just committed, there are no differences. The second time I called it passing two revision numbers. Obviously, those two revisions are being compared. Instead of a revision number, I could have given it a tag (for rev. 1) or the unique name for the revision. You can change the diff program used. That is outside the scope of this post, look into the Extdiff extension and the general Mercurial merge program options. - Say we wanted to go back to the initial commit, before our latest modification. We want to ‘checkout’ a previous revision.
hg update -r 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved hg status (nothing printed) hg log changeset: 1:95f4400ad38a tag: tip user: Jonathan Bernard date: Wed Sep 23 08:06:01 2009 -0500 summary: Added cycleLength function. changeset: 0:2740894f4023 user: Jonathan Bernard date: Tue Sep 22 11:05:57 2009 -0500 summary: Initial commit hg identify 2740894f4023
hg update updates our working directory to reflect the revision we gave it. If we do not give it a revision, it defaults to the latest revision. hg status shows no modifications in the working directory relative to the repository, but hg log shows us that the repository is unchanged. If we look at mani.c++, we can clearly see that the cycleLength function we added is missing. So we see that hg status (and diff, etc) compare to the revision that is currently checked out. hg identify tells us which revision is currently in the working directory. Let’s go back to the latest revision.hg up 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
For fun, let’s change main.c++ and then try to go back to revision 0.echo "This is my nonsensical change." > main.c++ hg status M main.c++ hg up -r 0 0 files updated, 0 files merged, 0 files removed, 0 files unresolved hg status M main.c++ hg identify 2740894f4023+
The important thing to notice is that it did not revert our modified main.c++ file. Mercurial does not want to wipe out changes that we have made if they have not been committed. Also, hg identify adds a ‘+’ to indicate that there are modifications in the working directory. Of course, we can tell Mercurial that we don’t care about our current modifications, we just want revision 0.hg up -r 0 -C 1 files updated, 0 files merged, 0 files removed, 0 files unresolved hg status (no output)
More Information
Mercurial has an excellent built-in help system. Running hg help gives you a list of available commands and hg help <command> will give you detailed information about a particular command.
Additionally, there are many excellent tutorials and much fine documentation available online. My recommendations for further reading are:
- Quick Start
- Tutorial on the Mercurial website: a more detailed version of what I’ve done here.
- Understanding Mercurial: a more detailed explanation of what is going on when you create revisions, etc.
- Mercurial: The Definitive Guide: an excellent book on Mercurial. Great detail.
- HOWTOs: common uses of Mercurial
- Branches: an explanation of branching in Mercurial
Final Note: most of this discussion of Mercurial also applies to Git. The workflow and repository concepts are very similar. The commands themselves overlap more often than not. If you know Git, there is almost no learning curve moving to Mercurial and if you know Mercurial, there is almost no learning curve moving to Git.
[...] http://blog.jdbernard.com/2009/09/quick-guide-to-mercurial/ [...]