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

Side by Side Diff: chromite/bin/cros_changelog

Issue 6371018: Remove chromite from crosutils.git. It's been moved to chromite.git. (Closed) Base URL: http://git.chromium.org/git/crosutils.git@master
Patch Set: Created 9 years, 10 months 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 | « chromite/bin/cros_build_packages ('k') | chromite/chromite » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 operator
12 import optparse
13 import os
14 import re
15 import sys
16
17 sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../lib'))
18 from cros_build_lib import RunCommand
19
20
21 # TODO(dianders):
22 # We use GData to access the tracker on code.google.com. Eventually, we
23 # want to create an ebuild and add the ebuild to hard-host-depends
24 # For now, we'll just include instructions for installing it.
25 INSTRS_FOR_GDATA = """
26 To access the tracker you need the GData library. To install in your home dir:
27
28 GDATA_INSTALL_DIR=~/gdatalib
29 mkdir -p "$GDATA_INSTALL_DIR"
30
31 TMP_DIR=`mktemp -d`
32 pushd $TMP_DIR
33 wget http://gdata-python-client.googlecode.com/files/gdata-2.0.12.zip
34 unzip gdata-2.0.12.zip
35 cd gdata-2.0.12/
36 python setup.py install --home="$GDATA_INSTALL_DIR"
37 popd
38
39 export PYTHONPATH="$GDATA_INSTALL_DIR/lib/python:$PYTHONPATH"
40
41 You should add the PYTHONPATH line to your .bashrc file (or equivalent)."""
42
43
44 DEFAULT_TRACKER = 'chromium-os'
45
46
47 def _GrabOutput(cmd):
48 """Returns output from specified command."""
49 return RunCommand(cmd, shell=True, print_cmd=False,
50 redirect_stdout=True).output
51
52
53 def _GrabTags():
54 """Returns list of tags from current git repository."""
55 # TODO(dianders): replace this with the python equivalent.
56 cmd = ("git for-each-ref refs/tags | awk '{print $3}' | "
57 "sed 's,refs/tags/,,g' | sort -t. -k3,3rn -k4,4rn")
58 return _GrabOutput(cmd).split()
59
60
61 def _GrabDirs():
62 """Returns list of directories managed by repo."""
63 return _GrabOutput('repo forall -c "pwd"').split()
64
65
66 class Issue(object):
67 """Class for holding info about issues (aka bugs)."""
68
69 def __init__(self, project_name, issue_id, tracker_acc):
70 """Constructor for Issue object.
71
72 Args:
73 project_name: The tracker project to query.
74 issue_id: The ID of the issue to query
75 tracker_acc: A TrackerAccess object, or None.
76 """
77 self.project_name = project_name
78 self.issue_id = issue_id
79 self.milestone = ''
80 self.priority = ''
81
82 if tracker_acc is not None:
83 keyed_labels = tracker_acc.GetKeyedLabels(project_name, issue_id)
84 if 'Mstone' in keyed_labels:
85 self.milestone = keyed_labels['Mstone']
86 if 'Pri' in keyed_labels:
87 self.priority = keyed_labels['Pri']
88
89 def GetUrl(self):
90 """Returns the URL to access the issue."""
91 bug_url_fmt = 'http://code.google.com/p/%s/issues/detail?id=%s'
92
93 # Get bug URL. We use short URLs to make the URLs a bit more readable.
94 if self.project_name == 'chromium-os':
95 bug_url = 'http://crosbug.com/%s' % self.issue_id
96 elif self.project_name == 'chrome-os-partner':
97 bug_url = 'http://crosbug.com/p/%s' % self.issue_id
98 else:
99 bug_url = bug_url_fmt % (self.project_name, self.issue_id)
100
101 return bug_url
102
103 def __str__(self):
104 """Provides a string representation of the issue.
105
106 Returns:
107 A string that looks something like:
108
109 project:id (milestone, priority)
110 """
111 if self.milestone and self.priority:
112 info_str = ' (%s, P%s)' % (self.milestone, self.priority)
113 elif self.milestone:
114 info_str = ' (%s)' % self.milestone
115 elif self.priority:
116 info_str = ' (P%s)' % self.priority
117 else:
118 info_str = ''
119
120 return '%s:%s%s' % (self.project_name, self.issue_id, info_str)
121
122 def __cmp__(self, other):
123 """Compare two Issue objects."""
124 return cmp((self.project_name.lower(), self.issue_id),
125 (other.project_name.lower(), other.issue_id))
126
127
128 class Commit(object):
129 """Class for tracking git commits."""
130
131 def __init__(self, commit, projectname, commit_email, commit_date, subject,
132 body, tracker_acc):
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 """
147 self.commit = commit
148 self.projectname = projectname
149 self.commit_email = commit_email
150 fmt = '%a %b %d %H:%M:%S %Y'
151 self.commit_date = datetime.strptime(commit_date, fmt)
152 self.subject = subject
153 self.body = body
154 self._tracker_acc = tracker_acc
155 self._issues = self._GetIssues()
156
157 def _GetIssues(self):
158 """Get bug info from commit logs and issue tracker.
159
160 This should be called as the last step of __init__, since it
161 assumes that our member variables are already setup.
162
163 Returns:
164 A list of Issue objects, each of which holds info about a bug.
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>
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 (!)
172 entries = []
173 for line in self.body.split('\n'):
174 match = re.match(r'^ *BUG *=(.*)', line)
175 if match:
176 for i in match.group(1).split(','):
177 entries.extend(filter(None, [x.strip() for x in i.split()]))
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.
186 issues = []
187 last_tracker = DEFAULT_TRACKER
188 regex = (r'http://code.google.com/p/(\S+)/issues/detail\?id=([0-9]+)'
189 r'|(\S+):([0-9]+)|(\b[0-9]+\b)')
190
191 for new_item in entries:
192 bug_numbers = re.findall(regex, new_item)
193 for bug_tuple in bug_numbers:
194 if bug_tuple[0] and bug_tuple[1]:
195 issues.append(Issue(bug_tuple[0], bug_tuple[1], self._tracker_acc))
196 last_tracker = bug_tuple[0]
197 elif bug_tuple[2] and bug_tuple[3]:
198 issues.append(Issue(bug_tuple[2], bug_tuple[3], self._tracker_acc))
199 last_tracker = bug_tuple[2]
200 elif bug_tuple[4]:
201 issues.append(Issue(last_tracker, bug_tuple[4], self._tracker_acc))
202
203 # Sort the issues and return...
204 issues.sort()
205 return issues
206
207 def AsHTMLTableRow(self):
208 """Returns HTML for this change, for printing as part of a table.
209
210 Columns: Project, Date, Commit, Committer, Bugs, Subject.
211
212 Returns:
213 A string usable as an HTML table row, like:
214
215 <tr><td>Blah</td><td>Blah blah</td></tr>
216 """
217
218 bugs = []
219 link_fmt = '<a href="%s">%s</a>'
220 for issue in self._issues:
221 bugs.append(link_fmt % (issue.GetUrl(), str(issue)))
222
223 url_fmt = 'http://chromiumos-git/git/?p=%s.git;a=commitdiff;h=%s'
224 url = url_fmt % (self.projectname, self.commit)
225 commit_desc = link_fmt % (url, self.commit[:8])
226 bug_str = '<br>'.join(bugs)
227 if not bug_str:
228 if (self.projectname == 'kernel-next' or
229 self.commit_email == 'chrome-bot@chromium.org'):
230 bug_str = 'not needed'
231 else:
232 bug_str = '<font color="red">none</font>'
233
234 cols = [
235 cgi.escape(self.projectname),
236 str(self.commit_date),
237 commit_desc,
238 cgi.escape(self.commit_email),
239 bug_str,
240 cgi.escape(self.subject[:100]),
241 ]
242 return '<tr><td>%s</td></tr>' % ('</td><td>'.join(cols))
243
244 def __cmp__(self, other):
245 """Compare two Commit objects first by project name, then by date."""
246 return (cmp(self.projectname, other.projectname) or
247 cmp(self.commit_date, other.commit_date))
248
249
250 def _GrabChanges(path, tag1, tag2, tracker_acc):
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 """
262
263 cmd = 'cd %s && git config --get remote.cros.projectname' % path
264 projectname = _GrabOutput(cmd).strip()
265 log_fmt = '%x00%H\t%ce\t%cd\t%s\t%b'
266 cmd_fmt = 'cd %s && git log --format="%s" --date=local "%s..%s"'
267 cmd = cmd_fmt % (path, log_fmt, tag1, tag2)
268 output = _GrabOutput(cmd)
269 commits = []
270 for log_data in output.split('\0')[1:]:
271 commit, commit_email, commit_date, subject, body = log_data.split('\t', 4)
272 change = Commit(commit, projectname, commit_email, commit_date, subject,
273 body, tracker_acc)
274 commits.append(change)
275 return commits
276
277
278 def _ParseArgs():
279 """Parse command-line arguments.
280
281 Returns:
282 An optparse.OptionParser object.
283 """
284 parser = optparse.OptionParser()
285 parser.add_option(
286 '--sort-by-date', dest='sort_by_date', default=False,
287 action='store_true', help='Sort commits by date.')
288 parser.add_option(
289 '--tracker-user', dest='tracker_user', default=None,
290 help='Specify a username to login to code.google.com.')
291 parser.add_option(
292 '--tracker-pass', dest='tracker_pass', default=None,
293 help='Specify a password to go w/ user.')
294 parser.add_option(
295 '--tracker-passfile', dest='tracker_passfile', default=None,
296 help='Specify a file containing a password to go w/ user.')
297 return parser.parse_args()
298
299
300 def main():
301 tags = _GrabTags()
302 tag1 = None
303 options, args = _ParseArgs()
304 if len(args) == 2:
305 tag1, tag2 = args
306 elif len(args) == 1:
307 tag2, = args
308 if tag2 in tags:
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]
314 else:
315 print >>sys.stderr, 'Unrecognized tag: %s' % tag2
316 sys.exit(1)
317 else:
318 print >>sys.stderr, 'Usage: %s [tag1] tag2' % sys.argv[0]
319 print >>sys.stderr, 'If only one tag is specified, we view the differences'
320 print >>sys.stderr, 'between that tag and the previous tag. You can also'
321 print >>sys.stderr, 'specify cros/master to show differences with'
322 print >>sys.stderr, 'tip-of-tree.'
323 print >>sys.stderr, 'E.g. %s %s cros/master' % (sys.argv[0], tags[0])
324 sys.exit(1)
325
326 if options.tracker_user is not None:
327 # TODO(dianders): Once we install GData automatically, move the import
328 # to the top of the file where it belongs. It's only here to allow
329 # people to run the script without GData.
330 try:
331 import tracker_access
332 except ImportError:
333 print >>sys.stderr, INSTRS_FOR_GDATA
334 sys.exit(1)
335 if options.tracker_passfile is not None:
336 options.tracker_pass = open(options.tracker_passfile, 'r').read().strip()
337 tracker_acc = tracker_access.TrackerAccess(options.tracker_user,
338 options.tracker_pass)
339 else:
340 tracker_acc = None
341
342 print >>sys.stderr, 'Finding differences between %s and %s' % (tag1, tag2)
343 paths = _GrabDirs()
344 changes = []
345 for path in paths:
346 changes.extend(_GrabChanges(path, tag1, tag2, tracker_acc))
347
348 title = 'Changelog for %s to %s' % (tag1, tag2)
349 print '<html>'
350 print '<head><title>%s</title></head>' % title
351 print '<h1>%s</h1>' % title
352 cols = ['Project', 'Date', 'Commit', 'Committer', 'Bugs', 'Subject']
353 print '<table border="1" cellpadding="4">'
354 print '<tr><th>%s</th>' % ('</th><th>'.join(cols))
355 if options.sort_by_date:
356 changes.sort(key=operator.attrgetter('commit_date'))
357 else:
358 changes.sort()
359 for change in changes:
360 print change.AsHTMLTableRow()
361 print '</table>'
362 print '</html>'
363
364
365 if __name__ == '__main__':
366 main()
OLDNEW
« no previous file with comments | « chromite/bin/cros_build_packages ('k') | chromite/chromite » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698