sapling/hgext/churn.py
Jun Wu 584656dff3 codemod: join the auto-formatter party
Summary:
Turned on the auto formatter. Ran `arc lint --apply-patches --take BLACK **/*.py`.
Then run `arc lint` again so some other autofixers like spellchecker etc. looked
at the code base. Manually accept the changes whenever they make sense, or use
a workaround (ex. changing "dict()" to "dict constructor") where autofix is false
positive. Disabled linters on files that are hard (i18n/polib.py) to fix, or less
interesting to fix (hgsubversion tests), or cannot be fixed without breaking
OSS build (FBPYTHON4).

Conflicted linters (test-check-module-imports.t, part of test-check-code.t,
test-check-pyflakes.t) are removed or disabled.

Duplicated linters (test-check-pyflakes.t, test-check-pylint.t) are removed.

An issue of the auto-formatter is lines are no longer guarnateed to be <= 80
chars. But that seems less important comparing with the benefit auto-formatter
provides.

As we're here, also remove test-check-py3-compat.t, as it is currently broken
if `PYTHON3=/bin/python3` is set.

Reviewed By: wez, phillco, simpkins, pkaush, singhsrb

Differential Revision: D8173629

fbshipit-source-id: 90e248ae0c5e6eaadbe25520a6ee42d32005621b
2018-05-25 22:17:29 -07:00

246 lines
7.1 KiB
Python

# churn.py - create a graph of revisions count grouped by template
#
# Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
# Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""command to display statistics about repository history"""
from __future__ import absolute_import
import datetime
import os
import time
from mercurial import (
cmdutil,
encoding,
patch,
progress,
pycompat,
registrar,
scmutil,
util,
)
from mercurial.i18n import _
cmdtable = {}
command = registrar.command(cmdtable)
# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
testedwith = "ships-with-hg-core"
def changedlines(ui, repo, ctx1, ctx2, fns):
added, removed = 0, 0
fmatch = scmutil.matchfiles(repo, fns)
diff = "".join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
for l in diff.split("\n"):
if l.startswith("+") and not l.startswith("+++ "):
added += 1
elif l.startswith("-") and not l.startswith("--- "):
removed += 1
return (added, removed)
def countrate(ui, repo, amap, *pats, **opts):
"""Calculate stats"""
opts = pycompat.byteskwargs(opts)
if opts.get("dateformat"):
def getkey(ctx):
t, tz = ctx.date()
date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
return date.strftime(opts["dateformat"])
else:
tmpl = opts.get("oldtemplate") or opts.get("template")
tmpl = cmdutil.makelogtemplater(ui, repo, tmpl)
def getkey(ctx):
ui.pushbuffer()
tmpl.show(ctx)
return ui.popbuffer()
rate = {}
df = False
if opts.get("date"):
df = util.matchdate(opts["date"])
prog = progress.bar(ui, _("analyzing"), _("revisions"), len(repo))
m = scmutil.match(repo[None], pats, opts)
def prep(ctx, fns):
rev = ctx.rev()
if df and not df(ctx.date()[0]): # doesn't match date format
return
key = getkey(ctx).strip()
key = amap.get(key, key) # alias remap
if opts.get("changesets"):
rate[key] = (rate.get(key, (0,))[0] + 1, 0)
else:
parents = ctx.parents()
if len(parents) > 1:
ui.note(_("revision %d is a merge, ignoring...\n") % (rev,))
return
ctx1 = parents[0]
lines = changedlines(ui, repo, ctx1, ctx, fns)
rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
prog.value += 1
with prog:
for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
continue
return rate
@command(
"churn",
[
(
"r",
"rev",
[],
_("count rate for the specified revision or revset"),
_("REV"),
),
("d", "date", "", _("count rate for revisions matching date spec"), _("DATE")),
(
"t",
"oldtemplate",
"",
_("template to group changesets (DEPRECATED)"),
_("TEMPLATE"),
),
(
"T",
"template",
"{author|email}",
_("template to group changesets"),
_("TEMPLATE"),
),
(
"f",
"dateformat",
"",
_("strftime-compatible format for grouping by date"),
_("FORMAT"),
),
("c", "changesets", False, _("count rate by number of changesets")),
("s", "sort", False, _("sort by key (default: sort by count)")),
("", "diffstat", False, _("display added/removed lines separately")),
("", "aliases", "", _("file with email aliases"), _("FILE")),
]
+ cmdutil.walkopts,
_("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
inferrepo=True,
)
def churn(ui, repo, *pats, **opts):
"""histogram of changes to the repository
This command will display a histogram representing the number
of changed lines or revisions, grouped according to the given
template. The default template will group changes by author.
The --dateformat option may be used to group the results by
date instead.
Statistics are based on the number of changed lines, or
alternatively the number of matching revisions if the
--changesets option is specified.
Examples::
# display count of changed lines for every committer
hg churn -T "{author|email}"
# display daily activity graph
hg churn -f "%H" -s -c
# display activity of developers by month
hg churn -f "%Y-%m" -s -c
# display count of lines changed in every year
hg churn -f "%Y" -s
It is possible to map alternate email addresses to a main address
by providing a file using the following format::
<alias email> = <actual email>
Such a file may be specified with the --aliases option, otherwise
a .hgchurn file will be looked for in the working directory root.
Aliases will be split from the rightmost "=".
"""
def pad(s, l):
return s + " " * (l - encoding.colwidth(s))
amap = {}
aliases = opts.get(r"aliases")
if not aliases and os.path.exists(repo.wjoin(".hgchurn")):
aliases = repo.wjoin(".hgchurn")
if aliases:
for l in open(aliases, "r"):
try:
alias, actual = l.rsplit("=" in l and "=" or None, 1)
amap[alias.strip()] = actual.strip()
except ValueError:
l = l.strip()
if l:
ui.warn(_("skipping malformed alias: %s\n") % l)
continue
rate = countrate(ui, repo, amap, *pats, **opts).items()
if not rate:
return
if opts.get(r"sort"):
rate.sort()
else:
rate.sort(key=lambda x: (-sum(x[1]), x))
# Be careful not to have a zero maxcount (issue833)
maxcount = float(max(sum(v) for k, v in rate)) or 1.0
maxname = max(len(k) for k, v in rate)
ttywidth = ui.termwidth()
ui.debug("assuming %i character terminal\n" % ttywidth)
width = ttywidth - maxname - 2 - 2 - 2
if opts.get(r"diffstat"):
width -= 15
def format(name, diffstat):
added, removed = diffstat
return "%s %15s %s%s\n" % (
pad(name, maxname),
"+%d/-%d" % (added, removed),
ui.label("+" * charnum(added), "diffstat.inserted"),
ui.label("-" * charnum(removed), "diffstat.deleted"),
)
else:
width -= 6
def format(name, count):
return "%s %6d %s\n" % (
pad(name, maxname),
sum(count),
"*" * charnum(sum(count)),
)
def charnum(count):
return int(round(count * width / maxcount))
for name, count in rate:
ui.write(format(name, count))