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 |
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: |