eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/churn.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # churn.py - create a graph of revisions count grouped by template
       
     2 #
       
     3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
       
     4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 '''command to display statistics about repository history'''
       
    10 
       
    11 from mercurial.i18n import _
       
    12 from mercurial import patch, cmdutil, util, templater, commands
       
    13 import os
       
    14 import time, datetime
       
    15 
       
    16 def maketemplater(ui, repo, tmpl):
       
    17     tmpl = templater.parsestring(tmpl, quoted=False)
       
    18     try:
       
    19         t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
       
    20     except SyntaxError, inst:
       
    21         raise util.Abort(inst.args[0])
       
    22     t.use_template(tmpl)
       
    23     return t
       
    24 
       
    25 def changedlines(ui, repo, ctx1, ctx2, fns):
       
    26     added, removed = 0, 0
       
    27     fmatch = cmdutil.matchfiles(repo, fns)
       
    28     diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
       
    29     for l in diff.split('\n'):
       
    30         if l.startswith("+") and not l.startswith("+++ "):
       
    31             added += 1
       
    32         elif l.startswith("-") and not l.startswith("--- "):
       
    33             removed += 1
       
    34     return (added, removed)
       
    35 
       
    36 def countrate(ui, repo, amap, *pats, **opts):
       
    37     """Calculate stats"""
       
    38     if opts.get('dateformat'):
       
    39         def getkey(ctx):
       
    40             t, tz = ctx.date()
       
    41             date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
       
    42             return date.strftime(opts['dateformat'])
       
    43     else:
       
    44         tmpl = opts.get('template', '{author|email}')
       
    45         tmpl = maketemplater(ui, repo, tmpl)
       
    46         def getkey(ctx):
       
    47             ui.pushbuffer()
       
    48             tmpl.show(ctx)
       
    49             return ui.popbuffer()
       
    50 
       
    51     state = {'count': 0}
       
    52     rate = {}
       
    53     df = False
       
    54     if opts.get('date'):
       
    55         df = util.matchdate(opts['date'])
       
    56 
       
    57     m = cmdutil.match(repo, pats, opts)
       
    58     def prep(ctx, fns):
       
    59         rev = ctx.rev()
       
    60         if df and not df(ctx.date()[0]): # doesn't match date format
       
    61             return
       
    62 
       
    63         key = getkey(ctx)
       
    64         key = amap.get(key, key) # alias remap
       
    65         key = key.strip() # ignore leading and trailing spaces
       
    66         if opts.get('changesets'):
       
    67             rate[key] = (rate.get(key, (0,))[0] + 1, 0)
       
    68         else:
       
    69             parents = ctx.parents()
       
    70             if len(parents) > 1:
       
    71                 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
       
    72                 return
       
    73 
       
    74             ctx1 = parents[0]
       
    75             lines = changedlines(ui, repo, ctx1, ctx, fns)
       
    76             rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
       
    77 
       
    78         state['count'] += 1
       
    79         ui.progress(_('analyzing'), state['count'], total=len(repo))
       
    80 
       
    81     for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
       
    82         continue
       
    83 
       
    84     ui.progress(_('analyzing'), None)
       
    85 
       
    86     return rate
       
    87 
       
    88 
       
    89 def churn(ui, repo, *pats, **opts):
       
    90     '''histogram of changes to the repository
       
    91 
       
    92     This command will display a histogram representing the number
       
    93     of changed lines or revisions, grouped according to the given
       
    94     template. The default template will group changes by author.
       
    95     The --dateformat option may be used to group the results by
       
    96     date instead.
       
    97 
       
    98     Statistics are based on the number of changed lines, or
       
    99     alternatively the number of matching revisions if the
       
   100     --changesets option is specified.
       
   101 
       
   102     Examples::
       
   103 
       
   104       # display count of changed lines for every committer
       
   105       hg churn -t '{author|email}'
       
   106 
       
   107       # display daily activity graph
       
   108       hg churn -f '%H' -s -c
       
   109 
       
   110       # display activity of developers by month
       
   111       hg churn -f '%Y-%m' -s -c
       
   112 
       
   113       # display count of lines changed in every year
       
   114       hg churn -f '%Y' -s
       
   115 
       
   116     It is possible to map alternate email addresses to a main address
       
   117     by providing a file using the following format::
       
   118 
       
   119       <alias email> = <actual email>
       
   120 
       
   121     Such a file may be specified with the --aliases option, otherwise
       
   122     a .hgchurn file will be looked for in the working directory root.
       
   123     '''
       
   124     def pad(s, l):
       
   125         return (s + " " * l)[:l]
       
   126 
       
   127     amap = {}
       
   128     aliases = opts.get('aliases')
       
   129     if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
       
   130         aliases = repo.wjoin('.hgchurn')
       
   131     if aliases:
       
   132         for l in open(aliases, "r"):
       
   133             try:
       
   134                 alias, actual = l.split('=' in l and '=' or None, 1)
       
   135                 amap[alias.strip()] = actual.strip()
       
   136             except ValueError:
       
   137                 l = l.strip()
       
   138                 if l:
       
   139                     ui.warn(_("skipping malformed alias: %s\n" % l))
       
   140                 continue
       
   141 
       
   142     rate = countrate(ui, repo, amap, *pats, **opts).items()
       
   143     if not rate:
       
   144         return
       
   145 
       
   146     sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
       
   147     rate.sort(key=sortkey)
       
   148 
       
   149     # Be careful not to have a zero maxcount (issue833)
       
   150     maxcount = float(max(sum(v) for k, v in rate)) or 1.0
       
   151     maxname = max(len(k) for k, v in rate)
       
   152 
       
   153     ttywidth = ui.termwidth()
       
   154     ui.debug("assuming %i character terminal\n" % ttywidth)
       
   155     width = ttywidth - maxname - 2 - 2 - 2
       
   156 
       
   157     if opts.get('diffstat'):
       
   158         width -= 15
       
   159         def format(name, diffstat):
       
   160             added, removed = diffstat
       
   161             return "%s %15s %s%s\n" % (pad(name, maxname),
       
   162                                        '+%d/-%d' % (added, removed),
       
   163                                        ui.label('+' * charnum(added),
       
   164                                                 'diffstat.inserted'),
       
   165                                        ui.label('-' * charnum(removed),
       
   166                                                 'diffstat.deleted'))
       
   167     else:
       
   168         width -= 6
       
   169         def format(name, count):
       
   170             return "%s %6d %s\n" % (pad(name, maxname), sum(count),
       
   171                                     '*' * charnum(sum(count)))
       
   172 
       
   173     def charnum(count):
       
   174         return int(round(count * width / maxcount))
       
   175 
       
   176     for name, count in rate:
       
   177         ui.write(format(name, count))
       
   178 
       
   179 
       
   180 cmdtable = {
       
   181     "churn":
       
   182         (churn,
       
   183          [('r', 'rev', [],
       
   184            _('count rate for the specified revision or range'), _('REV')),
       
   185           ('d', 'date', '',
       
   186            _('count rate for revisions matching date spec'), _('DATE')),
       
   187           ('t', 'template', '{author|email}',
       
   188            _('template to group changesets'), _('TEMPLATE')),
       
   189           ('f', 'dateformat', '',
       
   190            _('strftime-compatible format for grouping by date'), _('FORMAT')),
       
   191           ('c', 'changesets', False, _('count rate by number of changesets')),
       
   192           ('s', 'sort', False, _('sort by key (default: sort by count)')),
       
   193           ('', 'diffstat', False, _('display added/removed lines separately')),
       
   194           ('', 'aliases', '',
       
   195            _('file with email aliases'), _('FILE')),
       
   196           ] + commands.walkopts,
       
   197          _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
       
   198 }