hgext/churn.py
changeset 43076 2372284d9457
parent 40293 c303d65d2e34
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    32 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
    32 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
    33 # be specifying the version(s) of Mercurial they are tested with, or
    33 # be specifying the version(s) of Mercurial they are tested with, or
    34 # leave the attribute unspecified.
    34 # leave the attribute unspecified.
    35 testedwith = 'ships-with-hg-core'
    35 testedwith = 'ships-with-hg-core'
    36 
    36 
       
    37 
    37 def changedlines(ui, repo, ctx1, ctx2, fns):
    38 def changedlines(ui, repo, ctx1, ctx2, fns):
    38     added, removed = 0, 0
    39     added, removed = 0, 0
    39     fmatch = scmutil.matchfiles(repo, fns)
    40     fmatch = scmutil.matchfiles(repo, fns)
    40     diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
    41     diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
    41     for l in diff.split('\n'):
    42     for l in diff.split('\n'):
    43             added += 1
    44             added += 1
    44         elif l.startswith("-") and not l.startswith("--- "):
    45         elif l.startswith("-") and not l.startswith("--- "):
    45             removed += 1
    46             removed += 1
    46     return (added, removed)
    47     return (added, removed)
    47 
    48 
       
    49 
    48 def countrate(ui, repo, amap, *pats, **opts):
    50 def countrate(ui, repo, amap, *pats, **opts):
    49     """Calculate stats"""
    51     """Calculate stats"""
    50     opts = pycompat.byteskwargs(opts)
    52     opts = pycompat.byteskwargs(opts)
    51     if opts.get('dateformat'):
    53     if opts.get('dateformat'):
       
    54 
    52         def getkey(ctx):
    55         def getkey(ctx):
    53             t, tz = ctx.date()
    56             t, tz = ctx.date()
    54             date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
    57             date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
    55             return encoding.strtolocal(
    58             return encoding.strtolocal(
    56                 date.strftime(encoding.strfromlocal(opts['dateformat'])))
    59                 date.strftime(encoding.strfromlocal(opts['dateformat']))
       
    60             )
       
    61 
    57     else:
    62     else:
    58         tmpl = opts.get('oldtemplate') or opts.get('template')
    63         tmpl = opts.get('oldtemplate') or opts.get('template')
    59         tmpl = logcmdutil.maketemplater(ui, repo, tmpl)
    64         tmpl = logcmdutil.maketemplater(ui, repo, tmpl)
       
    65 
    60         def getkey(ctx):
    66         def getkey(ctx):
    61             ui.pushbuffer()
    67             ui.pushbuffer()
    62             tmpl.show(ctx)
    68             tmpl.show(ctx)
    63             return ui.popbuffer()
    69             return ui.popbuffer()
    64 
    70 
    65     progress = ui.makeprogress(_('analyzing'), unit=_('revisions'),
    71     progress = ui.makeprogress(
    66                                total=len(repo))
    72         _('analyzing'), unit=_('revisions'), total=len(repo)
       
    73     )
    67     rate = {}
    74     rate = {}
    68     df = False
    75     df = False
    69     if opts.get('date'):
    76     if opts.get('date'):
    70         df = dateutil.matchdate(opts['date'])
    77         df = dateutil.matchdate(opts['date'])
    71 
    78 
    72     m = scmutil.match(repo[None], pats, opts)
    79     m = scmutil.match(repo[None], pats, opts)
       
    80 
    73     def prep(ctx, fns):
    81     def prep(ctx, fns):
    74         rev = ctx.rev()
    82         rev = ctx.rev()
    75         if df and not df(ctx.date()[0]): # doesn't match date format
    83         if df and not df(ctx.date()[0]):  # doesn't match date format
    76             return
    84             return
    77 
    85 
    78         key = getkey(ctx).strip()
    86         key = getkey(ctx).strip()
    79         key = amap.get(key, key) # alias remap
    87         key = amap.get(key, key)  # alias remap
    80         if opts.get('changesets'):
    88         if opts.get('changesets'):
    81             rate[key] = (rate.get(key, (0,))[0] + 1, 0)
    89             rate[key] = (rate.get(key, (0,))[0] + 1, 0)
    82         else:
    90         else:
    83             parents = ctx.parents()
    91             parents = ctx.parents()
    84             if len(parents) > 1:
    92             if len(parents) > 1:
    97     progress.complete()
   105     progress.complete()
    98 
   106 
    99     return rate
   107     return rate
   100 
   108 
   101 
   109 
   102 @command('churn',
   110 @command(
   103     [('r', 'rev', [],
   111     'churn',
   104      _('count rate for the specified revision or revset'), _('REV')),
   112     [
   105     ('d', 'date', '',
   113         (
   106      _('count rate for revisions matching date spec'), _('DATE')),
   114             'r',
   107     ('t', 'oldtemplate', '',
   115             'rev',
   108      _('template to group changesets (DEPRECATED)'), _('TEMPLATE')),
   116             [],
   109     ('T', 'template', '{author|email}',
   117             _('count rate for the specified revision or revset'),
   110      _('template to group changesets'), _('TEMPLATE')),
   118             _('REV'),
   111     ('f', 'dateformat', '',
   119         ),
   112      _('strftime-compatible format for grouping by date'), _('FORMAT')),
   120         (
   113     ('c', 'changesets', False, _('count rate by number of changesets')),
   121             'd',
   114     ('s', 'sort', False, _('sort by key (default: sort by count)')),
   122             'date',
   115     ('', 'diffstat', False, _('display added/removed lines separately')),
   123             '',
   116     ('', 'aliases', '', _('file with email aliases'), _('FILE')),
   124             _('count rate for revisions matching date spec'),
   117     ] + cmdutil.walkopts,
   125             _('DATE'),
       
   126         ),
       
   127         (
       
   128             't',
       
   129             'oldtemplate',
       
   130             '',
       
   131             _('template to group changesets (DEPRECATED)'),
       
   132             _('TEMPLATE'),
       
   133         ),
       
   134         (
       
   135             'T',
       
   136             'template',
       
   137             '{author|email}',
       
   138             _('template to group changesets'),
       
   139             _('TEMPLATE'),
       
   140         ),
       
   141         (
       
   142             'f',
       
   143             'dateformat',
       
   144             '',
       
   145             _('strftime-compatible format for grouping by date'),
       
   146             _('FORMAT'),
       
   147         ),
       
   148         ('c', 'changesets', False, _('count rate by number of changesets')),
       
   149         ('s', 'sort', False, _('sort by key (default: sort by count)')),
       
   150         ('', 'diffstat', False, _('display added/removed lines separately')),
       
   151         ('', 'aliases', '', _('file with email aliases'), _('FILE')),
       
   152     ]
       
   153     + cmdutil.walkopts,
   118     _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
   154     _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
   119     helpcategory=command.CATEGORY_MAINTENANCE,
   155     helpcategory=command.CATEGORY_MAINTENANCE,
   120     inferrepo=True)
   156     inferrepo=True,
       
   157 )
   121 def churn(ui, repo, *pats, **opts):
   158 def churn(ui, repo, *pats, **opts):
   122     '''histogram of changes to the repository
   159     '''histogram of changes to the repository
   123 
   160 
   124     This command will display a histogram representing the number
   161     This command will display a histogram representing the number
   125     of changed lines or revisions, grouped according to the given
   162     of changed lines or revisions, grouped according to the given
   152 
   189 
   153     Such a file may be specified with the --aliases option, otherwise
   190     Such a file may be specified with the --aliases option, otherwise
   154     a .hgchurn file will be looked for in the working directory root.
   191     a .hgchurn file will be looked for in the working directory root.
   155     Aliases will be split from the rightmost "=".
   192     Aliases will be split from the rightmost "=".
   156     '''
   193     '''
       
   194 
   157     def pad(s, l):
   195     def pad(s, l):
   158         return s + " " * (l - encoding.colwidth(s))
   196         return s + " " * (l - encoding.colwidth(s))
   159 
   197 
   160     amap = {}
   198     amap = {}
   161     aliases = opts.get(r'aliases')
   199     aliases = opts.get(r'aliases')
   189     ui.debug("assuming %i character terminal\n" % ttywidth)
   227     ui.debug("assuming %i character terminal\n" % ttywidth)
   190     width = ttywidth - maxname - 2 - 2 - 2
   228     width = ttywidth - maxname - 2 - 2 - 2
   191 
   229 
   192     if opts.get(r'diffstat'):
   230     if opts.get(r'diffstat'):
   193         width -= 15
   231         width -= 15
       
   232 
   194         def format(name, diffstat):
   233         def format(name, diffstat):
   195             added, removed = diffstat
   234             added, removed = diffstat
   196             return "%s %15s %s%s\n" % (pad(name, maxname),
   235             return "%s %15s %s%s\n" % (
   197                                        '+%d/-%d' % (added, removed),
   236                 pad(name, maxname),
   198                                        ui.label('+' * charnum(added),
   237                 '+%d/-%d' % (added, removed),
   199                                                 'diffstat.inserted'),
   238                 ui.label('+' * charnum(added), 'diffstat.inserted'),
   200                                        ui.label('-' * charnum(removed),
   239                 ui.label('-' * charnum(removed), 'diffstat.deleted'),
   201                                                 'diffstat.deleted'))
   240             )
       
   241 
   202     else:
   242     else:
   203         width -= 6
   243         width -= 6
       
   244 
   204         def format(name, count):
   245         def format(name, count):
   205             return "%s %6d %s\n" % (pad(name, maxname), sum(count),
   246             return "%s %6d %s\n" % (
   206                                     '*' * charnum(sum(count)))
   247                 pad(name, maxname),
       
   248                 sum(count),
       
   249                 '*' * charnum(sum(count)),
       
   250             )
   207 
   251 
   208     def charnum(count):
   252     def charnum(count):
   209         return int(count * width // maxcount)
   253         return int(count * width // maxcount)
   210 
   254 
   211     for name, count in rate:
   255     for name, count in rate: