Mercurial scripting with python

Do you mean this documentation?
Note that, as stated in that page, there is no official API, because they still reserve the right to change it at any time. But you can see the list of changes in the last few versions, it is not very extensive.


If you are using Python 2, you want to use hglib.

I don't know what to use if you're using Python 3, sorry. Probably hgapi.

Contents of this answer

  • Mercurial's APIs
  • How to use hglib
  • Why hglib is the best choice for Python 2 users
  • If you're writing a hook, that discouraged internal interface is awfully convenient

Mercurial's APIs

Mercurial has two official APIs.

  1. The Mercurial command server. You can talk to it from Python 2 using the hglib (wiki, PyPI) package, which is maintained by the Mercurial team.
  2. Mercurial's command-line interface. You can talk to it via subprocess, or hgapi, or somesuch.

How to use hglib

Installation:

pip install python-hglib

Usage:

import hglib
client = hglib.open("/path/to/repo")

commit = client.log("tip")
print commit.author

More usage information on the hglib wiki page.

Why hglib is the best choice for Python 2 users

Because it is maintained by the Mercurial team, and it is what the Mercurial team recommend for interfacing with Mercurial.

From Mercurial's wiki, the following statement on interfacing with Mercurial:

For the vast majority of third party code, the best approach is to use Mercurial's published, documented, and stable API: the command line interface. Alternately, use the CommandServer or the libraries which are based on it to get a fast, stable, language-neutral interface.

From the command server page:

[The command server allows] third-party applications and libraries to communicate with Mercurial over a pipe that eliminates the per-command start-up overhead. Libraries can then encapsulate the command generation and parsing to present a language-appropriate API to these commands.

The Python interface to the Mercurial command-server, as said, is hglib.

The per-command overhead of the command line interface is no joke, by the way. I once built a very small test suite (only about 5 tests) that used hg via subprocess to create, commit by commit, a handful of repos with e.g. merge situations. Throughout the project, the runtime of suite stayed between 5 to 30 seconds, with nearly all time spent in the hg calls.

If you're writing a hook, that discouraged internal interface is awfully convenient

The signature of a Python hook function is like so:

# In the hgrc:
# [hooks]
# preupdate.my_hook = python:/path/to/file.py:my_hook

def my_hook(
    ui, repo, hooktype, 
    ... hook-specific args, find them in `hg help config` ..., 
    **kwargs)

ui and repo are part of the aforementioned discouraged unofficial internal API. The fact that they are right there in your function args makes them terribly convenient to use, such as in this example of a preupdate hook that disallows merges between certain branches.

def check_if_merge_is_allowed(ui, repo, hooktype, parent1, parent2, **kwargs):
    from_ = repo[parent2].branch()
    to_ = repo[parent1].branch()
    ...
    # return True if the hook fails and the merge should not proceed.

If your hook code is not so important, and you're not publishing it, you might choose to use the discouraged unofficial internal API. If your hook is part of an extension that you're publishing, better use hglib.


It's true there's no official API, but you can get an idea about best practices by reading other extensions, particularly those bundled with hg. For this particular problem, I would do something like this:

from mercurial import ui, hg
from mercurial.node import hex

repo = hg.repository('/path/to/repo/root', ui.ui())
fctx = repo.filectx('/path/to/file', 'tip')
hexnode = hex(fctx.node())

Update At some point the parameter order changed, now it's like this:

   repo = hg.repository(ui.ui(), '/path/to/repo/root' )

An updated, cleaner subprocess version (uses .check_output(), added in Python 2.7/3.1) that I use in my Django settings file for a crude end-to-end deployment check (I dump it into an HTML comment):

import subprocess

HG_REV = subprocess.check_output(['hg', 'id', '--id']).strip()

You could wrap it in a try if you don't want some strange hiccup to prevent startup:

try:
    HG_REV = subprocess.check_output(['hg', 'id', '--id']).strip()
except OSError:
    HG_REV = "? (Couldn't find hg)"
except subprocess.CalledProcessError as e:
    HG_REV = "? (Error {})".format(e.returncode)
except Exception:  # don't use bare 'except', mis-catches Ctrl-C and more
    # should never have to deal with a hangup 
    HG_REV = "???"