/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/plugins/stats/cmds.py

  • Committer: Jelmer Vernooij
  • Date: 2017-07-23 22:06:41 UTC
  • mfrom: (6738 trunk)
  • mto: This revision was merged to the branch mainline in revision 6739.
  • Revision ID: jelmer@jelmer.uk-20170723220641-69eczax9bmv8d6kk
Merge trunk, address review comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
"""A Simple bzr plugin to generate statistics about the history."""
17
17
 
18
 
import operator
 
18
from __future__ import absolute_import
19
19
 
20
20
from ... import (
21
21
    branch,
31
31
from ...revision import NULL_REVISION
32
32
from .classify import classify_delta
33
33
 
 
34
from itertools import izip
 
35
 
34
36
 
35
37
def collapse_by_person(revisions, canonical_committer):
36
38
    """The committers list is sorted by email, fix it up by person.
59
61
            info[2][username] = info[2].setdefault(username, 0) + 1
60
62
    res = [(len(revs), revs, emails, fnames)
61
63
           for revs, emails, fnames in committer_to_info.values()]
62
 
 
63
 
    def key_fn(item):
64
 
        return item[0], list(item[2].keys())
65
 
    res.sort(reverse=True, key=key_fn)
 
64
    res.sort(reverse=True)
66
65
    return res
67
66
 
68
67
 
98
97
            # email
99
98
            for user in usernames:
100
99
                if not user:
101
 
                    continue  # The mysterious ('', '') user
 
100
                    continue # The mysterious ('', '') user
102
101
                # When mapping, use case-insensitive names
103
102
                low_user = user.lower()
104
103
                user_id = username_to_id.get(low_user)
134
133
    combo_to_best_combo = {}
135
134
    for cur_id, combos in id_to_combos.items():
136
135
        best_combo = sorted(combos,
137
 
                            key=lambda x: combo_count[x],
 
136
                            key=lambda x:combo_count[x],
138
137
                            reverse=True)[0]
139
138
        for combo in combos:
140
139
            combo_to_best_combo[combo] = best_combo
144
143
def get_revisions_and_committers(a_repo, revids):
145
144
    """Get the Revision information, and the best-match for committer."""
146
145
 
147
 
    email_users = {}  # user@email.com => User Name
 
146
    email_users = {} # user@email.com => User Name
148
147
    combo_count = {}
149
 
    with ui.ui_factory.nested_progress_bar() as pb:
 
148
    pb = ui.ui_factory.nested_progress_bar()
 
149
    try:
150
150
        trace.note('getting revisions')
151
 
        revisions = list(a_repo.iter_revisions(revids))
 
151
        revisions = a_repo.iter_revisions(revids)
152
152
        for count, (revid, rev) in enumerate(revisions):
153
153
            pb.update('checking', count, len(revids))
154
154
            for author in rev.get_apparent_authors():
158
158
                email_users.setdefault(email, set()).add(username)
159
159
                combo = (username, email)
160
160
                combo_count[combo] = combo_count.setdefault(combo, 0) + 1
161
 
    return ((rev for (revid, rev) in revisions),
162
 
            collapse_email_and_users(email_users, combo_count))
 
161
    finally:
 
162
        pb.finished()
 
163
    return revisions, collapse_email_and_users(email_users, combo_count)
163
164
 
164
165
 
165
166
def get_info(a_repo, revision):
166
167
    """Get all of the information for a particular revision"""
167
 
    with ui.ui_factory.nested_progress_bar() as pb, a_repo.lock_read():
 
168
    pb = ui.ui_factory.nested_progress_bar()
 
169
    a_repo.lock_read()
 
170
    try:
168
171
        trace.note('getting ancestry')
169
172
        graph = a_repo.get_graph()
170
173
        ancestry = [
171
174
            r for (r, ps) in graph.iter_ancestry([revision])
172
175
            if ps is not None and r != NULL_REVISION]
173
 
        revs, canonical_committer = get_revisions_and_committers(
174
 
            a_repo, ancestry)
 
176
        revs, canonical_committer = get_revisions_and_committers(a_repo, ancestry)
 
177
    finally:
 
178
        a_repo.unlock()
 
179
        pb.finished()
175
180
 
176
181
    return collapse_by_person(revs, canonical_committer)
177
182
 
181
186
 
182
187
    This lets us figure out what has actually changed between 2 revisions.
183
188
    """
184
 
    with ui.ui_factory.nested_progress_bar() as pb, a_repo.lock_read():
 
189
    pb = ui.ui_factory.nested_progress_bar()
 
190
    a_repo.lock_read()
 
191
    try:
185
192
        graph = a_repo.get_graph()
186
193
        trace.note('getting ancestry diff')
187
194
        ancestry = graph.find_difference(start_rev, end_rev)[1]
188
 
        revs, canonical_committer = get_revisions_and_committers(
189
 
            a_repo, ancestry)
 
195
        revs, canonical_committer = get_revisions_and_committers(a_repo, ancestry)
 
196
    finally:
 
197
        a_repo.unlock()
 
198
        pb.finished()
190
199
 
191
200
    return collapse_by_person(revs, canonical_committer)
192
201
 
197
206
    for count, revs, emails, fullnames in info:
198
207
        # Get the most common email name
199
208
        sorted_emails = sorted(((count, email)
200
 
                                for email, count in emails.items()),
 
209
                               for email, count in emails.items()),
201
210
                               reverse=True)
202
211
        sorted_fullnames = sorted(((count, fullname)
203
 
                                   for fullname, count in fullnames.items()),
 
212
                                  for fullname, count in fullnames.items()),
204
213
                                  reverse=True)
205
214
        if sorted_fullnames[0][1] == '' and sorted_emails[0][1] == '':
