Mercurial tutorial

From AlliedModders Wiki
Jump to: navigation, search

Mercurial (Hg) is a distributed version control system. While it has similarities with Subverison, it is decentralized in nature.

Getting Started

Introduction

Mercurial keeps source code in repositories. Changes to the source code are made inside the repository and are periodically committed. When you commit changes, they are wrapped into a changeset. A repository is the sum of all changesets committed over time.

Unlike Subversion, Mercurial has no concept of a "master repository." Each user owns a complete local copy of the repository, and they can commit to it without needing any permissions. Users can share changesets by pulling from other people's repositories, or pushing to other repositories (which may require special privileges).

One very noticeable side effect is that when you commit changes in Mercurial, you do not need Internet access. You only need Internet access if want to push changesets to a remote repository. This means you can make as many commits as you want before pushing.

Now for the contradiction: You CAN have a master repository with Mercurial. For some projects it is ideal to have a reference copy. There is no difference between your copy and the master copy -- how it is managed is simply a matter of permissions and policy. For example, AlliedModders has a "sourcemod-central" repository where developers push their changesets. Only SourceMod developers can push to this copy, but anyone can copy it or pull its changes.

Basic Commands

These are a few essential Mercurial operations:

  • clone - Copies or downloads a repository.
  • add - Adds a file or directory to the local source tree.
  • remove - Removes a file or directory from the local source tree.
  • commit - Commits any local changes to your local source tree.
  • pull - Retrieves changesets from another repository.
  • update - Updates source code with all pending pulled changes.
  • push - Pushes your changesets to a remote repository.
  • merge - Merges two repositories together (explained later).

Installing

The author of this article uses command line Mercurial (on both Linux and Windows). You can download both the command line tools at the Mercurial Site, and there is a TortoiseHg graphical tool for those who would like to try it.

Getting a Repository

To retrieve a repository, you must use the "clone" command. You can clone a local or remote repository. Examples:

hg clone http://hg.alliedmods.net/sourcemod-central sourcemod-central

You can clone a repository from any location, even locally.

hg clone sourcemod-central sourcemod-copy

You can also create a new repository:

mkdir project
cd project
hg init

This will create a blank repository and initialize it with Mercurial.

Configuration

Mercurial lets you configure default settings for all repositories and settings specific to one repository.

On Linux, the main configuration file is ~/.hgrc. On Windows 2003/XP and prior, it is C:\Documents and Settings\<account>\Mercurial.ini. On Windows Vista, it is C:\Users\<account>\Mercurial.ini. If the file does not exist, you can create it.

Per-repository configuration is done in <repository>\.hg\hgrc.

Identity

Mercurial lets you configure an identity to associate with your changesets. By default it uses the name of the account your computer is logged in as. Many projects (AlliedModders included) use "Firstname Lastname <email>" instead.

To set up your identity, open either your hgrc file (either the main or local one). Look for a "[ui]" section (or add a new one), and configure something like this:

[ui]
username = David Anderson <[email protected]>

Push and Pull Locations

By default, all "pull" operations on a repository occur from the original location you cloned from. You can change that location by opening up <repository>\.hg\hgrc and looking at the "[paths]" section. You will see a lines like:

[paths]
default = http://hg.alliedmods.net/sourcemod-central

If you are going to be pushing to one main repository fairly often, you may want to set up a default push. For example:

[paths]
default = http://hg.alliedmods.net/sourcemod-central
default-push = ssh://[email protected]@hg.alliedmods.net/sourcemod-central

Note: Mercurial uses double slashes to specify an absolute file-system path. If you are using SSH with logical paths, you only need one slash. For more information about this, visit the Mercurial documentation. AlliedModders uses logical paths.

Changesets

Unlike Subversion, Mercurial can't use version numbers to uniquely identify a changeset. This is because sequential IDs may be duplicated across repositories. Instead, Mercurial uses 160-bit changeset identifiers. These are represented in hexadecimal (for example, fae5f33a1bb3).

Mercurial also uses local revision numbers which are sequential IDs. These are local to a repository and have no meaning in other repositories. They are mainly provided for convenience and usability. A full changeset in a local repository can be represented by "revision:changeset". For example, 19108:fae5f33a1bb3.

The latest changeset is called tip.

When using commands like "update," it is useful to refer to changesets. You can do this in one of a few ways:

  • Using the tip keyword. This is the latest changeset.
  • Using a revision number.
  • Using any subset of the changeset string that uniquely identifies the changeset. For example, fa might not be unique and would not work. But fa35 might work for fae5f33a1bb3. If it doesn't, you can pick a different substring.
  • Using tags. Mercurial lets you tag a changeset with a unique alias for convenience (hg tag).

Workflow

Let's go through a typical session of developing with Mercurial. If you are working against a project that has a centralized copy, you may want to make sure you're up to date first. This means pulling its changes and then updating.

For example:

hg pull
hg update

