Merge lp:~cjwatson/lp-dev-utils/loc-delta into lp:lp-dev-utils

Proposed by Colin Watson
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: 109
Merged at revision: 115
Proposed branch: lp:~cjwatson/lp-dev-utils/loc-delta
Merge into: lp:lp-dev-utils
Diff against target: 145 lines (+141/-0)
1 file modified
loc-contributions (+141/-0)
To merge this branch: bzr merge lp:~cjwatson/lp-dev-utils/loc-delta
Reviewer Review Type Date Requested Status
Jelmer Vernooij (community) Approve
Review via email: mp+106080@code.launchpad.net

Description of the change

Add a loc-delta script to report on LoC contributions by a single developer.

Since the advent of https://dev.launchpad.net/PolicyAndProcess/MaintenanceCosts, I've found it useful to be able to keep an eye on what my personal contributions to Launchpad's lines-of-code count have been, so that I know how much room I have to offset changes where I have no choice but to increase LoC at least temporarily. Others have asked similar questions in #launchpad-dev, and it strikes me that this might be useful to reviewers as well.

The slightly odd business with spotting PQM commits is to avoid a single developer being credited or debited with all parts of a multi-level commit, such as merges into devel via db-devel. Looking at only the leaf commits didn't work too well, as that includes developers merging from devel, and what matters is the delta at the point when they actually land the change.

Here's an egotistical demonstration transcript:

<cjwatson@sarantium ~/src/canonical/launchpad/lp-branches/devel>$ loc-delta 'Colin Watson'
-1305
<cjwatson@sarantium ~/src/canonical/launchpad/lp-branches/devel>$ loc-delta --verbose <email address hidden>
Revision 15032: 4 files changed, 531 deletions(-) (delta: -531)
Revision 15035: 19 files changed, 83 insertions(+), 95 deletions(-) (delta: -12)
Revision 15036: 2 files changed, 56 insertions(+), 57 deletions(-) (delta: -1)
Revision 15040: 11 files changed, 280 insertions(+), 378 deletions(-) (delta: -98)
Revision 15068: 2 files changed, 45 insertions(+), 3 deletions(-) (delta: 42)
Revision 15072: 3 files changed, 48 insertions(+), 46 deletions(-) (delta: 2)
Revision 15073: 6 files changed, 36 insertions(+), 141 deletions(-) (delta: -105)
Revision 15080: 2 files changed, 232 deletions(-) (delta: -232)
Revision 15103: 7 files changed, 54 insertions(+), 147 deletions(-) (delta: -93)
Revision 15108: 72 files changed, 30 insertions(+), 156 deletions(-) (delta: -126)
Revision 15137: 2 files changed, 53 insertions(+), 9 deletions(-) (delta: 44)
Revision 15145: 3 files changed, 1 insertion(+), 3 deletions(-) (delta: -2)
Revision 15159: 3 files changed, 1 insertion(+), 116 deletions(-) (delta: -115)
Revision 15169: 1 file changed, 18 deletions(-) (delta: -18)
Revision 15176: 2 files changed, 3 insertions(+), 71 deletions(-) (delta: -68)
Revision 7675.1298.88: 1 file changed, 8 insertions(+) (delta: 8)
Total LoC delta for <email address hidden>: -1305

To post a comment you must log in.
lp:~cjwatson/lp-dev-utils/loc-delta updated
108. By Colin Watson

return value from main()

109. By Colin Watson

better name

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Looks good and works well.

A nice addition might be to have AUTHOR default to the current users email address.

review: Approve
Revision history for this message
Colin Watson (cjwatson) wrote :

Good idea! I've pushed a change that does this. Belatedly, I see you
already had as well; I guess you can pick one.

I'm not in ~canonical-launchpad-branches, so could you land whenever
you're happy?

lp:~cjwatson/lp-dev-utils/loc-delta updated
110. By Colin Watson

loc-contributions: Default to current user's e-mail address. Suggested by Jelmer Vernooij.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