206
215
            to_file.write('%4d %s\n'
231
240
            for name, count in sorted(classes.items(), key=classify_key):
232
241
                if name is None:
233
242
                    name = "Unknown"
234
 
                to_file.write("     %4.0f%% %s\n" %
235
 
                              ((float(count) / total) * 100.0, name))
 
243
                to_file.write("     %4.0f%% %s\n" % ((float(count) / total) * 100.0, name))
236
244
 
237
245
 
238
246
class cmd_committer_statistics(commands.Command):
240
248
 
241
249
    aliases = ['stats', 'committer-stats']
242
250
    takes_args = ['location?']
243
 
    takes_options = ['revision',
244
 
                     option.Option('show-class', help="Show the class of contributions.")]
 
251
    takes_options = ['revision', 
 
252
            option.Option('show-class', help="Show the class of contributions.")]
245
253
 
246
254
    encoding_type = 'replace'
247
255
 
261
269
            if len(revision) > 1:
262
270
                alternate_rev = revision[1].in_history(a_branch).rev_id
263
271
 
264
 
        with a_branch.lock_read():
 
272
        a_branch.lock_read()
 
273
        try:
265
274
            if alternate_rev:
266
275
                info = get_diff_info(a_branch.repository, last_rev,
267
276
                                     alternate_rev)
268
277
            else:
269
278
                info = get_info(a_branch.repository, last_rev)
 
279
        finally:
 
280
            a_branch.unlock()
270
281
        if show_class:
271
282
            def fetch_class_stats(revs):
272
283
                return gather_class_stats(a_branch.repository, revs)
282
293
 
283
294
    encoding_type = 'replace'
284
295
 
285
 
    hidden = True
286
 
 
287
296
    def run(self, location='.'):
288
297
        try:
289
298
            wt = workingtree.WorkingTree.open_containing(location)[0]
294
303
            a_branch = wt.branch
295
304
            last_rev = wt.last_revision()
296
305
 
297
 
        with a_branch.lock_read():
 
306
        a_branch.lock_read()
 
307
        try:
298
308
            graph = a_branch.repository.get_graph()
299
309
            revno = 0
300
310
            cur_parents = 0
305
315
                if depth == 0:
306
316
                    revno += 1
307
317
                    self.outf.write('%4d, %4d\n' % (revno, cur_parents))
 
318
        finally:
 
319
            a_branch.unlock()
308
320
 
309
321
 
310
322
def gather_class_stats(repository, revs):
311
323
    ret = {}
312
324
    total = 0
313
 
    with ui.ui_factory.nested_progress_bar() as pb:
314
 
        with repository.lock_read():
 
325
    pb = ui.ui_factory.nested_progress_bar()
 
326
    try:
 
327
        repository.lock_read()
 
328
        try:
315
329
            i = 0
316
 
            for delta in repository.get_revision_deltas(revs):
 
330
            for delta in repository.get_deltas_for_revisions(revs):
317
331
                pb.update("classifying commits", i, len(revs))
318
332
                for c in classify_delta(delta):
319
 
                    if c not in ret:
 
333
                    if not c in ret:
320
334
                        ret[c] = 0
321
335
                    ret[c] += 1
322
336
                    total += 1
323
337
                i += 1
 
338
        finally:
 
339
            repository.unlock()
 
340
    finally:
 
341
        pb.finished()
324
342
    return ret, total
325
343
 
326
344
 
331
349
 
332
350
def display_credits(credits, to_file):
333
351
    (coders, documenters, artists, translators) = credits
334
 
 
335
352
    def print_section(name, lst):
336
353
        if len(lst) == 0:
337
354
            return
356
373
           "translation": {},
357
374
           None: {}
358
375
           }
359
 
    with repository.lock_read():
 
376
    repository.lock_read()
 
377
    try:
360
378
        graph = repository.get_graph()
361
379
        ancestry = [r for (r, ps) in graph.iter_ancestry([revid])
362
380
                    if ps is not None and r != NULL_REVISION]
363
381
        revs = repository.get_revisions(ancestry)
364
 
        with ui.ui_factory.nested_progress_bar() as pb:
365
 
            iterator = zip(revs, repository.get_revision_deltas(revs))
366
 
            for i, (rev, delta) in enumerate(iterator):
 
382
        pb = ui.ui_factory.nested_progress_bar()
 
383
        try:
 
384
            iterator = izip(revs, repository.get_deltas_for_revisions(revs))
 
385
            for i, (rev,delta) in enumerate(iterator):
367
386
                pb.update("analysing revisions", i, len(revs))
368
387
                # Don't count merges
369
388
                if len(rev.parent_ids) > 1:
370
389
                    continue
371
390
                for c in set(classify_delta(delta)):
372
391
                    for author in rev.get_apparent_authors():
373
 
                        if author not in ret[c]:
 
392
                        if not author in ret[c]:
374
393
                            ret[c][author] = 0
375
394
                        ret[c][author] += 1
376
 
 
 
395
        finally:
 
396
            pb.finished()
 
397
    finally:
 
398
        repository.unlock()
377
399
    def sort_class(name):
378
400
        return [author
379
 
                for author, _ in sorted(ret[name].items(), key=classify_key)]
 
401
            for author, _  in sorted(ret[name].items(), key=classify_key)]
380
402
    return (sort_class("code"), sort_class("documentation"), sort_class("art"), sort_class("translation"))
381
403
 
382
404
 
401
423
        if revision is not None:
402
424
            last_rev = revision[0].in_history(a_branch).rev_id
403
425
 
404
 
        with a_branch.lock_read():
 
426
        a_branch.lock_read()
 
427
        try:
405
428
            credits = find_credits(a_branch.repository, last_rev)
406
429
            display_credits(credits, self.outf)
 
430
        finally:
 
431
            a_branch.unlock()