OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 | |
3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 | |
7 """Helper script for printing differences between tags.""" | |
8 | |
9 import cgi | |
10 from datetime import datetime | |
11 import os | |
12 import re | |
13 import sys | |
14 | |
15 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../lib')) | |
16 from cros_build_lib import RunCommand | |
17 | |
18 DEFAULT_TRACKER = 'chromium-os' | |
19 | |
20 | |
21 def _GrabOutput(cmd): | |
22 """Returns output from specified command.""" | |
23 return RunCommand(cmd, shell=True, print_cmd=False, | |
24 redirect_stdout=True).output | |
25 | |
26 | |
27 def _GrabTags(): | |
28 """Returns list of tags from current git repository.""" | |
diandersAtChromium
2010/10/28 20:36:15
It would be a lot easier to understand / make chan
David James
2010/10/28 20:52:52
Good idea. Could you add a TODO for this? This cod
| |
29 cmd = ("git for-each-ref refs/tags | awk '{print $3}' | " | |
30 "sed 's,refs/tags/,,g' | sort -t. -k3,3rn -k4,4rn") | |
31 return _GrabOutput(cmd).split() | |
32 | |
33 | |
34 def _GrabDirs(): | |
35 """Returns list of directories managed by repo.""" | |
36 return _GrabOutput('repo forall -c "pwd"').split() | |
37 | |
38 | |
39 class Commit(object): | |
40 """Class for tracking git commits.""" | |
41 | |
42 def __init__(self, commit, projectname, commit_email, commit_date, subject, | |
43 body): | |
44 """Create commit logs.""" | |
diandersAtChromium
2010/10/28 20:36:15
Seems like parameters should be documented here?
David James
2010/10/28 20:52:52
+1
| |
45 self.commit = commit | |
46 self.projectname = projectname | |
47 self.commit_email = commit_email | |
48 fmt = '%a %b %d %H:%M:%S %Y' | |
49 self.commit_date = datetime.strptime(commit_date, fmt) | |
50 self.subject = subject | |
51 self.body = body | |
52 self.bug_ids = self._GetBugIDs() | |
53 | |
54 def _GetBugIDs(self): | |
55 """Get bug ID from commit logs.""" | |
diandersAtChromium
2010/10/28 20:36:15
Describe inputs and outputs for this function: inp
David James
2010/10/28 20:52:52
+1
| |
56 | |
diandersAtChromium
2010/10/28 20:36:15
Some comments about what this is doing would be ap
David James
2010/10/28 20:52:52
+1. We should also mention it's copied from bugdro
| |
57 entries = [] | |
58 for line in self.body.split('\n'): | |
59 match = re.match(r'^ *BUG *=(.*)', line) | |
60 if match: | |
61 for i in match.group(1).split(','): | |
62 entries.extend(filter(None, [x.strip() for x in i.split()])) | |
63 | |
64 bug_ids = [] | |
65 last_tracker = DEFAULT_TRACKER | |
66 regex = (r'http://code.google.com/p/(\S+)/issues/detail\?id=([0-9]+)' | |
67 r'|(\S+):([0-9]+)|(\b[0-9]+\b)') | |
68 | |
69 for new_item in entries: | |
70 bug_numbers = re.findall(regex, new_item) | |
71 for bug_tuple in bug_numbers: | |
72 if bug_tuple[0] and bug_tuple[1]: | |
73 bug_ids.append('%s:%s' % (bug_tuple[0], bug_tuple[1])) | |
74 last_tracker = bug_tuple[0] | |
75 elif bug_tuple[2] and bug_tuple[3]: | |
76 bug_ids.append('%s:%s' % (bug_tuple[2], bug_tuple[3])) | |
77 last_tracker = bug_tuple[2] | |
78 elif bug_tuple[4]: | |
79 bug_ids.append('%s:%s' % (last_tracker, bug_tuple[4])) | |
80 | |
81 bug_ids.sort(key=str.lower) | |
82 return bug_ids | |
83 | |
84 def AsHTMLTableRow(self): | |
85 """Returns HTML for this change, for printing as part of a table. | |
86 | |
87 Columns: Project, Date, Commit, Committer, Bugs, Subject. | |
88 """ | |
89 | |
90 bugs = [] | |
91 bug_url_fmt = 'http://code.google.com/p/%s/issues/detail?id=%s' | |
92 link_fmt = '<a href="%s">%s</a>' | |
93 for bug in self.bug_ids: | |
94 tracker, bug_id = bug.split(':') | |
95 | |
96 # Get bug URL. We use short URLs to make the URLs a bit more readable. | |
97 if tracker == 'chromium-os': | |
98 bug_url = 'http://crosbug.com/%s' % bug_id | |
99 elif tracker == 'chrome-os-partner': | |
100 bug_url = 'http://crosbug.com/p/%s' % bug_id | |
101 else: | |
102 bug_url = bug_url_fmt % (tracker, bug_id) | |
103 | |
104 bugs.append(link_fmt % (bug_url, bug)) | |
105 | |
106 url_fmt = 'http://chromiumos-git/git/?p=%s.git;a=commitdiff;h=%s' | |
David McMahon
2010/10/28 02:30:44
Probably want to use gitrw.chromium.org:9222.
diandersAtChromium
2010/10/28 20:36:15
I'm not sure I understand this comment. I think t
David James
2010/10/28 20:52:52
Yeah. That was a misunderstanding. djmm and I disc
| |
107 url = url_fmt % (self.projectname, self.commit) | |
108 commit_desc = link_fmt % (url, self.commit[:8]) | |
109 bug_str = '<br>'.join(bugs) | |
110 if not bug_str: | |
111 if (self.projectname == 'kernel-next' or | |
112 self.commit_email == 'chrome-bot@chromium.org'): | |
113 bug_str = 'not needed' | |
114 else: | |
115 bug_str = '<font color="red">none</font>' | |
116 | |
117 cols = [ | |
118 cgi.escape(self.projectname), | |
119 str(self.commit_date), | |
120 commit_desc, | |
121 cgi.escape(self.commit_email), | |
122 bug_str, | |
123 cgi.escape(self.subject[:100]), | |
124 ] | |
125 return '<tr><td>%s</td></tr>' % ('</td><td>'.join(cols)) | |
126 | |
127 def __cmp__(self, other): | |
128 """Compare two Commit objects first by project name, then by date.""" | |
129 return (cmp(self.projectname, other.projectname) or | |
130 cmp(self.commit_date, other.commit_date)) | |
131 | |
132 | |
133 def _GrabChanges(path, tag1, tag2): | |
134 """Return list of commits to path between tag1 and tag2.""" | |
diandersAtChromium
2010/10/28 20:36:15
Would be nice to see parameters / return values co
David James
2010/10/28 20:52:52
+1
| |
135 | |
136 cmd = 'cd %s && git config --get remote.cros.projectname' % path | |
137 projectname = _GrabOutput(cmd).strip() | |
138 log_fmt = '%x00%H\t%ce\t%cd\t%s\t%b' | |
139 cmd_fmt = 'cd %s && git log --format="%s" --date=local %s..%s' | |
140 cmd = cmd_fmt % (path, log_fmt, tag1, tag2) | |
141 output = _GrabOutput(cmd) | |
142 commits = [] | |
143 for log_data in output.split('\0')[1:]: | |
144 commit, commit_email, commit_date, subject, body = log_data.split('\t', 4) | |
145 change = Commit(commit, projectname, commit_email, commit_date, subject, | |
146 body) | |
147 commits.append(change) | |
148 return commits | |
149 | |
150 | |
151 def main(): | |
152 tags = _GrabTags() | |
153 tag1 = None | |
154 if len(sys.argv) == 3: | |
155 tag1 = sys.argv[1] | |
156 tag2 = sys.argv[2] | |
157 elif len(sys.argv) == 2: | |
158 tag2 = sys.argv[1] | |
159 else: | |
160 print >>sys.stderr, 'Usage: %s [tag1] tag2' % sys.argv[0] | |
161 print >>sys.stderr, 'If only one tag is specified, we view the differences' | |
162 print >>sys.stderr, 'between that tag and the previous tag. You can also' | |
163 print >>sys.stderr, 'specify cros/master to show differences with' | |
164 print >>sys.stderr, 'tip-of-tree.' | |
165 sys.exit(1) | |
166 if not tag2.startswith('cros/') and tag2 not in tags: | |
167 print >>sys.stderr, 'Unrecognized tag: %s' % tag2 | |
168 sys.exit(1) | |
169 if not tag1: | |
diandersAtChromium
2010/10/28 20:36:15
Should be "if tag1 is None" unless you really want
David James
2010/10/28 20:52:52
Empty string is bad too, so let's leave this as-is
| |
170 tag1 = tags[tags.index(tag2) + 1] | |
diandersAtChromium
2010/10/28 20:36:15
Potential out of bounds issue here if tag2 is the
David James
2010/10/28 20:52:52
+1, please fix
| |
171 if not tag1.startswith('cros/') and tag1 not in tags: | |
172 print >>sys.stderr, 'Unrecognized tag: %s' % tag1 | |
173 sys.exit(1) | |
174 | |
175 print >>sys.stderr, 'Finding differences between %s and %s' % (tag1, tag2) | |
176 paths = _GrabDirs() | |
177 changes = [] | |
178 for path in paths: | |
179 changes.extend(_GrabChanges(path, tag1, tag2)) | |
180 | |
181 title = 'Changelog for %s to %s' % (tag1, tag2) | |
182 print '<html>' | |
183 print '<head><title>%s</title></head>' % title | |
184 print '<h1>%s</h1>' % title | |
185 cols = ['Project', 'Date', 'Commit', 'Committer', 'Bugs', 'Subject'] | |
186 print '<table border="1" cellpadding="4">' | |
187 print '<tr><th>%s</th>' % ('</th><th>'.join(cols)) | |
188 for change in sorted(changes): | |
189 print change.AsHTMLTableRow() | |
190 print '</table>' | |
191 print '</html>' | |
192 | |
193 | |
194 if __name__ == '__main__': | |
195 main() | |
OLD | NEW |