Hooks
Basic information about hooks
Contents
1. Hook return values
Hooks can be implemented as either external programs, or internal Python calls. The meaning of the return value in both cases is based on the convention for external executables: a value of 0 means "success". For hooks implemented in Python this can be a bit misleading, since it means you return "False" to indicate success and "True" (or raise an exception) to indicate failure.
2. Commit Hook
You can use the hook feature to, for example, automatically send mail when a commit occurs. Add the following to .hg/hgrc:
[hooks] commit = commithook
And put this in a file called "commithook" in your path:
#!/bin/sh SUBJECT=$(hg log -r $HG_NODE --template '{desc|firstline}') hg log -vpr $HG_NODE | mail -s "commit: $SUBJECT" commit-list@example.com
And something like this can be used for closing bugs in Roundup:
#!/bin/sh ISSUE=`hg log -vr $HG_NODE | grep -o "\<fix:[[:alnum:]]*" | head -n 1 | sed s/fix://` [ -z $ISSUE ] && exit SUBJECT=`hg log -r $HG_NODE | grep "^summary:" | cut -b 14-` hg log -vr $HG_NODE | mail -s "[$ISSUE] [status=testing] commit: $SUBJECT" roundup@example.com
If you want multiple hooks, just add a suffix to the action; an empty command can be used to unset a site-wide hook:
[hooks] # do not use the site-wide hook commit = commit.email = /my/email/hook commit.autobuild = /my/build/hook
3. Tips for centralized repositories
If you are using some central repo with the shared ssh method and want to get notified on pushes to it don't use the commit hook (which won't get triggered), but the incoming hook.
If you push a large number of changesets, the script above will flood users with one email per commit. Instead, consider using the changegroup hook, which is activated once for each push/pull/unbundle instead of once for each changeset.
4. The changegroup hook
The changegroup hook is activated once for each push/pull/unbundle, unlike the commit hook, which is run once for each changeset.
To send email containing all changesets of a push/pull/unbundle, put this in .hg/hgrc:
[hooks] changegroup = /path/to/changegrouphook
Then put something like this in /path/to/changegrouphook:
#!/bin/sh dest=" commit-list@example.com " repo="http://hg.example.com/myrepo" subject="New changesets in $repo" hg log -r $HG_NODE: | mail -s "$subject" $dest
For a nicer display of the changes, use hg log -G like this:
#!/bin/sh dest=" commit-list@example.com " repo="http://hg.example.com/myrepo" subject="New changesets in $repo" hg log -G -r $HG_NODE: | mail -s "$subject" $dest
An alternative is to use the NotifyExtension, which sends email notifications to a list of subscribed addresses.
5. Commit mail for patch review
One common reason for commit emails is to review changes. When multiple committers push to a central, canonical repository and have changes mailed only when they push to the repository rather than whenever they do commits to their local clones, you can get the same data coming through multiple times, making it hard to review the changes. This happens because of merge commits; hg pull; hg update do work hg commit; hg pull; hg update -m; hg commit will give two commit messages; one for the work and one for the merge. Except when there are conflicts, the text of the commit message for the merge is a diff that is really part of the commit, but which doesn't represent an actual change to the central, canonical repository. So while the commit message is absolutely technically correct, the duplicate information that it provides tends to degrade the reviewing process, and in practice, we (rPath) saw it obscure accidental reversion from a bad merge.
It is possible to remove almost all of the duplicate information with a bit of scripting. The following script does the trick:
#!/bin/bash RECIP="" MODULE="" while [ $# -gt 0 ] ; do . case $1 in . --recip) . shift RECIP="$RECIP $1" shift ;; --module) . shift MODULE=$1 shift ;; esac done hg update -C SUBJECT=$(hg log -r $HG_NODE --template '{desc|firstline}') PARENT=$(hg log --debug -r $HG_NODE | sed -n '/^parent:/{s/^.*: *.*://;p}' | head -1 ) FILES=$(hg log -v -r $HG_NODE | sed -n '/^files:/{s/^.*: *//;p}') if [ -z "$FILES" ]; then . exit 0 fi (hg log -vr $HG_NODE | egrep -v '^(changeset|parent|date):' | sed 's/^description:$//' | cat -s ; hg diff -r $PARENT -r $HG_NODE $FILES) | mail -s "$MODULE: $SUBJECT" $RECIP
This script filters out some of the other information that is really not needed for patch review: the changeset hash exists in the diff; the parents are available by using hg log on the changeset hash; the word "description" isn't really needed in this context; the date is in the email header, etc. Some of the brevity introduced in this script was driven by one of its authors being in the habit of reading changemail on his Treo, but it seems to generally help focus the review process on the things that really changed. When we pointed this script at the node with the accidental inappropriate reversion, it went from being hidden in the noise to being painfully obvious.
6. Change temporary directory used on remote when pushing
By default Mercurial uses /tmp (or one of the directories defined by environment variables TMPDIR, TEMP, TMP) to uncompress the bundle received from a remote host. This may be problematic on some servers with a small /tmp directory or with small quotas on that partition. To circumvent that, you can define a hook to set TMPDIR to another location before mercurial sets up its serving thread.
On remote global hgrc (/etc/mercurial/hgrc or MERCURIAL.INI, set the following hook:
[hooks] pre-serve.tmpdir = python:hgenviron.settmpdir
Somewhere in your $PYTHONPATH, put the following hgenviron.py file :
import os #see http://docs.python.org/lib/module-tempfile.html def settmpdir(ui, repo, hooktype, node=None, source=None, **kwargs): os.environ["TMPDIR"] = "/home/tmp"
Now Mercurial on remote will use /home/tmp as the temporary directory when receiving changesets, for every user (but only for mercurial).
7. Automatically add a "Signed-off-by:" tag line to commit messages
To automate the process of adding the "signed-off-by" tag line like it's used by the Linux Kernel community and many other projects, you could use a commit hook like this one: https://bitbucket.org/snippets/LenzGr/aygXb To enable it, simply download and place the file signoff.py into your repository's .hg directory. Then, add the following to the [hooks] section of .hg/hgrc:
[hooks] precommit = python:.hg/signoff.py:sign_commit_message
8. Dealing with demandimport in hooks
With in-process hooks, you can not turn off demandimport because has already been enabled by Mercurial.
So appending to the ignore list might not work, ex:
# this might not work from mercurial import demandimport demandimport.ignore.extend([ "requests.packages.urllib3.util.ssl_", ".ssl_", # requests uses relative imports ".util.ssl_", # requests uses relative imports ])
However, there is another way to "by-pass" demandimport: it is to actually use something in the module.
For example, if you have this error with your server-side push hook:
remote: File "/usr/local/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py", line 229, in create_urllib3_context remote: options |= OP_NO_COMPRESSION remote: TypeError: unsupported operand type(s) for |=: 'int' and '_demandmod'
This is because requests/packages/urllib3/util/ssl_.py has this code:
try: from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION except ImportError: OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 OP_NO_COMPRESSION = 0x20000
One way to deal with this "try except ImportError" not working with demandimport is to put the following before the code that actually needs it.
from requests.packages.urllib3.util import ssl_ ssl_.OP_NO_COMPRESSION = 0x20000 # then the code that uses the request package with ssl that used to blow up as above
9. Listing active hooks
If hooks cause problems you'll want to know which hooks are defined/active and which configuration file they are defined in.
Using the following command you will get a list of files hooks are read from and the list of active hooks:
hg config hooks --debug
10. See also
hgrc for a complete list of hooks