##// END OF EJS Templates
churn: use absolute_import
Gregory Szorc -
r28094:79fc6275 default
parent child Browse files
Show More
@@ -1,205 +1,216 b''
1 1 # churn.py - create a graph of revisions count grouped by template
2 2 #
3 3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''command to display statistics about repository history'''
10 10
11 from __future__ import absolute_import
12
13 import datetime
14 import os
15 import time
16
11 17 from mercurial.i18n import _
12 from mercurial import patch, cmdutil, scmutil, util, commands, error
13 from mercurial import encoding
14 import os
15 import time, datetime
18 from mercurial import (
19 cmdutil,
20 commands,
21 encoding,
22 error,
23 patch,
24 scmutil,
25 util,
26 )
16 27
17 28 cmdtable = {}
18 29 command = cmdutil.command(cmdtable)
19 30 # Note for extension authors: ONLY specify testedwith = 'internal' for
20 31 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
21 32 # be specifying the version(s) of Mercurial they are tested with, or
22 33 # leave the attribute unspecified.
23 34 testedwith = 'internal'
24 35
25 36 def maketemplater(ui, repo, tmpl):
26 37 try:
27 38 t = cmdutil.changeset_templater(ui, repo, False, None, tmpl,
28 39 None, False)
29 40 except SyntaxError as inst:
30 41 raise error.Abort(inst.args[0])
31 42 return t
32 43
33 44 def changedlines(ui, repo, ctx1, ctx2, fns):
34 45 added, removed = 0, 0
35 46 fmatch = scmutil.matchfiles(repo, fns)
36 47 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
37 48 for l in diff.split('\n'):
38 49 if l.startswith("+") and not l.startswith("+++ "):
39 50 added += 1
40 51 elif l.startswith("-") and not l.startswith("--- "):
41 52 removed += 1
42 53 return (added, removed)
43 54
44 55 def countrate(ui, repo, amap, *pats, **opts):
45 56 """Calculate stats"""
46 57 if opts.get('dateformat'):
47 58 def getkey(ctx):
48 59 t, tz = ctx.date()
49 60 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
50 61 return date.strftime(opts['dateformat'])
51 62 else:
52 63 tmpl = opts.get('oldtemplate') or opts.get('template')
53 64 tmpl = maketemplater(ui, repo, tmpl)
54 65 def getkey(ctx):
55 66 ui.pushbuffer()
56 67 tmpl.show(ctx)
57 68 return ui.popbuffer()
58 69
59 70 state = {'count': 0}
60 71 rate = {}
61 72 df = False
62 73 if opts.get('date'):
63 74 df = util.matchdate(opts['date'])
64 75
65 76 m = scmutil.match(repo[None], pats, opts)
66 77 def prep(ctx, fns):
67 78 rev = ctx.rev()
68 79 if df and not df(ctx.date()[0]): # doesn't match date format
69 80 return
70 81
71 82 key = getkey(ctx).strip()
72 83 key = amap.get(key, key) # alias remap
73 84 if opts.get('changesets'):
74 85 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
75 86 else:
76 87 parents = ctx.parents()
77 88 if len(parents) > 1:
78 89 ui.note(_('revision %d is a merge, ignoring...\n') % (rev,))
79 90 return
80 91
81 92 ctx1 = parents[0]
82 93 lines = changedlines(ui, repo, ctx1, ctx, fns)
83 94 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
84 95
85 96 state['count'] += 1
86 97 ui.progress(_('analyzing'), state['count'], total=len(repo))
87 98
88 99 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
89 100 continue
90 101
91 102 ui.progress(_('analyzing'), None)
92 103
93 104 return rate
94 105
95 106
96 107 @command('churn',
97 108 [('r', 'rev', [],
98 109 _('count rate for the specified revision or revset'), _('REV')),
99 110 ('d', 'date', '',
100 111 _('count rate for revisions matching date spec'), _('DATE')),
101 112 ('t', 'oldtemplate', '',
102 113 _('template to group changesets (DEPRECATED)'), _('TEMPLATE')),
103 114 ('T', 'template', '{author|email}',
104 115 _('template to group changesets'), _('TEMPLATE')),
105 116 ('f', 'dateformat', '',
106 117 _('strftime-compatible format for grouping by date'), _('FORMAT')),
107 118 ('c', 'changesets', False, _('count rate by number of changesets')),
108 119 ('s', 'sort', False, _('sort by key (default: sort by count)')),
109 120 ('', 'diffstat', False, _('display added/removed lines separately')),
110 121 ('', 'aliases', '', _('file with email aliases'), _('FILE')),
111 122 ] + commands.walkopts,
112 123 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
113 124 inferrepo=True)
114 125 def churn(ui, repo, *pats, **opts):
115 126 '''histogram of changes to the repository
116 127
117 128 This command will display a histogram representing the number
118 129 of changed lines or revisions, grouped according to the given
119 130 template. The default template will group changes by author.
120 131 The --dateformat option may be used to group the results by
121 132 date instead.
122 133
123 134 Statistics are based on the number of changed lines, or
124 135 alternatively the number of matching revisions if the
125 136 --changesets option is specified.
126 137
127 138 Examples::
128 139
129 140 # display count of changed lines for every committer
130 141 hg churn -t "{author|email}"
131 142
132 143 # display daily activity graph
133 144 hg churn -f "%H" -s -c
134 145
135 146 # display activity of developers by month
136 147 hg churn -f "%Y-%m" -s -c
137 148
138 149 # display count of lines changed in every year
139 150 hg churn -f "%Y" -s
140 151
141 152 It is possible to map alternate email addresses to a main address
142 153 by providing a file using the following format::
143 154
144 155 <alias email> = <actual email>
145 156
146 157 Such a file may be specified with the --aliases option, otherwise
147 158 a .hgchurn file will be looked for in the working directory root.
148 159 Aliases will be split from the rightmost "=".
149 160 '''
150 161 def pad(s, l):
151 162 return s + " " * (l - encoding.colwidth(s))
152 163
153 164 amap = {}
154 165 aliases = opts.get('aliases')
155 166 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
156 167 aliases = repo.wjoin('.hgchurn')
157 168 if aliases:
158 169 for l in open(aliases, "r"):
159 170 try:
160 171 alias, actual = l.rsplit('=' in l and '=' or None, 1)
161 172 amap[alias.strip()] = actual.strip()
162 173 except ValueError:
163 174 l = l.strip()
164 175 if l:
165 176 ui.warn(_("skipping malformed alias: %s\n") % l)
166 177 continue
167 178
168 179 rate = countrate(ui, repo, amap, *pats, **opts).items()
169 180 if not rate:
170 181 return
171 182
172 183 if opts.get('sort'):
173 184 rate.sort()
174 185 else:
175 186 rate.sort(key=lambda x: (-sum(x[1]), x))
176 187
177 188 # Be careful not to have a zero maxcount (issue833)
178 189 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
179 190 maxname = max(len(k) for k, v in rate)
180 191
181 192 ttywidth = ui.termwidth()
182 193 ui.debug("assuming %i character terminal\n" % ttywidth)
183 194 width = ttywidth - maxname - 2 - 2 - 2
184 195
185 196 if opts.get('diffstat'):
186 197 width -= 15
187 198 def format(name, diffstat):
188 199 added, removed = diffstat
189 200 return "%s %15s %s%s\n" % (pad(name, maxname),
190 201 '+%d/-%d' % (added, removed),
191 202 ui.label('+' * charnum(added),
192 203 'diffstat.inserted'),
193 204 ui.label('-' * charnum(removed),
194 205 'diffstat.deleted'))
195 206 else:
196 207 width -= 6
197 208 def format(name, count):
198 209 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
199 210 '*' * charnum(sum(count)))
200 211
201 212 def charnum(count):
202 213 return int(round(count * width / maxcount))
203 214
204 215 for name, count in rate:
205 216 ui.write(format(name, count))
@@ -1,176 +1,175 b''
1 1 #require test-repo
2 2
3 3 $ cd "$TESTDIR"/..
4 4
5 5 $ hg files 'set:(**.py)' | sed 's|\\|/|g' | xargs python contrib/check-py3-compat.py
6 6 contrib/casesmash.py not using absolute_import
7 7 contrib/check-code.py not using absolute_import
8 8 contrib/check-code.py requires print_function
9 9 contrib/check-config.py not using absolute_import
10 10 contrib/check-config.py requires print_function
11 11 contrib/debugcmdserver.py not using absolute_import
12 12 contrib/debugcmdserver.py requires print_function
13 13 contrib/debugshell.py not using absolute_import
14 14 contrib/fixpax.py not using absolute_import
15 15 contrib/fixpax.py requires print_function
16 16 contrib/hgclient.py not using absolute_import
17 17 contrib/hgclient.py requires print_function
18 18 contrib/hgfixes/fix_bytes.py not using absolute_import
19 19 contrib/hgfixes/fix_bytesmod.py not using absolute_import
20 20 contrib/hgfixes/fix_leftover_imports.py not using absolute_import
21 21 contrib/import-checker.py not using absolute_import
22 22 contrib/import-checker.py requires print_function
23 23 contrib/memory.py not using absolute_import
24 24 contrib/perf.py not using absolute_import
25 25 contrib/python-hook-examples.py not using absolute_import
26 26 contrib/revsetbenchmarks.py not using absolute_import
27 27 contrib/revsetbenchmarks.py requires print_function
28 28 contrib/showstack.py not using absolute_import
29 29 contrib/synthrepo.py not using absolute_import
30 30 contrib/win32/hgwebdir_wsgi.py not using absolute_import
31 31 doc/check-seclevel.py not using absolute_import
32 32 doc/gendoc.py not using absolute_import
33 33 doc/hgmanpage.py not using absolute_import
34 34 hgext/__init__.py not using absolute_import
35 hgext/churn.py not using absolute_import
36 35 hgext/clonebundles.py not using absolute_import
37 36 hgext/color.py not using absolute_import
38 37 hgext/convert/__init__.py not using absolute_import
39 38 hgext/convert/bzr.py not using absolute_import
40 39 hgext/convert/common.py not using absolute_import
41 40 hgext/convert/convcmd.py not using absolute_import
42 41 hgext/convert/cvs.py not using absolute_import
43 42 hgext/convert/cvsps.py not using absolute_import
44 43 hgext/convert/darcs.py not using absolute_import
45 44 hgext/convert/filemap.py not using absolute_import
46 45 hgext/convert/git.py not using absolute_import
47 46 hgext/convert/gnuarch.py not using absolute_import
48 47 hgext/convert/hg.py not using absolute_import
49 48 hgext/convert/monotone.py not using absolute_import
50 49 hgext/convert/p4.py not using absolute_import
51 50 hgext/convert/subversion.py not using absolute_import
52 51 hgext/convert/transport.py not using absolute_import
53 52 hgext/eol.py not using absolute_import
54 53 hgext/extdiff.py not using absolute_import
55 54 hgext/factotum.py not using absolute_import
56 55 hgext/fetch.py not using absolute_import
57 56 hgext/gpg.py not using absolute_import
58 57 hgext/graphlog.py not using absolute_import
59 58 hgext/hgcia.py not using absolute_import
60 59 hgext/hgk.py not using absolute_import
61 60 hgext/highlight/__init__.py not using absolute_import
62 61 hgext/highlight/highlight.py not using absolute_import
63 62 hgext/histedit.py not using absolute_import
64 63 hgext/keyword.py not using absolute_import
65 64 hgext/largefiles/__init__.py not using absolute_import
66 65 hgext/largefiles/basestore.py not using absolute_import
67 66 hgext/largefiles/lfcommands.py not using absolute_import
68 67 hgext/largefiles/lfutil.py not using absolute_import
69 68 hgext/largefiles/localstore.py not using absolute_import
70 69 hgext/largefiles/overrides.py not using absolute_import
71 70 hgext/largefiles/proto.py not using absolute_import
72 71 hgext/largefiles/remotestore.py not using absolute_import
73 72 hgext/largefiles/reposetup.py not using absolute_import
74 73 hgext/largefiles/uisetup.py not using absolute_import
75 74 hgext/largefiles/wirestore.py not using absolute_import
76 75 hgext/mq.py not using absolute_import
77 76 hgext/notify.py not using absolute_import
78 77 hgext/pager.py not using absolute_import
79 78 hgext/patchbomb.py not using absolute_import
80 79 hgext/purge.py not using absolute_import
81 80 hgext/rebase.py not using absolute_import
82 81 hgext/record.py not using absolute_import
83 82 hgext/relink.py not using absolute_import
84 83 hgext/schemes.py not using absolute_import
85 84 hgext/share.py not using absolute_import
86 85 hgext/shelve.py not using absolute_import
87 86 hgext/strip.py not using absolute_import
88 87 hgext/transplant.py not using absolute_import
89 88 hgext/win32mbcs.py not using absolute_import
90 89 hgext/win32text.py not using absolute_import
91 90 hgext/zeroconf/Zeroconf.py not using absolute_import
92 91 hgext/zeroconf/Zeroconf.py requires print_function
93 92 hgext/zeroconf/__init__.py not using absolute_import
94 93 i18n/check-translation.py not using absolute_import
95 94 i18n/polib.py not using absolute_import
96 95 mercurial/cmdutil.py not using absolute_import
97 96 mercurial/commands.py not using absolute_import
98 97 setup.py not using absolute_import
99 98 tests/filterpyflakes.py requires print_function
100 99 tests/generate-working-copy-states.py requires print_function
101 100 tests/get-with-headers.py requires print_function
102 101 tests/heredoctest.py requires print_function
103 102 tests/hypothesishelpers.py not using absolute_import
104 103 tests/hypothesishelpers.py requires print_function
105 104 tests/killdaemons.py not using absolute_import
106 105 tests/md5sum.py not using absolute_import
107 106 tests/mockblackbox.py not using absolute_import
108 107 tests/printenv.py not using absolute_import
109 108 tests/readlink.py not using absolute_import
110 109 tests/readlink.py requires print_function
111 110 tests/revlog-formatv0.py not using absolute_import
112 111 tests/run-tests.py not using absolute_import
113 112 tests/seq.py not using absolute_import
114 113 tests/seq.py requires print_function
115 114 tests/silenttestrunner.py not using absolute_import
116 115 tests/silenttestrunner.py requires print_function
117 116 tests/sitecustomize.py not using absolute_import
118 117 tests/svn-safe-append.py not using absolute_import
119 118 tests/svnxml.py not using absolute_import
120 119 tests/test-ancestor.py requires print_function
121 120 tests/test-atomictempfile.py not using absolute_import
122 121 tests/test-batching.py not using absolute_import
123 122 tests/test-batching.py requires print_function
124 123 tests/test-bdiff.py not using absolute_import
125 124 tests/test-bdiff.py requires print_function
126 125 tests/test-context.py not using absolute_import
127 126 tests/test-context.py requires print_function
128 127 tests/test-demandimport.py not using absolute_import
129 128 tests/test-demandimport.py requires print_function
130 129 tests/test-dispatch.py not using absolute_import
131 130 tests/test-dispatch.py requires print_function
132 131 tests/test-doctest.py not using absolute_import
133 132 tests/test-duplicateoptions.py not using absolute_import
134 133 tests/test-duplicateoptions.py requires print_function
135 134 tests/test-filecache.py not using absolute_import
136 135 tests/test-filecache.py requires print_function
137 136 tests/test-filelog.py not using absolute_import
138 137 tests/test-filelog.py requires print_function
139 138 tests/test-hg-parseurl.py not using absolute_import
140 139 tests/test-hg-parseurl.py requires print_function
141 140 tests/test-hgweb-auth.py not using absolute_import
142 141 tests/test-hgweb-auth.py requires print_function
143 142 tests/test-hgwebdir-paths.py not using absolute_import
144 143 tests/test-hybridencode.py not using absolute_import
145 144 tests/test-hybridencode.py requires print_function
146 145 tests/test-lrucachedict.py not using absolute_import
147 146 tests/test-lrucachedict.py requires print_function
148 147 tests/test-manifest.py not using absolute_import
149 148 tests/test-minirst.py not using absolute_import
150 149 tests/test-minirst.py requires print_function
151 150 tests/test-parseindex2.py not using absolute_import
152 151 tests/test-parseindex2.py requires print_function
153 152 tests/test-pathencode.py not using absolute_import
154 153 tests/test-pathencode.py requires print_function
155 154 tests/test-propertycache.py not using absolute_import
156 155 tests/test-propertycache.py requires print_function
157 156 tests/test-revlog-ancestry.py not using absolute_import
158 157 tests/test-revlog-ancestry.py requires print_function
159 158 tests/test-run-tests.py not using absolute_import
160 159 tests/test-simplemerge.py not using absolute_import
161 160 tests/test-status-inprocess.py not using absolute_import
162 161 tests/test-status-inprocess.py requires print_function
163 162 tests/test-symlink-os-yes-fs-no.py not using absolute_import
164 163 tests/test-trusted.py not using absolute_import
165 164 tests/test-trusted.py requires print_function
166 165 tests/test-ui-color.py not using absolute_import
167 166 tests/test-ui-color.py requires print_function
168 167 tests/test-ui-config.py not using absolute_import
169 168 tests/test-ui-config.py requires print_function
170 169 tests/test-ui-verbosity.py not using absolute_import
171 170 tests/test-ui-verbosity.py requires print_function
172 171 tests/test-url.py not using absolute_import
173 172 tests/test-url.py requires print_function
174 173 tests/test-walkrepo.py requires print_function
175 174 tests/test-wireproto.py requires print_function
176 175 tests/tinyproxy.py requires print_function
General Comments 0
You need to be logged in to leave comments. Login now