We are using pretxncommit hook for HG to run a quick static analysis check on code commited. However the hook is triggered on applying any changes to commit tree - which includes rebasing and using MQ to edit and squash commits.
Is it possible to somehow check what type of change is happening in hook itself? Like
def analyze_hook(ui, repo, node=None, **kwargs):
if repo.state.is_commit_added and not (repo.state.is_rebase or repo.state.is_patch):
return 0
you'd want to check the HG_SOURCE to see what operation is bringing you those commits
https://book.mercurial-scm.org/read/hook.html#sources-of-changesets
e.g. https://github.com/vmg/hg-stable/blob/master/tests/test-hook.t
e.g. https://stackoverflow.com/a/11105493/1681985
Related
I would like to run clang-format (actually clang-format-diff.py, to format only what's changed) on the code I commit in Mercurial automatically. I know I can do it with a precommit hook. In fact, I've done it in the past but it messed up some histedits, so I removed the hook, and now do it manually, running the command before commiting.
Obviously, the problem is I can, and do, forget to do it sometimes.
Is there any way to run the hook only on "normal" commits, not the ones on histedit or rebase?
There's not a direct way as far as I know. But you can create yourself an alias to use instead of rebase and histedit, let's call them hrebase and hhistedit which disable the hook for their usage.
In order to disable a hook for a single run on the command line you can use --config hook.HOOKNAME=, thus for instance:
hg --config hook.HOOKNAME= rebase -d2 -b5
and you thus define your alias:
[alias]
hrebase = rebase --config hook.HOOKNAME=
hhistedit = histedit --config hook.HOOKNAME=
In order to wrap a single command (in your case, commit) you can either use an alias or an extension. The alias approach is fairly simple, but has some drawbacks. Example for an alias:
commit = !$HG commit --config alias.commit=commit --config hooks.precommit.clang=/tmp/msg "$#"
There are a few subtle issues involved in creating such an alias: first, normal aliases do not accept a --config paramater (all configuration has already been parsed at the point of alias expansion). Therefore, we need to use a shell alias (!$HG) in order to work around this problem; second, in order to avoid getting stuck in a recursion during shell alias expansion (unlike normal aliases, Mercurial cannot do this for shell aliases), we have to realias commit to itself (hence the --config alias.commit=commit part).
This approach has a couple of downsides: First, it doubles startup time (because Mercurial gets invoked twice for a shell alias); while this is relatively little overhead, it can be sufficient to be annoying for a human user. Second, it interacts poorly with scripting; scripts and GUIs may either unintentionally use the alias when they don't intend to or (worse) disable it, thus bypassing the hook.
An alternative is to use an extension to wrap the commit command. For example:
# Simple extension to provide a hook for manual commits only
"""hook for manual commits
This extension allows the selective definition of a hook for
manual commits only (i.e. outside graft, histedit, rebase, etc.).
In order to use it, add the following lines to your ``.hg/hgrc`` or
``~/.hgrc`` file::
[extensions]
manualcommithook=/path/to/extension
[hooks]
premanualcommit=/path/to/hook
The ``hooks.premanualcommit`` hook will then be (temporarily) installed
under ``hooks.precommit.manual``, but only for manual commits.
"""
from mercurial import commands, extensions
def commit_with_hook(original_cmd, ui, repo, *pats, **opts):
hook = ui.config("hooks", "premanualcommit")
if hook:
if ui.config("hooks", "precommit.manual"):
ui.warn("overriding existing precommit.manual hook\n")
ui.setconfig("hooks", "precommit.manual", hook)
return original_cmd(ui, repo, *pats, **opts)
def uisetup(ui):
extensions.wrapcommand(commands.table, "commit", commit_with_hook)
See the doc comment for instructions on how to use the extension.
Is it possible to detect if a commit creates a new bookmark or branch via hooks in .hgrc?
I've tried to see if I can find out using hg log, but it just shows on what branch/bookmark the commit has been created: http://hgbook.red-bean.com/read/customizing-the-output-of-mercurial.html
There don't seem to be hooks for it: http://hgbook.red-bean.com/read/handling-repository-events-with-hooks.html
It would make sense I suppose that there isn't a hook for it, because it is also not possible to make a commit which is 'just' the creation of the branch indicating branches/bookmarks only exists when added to a specific commit.
I figured I could check hg branches and hg bookmarks before and after each commit and determine which are removed and added, but is there a cleaner way for detecting branch/bookmark adds/removes?
The pushkey and prepushkey hooks can detect the addition, deletion, and moves of bookmarks.
In hgrc:
[hooks]
prepushkey=echo %HG_NAMESPACE% %HG_KEY% %HG_OLD% %HG_NEW%\n >> out.txt
HG_NAMESPACE will contain "bookmark" and HG_KEY will contain the name of the bookmark.
HG_OLD will contain the hash of the commit the bookmark was before the operation. It won't be set if the bookmark is being created.
HG_NEW will contain the hash of the commit the bookmark will be after the operation. It won't be set if the bookmark is being deleted.
Bookmarks
Bookmarks-handling does not require commit
Bookmark can be created|modified for any changeset in history, not only for current
Bookmark(s) can appear as result of data-exchange (pull|push), not local user's actions
Only part of all possible cases can be covered by hook
Branches
Changing branch always reflected in commit
Branch in changeset may differ from parent's branch not only as result of hg branch (see "merge branches" and "mergesets") - and I haven't nice and easy and ready to use solution for this case
Common notes
You can use standard precommit hook (executed before commit and can enable|disable commit) for testing different conditions in commit or or pretxncommit
Mercurial template-engine has keywords for branch and bookmark for using in hg log -T
In pretxncommit hook commit already exist, but not finalized - it means you can manipulate with commit's data using -r tip and tip's parent in any Mercurial command
Dirty skeleton for hook's body
hg log -T "{KEYWORD}\n" -r "tip + tip^" | ....
where KEYWORD may be branch or bookmarks. Result of log is two-strings output, which can be identical of different (not sure for bookmark, TBT!!), which you have to compare (as you want and can)
PS: Idea inspired by EnsureCommitPolicy Wiki and Mercurial pre commit hook topic here
I want to write a hook that performs some actions each time I run hg branch branch_name (e.g. set "In progress" status for a JIRA ticket), but I can't find anything that runs during branching. Is there a way I can do it?
The is a pre-<command> hook (with a hyphen) for each command. Note that is is distinct from any hook that may exist without a hyphen, sush as precommit.
Thus you can do:
[hooks]
pre-bookmark = /usr/bin/notify_jira.sh ${HG_ARGS#bookmark }
to invoke:
/usr/bin/notify_jira.sh PROJ-415
when you run:
hg bookmark PROJ-415
Full details on the generic pre-<command> (and post-<command>) hooks can be found on the hgrc man page.
It also looks like pushkey hook might do what you want, but pre-bookmark (or better, post-bookmark) is probably more straightforward.
I have a need for a hook to run after update (this will build the solution they have updated) and I don't want to have to add that hook manually for each person that clones my central repository.
When someone first clones my central repository, is it possible to include hooks into that clone? It seems that the .hgrc file doesn't get cloned automatically.
I did read about site-wide hooks, but as far as I understand it, they work on each created repository, where I only want to have the hooks on some repos.
As Rudi already said, this is (thankfully) not possible for security reasons.
However, you can reduce the per-clone workload to set up hooks manually: Ship the hook scripts as part of your repository, e.g. in a directory .hghooks, and additionally include a script in your repo which sets up these hooks in a clone's hgrc. Each coworker now only needs to call the setup script once per clone.
This is not possible, since that hooks do not propagate to clones is a security measure. If this were possible, one could set up a rouge repository, which runs arbitrary commands on any machine where the repo is cloned.
See http://hgbook.red-bean.com/read/handling-repository-events-with-hooks.html#id402330 for more details.
This will allow for centralised per-repo hooks, with a single setup step per user. It will however cause problems for users who are disconnected from the network. An alternative if you tend to have disconnected developers (or ones over high-latency/low bandwidth links) would be to have a repo containing the hooks, and set up each user's global hgrc to point into that repo (and require regular pulls from a central hook repo).
Note that I treat the ID of the first commit as the "repo ID" - this assumes that the first commit in each repository is unique in some way - contents or commit message. If this is not the case you could do the same thing but applying it over the first N commits - but you would then have to account for repos that have fewer than N commits - can't just take repo[:5] for example as newer commits would then change the repo ID. I'd personally suggest that the first commit should probably be a standard .ignore file with a commit message unique to that repo.
Have a central shared_hgrc file, accessible from a network share (or in a hook repo).
Each user's global hgrc has:
%include /path/to/shared_hgrc
Create a shared repository of python hook modules. The hooks must be written in python.
Create your hook functions. In each function, check which repo the hook has been called on by checking the ID of the first commit:
# hooktest.py
import mercurial.util
FOOBAR_REPO = 'b88c69276866d73310be679b6a4b40d875e26d84'
ALLOW_PRECOMMIT_REPOS = set((
FOOBAR_REPO,
))
def precommit_deny_if_wrong_repo(ui, repo, **kwargs):
"""Aborts if the repo is not allowed to do this.
The repo ID is the ID of the first commit to the repo."""
repo_id = repo[0].hex().lower()
if repo_id not in ALLOW_PRECOMMIT_REPOS:
raise mercurial.util.Abort('Repository denied: %s' % (repo_id,))
ui.status('Repository allowed: %s\n' % (repo_id,))
def precommit_skip_if_wrong_repo(ui, repo, **kwargs):
"""Skips the hook if the repo is not allowed to do this.
The repo ID is the ID of the first commit to the repo."""
repo_id = repo[0].hex().lower()
if repo_id not in ALLOW_PRECOMMIT_REPOS:
ui.debug('Repository hook skipped: %s\n' % (repo_id,))
return
ui.status('Repository hook allowed: %s\n' % (repo_id,))
In the shared_hgrc file, set up the hooks you need (make sure you qualify the hook names to prevent conflicts):
[hooks]
pre-commit.00_skip = python:/path/to/hooktest.py:precommit_skip_if_wrong_repo
pre-commit.01_deny = python:/path/to/hooktest.py:precommit_deny_if_wrong_repo
As #Rudi said first, it can't be done for security reasons.
With some prior setup you can make it so that hooks are run on clone, but putting a hook with a repo-relative path in /etc/mercurial or in each user's ~/.hgrc, which in a corporate setting can be done via your system management tools or by building a custom Mercurial installer. In a non-corporate setting follow #Oben's advice and provide the scripts and a readme.
I have a "central" repository that I want to ensure that no one pushes changes in to with a wrong user name.
But I can not figure out how to make a hook that tests the user name against a positive list. I have found in the Mercurial API a ctx.user() call that seems to be what I want to test my positive list against.
Also the hook could be a precommit hook that is distributed as part of the repository clone or it could be a hook on the central repository as a pre-incoming or something like that.
Any help or pointers would be greatly appreciated.
I have posted two functional examples on Bitbucket. Both examples are for searching a commit message for some specifically formatted text (like an issue tracked case ID), but could be easily modified to check a user against a list of valid users.
The first example is actually a Mercurial extension that wraps the 'commit' command. If it fails to find the appropriate text (or valid user in your case), it will prevent the commit from occurring at all. You can enable this in your .hgrc file by adding these lines:
[extensions]
someName = path/to/script/commit-msg-check.py
The second example uses a in-process pretxncommit hook, which runs between when the commit has been made, but before it becomes permanent. If this check fails it will automatically roll back the commit. You can enable this in your .hgrc file by adding these lines (assuming you kept the same file/function names):
[hooks]
pretxncommit.example = python:commit-msg-check-hook.CheckForIssueRecord
You can execute any Python code you like inside of these hooks, so user validation could be done in many ways.
Thanks for the examples dls.
In the end I decided to run it as a pretxnchangegroup hook and then use the hg log and grep to test the author field of the commits:
[hooks]
pretxnchangegroup.usercheck = hg log --template '{author}\n' -r \
$HG_NODE: | grep -qe 'user1\|user2\|etc'
It does of course not provide a very good feedback other than usercheck failed. But I think it is good enough for now.