1. Benoît Allard
  2. hotfiles


hotfiles / hotfiles.py

"""detect hot files

This work is based on the following blog article:


This extension can be configured through the configuration file to avoid
repeating the same parameters again and again on the command line. The
configuration keys have to be under the ``hotfiles`` section, and can have
one of the following values::

    pattern = issue\d+
    include.glob =
    exclude.glob =

import re

from math import exp

from mercurial import cmdutil, scmutil, commands, extensions
from mercurial.i18n import _
from mercurial.node import nullrev
from mercurial.match import match

cmdtable = {}
    command = cmdutil.command(cmdtable)
except AttributeError:
    print "Mercurial version too old, please upgrade to at least 1.9"

def getb():
        b = extensions.find('b')
    except KeyError:
        return None
    if b is None:
        return None
    bversionfunc = getattr(b, 'version')
    if bversionfunc is None:
        return None
    if bversionfunc() < bversionfunc('0.6.2'):
        return None
    return b

         [('r', 'rev', '', _('operate up to a given revision'), 'REV'),
          ('p', 'pattern', '', _('pattern to filter issue-fixing commits'),
          ('n', 'nfiles', 10, _('number of files to show'), 'NUM'),
          ] + commands.walkopts,
         _('[-r REV] [-p REGEX]')
def hotfiles(ui, repo, node=None, rev='.', pattern=None, **opts):
    """ Print the ten files the most susceptible to contains issues.

    The calculation is based on the number of time a file was touched during
    a bug-fixing commit, later commits weight more than earlier ones.

    The list of files analysed is based on the files present in the sepcified
    revision, this list can be filtered through the usage of include/exclude
    patterns on the command line or in the configuration file.

    The list of revisions analysed is by default all the non-merge revision.
    This list can be filtered by using a pattern given through the command line
    or the configuration file.

    if not pattern:
        pattern = ui.config('hotfiles', 'pattern')
        if pattern is None:
            ui.debug('No pattern configured, taking all non-merge commits\n')
            pattern = '.*'

    ui.debug('using %s as pattern to filter changesets\n' % pattern)
    r = re.compile(pattern)

    b = getb()

    nfiles = opts.get('nfiles', 10)

    excludeglob = ui.configlist('hotfiles', 'exclude.glob', [])
    excludeglob.extend(opts.get('exclude', []))
    if b is not None:
    includeglob = ui.configlist('hotfiles', 'include.glob', [])
    includeglob.extend(opts.get('include', []))
    m = match(repo.root, '', None, exclude=excludeglob, include=includeglob)

    if not node:
        node = rev

    ctx = scmutil.revsingle(repo, node)

    data = {}
    for f in ctx:
        if m(f):
            # only take the files from the given revisions that match our set
            # of includes/excludes
            data[f] = []

    rev = ctx.rev()

    t, tz = repo[0].date()
    t0 = float(t) - tz
    t, tz = ctx.date()
    t1 = float(t) - tz

    def ti(t, tz):
        t = float(t) - tz
        return (t - t0) / (t1 - t0)

    for rv in xrange(rev):
        ctx = repo[rv]

        parents = [p for p in repo.changelog.parentrevs(rv)
                   if p != nullrev]
        if len(parents) == 2:
            # discard merges
        if not (r.search(ctx.description())):
        if b is not None and not b.is_bugfix_rev(ui, repo, ctx.rev()):

        t = ti(*ctx.date())

        for f in ctx.files():
            if f in data:

        ui.progress('revisions', rv, total=rev)
    ui.progress('revisions', None)

    pos = 0
    d = []
    for f, ts in data.iteritems():
        s = 0
        for t in ts:
            s += 1 / (1 + exp((-12 * t) + 12))
        if s != 0:
            d.append((f, s))
        ui.progress('score', pos, f, total=len(data))
        pos += 1
    ui.progress('score', None)

    d = sorted(d, key=lambda x: x[1], reverse=True)[:nfiles]

    if d:
        max = d[0][1]
    for f, s in d:
        if ui.verbose:
            ui.write('% 4d: %s\n' % (int(s / max * 100), f))
            ui.write('%s\n' % f)