I'll take yours, they seem to do pretty much the same thing.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'loc-contributions'
2--- loc-contributions 1970-01-01 00:00:00 +0000
3+++ loc-contributions 2012-05-17 12:17:20 +0000
4@@ -0,0 +1,141 @@
5+#!/usr/bin/python
6+#
7+# Copyright 2012 Canonical Ltd. This software is licensed under the
8+# GNU Affero General Public License version 3 (see the file LICENSE).
9+
10+"""Show LoC contributions by a given author."""
11+
12+from __future__ import print_function
13+
14+from optparse import OptionParser
15+import re
16+import subprocess
17+import sys
18+from textwrap import dedent
19+
20+from bzrlib import log
21+from bzrlib.branch import Branch
22+from bzrlib.config import (
23+ extract_email_address,
24+ parse_username,
25+ )
26+from bzrlib.diff import show_diff_trees
27+
28+
29+# The author of all top-level landings.
30+MERGE_AUTHOR = "Launchpad Patch Queue Manager <launchpad@pqm.canonical.com>"
31+
32+
33+class LogLinesOfCode(log.LogFormatter):
34+ """Log LoC contributions for an author."""
35+
36+ # See log.LogFormatter documentation.
37+ supports_merge_revisions = True
38+
39+ def __init__(self, want_author):
40+ super(LogLinesOfCode, self).__init__(to_file=None)
41+ self.want_author = want_author
42+ # Most-recently-seen merge revision.
43+ self.current_merge_rev = None
44+ self.matched_merge_revs = []
45+
46+ def log_revision(self, lr):
47+ """Log a revision.
48+ :param lr: The LogRevision to be logged.
49+ """
50+ # We count on always seeing the containing rev before its subrevs.
51+ if MERGE_AUTHOR in lr.rev.get_apparent_authors():
52+ self.current_merge_rev = lr.rev
53+ elif (self.matched_merge_revs and
54+ self.current_merge_rev == self.matched_merge_revs[-1]):
55+ # Already matched.
56+ return
57+ for author in lr.rev.get_apparent_authors():
58+ if (self.want_author == author or
59+ self.want_author in parse_username(author)):
60+ self.matched_merge_revs.append(self.current_merge_rev)
61+ return
62+
63+
64+def show_loc(options, author):
65+ branch = Branch.open(".")
66+ if author is None:
67+ author = extract_email_address(branch.get_config_stack().get("email"))
68+ logger = log.Logger(branch, {
69+ "direction": "forward",
70+ "start_revision": options.start_rev,
71+ "end_revision": branch.revno(),
72+ "levels": 0,
73+ })
74+ log_formatter = LogLinesOfCode(author)
75+ logger.show(log_formatter)
76+
77+ re_insertions = re.compile(r"(\d+) insertion")
78+ re_deletions = re.compile(r"(\d+) deletion")
79+
80+ total_delta = 0
81+
82+ try:
83+ branch.lock_read()
84+ for rev in log_formatter.matched_merge_revs:
85+ revid = rev.revision_id
86+
87+ old_tree = branch.repository.revision_tree(rev.parent_ids[0])
88+ new_tree = branch.repository.revision_tree(revid)
89+
90+ diffstat_proc = subprocess.Popen(
91+ ["diffstat", "-s"],
92+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
93+ show_diff_trees(old_tree, new_tree, diffstat_proc.stdin)
94+ diffstat = diffstat_proc.communicate()[0].strip()
95+
96+ insertions, deletions = 0, 0
97+ match = re_insertions.search(diffstat)
98+ if match:
99+ insertions = int(match.group(1))
100+ match = re_deletions.search(diffstat)
101+ if match:
102+ deletions = int(match.group(1))
103+ delta = insertions - deletions
104+ total_delta += delta
105+
106+ revno = ".".join(
107+ str(n) for n in branch.revision_id_to_dotted_revno(revid))
108+ if options.verbose:
109+ print("Revision %s: %s (delta: %d)" % (revno, diffstat, delta))
110+ finally:
111+ branch.unlock()
112+
113+ if options.verbose:
114+ print("Total LoC delta for %s: %d" % (author, total_delta))
115+ else:
116+ print(total_delta)
117+
118+
119+def main(args):
120+ usage = "%prog [options] [AUTHOR]"
121+ description = dedent("""\
122+ Show contributions to lines-of-code count by a given author.
123+
124+ AUTHOR may be a name or an e-mail address. It defaults to the
125+ current user's e-mail address, as reported by 'bzr whoami --email'.
126+ """)
127+ parser = OptionParser(usage=usage, description=description)
128+ parser.add_option("--start-rev", metavar="REV", type="int", default=14780,
129+ help="Consider only revision numbers starting at REV. "
130+ "(Defaults to 14780, the first under the "
131+ "maintenance costs policy.)")
132+ parser.add_option("--verbose", action="store_true", default=False,
133+ help="Show detailed information about each revision.")
134+ options, args = parser.parse_args(args)
135+ author = None
136+ if len(args) >= 1:
137+ author = args[0]
138+
139+ show_loc(options, author)
140+
141+ return 0
142+
143+
144+if __name__ == "__main__":
145+ sys.exit(main(sys.argv[1:]))

Subscribers

People subscribed via source and target branches