Difference between revisions of "Mercurial Tutorial"

From AlliedModders Wiki
Jump to: navigation, search
(initial import)
 
Line 1: Line 1:
 
Mercurial (Hg) is a distributed version control system.  While it has similarities with [[Subversion Tutorial|Subverison]], it is decentralized in nature.   
 
Mercurial (Hg) is a distributed version control system.  While it has similarities with [[Subversion Tutorial|Subverison]], it is decentralized in nature.   
  
=Essentials=
+
=Getting Started=
 
==Introduction==
 
==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.
 
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.
Line 74: Line 74:
  
 
'''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 absolute paths.
 
'''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 absolute 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, <tt>fae5f33a1bb3</tt>). 
 +
 +
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, <tt>19108:fae5f33a1bb3</tt>.
 +
 +
The latest changeset is called <tt>tip</tt>.
 +
 +
When using commands like "<tt>update</tt>," it is useful to refer to changesets.  You can do this in one of a few ways:
 +
*<tt>tip</tt> - Always refers to the latest changeset.
 +
*Using a revision number.
 +
*Using any subset of the changeset string that uniquely identifies the changeset.  For example, <tt>fa</tt> might not be unique and would not work.  But <tt>fa35</tt> might work for <tt>fae5f33a1bb3</tt>.  If it doesn't, you can pick a different substring.
 +
*Using tags.  Mercurial lets you tag a changeset with a unique alias for convenience (<tt>hg tag</tt>).
  
 
==Workflow==
 
==Workflow==
Line 95: Line 108:
 
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:
 
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:
  
<pre>hg push ssh://[email protected]//hg/sourcemod-central</pre>
+
<pre>hg push ssh://[email protected]//hg/sourcemod-central
 +
searching for changes
 +
adding changesets
 +
adding manifests
 +
adding file changes
 +
added 2 changesets with 2 changes to 2 files
 +
</pre>
  
 
Or, if you have a <tt>default-push</tt> configured:
 
Or, if you have a <tt>default-push</tt> configured:
  
 
<pre>hg push</pre>
 
<pre>hg push</pre>
 +
 +
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.
 +
 +
 +
=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 'X'.
 +
 +
Now Bob wants to pull.  He runs <tt>hg pull</tt> and gets:
 +
 +
<pre>
 +
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)
 +
</pre>
 +
 +
What happened here?  Let's visualize it.  When Alice commits, her repository has gone from A to B like this:
 +
 +
[[Image:Hg_alice_1.png]]
 +
 +
When Bob commits, his repository goes from A to C like this:
 +
 +
[[Image:Hg_bob_1.png]]
 +
 +
When Bob pulls from Alice, he now has '''multiple heads'''.  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.
 +
 +
[[Image:Hg_bob_2.png]]
 +
 +
Bob doesn't want to be in this state, so he '''merges'''.  Merging joins multiple heads back together.  Example:
 +
 +
<pre>hg merge
 +
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
 +
(branch merge, don't forget to commit)</pre>
 +
 +
Merges are changesets, and they must be committed.  Bob commits his change, and now his repository looks like this:
 +
 +
[[Image: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:
 +
 +
<pre>hg push
 +
searching for changes
 +
abort: push creates new remote heads!</pre>
 +
 +
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 <tt>merge</tt> and <tt>commit</tt>) are called '''trivial merges'''. 
 +
 +
  
 
=SSH Authentication=
 
=SSH Authentication=

Revision as of 04:56, 14 September 2008

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
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/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 absolute 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:

  • tip - Always refers to 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/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.


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 'X'.

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

When Bob pulls from Alice, he now has multiple heads. 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 his change, 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.


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).

External Links