Mercurial > hgbook
diff en/ch09-hook.xml @ 682:28b5a5befb08
Fold preface and intro into one
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Thu, 19 Mar 2009 20:54:12 -0700 |
parents | en/ch10-hook.xml@8366882f67f2 |
children | c838b3975bc6 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch09-hook.xml Thu Mar 19 20:54:12 2009 -0700 @@ -0,0 +1,2037 @@ +<!-- vim: set filetype=docbkxml shiftwidth=2 autoindent expandtab tw=77 : --> + +<chapter id="chap:hook"> + <?dbhtml filename="handling-repository-events-with-hooks.html"?> + <title>Handling repository events with hooks</title> + + <para>Mercurial offers a powerful mechanism to let you perform + automated actions in response to events that occur in a + repository. In some cases, you can even control Mercurial's + response to those events.</para> + + <para>The name Mercurial uses for one of these actions is a + <emphasis>hook</emphasis>. Hooks are called + <quote>triggers</quote> in some revision control systems, but the + two names refer to the same idea.</para> + + <sect1> + <title>An overview of hooks in Mercurial</title> + + <para>Here is a brief list of the hooks that Mercurial supports. + We will revisit each of these hooks in more detail later, in + section <xref linkend="sec:hook:ref"/>.</para> + + <itemizedlist> + <listitem><para><literal role="hook">changegroup</literal>: This + is run after a group of changesets has been brought into the + repository from elsewhere.</para> + </listitem> + <listitem><para><literal role="hook">commit</literal>: This is + run after a new changeset has been created in the local + repository.</para> + </listitem> + <listitem><para><literal role="hook">incoming</literal>: This is + run once for each new changeset that is brought into the + repository from elsewhere. Notice the difference from + <literal role="hook">changegroup</literal>, which is run + once per <emphasis>group</emphasis> of changesets brought + in.</para> + </listitem> + <listitem><para><literal role="hook">outgoing</literal>: This is + run after a group of changesets has been transmitted from + this repository.</para> + </listitem> + <listitem><para><literal role="hook">prechangegroup</literal>: + This is run before starting to bring a group of changesets + into the repository. + </para> + </listitem> + <listitem><para><literal role="hook">precommit</literal>: + Controlling. This is run before starting a commit. + </para> + </listitem> + <listitem><para><literal role="hook">preoutgoing</literal>: + Controlling. This is run before starting to transmit a group + of changesets from this repository. + </para> + </listitem> + <listitem><para><literal role="hook">pretag</literal>: + Controlling. This is run before creating a tag. + </para> + </listitem> + <listitem><para><literal + role="hook">pretxnchangegroup</literal>: Controlling. This + is run after a group of changesets has been brought into the + local repository from another, but before the transaction + completes that will make the changes permanent in the + repository. + </para> + </listitem> + <listitem><para><literal role="hook">pretxncommit</literal>: + Controlling. This is run after a new changeset has been + created in the local repository, but before the transaction + completes that will make it permanent. + </para> + </listitem> + <listitem><para><literal role="hook">preupdate</literal>: + Controlling. This is run before starting an update or merge + of the working directory. + </para> + </listitem> + <listitem><para><literal role="hook">tag</literal>: This is run + after a tag is created. + </para> + </listitem> + <listitem><para><literal role="hook">update</literal>: This is + run after an update or merge of the working directory has + finished. + </para> + </listitem></itemizedlist> + <para>Each of the hooks whose description begins with the word + <quote>Controlling</quote> has the ability to determine whether + an activity can proceed. If the hook succeeds, the activity may + proceed; if it fails, the activity is either not permitted or + undone, depending on the hook. + </para> + + </sect1> + <sect1> + <title>Hooks and security</title> + + <sect2> + <title>Hooks are run with your privileges</title> + + <para>When you run a Mercurial command in a repository, and the + command causes a hook to run, that hook runs on + <emphasis>your</emphasis> system, under + <emphasis>your</emphasis> user account, with + <emphasis>your</emphasis> privilege level. Since hooks are + arbitrary pieces of executable code, you should treat them + with an appropriate level of suspicion. Do not install a hook + unless you are confident that you know who created it and what + it does. + </para> + + <para>In some cases, you may be exposed to hooks that you did + not install yourself. If you work with Mercurial on an + unfamiliar system, Mercurial will run hooks defined in that + system's global <filename role="special">~/.hgrc</filename> + file. + </para> + + <para>If you are working with a repository owned by another + user, Mercurial can run hooks defined in that user's + repository, but it will still run them as <quote>you</quote>. + For example, if you <command role="hg-cmd">hg pull</command> + from that repository, and its <filename + role="special">.hg/hgrc</filename> defines a local <literal + role="hook">outgoing</literal> hook, that hook will run + under your user account, even though you don't own that + repository. + </para> + + <note> + <para> This only applies if you are pulling from a repository + on a local or network filesystem. If you're pulling over + http or ssh, any <literal role="hook">outgoing</literal> + hook will run under whatever account is executing the server + process, on the server. + </para> + </note> + + <para>XXX To see what hooks are defined in a repository, use the + <command role="hg-cmd">hg config hooks</command> command. If + you are working in one repository, but talking to another that + you do not own (e.g. using <command role="hg-cmd">hg + pull</command> or <command role="hg-cmd">hg + incoming</command>), remember that it is the other + repository's hooks you should be checking, not your own. + </para> + + </sect2> + <sect2> + <title>Hooks do not propagate</title> + + <para>In Mercurial, hooks are not revision controlled, and do + not propagate when you clone, or pull from, a repository. The + reason for this is simple: a hook is a completely arbitrary + piece of executable code. It runs under your user identity, + with your privilege level, on your machine. + </para> + + <para>It would be extremely reckless for any distributed + revision control system to implement revision-controlled + hooks, as this would offer an easily exploitable way to + subvert the accounts of users of the revision control system. + </para> + + <para>Since Mercurial does not propagate hooks, if you are + collaborating with other people on a common project, you + should not assume that they are using the same Mercurial hooks + as you are, or that theirs are correctly configured. You + should document the hooks you expect people to use. + </para> + + <para>In a corporate intranet, this is somewhat easier to + control, as you can for example provide a + <quote>standard</quote> installation of Mercurial on an NFS + filesystem, and use a site-wide <filename role="special">~/.hgrc</filename> file to define hooks that all users will + see. However, this too has its limits; see below. + </para> + + </sect2> + <sect2> + <title>Hooks can be overridden</title> + + <para>Mercurial allows you to override a hook definition by + redefining the hook. You can disable it by setting its value + to the empty string, or change its behaviour as you wish. + </para> + + <para>If you deploy a system- or site-wide <filename + role="special">~/.hgrc</filename> file that defines some + hooks, you should thus understand that your users can disable + or override those hooks. + </para> + + </sect2> + <sect2> + <title>Ensuring that critical hooks are run</title> + + <para>Sometimes you may want to enforce a policy that you do not + want others to be able to work around. For example, you may + have a requirement that every changeset must pass a rigorous + set of tests. Defining this requirement via a hook in a + site-wide <filename role="special">~/.hgrc</filename> won't + work for remote users on laptops, and of course local users + can subvert it at will by overriding the hook. + </para> + + <para>Instead, you can set up your policies for use of Mercurial + so that people are expected to propagate changes through a + well-known <quote>canonical</quote> server that you have + locked down and configured appropriately. + </para> + + <para>One way to do this is via a combination of social + engineering and technology. Set up a restricted-access + account; users can push changes over the network to + repositories managed by this account, but they cannot log into + the account and run normal shell commands. In this scenario, + a user can commit a changeset that contains any old garbage + they want. + </para> + + <para>When someone pushes a changeset to the server that + everyone pulls from, the server will test the changeset before + it accepts it as permanent, and reject it if it fails to pass + the test suite. If people only pull changes from this + filtering server, it will serve to ensure that all changes + that people pull have been automatically vetted. + </para> + + </sect2> + </sect1> + <sect1> + <title>Care with <literal>pretxn</literal> hooks in a + shared-access repository</title> + + <para>If you want to use hooks to do some automated work in a + repository that a number of people have shared access to, you + need to be careful in how you do this. + </para> + + <para>Mercurial only locks a repository when it is writing to the + repository, and only the parts of Mercurial that write to the + repository pay attention to locks. Write locks are necessary to + prevent multiple simultaneous writers from scribbling on each + other's work, corrupting the repository. + </para> + + <para>Because Mercurial is careful with the order in which it + reads and writes data, it does not need to acquire a lock when + it wants to read data from the repository. The parts of + Mercurial that read from the repository never pay attention to + locks. This lockless reading scheme greatly increases + performance and concurrency. + </para> + + <para>With great performance comes a trade-off, though, one which + has the potential to cause you trouble unless you're aware of + it. To describe this requires a little detail about how + Mercurial adds changesets to a repository and reads those + changes. + </para> + + <para>When Mercurial <emphasis>writes</emphasis> metadata, it + writes it straight into the destination file. It writes file + data first, then manifest data (which contains pointers to the + new file data), then changelog data (which contains pointers to + the new manifest data). Before the first write to each file, it + stores a record of where the end of the file was in its + transaction log. If the transaction must be rolled back, + Mercurial simply truncates each file back to the size it was + before the transaction began. + </para> + + <para>When Mercurial <emphasis>reads</emphasis> metadata, it reads + the changelog first, then everything else. Since a reader will + only access parts of the manifest or file metadata that it can + see in the changelog, it can never see partially written data. + </para> + + <para>Some controlling hooks (<literal + role="hook">pretxncommit</literal> and <literal + role="hook">pretxnchangegroup</literal>) run when a + transaction is almost complete. All of the metadata has been + written, but Mercurial can still roll the transaction back and + cause the newly-written data to disappear. + </para> + + <para>If one of these hooks runs for long, it opens a window of + time during which a reader can see the metadata for changesets + that are not yet permanent, and should not be thought of as + <quote>really there</quote>. The longer the hook runs, the + longer that window is open. + </para> + + <sect2> + <title>The problem illustrated</title> + + <para>In principle, a good use for the <literal + role="hook">pretxnchangegroup</literal> hook would be to + automatically build and test incoming changes before they are + accepted into a central repository. This could let you + guarantee that nobody can push changes to this repository that + <quote>break the build</quote>. But if a client can pull + changes while they're being tested, the usefulness of the test + is zero; an unsuspecting someone can pull untested changes, + potentially breaking their build. + </para> + + <para>The safest technological answer to this challenge is to + set up such a <quote>gatekeeper</quote> repository as + <emphasis>unidirectional</emphasis>. Let it take changes + pushed in from the outside, but do not allow anyone to pull + changes from it (use the <literal + role="hook">preoutgoing</literal> hook to lock it down). + Configure a <literal role="hook">changegroup</literal> hook so + that if a build or test succeeds, the hook will push the new + changes out to another repository that people + <emphasis>can</emphasis> pull from. + </para> + + <para>In practice, putting a centralised bottleneck like this in + place is not often a good idea, and transaction visibility has + nothing to do with the problem. As the size of a + project&emdash;and the time it takes to build and + test&emdash;grows, you rapidly run into a wall with this + <quote>try before you buy</quote> approach, where you have + more changesets to test than time in which to deal with them. + The inevitable result is frustration on the part of all + involved. + </para> + + <para>An approach that scales better is to get people to build + and test before they push, then run automated builds and tests + centrally <emphasis>after</emphasis> a push, to be sure all is + well. The advantage of this approach is that it does not + impose a limit on the rate at which the repository can accept + changes. + </para> + + </sect2> + </sect1> + <sect1 id="sec:hook:simple"> + <title>A short tutorial on using hooks</title> + + <para>It is easy to write a Mercurial hook. Let's start with a + hook that runs when you finish a <command role="hg-cmd">hg + commit</command>, and simply prints the hash of the changeset + you just created. The hook is called <literal + role="hook">commit</literal>. + </para> + + <para>All hooks follow the pattern in this example.</para> + +&interaction.hook.simple.init; + + <para>You add an entry to the <literal + role="rc-hooks">hooks</literal> section of your <filename + role="special">~/.hgrc</filename>. On the left is the name of + the event to trigger on; on the right is the action to take. As + you can see, you can run an arbitrary shell command in a hook. + Mercurial passes extra information to the hook using environment + variables (look for <envar>HG_NODE</envar> in the example). + </para> + + <sect2> + <title>Performing multiple actions per event</title> + + <para>Quite often, you will want to define more than one hook + for a particular kind of event, as shown below.</para> + +&interaction.hook.simple.ext; + + <para>Mercurial lets you do this by adding an + <emphasis>extension</emphasis> to the end of a hook's name. + You extend a hook's name by giving the name of the hook, + followed by a full stop (the + <quote><literal>.</literal></quote> character), followed by + some more text of your choosing. For example, Mercurial will + run both <literal>commit.foo</literal> and + <literal>commit.bar</literal> when the + <literal>commit</literal> event occurs. + </para> + + <para>To give a well-defined order of execution when there are + multiple hooks defined for an event, Mercurial sorts hooks by + extension, and executes the hook commands in this sorted + order. In the above example, it will execute + <literal>commit.bar</literal> before + <literal>commit.foo</literal>, and <literal>commit</literal> + before both. + </para> + + <para>It is a good idea to use a somewhat descriptive extension + when you define a new hook. This will help you to remember + what the hook was for. If the hook fails, you'll get an error + message that contains the hook name and extension, so using a + descriptive extension could give you an immediate hint as to + why the hook failed (see section <xref + linkend="sec:hook:perm"/> for an example). + </para> + + </sect2> + <sect2 id="sec:hook:perm"> + <title>Controlling whether an activity can proceed</title> + + <para>In our earlier examples, we used the <literal + role="hook">commit</literal> hook, which is run after a + commit has completed. This is one of several Mercurial hooks + that run after an activity finishes. Such hooks have no way + of influencing the activity itself. + </para> + + <para>Mercurial defines a number of events that occur before an + activity starts; or after it starts, but before it finishes. + Hooks that trigger on these events have the added ability to + choose whether the activity can continue, or will abort. + </para> + + <para>The <literal role="hook">pretxncommit</literal> hook runs + after a commit has all but completed. In other words, the + metadata representing the changeset has been written out to + disk, but the transaction has not yet been allowed to + complete. The <literal role="hook">pretxncommit</literal> + hook has the ability to decide whether the transaction can + complete, or must be rolled back. + </para> + + <para>If the <literal role="hook">pretxncommit</literal> hook + exits with a status code of zero, the transaction is allowed + to complete; the commit finishes; and the <literal + role="hook">commit</literal> hook is run. If the <literal + role="hook">pretxncommit</literal> hook exits with a + non-zero status code, the transaction is rolled back; the + metadata representing the changeset is erased; and the + <literal role="hook">commit</literal> hook is not run. + </para> + +&interaction.hook.simple.pretxncommit; + + <para>The hook in the example above checks that a commit comment + contains a bug ID. If it does, the commit can complete. If + not, the commit is rolled back. + </para> + + </sect2> + </sect1> + <sect1> + <title>Writing your own hooks</title> + + <para>When you are writing a hook, you might find it useful to run + Mercurial either with the <option + role="hg-opt-global">-v</option> option, or the <envar + role="rc-item-ui">verbose</envar> config item set to + <quote>true</quote>. When you do so, Mercurial will print a + message before it calls each hook. + </para> + + <sect2 id="sec:hook:lang"> + <title>Choosing how your hook should run</title> + + <para>You can write a hook either as a normal + program&emdash;typically a shell script&emdash;or as a Python + function that is executed within the Mercurial process. + </para> + + <para>Writing a hook as an external program has the advantage + that it requires no knowledge of Mercurial's internals. You + can call normal Mercurial commands to get any added + information you need. The trade-off is that external hooks + are slower than in-process hooks. + </para> + + <para>An in-process Python hook has complete access to the + Mercurial API, and does not <quote>shell out</quote> to + another process, so it is inherently faster than an external + hook. It is also easier to obtain much of the information + that a hook requires by using the Mercurial API than by + running Mercurial commands. + </para> + + <para>If you are comfortable with Python, or require high + performance, writing your hooks in Python may be a good + choice. However, when you have a straightforward hook to + write and you don't need to care about performance (probably + the majority of hooks), a shell script is perfectly fine. + </para> + + </sect2> + <sect2 id="sec:hook:param"> + <title>Hook parameters</title> + + <para>Mercurial calls each hook with a set of well-defined + parameters. In Python, a parameter is passed as a keyword + argument to your hook function. For an external program, a + parameter is passed as an environment variable. + </para> + + <para>Whether your hook is written in Python or as a shell + script, the hook-specific parameter names and values will be + the same. A boolean parameter will be represented as a + boolean value in Python, but as the number 1 (for + <quote>true</quote>) or 0 (for <quote>false</quote>) as an + environment variable for an external hook. If a hook + parameter is named <literal>foo</literal>, the keyword + argument for a Python hook will also be named + <literal>foo</literal>, while the environment variable for an + external hook will be named <literal>HG_FOO</literal>. + </para> + + </sect2> + <sect2> + <title>Hook return values and activity control</title> + + <para>A hook that executes successfully must exit with a status + of zero if external, or return boolean <quote>false</quote> if + in-process. Failure is indicated with a non-zero exit status + from an external hook, or an in-process hook returning boolean + <quote>true</quote>. If an in-process hook raises an + exception, the hook is considered to have failed. + </para> + + <para>For a hook that controls whether an activity can proceed, + zero/false means <quote>allow</quote>, while + non-zero/true/exception means <quote>deny</quote>. + </para> + + </sect2> + <sect2> + <title>Writing an external hook</title> + + <para>When you define an external hook in your <filename + role="special">~/.hgrc</filename> and the hook is run, its + value is passed to your shell, which interprets it. This + means that you can use normal shell constructs in the body of + the hook. + </para> + + <para>An executable hook is always run with its current + directory set to a repository's root directory. + </para> + + <para>Each hook parameter is passed in as an environment + variable; the name is upper-cased, and prefixed with the + string <quote><literal>HG_</literal></quote>. + </para> + + <para>With the exception of hook parameters, Mercurial does not + set or modify any environment variables when running a hook. + This is useful to remember if you are writing a site-wide hook + that may be run by a number of different users with differing + environment variables set. In multi-user situations, you + should not rely on environment variables being set to the + values you have in your environment when testing the hook. + </para> + + </sect2> + <sect2> + <title>Telling Mercurial to use an in-process hook</title> + + <para>The <filename role="special">~/.hgrc</filename> syntax + for defining an in-process hook is slightly different than for + an executable hook. The value of the hook must start with the + text <quote><literal>python:</literal></quote>, and continue + with the fully-qualified name of a callable object to use as + the hook's value. + </para> + + <para>The module in which a hook lives is automatically imported + when a hook is run. So long as you have the module name and + <envar>PYTHONPATH</envar> right, it should <quote>just + work</quote>. + </para> + + <para>The following <filename role="special">~/.hgrc</filename> + example snippet illustrates the syntax and meaning of the + notions we just described. + </para> + <programlisting>[hooks] +commit.example = python:mymodule.submodule.myhook</programlisting> + <para>When Mercurial runs the <literal>commit.example</literal> + hook, it imports <literal>mymodule.submodule</literal>, looks + for the callable object named <literal>myhook</literal>, and + calls it. + </para> + + </sect2> + <sect2> + <title>Writing an in-process hook</title> + + <para>The simplest in-process hook does nothing, but illustrates + the basic shape of the hook API: + </para> + <programlisting>def myhook(ui, repo, **kwargs): + pass</programlisting> + <para>The first argument to a Python hook is always a <literal + role="py-mod-mercurial.ui">ui</literal> object. The second + is a repository object; at the moment, it is always an + instance of <literal + role="py-mod-mercurial.localrepo">localrepository</literal>. + Following these two arguments are other keyword arguments. + Which ones are passed in depends on the hook being called, but + a hook can ignore arguments it doesn't care about by dropping + them into a keyword argument dict, as with + <literal>**kwargs</literal> above. + </para> + + </sect2> + </sect1> + <sect1> + <title>Some hook examples</title> + + <sect2> + <title>Writing meaningful commit messages</title> + + <para>It's hard to imagine a useful commit message being very + short. The simple <literal role="hook">pretxncommit</literal> + hook of the example below will prevent you from committing a + changeset with a message that is less than ten bytes long. + </para> + +&interaction.hook.msglen.go; + + </sect2> + <sect2> + <title>Checking for trailing whitespace</title> + + <para>An interesting use of a commit-related hook is to help you + to write cleaner code. A simple example of <quote>cleaner + code</quote> is the dictum that a change should not add any + new lines of text that contain <quote>trailing + whitespace</quote>. Trailing whitespace is a series of + space and tab characters at the end of a line of text. In + most cases, trailing whitespace is unnecessary, invisible + noise, but it is occasionally problematic, and people often + prefer to get rid of it. + </para> + + <para>You can use either the <literal + role="hook">precommit</literal> or <literal + role="hook">pretxncommit</literal> hook to tell whether you + have a trailing whitespace problem. If you use the <literal + role="hook">precommit</literal> hook, the hook will not know + which files you are committing, so it will have to check every + modified file in the repository for trailing white space. If + you want to commit a change to just the file + <filename>foo</filename>, but the file + <filename>bar</filename> contains trailing whitespace, doing a + check in the <literal role="hook">precommit</literal> hook + will prevent you from committing <filename>foo</filename> due + to the problem with <filename>bar</filename>. This doesn't + seem right. + </para> + + <para>Should you choose the <literal + role="hook">pretxncommit</literal> hook, the check won't + occur until just before the transaction for the commit + completes. This will allow you to check for problems only the + exact files that are being committed. However, if you entered + the commit message interactively and the hook fails, the + transaction will roll back; you'll have to re-enter the commit + message after you fix the trailing whitespace and run <command + role="hg-cmd">hg commit</command> again. + </para> + +&interaction.hook.ws.simple; + + <para>In this example, we introduce a simple <literal + role="hook">pretxncommit</literal> hook that checks for + trailing whitespace. This hook is short, but not very + helpful. It exits with an error status if a change adds a + line with trailing whitespace to any file, but does not print + any information that might help us to identify the offending + file or line. It also has the nice property of not paying + attention to unmodified lines; only lines that introduce new + trailing whitespace cause problems. + </para> + + <para>The above version is much more complex, but also more + useful. It parses a unified diff to see if any lines add + trailing whitespace, and prints the name of the file and the + line number of each such occurrence. Even better, if the + change adds trailing whitespace, this hook saves the commit + comment and prints the name of the save file before exiting + and telling Mercurial to roll the transaction back, so you can + use the <option role="hg-opt-commit">-l filename</option> + option to <command role="hg-cmd">hg commit</command> to reuse + the saved commit message once you've corrected the problem. + </para> + +&interaction.hook.ws.better; + + <para>As a final aside, note in the example above the use of + <command>perl</command>'s in-place editing feature to get rid + of trailing whitespace from a file. This is concise and + useful enough that I will reproduce it here. + </para> + <programlisting>perl -pi -e 's,\s+$,,' filename</programlisting> + + </sect2> + </sect1> + <sect1> + <title>Bundled hooks</title> + + <para>Mercurial ships with several bundled hooks. You can find + them in the <filename class="directory">hgext</filename> + directory of a Mercurial source tree. If you are using a + Mercurial binary package, the hooks will be located in the + <filename class="directory">hgext</filename> directory of + wherever your package installer put Mercurial. + </para> + + <sect2> + <title><literal role="hg-ext">acl</literal>&emdash;access + control for parts of a repository</title> + + <para>The <literal role="hg-ext">acl</literal> extension lets + you control which remote users are allowed to push changesets + to a networked server. You can protect any portion of a + repository (including the entire repo), so that a specific + remote user can push changes that do not affect the protected + portion. + </para> + + <para>This extension implements access control based on the + identity of the user performing a push, + <emphasis>not</emphasis> on who committed the changesets + they're pushing. It makes sense to use this hook only if you + have a locked-down server environment that authenticates + remote users, and you want to be sure that only specific users + are allowed to push changes to that server. + </para> + + <sect3> + <title>Configuring the <literal role="hook">acl</literal> + hook</title> + + <para>In order to manage incoming changesets, the <literal + role="hg-ext">acl</literal> hook must be used as a + <literal role="hook">pretxnchangegroup</literal> hook. This + lets it see which files are modified by each incoming + changeset, and roll back a group of changesets if they + modify <quote>forbidden</quote> files. Example: + </para> + <programlisting>[hooks] +pretxnchangegroup.acl = python:hgext.acl.hook</programlisting> + + <para>The <literal role="hg-ext">acl</literal> extension is + configured using three sections. + </para> + + <para>The <literal role="rc-acl">acl</literal> section has + only one entry, <envar role="rc-item-acl">sources</envar>, + which lists the sources of incoming changesets that the hook + should pay attention to. You don't normally need to + configure this section. + </para> + <itemizedlist> + <listitem><para><envar role="rc-item-acl">serve</envar>: + Control incoming changesets that are arriving from a + remote repository over http or ssh. This is the default + value of <envar role="rc-item-acl">sources</envar>, and + usually the only setting you'll need for this + configuration item. + </para> + </listitem> + <listitem><para><envar role="rc-item-acl">pull</envar>: + Control incoming changesets that are arriving via a pull + from a local repository. + </para> + </listitem> + <listitem><para><envar role="rc-item-acl">push</envar>: + Control incoming changesets that are arriving via a push + from a local repository. + </para> + </listitem> + <listitem><para><envar role="rc-item-acl">bundle</envar>: + Control incoming changesets that are arriving from + another repository via a bundle. + </para> + </listitem></itemizedlist> + + <para>The <literal role="rc-acl.allow">acl.allow</literal> + section controls the users that are allowed to add + changesets to the repository. If this section is not + present, all users that are not explicitly denied are + allowed. If this section is present, all users that are not + explicitly allowed are denied (so an empty section means + that all users are denied). + </para> + + <para>The <literal role="rc-acl.deny">acl.deny</literal> + section determines which users are denied from adding + changesets to the repository. If this section is not + present or is empty, no users are denied. + </para> + + <para>The syntaxes for the <literal + role="rc-acl.allow">acl.allow</literal> and <literal + role="rc-acl.deny">acl.deny</literal> sections are + identical. On the left of each entry is a glob pattern that + matches files or directories, relative to the root of the + repository; on the right, a user name. + </para> + + <para>In the following example, the user + <literal>docwriter</literal> can only push changes to the + <filename class="directory">docs</filename> subtree of the + repository, while <literal>intern</literal> can push changes + to any file or directory except <filename + class="directory">source/sensitive</filename>. + </para> + <programlisting>[acl.allow] +docs/** = docwriter +[acl.deny] +source/sensitive/** = intern</programlisting> + + </sect3> + <sect3> + <title>Testing and troubleshooting</title> + + <para>If you want to test the <literal + role="hg-ext">acl</literal> hook, run it with Mercurial's + debugging output enabled. Since you'll probably be running + it on a server where it's not convenient (or sometimes + possible) to pass in the <option + role="hg-opt-global">--debug</option> option, don't forget + that you can enable debugging output in your <filename + role="special">~/.hgrc</filename>: + </para> + <programlisting>[ui] +debug = true</programlisting> + <para>With this enabled, the <literal + role="hg-ext">acl</literal> hook will print enough + information to let you figure out why it is allowing or + forbidding pushes from specific users. + </para> + + </sect3> + </sect2> + <sect2> + <title><literal + role="hg-ext">bugzilla</literal>&emdash;integration with + Bugzilla</title> + + <para>The <literal role="hg-ext">bugzilla</literal> extension + adds a comment to a Bugzilla bug whenever it finds a reference + to that bug ID in a commit comment. You can install this hook + on a shared server, so that any time a remote user pushes + changes to this server, the hook gets run. + </para> + + <para>It adds a comment to the bug that looks like this (you can + configure the contents of the comment&emdash;see below): + </para> + <programlisting>Changeset aad8b264143a, made by Joe User + <joe.user@domain.com> in the frobnitz repository, refers + to this bug. For complete details, see + http://hg.domain.com/frobnitz?cmd=changeset;node=aad8b264143a + Changeset description: Fix bug 10483 by guarding against some + NULL pointers</programlisting> + <para>The value of this hook is that it automates the process of + updating a bug any time a changeset refers to it. If you + configure the hook properly, it makes it easy for people to + browse straight from a Bugzilla bug to a changeset that refers + to that bug. + </para> + + <para>You can use the code in this hook as a starting point for + some more exotic Bugzilla integration recipes. Here are a few + possibilities: + </para> + <itemizedlist> + <listitem><para>Require that every changeset pushed to the + server have a valid bug ID in its commit comment. In this + case, you'd want to configure the hook as a <literal + role="hook">pretxncommit</literal> hook. This would + allow the hook to reject changes that didn't contain bug + IDs. + </para> + </listitem> + <listitem><para>Allow incoming changesets to automatically + modify the <emphasis>state</emphasis> of a bug, as well as + simply adding a comment. For example, the hook could + recognise the string <quote>fixed bug 31337</quote> as + indicating that it should update the state of bug 31337 to + <quote>requires testing</quote>. + </para> + </listitem></itemizedlist> + + <sect3 id="sec:hook:bugzilla:config"> + <title>Configuring the <literal role="hook">bugzilla</literal> + hook</title> + + <para>You should configure this hook in your server's + <filename role="special">~/.hgrc</filename> as an <literal + role="hook">incoming</literal> hook, for example as + follows: + </para> + <programlisting>[hooks] +incoming.bugzilla = python:hgext.bugzilla.hook</programlisting> + + <para>Because of the specialised nature of this hook, and + because Bugzilla was not written with this kind of + integration in mind, configuring this hook is a somewhat + involved process. + </para> + + <para>Before you begin, you must install the MySQL bindings + for Python on the host(s) where you'll be running the hook. + If this is not available as a binary package for your + system, you can download it from + <citation>web:mysql-python</citation>. + </para> + + <para>Configuration information for this hook lives in the + <literal role="rc-bugzilla">bugzilla</literal> section of + your <filename role="special">~/.hgrc</filename>. + </para> + <itemizedlist> + <listitem><para><envar + role="rc-item-bugzilla">version</envar>: The version + of Bugzilla installed on the server. The database + schema that Bugzilla uses changes occasionally, so this + hook has to know exactly which schema to use. At the + moment, the only version supported is + <literal>2.16</literal>. + </para> + </listitem> + <listitem><para><envar role="rc-item-bugzilla">host</envar>: + The hostname of the MySQL server that stores your + Bugzilla data. The database must be configured to allow + connections from whatever host you are running the + <literal role="hook">bugzilla</literal> hook on. + </para> + </listitem> + <listitem><para><envar role="rc-item-bugzilla">user</envar>: + The username with which to connect to the MySQL server. + The database must be configured to allow this user to + connect from whatever host you are running the <literal + role="hook">bugzilla</literal> hook on. This user + must be able to access and modify Bugzilla tables. The + default value of this item is <literal>bugs</literal>, + which is the standard name of the Bugzilla user in a + MySQL database. + </para> + </listitem> + <listitem><para><envar + role="rc-item-bugzilla">password</envar>: The MySQL + password for the user you configured above. This is + stored as plain text, so you should make sure that + unauthorised users cannot read the <filename + role="special">~/.hgrc</filename> file where you + store this information. + </para> + </listitem> + <listitem><para><envar role="rc-item-bugzilla">db</envar>: + The name of the Bugzilla database on the MySQL server. + The default value of this item is + <literal>bugs</literal>, which is the standard name of + the MySQL database where Bugzilla stores its data. + </para> + </listitem> + <listitem><para><envar + role="rc-item-bugzilla">notify</envar>: If you want + Bugzilla to send out a notification email to subscribers + after this hook has added a comment to a bug, you will + need this hook to run a command whenever it updates the + database. The command to run depends on where you have + installed Bugzilla, but it will typically look something + like this, if you have Bugzilla installed in <filename + class="directory">/var/www/html/bugzilla</filename>: + </para> + <programlisting>cd /var/www/html/bugzilla && + ./processmail %s nobody@nowhere.com</programlisting> + </listitem> + <listitem><para> The Bugzilla + <literal>processmail</literal> program expects to be + given a bug ID (the hook replaces + <quote><literal>%s</literal></quote> with the bug ID) + and an email address. It also expects to be able to + write to some files in the directory that it runs in. + If Bugzilla and this hook are not installed on the same + machine, you will need to find a way to run + <literal>processmail</literal> on the server where + Bugzilla is installed. + </para> + </listitem></itemizedlist> + + </sect3> + <sect3> + <title>Mapping committer names to Bugzilla user names</title> + + <para>By default, the <literal + role="hg-ext">bugzilla</literal> hook tries to use the + email address of a changeset's committer as the Bugzilla + user name with which to update a bug. If this does not suit + your needs, you can map committer email addresses to + Bugzilla user names using a <literal + role="rc-usermap">usermap</literal> section. + </para> + + <para>Each item in the <literal + role="rc-usermap">usermap</literal> section contains an + email address on the left, and a Bugzilla user name on the + right. + </para> + <programlisting>[usermap] +jane.user@example.com = jane</programlisting> + <para>You can either keep the <literal + role="rc-usermap">usermap</literal> data in a normal + <filename role="special">~/.hgrc</filename>, or tell the + <literal role="hg-ext">bugzilla</literal> hook to read the + information from an external <filename>usermap</filename> + file. In the latter case, you can store + <filename>usermap</filename> data by itself in (for example) + a user-modifiable repository. This makes it possible to let + your users maintain their own <envar + role="rc-item-bugzilla">usermap</envar> entries. The main + <filename role="special">~/.hgrc</filename> file might look + like this: + </para> + <programlisting># regular hgrc file refers to external usermap file +[bugzilla] +usermap = /home/hg/repos/userdata/bugzilla-usermap.conf</programlisting> + <para>While the <filename>usermap</filename> file that it + refers to might look like this: + </para> + <programlisting># bugzilla-usermap.conf - inside a hg repository +[usermap] stephanie@example.com = steph</programlisting> + + </sect3> + <sect3> + <title>Configuring the text that gets added to a bug</title> + + <para>You can configure the text that this hook adds as a + comment; you specify it in the form of a Mercurial template. + Several <filename role="special">~/.hgrc</filename> entries + (still in the <literal role="rc-bugzilla">bugzilla</literal> + section) control this behaviour. + </para> + <itemizedlist> + <listitem><para><literal>strip</literal>: The number of + leading path elements to strip from a repository's path + name to construct a partial path for a URL. For example, + if the repositories on your server live under <filename + class="directory">/home/hg/repos</filename>, and you + have a repository whose path is <filename + class="directory">/home/hg/repos/app/tests</filename>, + then setting <literal>strip</literal> to + <literal>4</literal> will give a partial path of + <filename class="directory">app/tests</filename>. The + hook will make this partial path available when + expanding a template, as <literal>webroot</literal>. + </para> + </listitem> + <listitem><para><literal>template</literal>: The text of the + template to use. In addition to the usual + changeset-related variables, this template can use + <literal>hgweb</literal> (the value of the + <literal>hgweb</literal> configuration item above) and + <literal>webroot</literal> (the path constructed using + <literal>strip</literal> above). + </para> + </listitem></itemizedlist> + + <para>In addition, you can add a <envar + role="rc-item-web">baseurl</envar> item to the <literal + role="rc-web">web</literal> section of your <filename + role="special">~/.hgrc</filename>. The <literal + role="hg-ext">bugzilla</literal> hook will make this + available when expanding a template, as the base string to + use when constructing a URL that will let users browse from + a Bugzilla comment to view a changeset. Example: + </para> + <programlisting>[web] +baseurl = http://hg.domain.com/</programlisting> + + <para>Here is an example set of <literal + role="hg-ext">bugzilla</literal> hook config information. + </para> + + &ch10-bugzilla-config.lst; + + </sect3> + <sect3> + <title>Testing and troubleshooting</title> + + <para>The most common problems with configuring the <literal + role="hg-ext">bugzilla</literal> hook relate to running + Bugzilla's <filename>processmail</filename> script and + mapping committer names to user names. + </para> + + <para>Recall from section <xref + linkend="sec:hook:bugzilla:config"/> above that the user + that runs the Mercurial process on the server is also the + one that will run the <filename>processmail</filename> + script. The <filename>processmail</filename> script + sometimes causes Bugzilla to write to files in its + configuration directory, and Bugzilla's configuration files + are usually owned by the user that your web server runs + under. + </para> + + <para>You can cause <filename>processmail</filename> to be run + with the suitable user's identity using the + <command>sudo</command> command. Here is an example entry + for a <filename>sudoers</filename> file. + </para> + <programlisting>hg_user = (httpd_user) +NOPASSWD: /var/www/html/bugzilla/processmail-wrapper %s</programlisting> + <para>This allows the <literal>hg_user</literal> user to run a + <filename>processmail-wrapper</filename> program under the + identity of <literal>httpd_user</literal>. + </para> + + <para>This indirection through a wrapper script is necessary, + because <filename>processmail</filename> expects to be run + with its current directory set to wherever you installed + Bugzilla; you can't specify that kind of constraint in a + <filename>sudoers</filename> file. The contents of the + wrapper script are simple: + </para> + <programlisting>#!/bin/sh +cd `dirname $0` && ./processmail "$1" nobody@example.com</programlisting> + <para>It doesn't seem to matter what email address you pass to + <filename>processmail</filename>. + </para> + + <para>If your <literal role="rc-usermap">usermap</literal> is + not set up correctly, users will see an error message from + the <literal role="hg-ext">bugzilla</literal> hook when they + push changes to the server. The error message will look + like this: + </para> + <programlisting>cannot find bugzilla user id for john.q.public@example.com</programlisting> + <para>What this means is that the committer's address, + <literal>john.q.public@example.com</literal>, is not a valid + Bugzilla user name, nor does it have an entry in your + <literal role="rc-usermap">usermap</literal> that maps it to + a valid Bugzilla user name. + </para> + + </sect3> + </sect2> + <sect2> + <title><literal role="hg-ext">notify</literal>&emdash;send email + notifications</title> + + <para>Although Mercurial's built-in web server provides RSS + feeds of changes in every repository, many people prefer to + receive change notifications via email. The <literal + role="hg-ext">notify</literal> hook lets you send out + notifications to a set of email addresses whenever changesets + arrive that those subscribers are interested in. + </para> + + <para>As with the <literal role="hg-ext">bugzilla</literal> + hook, the <literal role="hg-ext">notify</literal> hook is + template-driven, so you can customise the contents of the + notification messages that it sends. + </para> + + <para>By default, the <literal role="hg-ext">notify</literal> + hook includes a diff of every changeset that it sends out; you + can limit the size of the diff, or turn this feature off + entirely. It is useful for letting subscribers review changes + immediately, rather than clicking to follow a URL. + </para> + + <sect3> + <title>Configuring the <literal role="hg-ext">notify</literal> + hook</title> + + <para>You can set up the <literal + role="hg-ext">notify</literal> hook to send one email + message per incoming changeset, or one per incoming group of + changesets (all those that arrived in a single pull or + push). + </para> + <programlisting>[hooks] +# send one email per group of changes +changegroup.notify = python:hgext.notify.hook +# send one email per change +incoming.notify = python:hgext.notify.hook</programlisting> + + <para>Configuration information for this hook lives in the + <literal role="rc-notify">notify</literal> section of a + <filename role="special">~/.hgrc</filename> file. + </para> + <itemizedlist> + <listitem><para><envar role="rc-item-notify">test</envar>: + By default, this hook does not send out email at all; + instead, it prints the message that it + <emphasis>would</emphasis> send. Set this item to + <literal>false</literal> to allow email to be sent. The + reason that sending of email is turned off by default is + that it takes several tries to configure this extension + exactly as you would like, and it would be bad form to + spam subscribers with a number of <quote>broken</quote> + notifications while you debug your configuration. + </para> + </listitem> + <listitem><para><envar role="rc-item-notify">config</envar>: + The path to a configuration file that contains + subscription information. This is kept separate from + the main <filename role="special">~/.hgrc</filename> so + that you can maintain it in a repository of its own. + People can then clone that repository, update their + subscriptions, and push the changes back to your server. + </para> + </listitem> + <listitem><para><envar role="rc-item-notify">strip</envar>: + The number of leading path separator characters to strip + from a repository's path, when deciding whether a + repository has subscribers. For example, if the + repositories on your server live in <filename + class="directory">/home/hg/repos</filename>, and + <literal role="hg-ext">notify</literal> is considering a + repository named <filename + class="directory">/home/hg/repos/shared/test</filename>, + setting <envar role="rc-item-notify">strip</envar> to + <literal>4</literal> will cause <literal + role="hg-ext">notify</literal> to trim the path it + considers down to <filename + class="directory">shared/test</filename>, and it will + match subscribers against that. + </para> + </listitem> + <listitem><para><envar + role="rc-item-notify">template</envar>: The template + text to use when sending messages. This specifies both + the contents of the message header and its body. + </para> + </listitem> + <listitem><para><envar + role="rc-item-notify">maxdiff</envar>: The maximum + number of lines of diff data to append to the end of a + message. If a diff is longer than this, it is + truncated. By default, this is set to 300. Set this to + <literal>0</literal> to omit diffs from notification + emails. + </para> + </listitem> + <listitem><para><envar + role="rc-item-notify">sources</envar>: A list of + sources of changesets to consider. This lets you limit + <literal role="hg-ext">notify</literal> to only sending + out email about changes that remote users pushed into + this repository via a server, for example. See section + <xref + linkend="sec:hook:sources"/> for the sources you can + specify here. + </para> + </listitem></itemizedlist> + + <para>If you set the <envar role="rc-item-web">baseurl</envar> + item in the <literal role="rc-web">web</literal> section, + you can use it in a template; it will be available as + <literal>webroot</literal>. + </para> + + <para>Here is an example set of <literal + role="hg-ext">notify</literal> configuration information. + </para> + + &ch10-notify-config.lst; + + <para>This will produce a message that looks like the + following: + </para> + + &ch10-notify-config-mail.lst; + + </sect3> + <sect3> + <title>Testing and troubleshooting</title> + + <para>Do not forget that by default, the <literal + role="hg-ext">notify</literal> extension <emphasis>will not + send any mail</emphasis> until you explicitly configure it to do so, + by setting <envar role="rc-item-notify">test</envar> to + <literal>false</literal>. Until you do that, it simply + prints the message it <emphasis>would</emphasis> send. + </para> + + </sect3> + </sect2> + </sect1> + <sect1 id="sec:hook:ref"> + <title>Information for writers of hooks</title> + + <sect2> + <title>In-process hook execution</title> + + <para>An in-process hook is called with arguments of the + following form: + </para> + <programlisting>def myhook(ui, repo, **kwargs): pass</programlisting> + <para>The <literal>ui</literal> parameter is a <literal + role="py-mod-mercurial.ui">ui</literal> object. The + <literal>repo</literal> parameter is a <literal + role="py-mod-mercurial.localrepo">localrepository</literal> + object. The names and values of the + <literal>**kwargs</literal> parameters depend on the hook + being invoked, with the following common features: + </para> + <itemizedlist> + <listitem><para>If a parameter is named + <literal>node</literal> or <literal>parentN</literal>, it + will contain a hexadecimal changeset ID. The empty string + is used to represent <quote>null changeset ID</quote> + instead of a string of zeroes. + </para> + </listitem> + <listitem><para>If a parameter is named + <literal>url</literal>, it will contain the URL of a + remote repository, if that can be determined. + </para> + </listitem> + <listitem><para>Boolean-valued parameters are represented as + Python <literal>bool</literal> objects. + </para> + </listitem></itemizedlist> + + <para>An in-process hook is called without a change to the + process's working directory (unlike external hooks, which are + run in the root of the repository). It must not change the + process's working directory, or it will cause any calls it + makes into the Mercurial API to fail. + </para> + + <para>If a hook returns a boolean <quote>false</quote> value, it + is considered to have succeeded. If it returns a boolean + <quote>true</quote> value or raises an exception, it is + considered to have failed. A useful way to think of the + calling convention is <quote>tell me if you fail</quote>. + </para> + + <para>Note that changeset IDs are passed into Python hooks as + hexadecimal strings, not the binary hashes that Mercurial's + APIs normally use. To convert a hash from hex to binary, use + the <literal>bin</literal> function. + </para> + + </sect2> + <sect2> + <title>External hook execution</title> + + <para>An external hook is passed to the shell of the user + running Mercurial. Features of that shell, such as variable + substitution and command redirection, are available. The hook + is run in the root directory of the repository (unlike + in-process hooks, which are run in the same directory that + Mercurial was run in). + </para> + + <para>Hook parameters are passed to the hook as environment + variables. Each environment variable's name is converted in + upper case and prefixed with the string + <quote><literal>HG_</literal></quote>. For example, if the + name of a parameter is <quote><literal>node</literal></quote>, + the name of the environment variable representing that + parameter will be <quote><literal>HG_NODE</literal></quote>. + </para> + + <para>A boolean parameter is represented as the string + <quote><literal>1</literal></quote> for <quote>true</quote>, + <quote><literal>0</literal></quote> for <quote>false</quote>. + If an environment variable is named <envar>HG_NODE</envar>, + <envar>HG_PARENT1</envar> or <envar>HG_PARENT2</envar>, it + contains a changeset ID represented as a hexadecimal string. + The empty string is used to represent <quote>null changeset + ID</quote> instead of a string of zeroes. If an environment + variable is named <envar>HG_URL</envar>, it will contain the + URL of a remote repository, if that can be determined. + </para> + + <para>If a hook exits with a status of zero, it is considered to + have succeeded. If it exits with a non-zero status, it is + considered to have failed. + </para> + + </sect2> + <sect2> + <title>Finding out where changesets come from</title> + + <para>A hook that involves the transfer of changesets between a + local repository and another may be able to find out + information about the <quote>far side</quote>. Mercurial + knows <emphasis>how</emphasis> changes are being transferred, + and in many cases <emphasis>where</emphasis> they are being + transferred to or from. + </para> + + <sect3 id="sec:hook:sources"> + <title>Sources of changesets</title> + + <para>Mercurial will tell a hook what means are, or were, used + to transfer changesets between repositories. This is + provided by Mercurial in a Python parameter named + <literal>source</literal>, or an environment variable named + <envar>HG_SOURCE</envar>. + </para> + + <itemizedlist> + <listitem><para><literal>serve</literal>: Changesets are + transferred to or from a remote repository over http or + ssh. + </para> + </listitem> + <listitem><para><literal>pull</literal>: Changesets are + being transferred via a pull from one repository into + another. + </para> + </listitem> + <listitem><para><literal>push</literal>: Changesets are + being transferred via a push from one repository into + another. + </para> + </listitem> + <listitem><para><literal>bundle</literal>: Changesets are + being transferred to or from a bundle. + </para> + </listitem></itemizedlist> + + </sect3> + <sect3 id="sec:hook:url"> + <title>Where changes are going&emdash;remote repository + URLs</title> + + <para>When possible, Mercurial will tell a hook the location + of the <quote>far side</quote> of an activity that transfers + changeset data between repositories. This is provided by + Mercurial in a Python parameter named + <literal>url</literal>, or an environment variable named + <envar>HG_URL</envar>. + </para> + + <para>This information is not always known. If a hook is + invoked in a repository that is being served via http or + ssh, Mercurial cannot tell where the remote repository is, + but it may know where the client is connecting from. In + such cases, the URL will take one of the following forms: + </para> + <itemizedlist> + <listitem><para><literal>remote:ssh:1.2.3.4</literal>&emdash;remote + ssh client, at the IP address + <literal>1.2.3.4</literal>. + </para> + </listitem> + <listitem><para><literal>remote:http:1.2.3.4</literal>&emdash;remote + http client, at the IP address + <literal>1.2.3.4</literal>. If the client is using SSL, + this will be of the form + <literal>remote:https:1.2.3.4</literal>. + </para> + </listitem> + <listitem><para>Empty&emdash;no information could be + discovered about the remote client. + </para> + </listitem></itemizedlist> + + </sect3> + </sect2> + </sect1> + <sect1> + <title>Hook reference</title> + + <sect2 id="sec:hook:changegroup"> + <title><literal role="hook">changegroup</literal>&emdash;after + remote changesets added</title> + + <para>This hook is run after a group of pre-existing changesets + has been added to the repository, for example via a <command + role="hg-cmd">hg pull</command> or <command role="hg-cmd">hg + unbundle</command>. This hook is run once per operation + that added one or more changesets. This is in contrast to the + <literal role="hook">incoming</literal> hook, which is run + once per changeset, regardless of whether the changesets + arrive in a group. + </para> + + <para>Some possible uses for this hook include kicking off an + automated build or test of the added changesets, updating a + bug database, or notifying subscribers that a repository + contains new changes. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>node</literal>: A changeset ID. The + changeset ID of the first changeset in the group that was + added. All changesets between this and + <literal role="tag">tip</literal>, inclusive, were added by a single + <command role="hg-cmd">hg pull</command>, <command + role="hg-cmd">hg push</command> or <command + role="hg-cmd">hg unbundle</command>. + </para> + </listitem> + <listitem><para><literal>source</literal>: A string. The + source of these changes. See section <xref + linkend="sec:hook:sources"/> for details. + </para> + </listitem> + <listitem><para><literal>url</literal>: A URL. The location + of the remote repository, if known. See section <xref + linkend="sec:hook:url"/> for more + information. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">incoming</literal> (section + <xref linkend="sec:hook:incoming"/>), <literal + role="hook">prechangegroup</literal> (section <xref + linkend="sec:hook:prechangegroup"/>), <literal + role="hook">pretxnchangegroup</literal> (section <xref + linkend="sec:hook:pretxnchangegroup"/>) + </para> + + </sect2> + <sect2 id="sec:hook:commit"> + <title><literal role="hook">commit</literal>&emdash;after a new + changeset is created</title> + + <para>This hook is run after a new changeset has been created. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>node</literal>: A changeset ID. The + changeset ID of the newly committed changeset. + </para> + </listitem> + <listitem><para><literal>parent1</literal>: A changeset ID. + The changeset ID of the first parent of the newly + committed changeset. + </para> + </listitem> + <listitem><para><literal>parent2</literal>: A changeset ID. + The changeset ID of the second parent of the newly + committed changeset. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">precommit</literal> + (section <xref linkend="sec:hook:precommit"/>), <literal + role="hook">pretxncommit</literal> (section <xref + linkend="sec:hook:pretxncommit"/>) + </para> + + </sect2> + <sect2 id="sec:hook:incoming"> + <title><literal role="hook">incoming</literal>&emdash;after one + remote changeset is added</title> + + <para>This hook is run after a pre-existing changeset has been + added to the repository, for example via a <command + role="hg-cmd">hg push</command>. If a group of changesets + was added in a single operation, this hook is called once for + each added changeset. + </para> + + <para>You can use this hook for the same purposes as the + <literal role="hook">changegroup</literal> hook (section <xref + linkend="sec:hook:changegroup"/>); it's simply + more convenient sometimes to run a hook once per group of + changesets, while other times it's handier once per changeset. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>node</literal>: A changeset ID. The + ID of the newly added changeset. + </para> + </listitem> + <listitem><para><literal>source</literal>: A string. The + source of these changes. See section <xref + linkend="sec:hook:sources"/> for details. + </para> + </listitem> + <listitem><para><literal>url</literal>: A URL. The location + of the remote repository, if known. See section <xref + linkend="sec:hook:url"/> for more + information. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">changegroup</literal> + (section <xref linkend="sec:hook:changegroup"/>) <literal + role="hook">prechangegroup</literal> (section <xref + linkend="sec:hook:prechangegroup"/>), <literal + role="hook">pretxnchangegroup</literal> (section <xref + linkend="sec:hook:pretxnchangegroup"/>) + </para> + + </sect2> + <sect2 id="sec:hook:outgoing"> + <title><literal role="hook">outgoing</literal>&emdash;after + changesets are propagated</title> + + <para>This hook is run after a group of changesets has been + propagated out of this repository, for example by a <command + role="hg-cmd">hg push</command> or <command role="hg-cmd">hg + bundle</command> command. + </para> + + <para>One possible use for this hook is to notify administrators + that changes have been pulled. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>node</literal>: A changeset ID. The + changeset ID of the first changeset of the group that was + sent. + </para> + </listitem> + <listitem><para><literal>source</literal>: A string. The + source of the of the operation (see section <xref + linkend="sec:hook:sources"/>). If a remote + client pulled changes from this repository, + <literal>source</literal> will be + <literal>serve</literal>. If the client that obtained + changes from this repository was local, + <literal>source</literal> will be + <literal>bundle</literal>, <literal>pull</literal>, or + <literal>push</literal>, depending on the operation the + client performed. + </para> + </listitem> + <listitem><para><literal>url</literal>: A URL. The location + of the remote repository, if known. See section <xref + linkend="sec:hook:url"/> for more + information. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">preoutgoing</literal> + (section <xref linkend="sec:hook:preoutgoing"/>) + </para> + + </sect2> + <sect2 id="sec:hook:prechangegroup"> + <title><literal + role="hook">prechangegroup</literal>&emdash;before starting + to add remote changesets</title> + + <para>This controlling hook is run before Mercurial begins to + add a group of changesets from another repository. + </para> + + <para>This hook does not have any information about the + changesets to be added, because it is run before transmission + of those changesets is allowed to begin. If this hook fails, + the changesets will not be transmitted. + </para> + + <para>One use for this hook is to prevent external changes from + being added to a repository. For example, you could use this + to <quote>freeze</quote> a server-hosted branch temporarily or + permanently so that users cannot push to it, while still + allowing a local administrator to modify the repository. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>source</literal>: A string. The + source of these changes. See section <xref + linkend="sec:hook:sources"/> for details. + </para> + </listitem> + <listitem><para><literal>url</literal>: A URL. The location + of the remote repository, if known. See section <xref + linkend="sec:hook:url"/> for more + information. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">changegroup</literal> + (section <xref linkend="sec:hook:changegroup"/>), <literal + role="hook">incoming</literal> (section <xref + linkend="sec:hook:incoming"/>), , <literal + role="hook">pretxnchangegroup</literal> (section <xref + linkend="sec:hook:pretxnchangegroup"/>) + </para> + + </sect2> + <sect2 id="sec:hook:precommit"> + <title><literal role="hook">precommit</literal>&emdash;before + starting to commit a changeset</title> + + <para>This hook is run before Mercurial begins to commit a new + changeset. It is run before Mercurial has any of the metadata + for the commit, such as the files to be committed, the commit + message, or the commit date. + </para> + + <para>One use for this hook is to disable the ability to commit + new changesets, while still allowing incoming changesets. + Another is to run a build or test, and only allow the commit + to begin if the build or test succeeds. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>parent1</literal>: A changeset ID. + The changeset ID of the first parent of the working + directory. + </para> + </listitem> + <listitem><para><literal>parent2</literal>: A changeset ID. + The changeset ID of the second parent of the working + directory. + </para> + </listitem></itemizedlist> + <para>If the commit proceeds, the parents of the working + directory will become the parents of the new changeset. + </para> + + <para>See also: <literal role="hook">commit</literal> (section + <xref linkend="sec:hook:commit"/>), <literal + role="hook">pretxncommit</literal> (section <xref + linkend="sec:hook:pretxncommit"/>) + </para> + + </sect2> + <sect2 id="sec:hook:preoutgoing"> + <title><literal role="hook">preoutgoing</literal>&emdash;before + starting to propagate changesets</title> + + <para>This hook is invoked before Mercurial knows the identities + of the changesets to be transmitted. + </para> + + <para>One use for this hook is to prevent changes from being + transmitted to another repository. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>source</literal>: A string. The + source of the operation that is attempting to obtain + changes from this repository (see section <xref + linkend="sec:hook:sources"/>). See the documentation + for the <literal>source</literal> parameter to the + <literal role="hook">outgoing</literal> hook, in section + <xref linkend="sec:hook:outgoing"/>, for possible values + of + this parameter. + </para> + </listitem> + <listitem><para><literal>url</literal>: A URL. The location + of the remote repository, if known. See section <xref + linkend="sec:hook:url"/> for more + information. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">outgoing</literal> (section + <xref linkend="sec:hook:outgoing"/>) + </para> + + </sect2> + <sect2 id="sec:hook:pretag"> + <title><literal role="hook">pretag</literal>&emdash;before + tagging a changeset</title> + + <para>This controlling hook is run before a tag is created. If + the hook succeeds, creation of the tag proceeds. If the hook + fails, the tag is not created. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>local</literal>: A boolean. Whether + the tag is local to this repository instance (i.e. stored + in <filename role="special">.hg/localtags</filename>) or + managed by Mercurial (stored in <filename + role="special">.hgtags</filename>). + </para> + </listitem> + <listitem><para><literal>node</literal>: A changeset ID. The + ID of the changeset to be tagged. + </para> + </listitem> + <listitem><para><literal>tag</literal>: A string. The name of + the tag to be created. + </para> + </listitem></itemizedlist> + + <para>If the tag to be created is revision-controlled, the + <literal role="hook">precommit</literal> and <literal + role="hook">pretxncommit</literal> hooks (sections <xref + linkend="sec:hook:commit"/> and <xref + linkend="sec:hook:pretxncommit"/>) will also be run. + </para> + + <para>See also: <literal role="hook">tag</literal> (section + <xref linkend="sec:hook:tag"/>) + </para> + </sect2> + <sect2 id="sec:hook:pretxnchangegroup"> + <title><literal + role="hook">pretxnchangegroup</literal>&emdash;before + completing addition of remote changesets</title> + + <para>This controlling hook is run before a + transaction&emdash;that manages the addition of a group of new + changesets from outside the repository&emdash;completes. If + the hook succeeds, the transaction completes, and all of the + changesets become permanent within this repository. If the + hook fails, the transaction is rolled back, and the data for + the changesets is erased. + </para> + + <para>This hook can access the metadata associated with the + almost-added changesets, but it should not do anything + permanent with this data. It must also not modify the working + directory. + </para> + + <para>While this hook is running, if other Mercurial processes + access this repository, they will be able to see the + almost-added changesets as if they are permanent. This may + lead to race conditions if you do not take steps to avoid + them. + </para> + + <para>This hook can be used to automatically vet a group of + changesets. If the hook fails, all of the changesets are + <quote>rejected</quote> when the transaction rolls back. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>node</literal>: A changeset ID. The + changeset ID of the first changeset in the group that was + added. All changesets between this and + <literal role="tag">tip</literal>, + inclusive, were added by a single <command + role="hg-cmd">hg pull</command>, <command + role="hg-cmd">hg push</command> or <command + role="hg-cmd">hg unbundle</command>. + </para> + </listitem> + <listitem><para><literal>source</literal>: A string. The + source of these changes. See section <xref + linkend="sec:hook:sources"/> for details. + </para> + </listitem> + <listitem><para><literal>url</literal>: A URL. The location + of the remote repository, if known. See section <xref + linkend="sec:hook:url"/> for more + information. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">changegroup</literal> + (section <xref linkend="sec:hook:changegroup"/>), <literal + role="hook">incoming</literal> (section <xref + linkend="sec:hook:incoming"/>), <literal + role="hook">prechangegroup</literal> (section <xref + linkend="sec:hook:prechangegroup"/>) + </para> + + </sect2> + <sect2 id="sec:hook:pretxncommit"> + <title><literal role="hook">pretxncommit</literal>&emdash;before + completing commit of new changeset</title> + + <para>This controlling hook is run before a + transaction&emdash;that manages a new commit&emdash;completes. + If the hook succeeds, the transaction completes and the + changeset becomes permanent within this repository. If the + hook fails, the transaction is rolled back, and the commit + data is erased. + </para> + + <para>This hook can access the metadata associated with the + almost-new changeset, but it should not do anything permanent + with this data. It must also not modify the working + directory. + </para> + + <para>While this hook is running, if other Mercurial processes + access this repository, they will be able to see the + almost-new changeset as if it is permanent. This may lead to + race conditions if you do not take steps to avoid them. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>node</literal>: A changeset ID. The + changeset ID of the newly committed changeset. + </para> + </listitem> + <listitem><para><literal>parent1</literal>: A changeset ID. + The changeset ID of the first parent of the newly + committed changeset. + </para> + </listitem> + <listitem><para><literal>parent2</literal>: A changeset ID. + The changeset ID of the second parent of the newly + committed changeset. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">precommit</literal> + (section <xref linkend="sec:hook:precommit"/>) + </para> + + </sect2> + <sect2 id="sec:hook:preupdate"> + <title><literal role="hook">preupdate</literal>&emdash;before + updating or merging working directory</title> + + <para>This controlling hook is run before an update or merge of + the working directory begins. It is run only if Mercurial's + normal pre-update checks determine that the update or merge + can proceed. If the hook succeeds, the update or merge may + proceed; if it fails, the update or merge does not start. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>parent1</literal>: A changeset ID. + The ID of the parent that the working directory is to be + updated to. If the working directory is being merged, it + will not change this parent. + </para> + </listitem> + <listitem><para><literal>parent2</literal>: A changeset ID. + Only set if the working directory is being merged. The ID + of the revision that the working directory is being merged + with. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">update</literal> (section + <xref linkend="sec:hook:update"/>) + </para> + + </sect2> + <sect2 id="sec:hook:tag"> + <title><literal role="hook">tag</literal>&emdash;after tagging a + changeset</title> + + <para>This hook is run after a tag has been created. + </para> + + <para>Parameters to this hook: + </para> + <itemizedlist> + <listitem><para><literal>local</literal>: A boolean. Whether + the new tag is local to this repository instance (i.e. + stored in <filename + role="special">.hg/localtags</filename>) or managed by + Mercurial (stored in <filename + role="special">.hgtags</filename>). + </para> + </listitem> + <listitem><para><literal>node</literal>: A changeset ID. The + ID of the changeset that was tagged. + </para> + </listitem> + <listitem><para><literal>tag</literal>: A string. The name of + the tag that was created. + </para> + </listitem></itemizedlist> + + <para>If the created tag is revision-controlled, the <literal + role="hook">commit</literal> hook (section <xref + linkend="sec:hook:commit"/>) is run before this hook. + </para> + + <para>See also: <literal role="hook">pretag</literal> (section + <xref linkend="sec:hook:pretag"/>) + </para> + + </sect2> + <sect2 id="sec:hook:update"> + <title><literal role="hook">update</literal>&emdash;after + updating or merging working directory</title> + + <para>This hook is run after an update or merge of the working + directory completes. Since a merge can fail (if the external + <command>hgmerge</command> command fails to resolve conflicts + in a file), this hook communicates whether the update or merge + completed cleanly. + </para> + + <itemizedlist> + <listitem><para><literal>error</literal>: A boolean. + Indicates whether the update or merge completed + successfully. + </para> + </listitem> + <listitem><para><literal>parent1</literal>: A changeset ID. + The ID of the parent that the working directory was + updated to. If the working directory was merged, it will + not have changed this parent. + </para> + </listitem> + <listitem><para><literal>parent2</literal>: A changeset ID. + Only set if the working directory was merged. The ID of + the revision that the working directory was merged with. + </para> + </listitem></itemizedlist> + + <para>See also: <literal role="hook">preupdate</literal> + (section <xref linkend="sec:hook:preupdate"/>) + </para> + + </sect2> + </sect1> +</chapter> + +<!-- +local variables: +sgml-parent-document: ("00book.xml" "book" "chapter") +end: +-->