Differences between revisions 58 and 90 (spanning 32 versions)
Revision 58 as of 2010-08-28 15:41:36
Size: 10703
Editor: abuehl
Comment:
Revision 90 as of 2019-01-02 10:23:38
Size: 14999
Editor: KimRandell
Comment: Updated example code to use registrar instead of deprecated cmdutil.
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
## page was renamed from ExtensionHowto
= Writing Mercurial extensions =
#pragma section-numbers 2
<<Include(A:dev)>>

= Writing Mercurial Extensions =

Mercurial features an extension mechanism for adding new commands.

Extensions allow the creation of new features and using them directly from the main hg command line as if they were built-in commands. The extensions have full access to the ''internal'' MercurialApi.

/!\ Use of Mercurial's internal API very likely makes your code subject to Mercurial's license. Before going any further, read the [[License]] page.

/!\ There are NO guarantees that third-party code calling into Mercurial's internals won't break from release to release. If you do use Mercurial's API for published third-party code, we expect you to test your code before each major Mercurial release (see TimeBasedReleasePlan). This will prevent various bug reports from your users when they upgrade their copy of Mercurial.

Line 5: Line 17:
Mercurial features an extension mechanism for adding new commands.

Extensions allow the creation of new features and using them directly from the main hg command line as if they were built-in commands. The extensions have full access to the MercurialApi.

== Writing your own extension ==
=== File Layout ===
== File Layout ==
Line 13: Line 20:
=== Command table ===
To write your own extension, your python module can provide an optional dict named `cmdtable` with entries describing each command.

==== The cmdtable dictionary ====
== Command table ==
To write your own extension, your python module can provide an optional dict
named `cmdtable` with entries describing each command. A command should be
registered to the `cmdtable` by `@command` decorator.

Example using `@command` decorator ''(requires Mercurial 1.9)'':

{{{#!python
from mercurial import registrar
from mercurial.i18n import _

cmdtable = {}
command = registrar.command(cmdtable)

@command('print-parents',
    [('s', 'short', None, _('print short form')),
     ('l', 'long', None, _('print long form'))],
    _('[options] node'))
def printparents(ui, repo, node, **opts):
    ...
}}}