This will grab the remote changes from the location you first cloned from. Then it will apply the changes. You can do this in one go with:

hg pull -u

You can also pull from a different repository if you want:

hg pull -u http://some.other.site/sourcemod-central

Now let's say you make some changes. You edit file A.cpp and you want to commit your change. You can do this with:

hg commit

An editor will pop-up asking you to write a message describing your change. This is required.

Now you edit B.cpp and commit again. You're done for the day, and you have two changesets sitting in your repository. You want to push these upstream. For example:

hg push ssh://[email protected]@hg.alliedmods.net/sourcemod-central
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files

Or, if you have a default-push configured:

hg push

If there is more than one person sharing and working on the code base, eventually you will need to deal with merges. This is an extremely powerful (but also deceptively complex) aspect to Mercurial, and it is extremely important to read the next session.

Note: Since only the first line of a commit message is shown by the hg log command by default, it is highly recommended that the first line of your message be able to stand alone. For example, you might have a short summary of the commit on the first line that says "Added functions for creating dynamic hooks on virtual functions." Further lines could give more details on the changes involved such as a list of all the functions that were added.

Merging and You

Introduction

Imagine this scenario:

  • Alice and Bob both have a copy of the source code at revision 'A'.
  • Alice commits and pushes a change. The main repository is now at changeset 'B'.
  • Bob commits a change and is at changeset 'C'.

Now Bob wants to pull. He runs hg pull and gets:

searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)

What happened here? Let's visualize it. When Alice commits, her repository has gone from A to B like this:

Hg alice 1.png

When Bob commits, his repository goes from A to C like this:

Hg bob 1.png

After Bob pulls from Alice, his repository has multiple heads of development. He's got two forks of the same codebase in his repository. You can visualize it like this. The blue arrow is Bob's changes, and the red arrow is Alice's changes. They diverge from A.

Hg bob 2.png

Bob doesn't want to be in this state, so he merges. Merging joins multiple heads back together. Example:

hg merge
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

Merges are changesets, and they must be committed. Bob commits the merge, and now his repository looks like this:

Hg bob 3.png

'D' is the changeset that joins the two heads back together. Now Bob can safely push his changes back, and Alice can pull from Bob.

The above example happened from Bob trying to pull. But if Bob had tried to push, he would have gotten a nasty error message:

hg push
searching for changes
abort: push creates new remote heads!

This means Bob's push would have introduced multiple heads on the target repository, rather than his own. This is generally a bad idea and as a policy projects often don't allow it at all. Bob has to pull, update, and resolve the merge.

Note: For most merges, it is a good policy to write simple commit messages. For example, "Merge." would suffice.

Conflicts

Merges which don't require any work on your part (other than running merge and commit) are called trivial merges.

It is possible that Alice and Bob edited the same file however. In many cases, Mercurial will be smart enough to handle this. If Mercurial can't figure it out though -- you have to do manually merge the difference between the two files. This is best done with a three-way merge program.

An example of a merge conflict:

hg merge
merging crab.cpp
merge: warning: conflicts during merge
merging crab.cpp failed!

Conflicts must be resolved immediately. You should never leave your source code in a conflicted state. If you don't have a merge program installed, the conflicted file(s) will be marked up. For example, crab.cpp might contain lines like these for each conflict:

<<<<<<< /tmp/conflict/crab.cpp
void function() {
=======
int function() {
>>>>>>> /tmp/crab.cpp

The two "sides" of the ======= bar represent the local and incoming changes. You should delete the markup and decide how to resolve the two sides.

If you want to use and configure a merge program, see MergeProgram at the Mercurial docs. Though it's not listed, you can also use Beyond Compare (the author's personal choice).


Other Commands

hg diff is very useful for reviewing changes before you commit them. You can also use it for generating patches.

hg log lets you view the changeset history.

hg glog lets you view an ASCII graph of the changeset history.

hg identity tells you the state of your repository.

hg status shows you any changed or unknown files.

hg status -m shows you any changed files.

hg revert will remove any changes you've made to one or more files. It will save the originals as the same file name with .orig at the end.

For many commands you can specify a folder within the repository to only affect that folder and its children. This can speed up slow operations.


SSH Authentication

If you are accessing your repository via SSH using the ssh protocol, entering your password is annoying and could be a security risk. You should look into using SSH Keys (full instructions are provided).


AlliedModders

AlliedModders has a public Mercurial listing at http://hg.alliedmods.net/.

Anyone can clone or pull from these. Only developers with SSH access can push. Developers should use the following URL formula for pushing:
ssh://[email protected]/PROJECT

SSH keys are required for security. You should also use the "Firstname Lastname <email>" authorship style. It is a good idea to configure this ahead of time (read the configuration section above).

It is also highly recommended that long commit messages be split with a short summary on the first line and details on further lines. (See the note at the bottom of the Workflow section above.)

Note that unlike Subversion, you cannot checkout a folder within a repository. This is by design.

External Links