Mercurial for Subversion Users
Mercurial
is a distributed version control system written in Python that is gaining popularity. As of Pylons 0.9.6, the Pylons development code is kept in a Mercurial repository. We'll assume you have Mercurial installed and a command-line console open. All examples are on Linux Ubuntu.
Mercurial is intentionally similar to Subversion in its command syntax, but it has some unfamiliar concepts. This article is based on the Mercurial book
and usage chart
. I'd recommend reading the book after this article because it contains several tips not covered here. The Mercurial site also contains other good tutorials and references. "hg help" and "hg help COMMAND" give exensive usage notes.
Checkout
Subversion's .svn directory contains only the previous revision and some administrative files. Mercurial's .hg directory contains the entire history of the project – a complete and independent repository. You could put it on a server and falsely call it the official Pylons repository, although that wouldn't be nice and other users would probably ignore you. Another difference is that Mercurial has only one .hg directory at the top level, while Subversion has a .svn directory inside every subdirectory. Subversion keeps a copy of each original file in its .svn directories, which causes false hits if you grep the repository for a string.
Two nice features of the .hg directory:
- There's only one per project, while Subversion puts an .svn directory in every subdirectory. This makes it easier to export the directory or recursively list the filenames.
- .hg contains compressed changesets, while .svn contains a backup copy of each file. This means you don't get false hits when you grep the project for all files containing a string.
The .hgignore file lists filename patterns which should not be version controlled, same as .cvsignore in CVS. Subversion uses an "svn:ignore" property instead of an ignore file.
Making changes
These commands work the same as in Subversion. "hg status" shows which files have uncommitted changes. "hg commit" commits the changes to the local repository and creates a new revision (called a changeset). Use "hg revert" to undo uncommitted changes. Before committing you should set your username; see section 2.7.1 in the Mercurial book.
Note: many of these commands and those below accept a filename argument to operate only on a certain file/directory, and a revision number or tag name. The revision or tag may or may not have an -r option preceding it, depending on the command.
Updating
Sometime later you'll want to pick up updates from the central repository.
Here things get a little different because of the multiple repositories involved. Pulling means copying changesets from the central repository into your local one. It puts them on a branch to keep them separate from your locally-committed changes. Updating means making your working directory match a repository version (usually the latest). Merging means copying changes from the upstream branch into your working directory so they can be committed onto the local branch. Thus, "svn update" is equivalent to "hg pull" plus one of the other commands.
"hg update" makes your sandbox match a particular revision; it fails if there are any uncommitted changes. "hg pull -u" is a shorthand for "hg pull" plus "hg update". This is the command you want to use if you're only reading the repository and not making changes.
"hg update -C" overwrites (clobbers) uncommitted changes.
"hg fetch" intelligently combines "hg pull + hg update" or "hg pull + hg merge". It's not enabled by default; you have to modify your .hgrc file as described in section 3.3 of the Mercurial manual to use it.
"hg incoming" tells what changes "hg pull" would do, without actually doing them.
Conflicts
A conflict happens when you've modified certain lines in a file and somebody else has modified those same lines in a different way. Mercurial handles this by putting both changes into the working file surrounded by "merge markers", similar to a diff report. It reports this as a "failed merge; N files unresolved". You should manually delete the undesired lines, or run "hgmerge" or "kdiff3" as described in section 3,2 of the Mercurial book. Unlike Subversion, there are no extra files or a "resolved" command. Simply edit the files as desired and commit them.
Revision numbers and time traveling
A typical log entry looks like this:
Mercurial calls a revision a "changeset"; i.e., the delta of all the changes in all files between that revision and the previous one (its parent). That's exactly what a Subversion revision is, so the difference is merely the terminology. Each changeset has a local ID (a consecutive integer, here "4") and a universal ID (a hexidecimal number, here "b57f9a090b62"). The local ID is valid only within this repository. The universal ID is valid among all repositories that were cloned off each other. Always use the universal ID when sharing/discussing changesets with others.
"tip" is a built-in tag referring to the latest revision created, either by "hg commit" or "hg pull". Use "hg tip" to see the tip's log entry.
A "head" is the latest revision on each branch. Thus there can be multiple heads simultaneously but only one tip. Remember that "hg pull" creates a branch. You can create your own branch with "hg branch", though more commonly users clone a new repository instead. Use "hg branches" to see all the branches, and "hg heads" to see all the heads. Note that Subversion's HEAD tag is not equivalent to Mercurial's concept of "head"; it's closer to "tip", though not identical because the tip can be on a branch.
You can create your own tag with "hg tag". You can tag only a committed revision, so you must commit first and then tag rather than doing both simultaneously. Use "hg tags" to see all existing tags.
If you decide to abandon the latest changeset you've committed, use "hg rollback" to uncommit it. This cannot be used to uncommit older changesets, or to recall a changeset you've shared with others. If you decide to abandon a series of changesets, "hg update" to the desired version and commit it (creating a new changeset after the latest).
It appears to be impossible to "hg update" a single file to an older revision; you can only update the entire current directory. To restore an individual file to an older version, "hg cat" it, then commit it.
Other commands
"hg add" and "hg rm" work the same as their Subversion counterparts.
"hg archive" is the equivalent of "svn export".
"hg cp" works like "svn cp" except that it tries to be smart about subsequent changes to the original. If you commit a change to the original file and the copy file has never been independently modified, the change will automatically be propagated to the copy file. This allows unmodified copy files to remain in step with their originals. But if you commit a change to the copy file, it stops any future propagations to it. Subversion never propagates changes to a copied file.
If you modify a file and then "hg cp" it, and then modify it some more without committing, the copy will contain the uncommitted changes up to the "hg cp" but not those afterward. To avoid confusion on this point, commit immediately before and after "hg cp" and/or "hg mv".
"hg locate" helps you find files. "hg grep" helps you find text in files.
Submitting patches to Pylons
Create a ticket in TRAC.
First, commit your patches to your local repository:
Create your patch by running "hg bundle". This will create an archive of all your changes, that can easily be applied to trunk/tip.
Make sure you've got your name set in ".hgrc or mercurial.ini" as seen below.
Upload the bundle to the TRAC ticket.