Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(833)

Side by Side Diff: chromite/bin/cros_changelog

Issue 4263001: Code cleanup: comments, docstrings, and gpylint fixes. (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/crosutils.git@master
Patch Set: Created 10 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 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 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 """Helper script for printing differences between tags.""" 7 """Helper script for printing differences between tags."""
8 8
9 import cgi 9 import cgi
10 from datetime import datetime 10 from datetime import datetime
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
45 45
46 46
47 def _GrabOutput(cmd): 47 def _GrabOutput(cmd):
48 """Returns output from specified command.""" 48 """Returns output from specified command."""
49 return RunCommand(cmd, shell=True, print_cmd=False, 49 return RunCommand(cmd, shell=True, print_cmd=False,
50 redirect_stdout=True).output 50 redirect_stdout=True).output
51 51
52 52
53 def _GrabTags(): 53 def _GrabTags():
54 """Returns list of tags from current git repository.""" 54 """Returns list of tags from current git repository."""
55 # TODO(dianders): replace this with the python equivalent.
55 cmd = ("git for-each-ref refs/tags | awk '{print $3}' | " 56 cmd = ("git for-each-ref refs/tags | awk '{print $3}' | "
56 "sed 's,refs/tags/,,g' | sort -t. -k3,3rn -k4,4rn") 57 "sed 's,refs/tags/,,g' | sort -t. -k3,3rn -k4,4rn")
57 return _GrabOutput(cmd).split() 58 return _GrabOutput(cmd).split()
58 59
59 60
60 def _GrabDirs(): 61 def _GrabDirs():
61 """Returns list of directories managed by repo.""" 62 """Returns list of directories managed by repo."""
62 return _GrabOutput('repo forall -c "pwd"').split() 63 return _GrabOutput('repo forall -c "pwd"').split()
63 64
64 65
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
122 """Compare two Issue objects.""" 123 """Compare two Issue objects."""
123 return cmp((self.project_name.lower(), self.issue_id), 124 return cmp((self.project_name.lower(), self.issue_id),
124 (other.project_name.lower(), other.issue_id)) 125 (other.project_name.lower(), other.issue_id))
125 126
126 127
127 class Commit(object): 128 class Commit(object):
128 """Class for tracking git commits.""" 129 """Class for tracking git commits."""
129 130
130 def __init__(self, commit, projectname, commit_email, commit_date, subject, 131 def __init__(self, commit, projectname, commit_email, commit_date, subject,
131 body, tracker_acc): 132 body, tracker_acc):
132 """Create commit logs.""" 133 """Create commit logs.
134
135 Args:
136 commit: The commit hash (sha) from git.
137 projectname: The project name, from:
138 git config --get remote.cros.projectname
139 commit_email: The email address associated with the commit (%ce in git
140 log)
141 commit_date: The date of the commit, like "Mon Nov 1 17:34:14 2010 -0500"
142 (%cd in git log))
143 subject: The subject of the commit (%s in git log)
144 body: The body of the commit (%b in git log)
145 tracker_acc: A tracker_access.TrackerAccess object.
146 """
133 self.commit = commit 147 self.commit = commit
134 self.projectname = projectname 148 self.projectname = projectname
135 self.commit_email = commit_email 149 self.commit_email = commit_email
136 fmt = '%a %b %d %H:%M:%S %Y' 150 fmt = '%a %b %d %H:%M:%S %Y'
137 self.commit_date = datetime.strptime(commit_date, fmt) 151 self.commit_date = datetime.strptime(commit_date, fmt)
138 self.subject = subject 152 self.subject = subject
139 self.body = body 153 self.body = body
140 self._tracker_acc = tracker_acc 154 self._tracker_acc = tracker_acc
141 self._issues = self._GetIssues() 155 self._issues = self._GetIssues()
142 156
143 def _GetIssues(self): 157 def _GetIssues(self):
144 """Get bug info from commit logs and issue tracker. 158 """Get bug info from commit logs and issue tracker.
145 159
146 This should be called as the last step of __init__, since it 160 This should be called as the last step of __init__, since it
147 assumes that our member variables are already setup. 161 assumes that our member variables are already setup.
148 162
149 Returns: 163 Returns:
150 A list of Issue objects, each of which holds info about a bug. 164 A list of Issue objects, each of which holds info about a bug.
151 """ 165 """
166 # NOTE: most of this code is copied from bugdroid:
167 # <http://src.chromium.org/viewvc/chrome/trunk/tools/bugdroid/bugdroid.py? revision=59229&view=markup>
152 168
169 # Get a list of bugs. Handle lots of possibilities:
170 # - Multiple "BUG=" lines, with varying amounts of whitespace.
171 # - For each BUG= line, bugs can be split by commas _or_ by whitespace (!)
153 entries = [] 172 entries = []
154 for line in self.body.split('\n'): 173 for line in self.body.split('\n'):
155 match = re.match(r'^ *BUG *=(.*)', line) 174 match = re.match(r'^ *BUG *=(.*)', line)
156 if match: 175 if match:
157 for i in match.group(1).split(','): 176 for i in match.group(1).split(','):
158 entries.extend(filter(None, [x.strip() for x in i.split()])) 177 entries.extend(filter(None, [x.strip() for x in i.split()]))
159 178
179 # Try to parse the bugs. Handle lots of different formats:
180 # - The whole URL, from which we parse the project and bug.
181 # - A simple string that looks like "project:bug"
182 # - A string that looks like "bug", which will always refer to the previous
183 # tracker referenced (defaulting to the default tracker).
184 #
185 # We will create an "Issue" object for each bug.
160 issues = [] 186 issues = []
161 last_tracker = DEFAULT_TRACKER 187 last_tracker = DEFAULT_TRACKER
162 regex = (r'http://code.google.com/p/(\S+)/issues/detail\?id=([0-9]+)' 188 regex = (r'http://code.google.com/p/(\S+)/issues/detail\?id=([0-9]+)'
163 r'|(\S+):([0-9]+)|(\b[0-9]+\b)') 189 r'|(\S+):([0-9]+)|(\b[0-9]+\b)')
164 190
165 for new_item in entries: 191 for new_item in entries:
166 bug_numbers = re.findall(regex, new_item) 192 bug_numbers = re.findall(regex, new_item)
167 for bug_tuple in bug_numbers: 193 for bug_tuple in bug_numbers:
168 if bug_tuple[0] and bug_tuple[1]: 194 if bug_tuple[0] and bug_tuple[1]:
169 issues.append(Issue(bug_tuple[0], bug_tuple[1], self._tracker_acc)) 195 issues.append(Issue(bug_tuple[0], bug_tuple[1], self._tracker_acc))
170 last_tracker = bug_tuple[0] 196 last_tracker = bug_tuple[0]
171 elif bug_tuple[2] and bug_tuple[3]: 197 elif bug_tuple[2] and bug_tuple[3]:
172 issues.append(Issue(bug_tuple[2], bug_tuple[3], self._tracker_acc)) 198 issues.append(Issue(bug_tuple[2], bug_tuple[3], self._tracker_acc))
173 last_tracker = bug_tuple[2] 199 last_tracker = bug_tuple[2]
174 elif bug_tuple[4]: 200 elif bug_tuple[4]:
175 issues.append(Issue(last_tracker, bug_tuple[4], self._tracker_acc)) 201 issues.append(Issue(last_tracker, bug_tuple[4], self._tracker_acc))
176 202
203 # Sort the issues and return...
davidjames 2010/11/08 22:32:49 Only one period is needed here...
177 issues.sort() 204 issues.sort()
178 return issues 205 return issues
179 206
180 def AsHTMLTableRow(self): 207 def AsHTMLTableRow(self):
181 """Returns HTML for this change, for printing as part of a table. 208 """Returns HTML for this change, for printing as part of a table.
182 209
183 Columns: Project, Date, Commit, Committer, Bugs, Subject. 210 Columns: Project, Date, Commit, Committer, Bugs, Subject.
184 211
185 Returns: 212 Returns:
186 A string usable as an HTML table row, like: 213 A string usable as an HTML table row, like:
(...skipping 11 matching lines...) Expand all
198 commit_desc = link_fmt % (url, self.commit[:8]) 225 commit_desc = link_fmt % (url, self.commit[:8])
199 bug_str = '<br>'.join(bugs) 226 bug_str = '<br>'.join(bugs)
200 if not bug_str: 227 if not bug_str:
201 if (self.projectname == 'kernel-next' or 228 if (self.projectname == 'kernel-next' or
202 self.commit_email == 'chrome-bot@chromium.org'): 229 self.commit_email == 'chrome-bot@chromium.org'):
203 bug_str = 'not needed' 230 bug_str = 'not needed'
204 else: 231 else:
205 bug_str = '<font color="red">none</font>' 232 bug_str = '<font color="red">none</font>'
206 233
207 cols = [ 234 cols = [
208 cgi.escape(self.projectname), 235 cgi.escape(self.projectname),
209 str(self.commit_date), 236 str(self.commit_date),
210 commit_desc, 237 commit_desc,
211 cgi.escape(self.commit_email), 238 cgi.escape(self.commit_email),
212 bug_str, 239 bug_str,
213 cgi.escape(self.subject[:100]), 240 cgi.escape(self.subject[:100]),
214 ] 241 ]
215 return '<tr><td>%s</td></tr>' % ('</td><td>'.join(cols)) 242 return '<tr><td>%s</td></tr>' % ('</td><td>'.join(cols))
216 243
217 def __cmp__(self, other): 244 def __cmp__(self, other):
218 """Compare two Commit objects first by project name, then by date.""" 245 """Compare two Commit objects first by project name, then by date."""
219 return (cmp(self.projectname, other.projectname) or 246 return (cmp(self.projectname, other.projectname) or
220 cmp(self.commit_date, other.commit_date)) 247 cmp(self.commit_date, other.commit_date))
221 248
222 249
223 def _GrabChanges(path, tag1, tag2, tracker_acc): 250 def _GrabChanges(path, tag1, tag2, tracker_acc):
224 """Return list of commits to path between tag1 and tag2.""" 251 """Return list of commits to path between tag1 and tag2.
252
253 Args:
254 path: One of the directories managed by repo.
255 tag1: The first of the two tags to pass to git log.
256 tag2: The second of the two tags to pass to git log.
257 tracker_acc: A tracker_access.TrackerAccess object.
258
259 Returns:
260 A list of "Commit" objects.
261 """
225 262
226 cmd = 'cd %s && git config --get remote.cros.projectname' % path 263 cmd = 'cd %s && git config --get remote.cros.projectname' % path
227 projectname = _GrabOutput(cmd).strip() 264 projectname = _GrabOutput(cmd).strip()
228 log_fmt = '%x00%H\t%ce\t%cd\t%s\t%b' 265 log_fmt = '%x00%H\t%ce\t%cd\t%s\t%b'
229 cmd_fmt = 'cd %s && git log --format="%s" --date=local "%s..%s"' 266 cmd_fmt = 'cd %s && git log --format="%s" --date=local "%s..%s"'
230 cmd = cmd_fmt % (path, log_fmt, tag1, tag2) 267 cmd = cmd_fmt % (path, log_fmt, tag1, tag2)
231 output = _GrabOutput(cmd) 268 output = _GrabOutput(cmd)
232 commits = [] 269 commits = []
233 for log_data in output.split('\0')[1:]: 270 for log_data in output.split('\0')[1:]:
234 commit, commit_email, commit_date, subject, body = log_data.split('\t', 4) 271 commit, commit_email, commit_date, subject, body = log_data.split('\t', 4)
235 change = Commit(commit, projectname, commit_email, commit_date, subject, 272 change = Commit(commit, projectname, commit_email, commit_date, subject,
236 body, tracker_acc) 273 body, tracker_acc)
237 commits.append(change) 274 commits.append(change)
238 return commits 275 return commits
239 276
240 277
241 def _ParseArgs(): 278 def _ParseArgs():
279 """Parse command-line arguments.
280
281 Returns:
282 An optparse.OptionParser object.
283 """
242 parser = optparse.OptionParser() 284 parser = optparse.OptionParser()
243 parser.add_option( 285 parser.add_option(
244 "--sort-by-date", dest="sort_by_date", default=False, 286 '--sort-by-date', dest='sort_by_date', default=False,
245 action='store_true', help="Sort commits by date.") 287 action='store_true', help='Sort commits by date.')
246 parser.add_option( 288 parser.add_option(
247 "--tracker-user", dest="tracker_user", default=None, 289 '--tracker-user', dest='tracker_user', default=None,
248 help="Specify a username to login to code.google.com.") 290 help='Specify a username to login to code.google.com.')
249 parser.add_option( 291 parser.add_option(
250 "--tracker-pass", dest="tracker_pass", default=None, 292 '--tracker-pass', dest='tracker_pass', default=None,
251 help="Specify a password to go w/ user.") 293 help='Specify a password to go w/ user.')
252 parser.add_option( 294 parser.add_option(
253 "--tracker-passfile", dest="tracker_passfile", default=None, 295 '--tracker-passfile', dest='tracker_passfile', default=None,
254 help="Specify a file containing a password to go w/ user.") 296 help='Specify a file containing a password to go w/ user.')
255 return parser.parse_args() 297 return parser.parse_args()
256 298
257 299
258 def main(): 300 def main():
259 tags = _GrabTags() 301 tags = _GrabTags()
260 tag1 = None 302 tag1 = None
261 options, args = _ParseArgs() 303 options, args = _ParseArgs()
262 if len(args) == 2: 304 if len(args) == 2:
263 tag1, tag2 = args 305 tag1, tag2 = args
264 elif len(args) == 1: 306 elif len(args) == 1:
265 tag2, = args 307 tag2, = args
266 if tag2 in tags: 308 if tag2 in tags:
267 tag1 = tags[tags.index(tag2) + 1] 309 tag2_index = tags.index(tag2)
310 if tag2_index == len(tags) - 1:
311 print >>sys.stderr, 'No previous tag for %s' % tag2
312 sys.exit(1)
313 tag1 = tags[tag2_index + 1]
268 else: 314 else:
269 print >>sys.stderr, 'Unrecognized tag: %s' % tag2 315 print >>sys.stderr, 'Unrecognized tag: %s' % tag2
270 sys.exit(1) 316 sys.exit(1)
271 else: 317 else:
272 print >>sys.stderr, 'Usage: %s [tag1] tag2' % sys.argv[0] 318 print >>sys.stderr, 'Usage: %s [tag1] tag2' % sys.argv[0]
273 print >>sys.stderr, 'If only one tag is specified, we view the differences' 319 print >>sys.stderr, 'If only one tag is specified, we view the differences'
274 print >>sys.stderr, 'between that tag and the previous tag. You can also' 320 print >>sys.stderr, 'between that tag and the previous tag. You can also'
275 print >>sys.stderr, 'specify cros/master to show differences with' 321 print >>sys.stderr, 'specify cros/master to show differences with'
276 print >>sys.stderr, 'tip-of-tree.' 322 print >>sys.stderr, 'tip-of-tree.'
277 print >>sys.stderr, 'E.g. %s %s cros/master' % (sys.argv[0], tags[0]) 323 print >>sys.stderr, 'E.g. %s %s cros/master' % (sys.argv[0], tags[0])
278 sys.exit(1) 324 sys.exit(1)
279 325
280 if options.tracker_user is not None: 326 if options.tracker_user is not None:
281 # TODO(dianders): Once we install GData automatically, move the import 327 # TODO(dianders): Once we install GData automatically, move the import
282 # to the top of the file where it belongs. It's only here to allow 328 # to the top of the file where it belongs. It's only here to allow
283 # people to run the script without GData. 329 # people to run the script without GData.
284 try: 330 try:
285 import tracker_access 331 import tracker_access
286 except ImportError: 332 except ImportError:
287 print >>sys.stderr, INSTRS_FOR_GDATA 333 print >>sys.stderr, INSTRS_FOR_GDATA
288 sys.exit(1) 334 sys.exit(1)
289 if options.tracker_passfile is not None: 335 if options.tracker_passfile is not None:
290 options.tracker_pass = open(options.tracker_passfile, "r").read().strip() 336 options.tracker_pass = open(options.tracker_passfile, 'r').read().strip()
291 tracker_acc = tracker_access.TrackerAccess(options.tracker_user, 337 tracker_acc = tracker_access.TrackerAccess(options.tracker_user,
292 options.tracker_pass) 338 options.tracker_pass)
293 else: 339 else:
294 tracker_acc = None 340 tracker_acc = None
295 341
296 print >>sys.stderr, 'Finding differences between %s and %s' % (tag1, tag2) 342 print >>sys.stderr, 'Finding differences between %s and %s' % (tag1, tag2)
297 paths = _GrabDirs() 343 paths = _GrabDirs()
298 changes = [] 344 changes = []
299 for path in paths: 345 for path in paths:
300 changes.extend(_GrabChanges(path, tag1, tag2, tracker_acc)) 346 changes.extend(_GrabChanges(path, tag1, tag2, tracker_acc))
301 347
302 title = 'Changelog for %s to %s' % (tag1, tag2) 348 title = 'Changelog for %s to %s' % (tag1, tag2)
303 print '<html>' 349 print '<html>'
304 print '<head><title>%s</title></head>' % title 350 print '<head><title>%s</title></head>' % title
305 print '<h1>%s</h1>' % title 351 print '<h1>%s</h1>' % title
306 cols = ['Project', 'Date', 'Commit', 'Committer', 'Bugs', 'Subject'] 352 cols = ['Project', 'Date', 'Commit', 'Committer', 'Bugs', 'Subject']
307 print '<table border="1" cellpadding="4">' 353 print '<table border="1" cellpadding="4">'
308 print '<tr><th>%s</th>' % ('</th><th>'.join(cols)) 354 print '<tr><th>%s</th>' % ('</th><th>'.join(cols))
309 if options.sort_by_date: 355 if options.sort_by_date:
310 changes.sort(key=operator.attrgetter("commit_date")) 356 changes.sort(key=operator.attrgetter('commit_date'))
311 else: 357 else:
312 changes.sort() 358 changes.sort()
313 for change in changes: 359 for change in changes:
314 print change.AsHTMLTableRow() 360 print change.AsHTMLTableRow()
315 print '</table>' 361 print '</table>'
316 print '</html>' 362 print '</html>'
317 363
318 364
319 if __name__ == '__main__': 365 if __name__ == '__main__':
320 main() 366 main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698