Mercurial Hook - change a commit message pre commit
Asked Answered
J

2

9

Edit Made this basic hook to prevent branch name & commit message bugID mismatches. https://gist.github.com/2583189

So basically the idea is that the hook should append " BugID:xyz" to the end of the commit messages if the branch name is like bug_123, or feature_123. However I'm having problems finding out how to do this, as most examples of pretxncommit people don't want to mutate the changeset description.

This is what I have so far. It updates .hg/commit.save with the right message, but this message is never transferred to the commit. It is however displayed in the default message box (tortoisehg) of the next commit. Perhaps pretxncommit isn't the right hook?

Could I use a precommit hook, read the commit.save and repo['tip'].branch() file and change that, if so where would I fetch the branch name from?

#
# Fogbugz automaticically add BugID:123 to commit messages based on branch names.
# Your branch name must be in the format feature_123_description or bug_123_description
#

import re
import mercurial, sys, os

_branch_regex = re.compile('(feature|bug|case|bugid|fogbugz)_(\d+)')
_commit_regex = re.compile(r'\b(?P<case>(review|case|bug[zs]?(\s| )*(id)?:?)s?(\s| )*([#:; ]| )+)((([ ,:;#]|and)*)(?P<bugid>\d+))+',re.I)

def pretxncommithook(ui, repo, **kwargs):
    ui.write('hook pretxncommithook running from fogbugz.py\n')
    """
    Checks a single commit message for adherence to commit message rules.

    To use add the following to your project .hg/hgrc for each
    project you want to check, or to your user hgrc to apply to all projects.

    [hooks]
    pretxncommit.fogbugz = python:fogbugz.pretxncommithook
    """
    hg_commit_message = repo['tip'].description()
    commit_has_bugid = _commit_regex.match(hg_commit_message) is not None

    match = _branch_regex.match(repo['tip'].branch())
    if match:
        hg_commit_message = hg_commit_message + ' BugID:'+ match.groups()[1]
            #hg_commit_message needs to be escaped for characters like >
        os.system('echo ' + hg_commit_message + ' > .hg/commit.save')

On a slightly unrelated note, if anyone from the Fogbugz/Kiln team sees this... please update your software to read the branch name, I should not need to put a BugID:x on every damn commit. First of all it wastes my time. Secondly if a the case ID is typed incorrectly it will not show up on the bug without a lot of messing about. A lot of developers use a branch per bug/feature system. It's company policy where I work. Fogbugz sucks.

Jacinto answered 29/4, 2012 at 23:47 Comment(4)
Out of curiosity: why do you need that? You can always retrieve the branch name the commit belongs to. PS: oh, it is Kiln requirement :-S So strangeSinistrous
It's not for human use. Fogbugz, our bug tracker associates commits to bugs by reading the BugID:x or case y in the commit description. Yes, it's ridiculous that I even need it.Jacinto
The FogBugz mercurial integration uses a changegroup hook to push the relevent information into FogBugz. You could possibly alter that to get the FogBugz case id from the branch name instead of from the commit message. Of course you would have to keep your changegroup hook up to date if you upgrade your version of FogBugz.Shanty
I don't have access to the fogbugz servers.Jacinto
L
6

i think the problem here is that the pretxncommit hook is executed at a point where you can't really change anything anymore. And you can't really get the commit message at that point either, because it's not set on any context object accessible.

repo['tip'].description() doesn't refer to the changelog being committed but to old tip already committed, that would be repo[None], but as some digging in the source revealed it's not the same context object that's being persisted, so it's no point in changing it.

the only way i could find would be to use an earlier hook - like precommit - and monkeypatch the commitctx method of the repository like this:

def precommit_hook(repo, **kwargs):

    # keep a copy of repo.commitctx
    commitctx = repo.commitctx

    def updatectx(ctx, error):

        # check if `ctx.branch()` matches ...

        # update commit text
        ctx._text += " ... additional text"

        # call original
        return commitctx(ctx, error)

    # monkeypatch the commit method
    repo.commitctx = updatectx

this way cou can access the context object just before it's committed.

Lalita answered 6/5, 2012 at 0:19 Comment(2)
Looks good. I was trying to use precommit + .hg/commit.save to do this. I'll let you know how it goes.Jacinto
this is certainly sneaky but it helped for meOribel
S
6

mata's answer is clever, but there's actually a built in way to do this if you're willing to write your own extension (it's really easy, little more than just writing the hook functionality you wanted to write anyway).

The "right" way to do this is to subclass the repository in reposetup, as shown in the docstring of mercurial.extensions.wrapfunction (because it turns out wrapfunction is not the right way to do it for repos:

Wrapping methods of the repository object is not recommended since
it conflicts with extensions that extend the repository by
subclassing. All extensions that need to extend methods of
localrepository should use this subclassing trick: namely,
reposetup() should look like

  def reposetup(ui, repo):
      class myrepo(repo.__class__):
          def whatever(self, *args, **kwargs):
              [...extension stuff...]
              super(myrepo, self).whatever(*args, **kwargs)
              [...extension stuff...]

      repo.__class__ = myrepo

So for instance, your extension would look like this (stripped down):

#!/usr/bin/python
import re
import mercurial, sys, os

_branch_regex = re.compile('(feature|bug|case|bugid|fogbugz)_(\d+)')
_commit_regex = re.compile(r'\b(?P<case>(review|case|bug[zs]?(\s| )*(id)?:?)s?(\s| )*([#:; ]| )+)((([ ,:;#]|and)*)(?P<bugid>\d+))+',re.I)

#One of mercurial's callbacks for extensions. This is where you
# you want to subclass repo to add your functionality.
def reposetup(ui, repo):

    #Create a derived class that actually does what you want.
    class myrepo(repo.__class__):
        def commitctx(self, ctx, *args, **kwargs):
            match = _branch_regex.match(ctx.branch())
            if match:
                ctx._text += ' BugID:'+ match.groups()[1]

    #Make sure to actually use the new subclass.
    repo.__class__ = myrepo

### Finish off the extensions stuff

# We aren't adding any commands to hg.
cmdtables = {}

#List of known compatible versions of hg.
testedwith = '2.7.1'

I have tested this and it works fine. You can use the extension by saving it in a python file, say /some-path/fogbugz.py, and adding it under the extensions group in your hgrc:

[extensions]
fogbugz = /some-path/fogbugz.py
Schell answered 29/10, 2013 at 14:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.