=== The cmdtable dictionary ===
Line 23: Line 49:
==== List of options ==== === List of options ===
Line 31: Line 57:
 1. a help string for the option (it's possible to ommit the "hg newcommand" part and only the options and parameter subsstring is needed).

==== Example cmdtable ====
{{{
cmdtable = {
    # "command-name": (function-call, options-list, help-string)
    "print-parents": (printparents,
                     [('s', 'short', None, 'print short form'),
                      ('l', 'long', None, 'print long form')],
                     "hg print-parents [options] node")
}
}}}
 1. a help string for the option (it's possible to omit the "hg newcommand" part and only the options and parameter substring is needed).
Line 46: Line 62:
If there is no repo to be associated with the command and consequently no {{{repo}}} passed then {{{commands}}} should be imported from {{{mercurial}}} and the extension name should be added to {{{commands.norepo}}} like this:

{{{
from mercurial import commands
...
commands.norepo += " mycommand"
}}}
For examples of {{{norepo}}} see the source code to the RcpathExtension (direct link to [[attachment:RcpathExtension/rcpath.py]] extension source) or the ConvertExtension (direct link to [[http://hg.intevation.org/mercurial/crew/file/f5f6b7dcd217/hgext/convert/__init__.py#l186|convert]] extension source).

=== Command function docstrings ===
If there is no repo to be associated with the command and consequently no
`repo` passed, then `norepo=True` should be passed to the `@command` decorator.

{{{#!python
@command('mycommand', [], norepo=True)
def mycommand(ui, **opts):
    ...
}}}

For examples of `norepo`, see the ConvertExtension (direct link to
[[https://selenic.com/repo/hg/file/3.6.3/hgext/convert/__init__.py#l26|convert]]
extension source).

== Command function docstrings ==
Line 66: Line 86:
A verbatim block is introduced with a double colon followed by an indented block. The double colos is turned into a single colon on display: A verbatim block is introduced with a double colon followed by an indented block. The double colon is turned into a single colon on display:
Line 95: Line 115:
=== Communicating with the user === == Communicating with the user ==
Line 100: Line 120:
=== Setup Callbacks ===
Extensions are loaded in phases, all extensions are processed in a given phase before the next phase begins. In the first phase all extension modules are loaded and registered with Mercurial. this means that you can find all enabled extensions with `extensions.find` in the following phases.

==== ui setup ====
== Setup Callbacks ==
Extensions are loaded in phases. All extensions are processed in a given phase before the next phase begins. In the first phase, all extension modules are loaded and registered with Mercurial. This means that you can find all enabled extensions with `extensions.find` in the following phases.

=== ui setup ===
Line 110: Line 130:
==== Extension setup ====
=== Extension setup ===
Line 117: Line 138:
Mercurial version 8e6019b16a7d and later (that is post-1.3.1) will pass a `ui` argument to `extsetup`.

==== Command table setup ====

Mercurial version 8e6019b16a7d and later (that is post-1.3.1) will pass a `ui` argument to `extsetup`:
{{{
def extsetup(ui):
    # ..
.
}}}

=== Command table setup ===
Line 122: Line 148:
==== Repository setup ==== === Repository setup ===
Line 131: Line 157:
It is important to take into account that the `ui` object that is received by the `reposetup` function is not the same as the one received by the `uisetup` and `extsetup` functions. This is particularly important when setting up hooks as described in the following section, since not all hooks us the same `ui` object and hence different hooks must be configured in different setup functions.

=== Configuring Hooks ===
It is important to take into account that the `ui` object that is received by the `reposetup` function is not the same as the one received by the `uisetup` and `extsetup` functions. This is particularly important when setting up hooks as described in the following section, since not all hooks use the same `ui` object and hence different hooks must be configured in different setup functions.

=== Wrapping methods on the ui and repo classes ===

Because extensions can be loaded ''per repository'', you should avoid using `extensions.wrapfunction()` on methods of the `ui` and `repo` objects. Instead, create a subclass of the specific class of the instance passed into the `*setup()` hook; e.g. use `ui.__class__` as the base class, then reassign your new class to `ui.__class__` again. Mercurial will then use your updated `ui` or `repo` instance only for repositories where your extension is enabled (or copies thereof, reusing your new class).

For example:

{{{
def uisetup(ui):
    class echologui(ui.__class__):
        def log(self, service, *msg, **opts):
            if msg:
                self.write('%s: %s\n' % (service, msg[0] % msg[1:]))
            super(echologui, self).log(service, *msg, **opts)
    
    ui.__class__ = echologui
}}}

== Configuring Hooks ==
Line 143: Line 186:
    print("Pre-update hook triggered")     ui.write("Pre-update hook triggered\n")
Line 146: Line 189:
    print("Update hook triggered")     ui.write("Update hook triggered\n")
Line 162: Line 205:
== Marking compatible versions ==

Every extension should use the `testedwith` variable to specify Mercurial releases it's known to be compatible with. This helps us and users diagnose where problems are coming from.

{{{#!python
testedwith = '2.0 2.0.1 2.1 2.1.1 2.1.2'
}}}

Do not use the `internal` marker in third-party extensions; we will immediately drop all bug reports mentioning your extension if we catch you doing this.

Similarly, an extension can use the `buglink` variable to specify how users should report issues with the extension. This link will be included in the error message if the extension produces errors.

{{{#!python
buglink = 'https://bitbucket.org/USER/REPO/issues'
}}}
Line 164: Line 223:
#!/usr/bin/env python

'''printparents
"""printparents
Line 169: Line 226:
'''

from mercurial import hg


# every command must take a ui and and repo as arguments.
# opts is a dict where you can find other command line flags
"""

from mercurial import registrar, error
from mercurial.i18n import _

cmdtable = {}
command = registrar.command(cmdtable)

testedwith =
'2.2 2.3'

# Every command must take ui and and repo as arguments.
# opts is a dict where you can find other command line flags.
Line 177: Line 240:
# don't start with a dash.  If no default value is given in the parameter list, # don't start with a dash. If no default value is given in the parameter list,
Line 185: Line 248:
@command('print-parents',
    [('s', 'short', None, _('print short form')),
     ('l', 'long', None, _('print long form'))],
    _('[options] node'))
Line 186: Line 253:
    # The doc string below will show up in hg help
    """Print parent information"""

    # repo can be indexed based on tags, an sha1, or a revision number
    # The doc string below will show up in hg help.
    """Print parent information."""
    # repo can be indexed based on tags, an sha1, or a revision number.
Line 193: Line 259:
    if opts['short']:
        # the string representation of a context returns a smaller portion of the sha1
ui.write("short %s %s\n" % (parents[0], parents[1]))
    elif opts['long']:
        # the hex representation of a context returns the full sha1
ui.write("long %s %s\n" % (parents[0].hex(), parents[1].hex()))
    else:
        ui.write("default %s %s\n" % (parents[0], parents[1]))

cmdtable = {
    #
cmd name function call
    "print-parents": (printparents,
                     # see mercurial/fanc
yopts.py for all of the command
                     # flag options.
                     [('s', 'short', None, 'print short form'),
                      ('l', 'long', None, 'print long form')],
                     "[options] REV")
}
    try:
        if opts['short']:
            
# The string representation of a context returns a smaller portion
            #
of the sha1.
    
ui.write(_('short %s %s\n') % (parents[0], parents[1]))
        elif opts['long']:
            # The hex representation of a context returns the full sha1.
    
ui.write(_('long %s %s\n') % (parents[0].hex(), parents[1].hex()))
        else:
            ui.write(_('default %s %s\n') % (parents[0], parents[1]))
    except IndexError:
        # Raise an A
bort exception if the node has only one parent.
        raise error.Abort(_('revision %s has onl
y one parent') % node)
Line 214: Line 275:
== Where to put extensions in the source tree ==
As of a change shortly after the 0.7 release, the recommended location for installing extensions in the source tree is the `hgext` directory. If you put a file in there called `foo.py`, you will need to refer to it in the `hgrc` file as a qualified package name, `hgext.foo`.

The contents of the `hgext` directory will be installed by the top-level `setup.py` script along with the rest of Mercurial.
== Testing the example extension ==

This is a test for example extension above:

{{{
Test printparents extension.

Activate the printparents extension:
  $ echo "[extensions]" >> $HGRCPATH
  $ echo "printparents=" >> $HGRCPATH

Create a new repo:
  $ hg init r
  $ cd r

Add two new files and commit them separately:
  $ echo c1 > f1
  $ hg commit -Am 0
  adding f1
  $ echo c2 > f2
  $ hg commit -Am 1
  adding f2

Update to revision 0. Add and commit a third file creating a new head:
  $ hg up 0
  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
  $ echo c3 > f3
  $ hg commit -Am 2
  adding f3
  created new head

Merge the two heads and commit:
  $ hg merge
  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
  (branch merge, don't forget to commit)
  $ hg commit -m 3

Test printparents with the (merged) tip:
  $ hg print-parents tip
  default 33960aadc16f c3adabd1a5f4

Testing printparents with revision 2 will fail (because there is only one parent):
  $ hg print-parents 2
  abort: revision 2 has only one parent
  [255]
}}}

Learn more about testing Mercurial: WritingTests

== Wrap up: what belongs where? ==

You will find here a list of most common tasks, based on setups from the extensions included in Mercurial core.

=== uisetup ===

 * Changes to `ui.__class__` . The `ui` object that will be used to run the command has not yet been created. Changes made here will affect `ui` objects created after this, and in particular the `ui` that will be passed to `runcommand`
 * Command wraps ({{{extensions.wrapcommand}}})
 * Changes that need to be visible by other extensions: because initialization occurs in phases (all extensions run `uisetup`, then all run `extsetup`), a change made here will be visible by other extensions during `extsetup`
 * Monkeypatches or function wraps ({{{extensions.wrapfunction}}}) of `dispatch` module members
 * Setup of pre-* and post-* hooks
 * `pushkey` setup

=== extsetup ===

 * Changes depending on the status of other extensions. ({{{if extensions.find('mq')}}})
 * Add a global option to all commands
 * Extend revsets

=== reposetup ===

 * All hooks but pre-* and post-*
 * Modify configuration variables
 * Changes to `repo.__class__`, `repo.dirstate.__class__`

== Publishing your extension ==

If you think your extension is general purpose, high quality, and of interest to more than just you, see PublishingExtensions.
Line 220: Line 354:
CategoryExtension CategoryHowTo CategoryInternals CategoryHowTo CategoryDeveloper

[[JapaneseWritingExtensions|日本語]]

Note:

This page is primarily intended for developers of Mercurial.

Writing Mercurial Extensions

Mercurial features an extension mechanism for adding new commands.

Extensions allow the creation of new features and using them directly from the main hg command line as if they were built-in commands. The extensions have full access to the internal MercurialApi.

/!\ Use of Mercurial's internal API very likely makes your code subject to Mercurial's license. Before going any further, read the License page.

/!\ There are NO guarantees that third-party code calling into Mercurial's internals won't break from release to release. If you do use Mercurial's API for published third-party code, we expect you to test your code before each major Mercurial release (see TimeBasedReleasePlan). This will prevent various bug reports from your users when they upgrade their copy of Mercurial.

1. File Layout

Extensions are usually written as simple python modules. Larger ones are better split into multiple modules of a single package (see ConvertExtension). The package root module gives its name to the extension and implements the cmdtable and optional callbacks described below.

2. Command table

To write your own extension, your python module can provide an optional dict named cmdtable with entries describing each command. A command should be registered to the cmdtable by @command decorator.

Example using @command decorator (requires Mercurial 1.9):

   1 from mercurial import registrar
   2 from mercurial.i18n import _
   3 
   4 cmdtable = {}
   5 command = registrar.command(cmdtable)
   6 
   7 @command('print-parents',
   8     [('s', 'short', None, _('print short form')),
   9      ('l', 'long', None, _('print long form'))],
  10     _('[options] node'))
  11 def printparents(ui, repo, node, **opts):
  12     ...

2.1. The cmdtable dictionary

The cmdtable dictionary uses as key the new command names, and, as value, a tuple containing:

  1. the function to be called when the command is used.
  2. a list of options the command can take.
  3. a command line synopsis for the command (the function docstring is used for the full help).

2.2. List of options

All the command flag options are documented in the mercurial/fancyopts.py sources.

The options list is a list of tuples containing:

  1. the short option letter, or '' if no short option is available (for example, o for a -o option).

  2. the long option name (for example, option for a --option option).

  3. a default value for the option.
  4. a help string for the option (it's possible to omit the "hg newcommand" part and only the options and parameter substring is needed).

2.3. Command function signatures

Functions that implement new commands always receive a ui and usually a repo parameter. Please see the MercurialApi for information on how to use these. The rest of parameters are taken from the command line items that don't start with a dash and are passed in the same order they were written. If no default value is given in the parameter list they are required.

If there is no repo to be associated with the command and consequently no repo passed, then norepo=True should be passed to the @command decorator.

   1 @command('mycommand', [], norepo=True)
   2 def mycommand(ui, **opts):
   3     ...

For examples of norepo, see the ConvertExtension (direct link to convert extension source).

3. Command function docstrings

The docstring of your function is used as the main help text, shown by hg help mycommand. The docstring should be formatted using a simple subset of reStructuredText markup. The supported constructs include:

Paragraphs:

This is a paragraph.

Paragraphs are separated
by blank lines.

A verbatim block is introduced with a double colon followed by an indented block. The double colon is turned into a single colon on display:

Some text::

  verbatim
    text
     !!

We have field lists:

:key1: value1
:key2: value2

Bullet lists:

- foo
- bar

Enumerated lists:

1. foo
2. bar

Inline markup: *bold*, ``monospace``. Mark Mercurial commands as :hg:`command` to make a nice link to the corresponding documentation. We'll expand the support if new constructs can be parsed without too much trouble.

4. Communicating with the user

Besides the ui methods listed in MercurialApi, like ui.write(*msg) or ui.prompt(msg, default="y"), an extension can add help text for each of its commands and the extension itself.

The module docstring will be used as help string when hg help extensionname is used and, similarly, the help string for a command and the docstring belonging to the function that's wrapped by the command will be shown when hg help command is invoked.

5. Setup Callbacks

Extensions are loaded in phases. All extensions are processed in a given phase before the next phase begins. In the first phase, all extension modules are loaded and registered with Mercurial. This means that you can find all enabled extensions with extensions.find in the following phases.

5.1. ui setup

Extensions can implement an optional callback named uisetup. uisetup is called when the extension is first loaded and receives a ui object:

def uisetup(ui):
    # ...

5.2. Extension setup

Extensions can implement an optional callback named extsetup. It is called after all the extension are loaded, and can be useful in case one extension optionally depends on another extension. Signature:

def extsetup():
    # ...

Mercurial version 8e6019b16a7d and later (that is post-1.3.1) will pass a ui argument to extsetup:

def extsetup(ui):
    # ...

5.3. Command table setup

After extsetup, the cmdtable is copied into the global command table in Mercurial.

5.4. Repository setup

Extensions can implement an optional callback named reposetup. It is called after the main Mercurial repository initialization, and can be used to setup any local state the extension might need.

As other command functions it receives an ui object and a repo object (no additional parameters for this, though):

def reposetup(ui, repo):
    #do initialization here.

It is important to take into account that the ui object that is received by the reposetup function is not the same as the one received by the uisetup and extsetup functions. This is particularly important when setting up hooks as described in the following section, since not all hooks use the same ui object and hence different hooks must be configured in different setup functions.

5.5. Wrapping methods on the ui and repo classes

Because extensions can be loaded per repository, you should avoid using extensions.wrapfunction() on methods of the ui and repo objects. Instead, create a subclass of the specific class of the instance passed into the *setup() hook; e.g. use ui.__class__ as the base class, then reassign your new class to ui.__class__ again. Mercurial will then use your updated ui or repo instance only for repositories where your extension is enabled (or copies thereof, reusing your new class).

For example:

def uisetup(ui):
    class echologui(ui.__class__):
        def log(self, service, *msg, **opts):
            if msg:
                self.write('%s: %s\n' % (service, msg[0] % msg[1:]))
            super(echologui, self).log(service, *msg, **opts)
    
    ui.__class__ = echologui

6. Configuring Hooks

Some extensions must use hooks to do their work. These required hooks can be configured manually by the user by modifying the [hook] section of their hgrc, but they can also be configured automatically by calling the ui.setconfig('hooks', ...) function in one of the setup functions described above.

The main difference between manually modifying the hooks section in the hgrc and using ui.setconfig() is that when using ui.setconfig() you have access to the actual hook function object, which you can pass directly to ui.setconfig(), while when you use the hooks section of the hgrc file you must refer to the hook function by using the "python:modulename.functioname" idiom (e.g. "python:hgext.notify.hook").

For example:

# Define hooks -- note that the actual function name it irrelevant.
def preupdatehook(ui, repo, **kwargs):
    ui.write("Pre-update hook triggered\n")

def updatehook(ui, repo, **kwargs):
    ui.write("Update hook triggered\n")

def uisetup(ui):
    # When pre-<cmd> and post-<cmd> hooks are configured by means of
    # the ui.setconfig() function, you must use the ui object passed
    # to uisetup or extsetup.
    ui.setconfig("hooks", "pre-update.myextension", preupdatehook)

def reposetup(ui, repo):
    # Repository-specific hooks can be configured here. These include
    # the update hook.
    ui.setconfig("hooks", "update.myextension", updatehook)

Note how different hooks may need to be configured in different setup functions. In the example you can see that the update hook must be configured in the reposetup function, while the pre-update hook must be configured on the uisetup or the extsetup functions.

7. Marking compatible versions

Every extension should use the testedwith variable to specify Mercurial releases it's known to be compatible with. This helps us and users diagnose where problems are coming from.

   1 testedwith = '2.0 2.0.1 2.1 2.1.1 2.1.2'

Do not use the internal marker in third-party extensions; we will immediately drop all bug reports mentioning your extension if we catch you doing this.

Similarly, an extension can use the buglink variable to specify how users should report issues with the extension. This link will be included in the error message if the extension produces errors.

   1 buglink = 'https://bitbucket.org/USER/REPO/issues'

8. Example extension

   1 """printparents
   2 
   3 Prints the parents of a given revision.
   4 """
   5 
   6 from mercurial import registrar, error
   7 from mercurial.i18n import _
   8 
   9 cmdtable = {}
  10 command = registrar.command(cmdtable)
  11 
  12 testedwith = '2.2 2.3'
  13 
  14 # Every command must take ui and and repo as arguments.
  15 # opts is a dict where you can find other command line flags.
  16 #
  17 # Other parameters are taken in order from items on the command line that
  18 # don't start with a dash. If no default value is given in the parameter list,
  19 # they are required.
  20 #
  21 # For experimenting with Mercurial in the python interpreter:
  22 # Getting the repository of the current dir:
  23 #    >>> from mercurial import hg, ui
  24 #    >>> repo = hg.repository(ui.ui(), path = ".")
  25 
  26 @command('print-parents',
  27     [('s', 'short', None, _('print short form')),
  28      ('l', 'long', None, _('print long form'))],
  29     _('[options] node'))
  30 def printparents(ui, repo, node, **opts):
  31     # The doc string below will show up in hg help.
  32     """Print parent information."""
  33     # repo can be indexed based on tags, an sha1, or a revision number.
  34     ctx = repo[node]
  35     parents = ctx.parents()
  36 
  37     try:
  38         if opts['short']:
  39             # The string representation of a context returns a smaller portion
  40             # of the sha1.
  41             ui.write(_('short %s %s\n') % (parents[0], parents[1]))
  42         elif opts['long']:
  43             # The hex representation of a context returns the full sha1.
  44             ui.write(_('long %s %s\n') % (parents[0].hex(), parents[1].hex()))
  45         else:
  46             ui.write(_('default %s %s\n') % (parents[0], parents[1]))
  47     except IndexError:
  48         # Raise an Abort exception if the node has only one parent.
  49         raise error.Abort(_('revision %s has only one parent') % node)

If cmdtable or reposetup is not present, your extension will still work. This means that an extension can work "silently", without making new functionality directly visible through the command line interface.

9. Testing the example extension

This is a test for example extension above:

Test printparents extension.

Activate the printparents extension:
  $ echo "[extensions]" >> $HGRCPATH
  $ echo "printparents=" >> $HGRCPATH

Create a new repo:
  $ hg init r
  $ cd r

Add two new files and commit them separately:
  $ echo c1 > f1
  $ hg commit -Am 0
  adding f1
  $ echo c2 > f2
  $ hg commit -Am 1
  adding f2

Update to revision 0. Add and commit a third file creating a new head:
  $ hg up 0
  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
  $ echo c3 > f3
  $ hg commit -Am 2
  adding f3
  created new head

Merge the two heads and commit:
  $ hg merge
  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
  (branch merge, don't forget to commit)
  $ hg commit -m 3

Test printparents with the (merged) tip:
  $ hg print-parents tip
  default 33960aadc16f c3adabd1a5f4

Testing printparents with revision 2 will fail (because there is only one parent):
  $ hg print-parents 2
  abort: revision 2 has only one parent
  [255]

Learn more about testing Mercurial: WritingTests

10. Wrap up: what belongs where?

You will find here a list of most common tasks, based on setups from the extensions included in Mercurial core.

10.1. uisetup

  • Changes to ui.__class__ . The ui object that will be used to run the command has not yet been created. Changes made here will affect ui objects created after this, and in particular the ui that will be passed to runcommand

  • Command wraps (extensions.wrapcommand)

  • Changes that need to be visible by other extensions: because initialization occurs in phases (all extensions run uisetup, then all run extsetup), a change made here will be visible by other extensions during extsetup

  • Monkeypatches or function wraps (extensions.wrapfunction) of dispatch module members

  • Setup of pre-* and post-* hooks
  • pushkey setup

10.2. extsetup

  • Changes depending on the status of other extensions. (if extensions.find('mq'))

  • Add a global option to all commands
  • Extend revsets

10.3. reposetup

  • All hooks but pre-* and post-*
  • Modify configuration variables
  • Changes to repo.__class__, repo.dirstate.__class__

11. Publishing your extension

If you think your extension is general purpose, high quality, and of interest to more than just you, see PublishingExtensions.


CategoryHowTo CategoryDeveloper

日本語

WritingExtensions (last edited 2020-07-29 10:00:07 by aayjaychan)