Introduction

Mercurial

Mercurial is a great distributed version control system written in Python. It is a "fast, lightweight source control management system designed for efficient handling of very large distributed projects".1 It is used by such projects as Aptitude, Mozilla, OpenJDK, OpenSolaris, Python, and Xen, among many others.2 However, I have always found that hosting Mercurial repositories is painful. There are many options, including CGI/FastCGI and SSH based approaches. But none easily provided what I was looking for: anonymous cloning and use of the web interface, with HTTP authorization required for pushing.

hg serve

hg serve is a Mercurial command that starts a lightweight, built-in Web server. However, it provides no authentication.

Nginx

Nginx is a lightweight, fast web server and proxy. It powers WordPress.com, Hulu, Github, and Ohloh, among others3, and is the fifth most popular server on the web today.4 It was already in heavy use on my server, so I decided to find a way to solve my Mercurial hosting problems with it.

The first line simply starts a server directive, inside which configuration for that server is defined. The next lines are standard directives for any server block. The limit_except block says to do the following for all requests except GETs: set the authentication realm to "Restricted", (or whatever you want to call it), use the specified htpasswd file for authentication (created in the next section), and then proxy to port 8080 on localhost if authentication succeeds. Since clones and web interface views are GETs, this means you will not have to be authorized for them. The proxy_pass directive simply needs to point at whatever port you have hg serve running on.

Configuring htpasswd

htpasswd is a utility used to manage flat files that store usernames and passwords for basic HTTP authentication. It is most likely provided in whatever package in your distribution provides the Apache server. To use it, cd into the directory you specified for auth_basic_user_file directive, and run htpasswd -c [file-name] [username] and enter a password. This creates (-c) an htpasswd file with the specified file name, with a user identified by the provided password. If you need to add another user, simply cd back to that directory and run htpasswd [file-name] [new-username].

Configuring Mercurial

The final step is to configure Mercurial. cd into the root of where you want your repositories to be served from, and create the hgweb.config file as discussed here. Then, inside each repository, modify /.hg/hgrc to contain the following in the [web] section:

push_ssl = false
allow_push = *

This disables SSL, and turns off the Mercurial repository trying to authenticate, since Nginx is taking care of that.

Launch

Now, in the directory where your repositories are stored and your hgweb.config is, run hg serve --webdir-conf hgweb.config (the -d option, which daemonizes the process, is also useful). If all went well, you should be able to browse your repositories via the web interface and clone repositories with no authentication, but when it comes time to push you will need a username and password from the htpasswd file.

Caveats

There are drawbacks to this approach. First, authentication is done over HTTP, not HTTPS. For me, hosting personal projects, this isn't a big deal. Second, authentication is done site wide, so once someone has push privileges to one repository they have them to all. This is easily worked around by creating a location /[repo-name] block for each repository, and point auth_basic_user_file to a different htpasswd file for each repository.

Discussion: Hosting Mercurial Repositories with Nginx

This is an awesome post Jason. Recent converts to Mercurial like myself appreciate it. I have one question, however. Is there any limitation in Mercurial that would prevent me from pushing to the Mercurial repo with SSL enabled? I've setup SSL in Nginx using the Slicehost tutorials; it was not difficult.