Index: chromite/bin/cros_changelog |
diff --git a/chromite/bin/cros_changelog b/chromite/bin/cros_changelog |
deleted file mode 100755 |
index f75ceab6a830468dd5f607cd99f66dce9d5c7fa5..0000000000000000000000000000000000000000 |
--- a/chromite/bin/cros_changelog |
+++ /dev/null |
@@ -1,366 +0,0 @@ |
-#!/usr/bin/python |
- |
-# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
-# Use of this source code is governed by a BSD-style license that can be |
-# found in the LICENSE file. |
- |
-"""Helper script for printing differences between tags.""" |
- |
-import cgi |
-from datetime import datetime |
-import operator |
-import optparse |
-import os |
-import re |
-import sys |
- |
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../lib')) |
-from cros_build_lib import RunCommand |
- |
- |
-# TODO(dianders): |
-# We use GData to access the tracker on code.google.com. Eventually, we |
-# want to create an ebuild and add the ebuild to hard-host-depends |
-# For now, we'll just include instructions for installing it. |
-INSTRS_FOR_GDATA = """ |
-To access the tracker you need the GData library. To install in your home dir: |
- |
- GDATA_INSTALL_DIR=~/gdatalib |
- mkdir -p "$GDATA_INSTALL_DIR" |
- |
- TMP_DIR=`mktemp -d` |
- pushd $TMP_DIR |
- wget http://gdata-python-client.googlecode.com/files/gdata-2.0.12.zip |
- unzip gdata-2.0.12.zip |
- cd gdata-2.0.12/ |
- python setup.py install --home="$GDATA_INSTALL_DIR" |
- popd |
- |
- export PYTHONPATH="$GDATA_INSTALL_DIR/lib/python:$PYTHONPATH" |
- |
-You should add the PYTHONPATH line to your .bashrc file (or equivalent).""" |
- |
- |
-DEFAULT_TRACKER = 'chromium-os' |
- |
- |
-def _GrabOutput(cmd): |
- """Returns output from specified command.""" |
- return RunCommand(cmd, shell=True, print_cmd=False, |
- redirect_stdout=True).output |
- |
- |
-def _GrabTags(): |
- """Returns list of tags from current git repository.""" |
- # TODO(dianders): replace this with the python equivalent. |
- cmd = ("git for-each-ref refs/tags | awk '{print $3}' | " |
- "sed 's,refs/tags/,,g' | sort -t. -k3,3rn -k4,4rn") |
- return _GrabOutput(cmd).split() |
- |
- |
-def _GrabDirs(): |
- """Returns list of directories managed by repo.""" |
- return _GrabOutput('repo forall -c "pwd"').split() |
- |
- |
-class Issue(object): |
- """Class for holding info about issues (aka bugs).""" |
- |
- def __init__(self, project_name, issue_id, tracker_acc): |
- """Constructor for Issue object. |
- |
- Args: |
- project_name: The tracker project to query. |
- issue_id: The ID of the issue to query |
- tracker_acc: A TrackerAccess object, or None. |
- """ |
- self.project_name = project_name |
- self.issue_id = issue_id |
- self.milestone = '' |
- self.priority = '' |
- |
- if tracker_acc is not None: |
- keyed_labels = tracker_acc.GetKeyedLabels(project_name, issue_id) |
- if 'Mstone' in keyed_labels: |
- self.milestone = keyed_labels['Mstone'] |
- if 'Pri' in keyed_labels: |
- self.priority = keyed_labels['Pri'] |
- |
- def GetUrl(self): |
- """Returns the URL to access the issue.""" |
- bug_url_fmt = 'http://code.google.com/p/%s/issues/detail?id=%s' |
- |
- # Get bug URL. We use short URLs to make the URLs a bit more readable. |
- if self.project_name == 'chromium-os': |
- bug_url = 'http://crosbug.com/%s' % self.issue_id |
- elif self.project_name == 'chrome-os-partner': |
- bug_url = 'http://crosbug.com/p/%s' % self.issue_id |
- else: |
- bug_url = bug_url_fmt % (self.project_name, self.issue_id) |
- |
- return bug_url |
- |
- def __str__(self): |
- """Provides a string representation of the issue. |
- |
- Returns: |
- A string that looks something like: |
- |
- project:id (milestone, priority) |
- """ |
- if self.milestone and self.priority: |
- info_str = ' (%s, P%s)' % (self.milestone, self.priority) |
- elif self.milestone: |
- info_str = ' (%s)' % self.milestone |
- elif self.priority: |
- info_str = ' (P%s)' % self.priority |
- else: |
- info_str = '' |
- |
- return '%s:%s%s' % (self.project_name, self.issue_id, info_str) |
- |
- def __cmp__(self, other): |
- """Compare two Issue objects.""" |
- return cmp((self.project_name.lower(), self.issue_id), |
- (other.project_name.lower(), other.issue_id)) |
- |
- |
-class Commit(object): |
- """Class for tracking git commits.""" |
- |
- def __init__(self, commit, projectname, commit_email, commit_date, subject, |
- body, tracker_acc): |
- """Create commit logs. |
- |
- Args: |
- commit: The commit hash (sha) from git. |
- projectname: The project name, from: |
- git config --get remote.cros.projectname |
- commit_email: The email address associated with the commit (%ce in git |
- log) |
- commit_date: The date of the commit, like "Mon Nov 1 17:34:14 2010 -0500" |
- (%cd in git log)) |
- subject: The subject of the commit (%s in git log) |
- body: The body of the commit (%b in git log) |
- tracker_acc: A tracker_access.TrackerAccess object. |
- """ |
- self.commit = commit |
- self.projectname = projectname |
- self.commit_email = commit_email |
- fmt = '%a %b %d %H:%M:%S %Y' |
- self.commit_date = datetime.strptime(commit_date, fmt) |
- self.subject = subject |
- self.body = body |
- self._tracker_acc = tracker_acc |
- self._issues = self._GetIssues() |
- |
- def _GetIssues(self): |
- """Get bug info from commit logs and issue tracker. |
- |
- This should be called as the last step of __init__, since it |
- assumes that our member variables are already setup. |
- |
- Returns: |
- A list of Issue objects, each of which holds info about a bug. |
- """ |
- # NOTE: most of this code is copied from bugdroid: |
- # <http://src.chromium.org/viewvc/chrome/trunk/tools/bugdroid/bugdroid.py?revision=59229&view=markup> |
- |
- # Get a list of bugs. Handle lots of possibilities: |
- # - Multiple "BUG=" lines, with varying amounts of whitespace. |
- # - For each BUG= line, bugs can be split by commas _or_ by whitespace (!) |
- entries = [] |
- for line in self.body.split('\n'): |
- match = re.match(r'^ *BUG *=(.*)', line) |
- if match: |
- for i in match.group(1).split(','): |
- entries.extend(filter(None, [x.strip() for x in i.split()])) |
- |
- # Try to parse the bugs. Handle lots of different formats: |
- # - The whole URL, from which we parse the project and bug. |
- # - A simple string that looks like "project:bug" |
- # - A string that looks like "bug", which will always refer to the previous |
- # tracker referenced (defaulting to the default tracker). |
- # |
- # We will create an "Issue" object for each bug. |
- issues = [] |
- last_tracker = DEFAULT_TRACKER |
- regex = (r'http://code.google.com/p/(\S+)/issues/detail\?id=([0-9]+)' |
- r'|(\S+):([0-9]+)|(\b[0-9]+\b)') |
- |
- for new_item in entries: |
- bug_numbers = re.findall(regex, new_item) |
- for bug_tuple in bug_numbers: |
- if bug_tuple[0] and bug_tuple[1]: |
- issues.append(Issue(bug_tuple[0], bug_tuple[1], self._tracker_acc)) |
- last_tracker = bug_tuple[0] |
- elif bug_tuple[2] and bug_tuple[3]: |
- issues.append(Issue(bug_tuple[2], bug_tuple[3], self._tracker_acc)) |
- last_tracker = bug_tuple[2] |
- elif bug_tuple[4]: |
- issues.append(Issue(last_tracker, bug_tuple[4], self._tracker_acc)) |
- |
- # Sort the issues and return... |
- issues.sort() |
- return issues |
- |
- def AsHTMLTableRow(self): |
- """Returns HTML for this change, for printing as part of a table. |
- |
- Columns: Project, Date, Commit, Committer, Bugs, Subject. |
- |
- Returns: |
- A string usable as an HTML table row, like: |
- |
- <tr><td>Blah</td><td>Blah blah</td></tr> |
- """ |
- |
- bugs = [] |
- link_fmt = '<a href="%s">%s</a>' |
- for issue in self._issues: |
- bugs.append(link_fmt % (issue.GetUrl(), str(issue))) |
- |
- url_fmt = 'http://chromiumos-git/git/?p=%s.git;a=commitdiff;h=%s' |
- url = url_fmt % (self.projectname, self.commit) |
- commit_desc = link_fmt % (url, self.commit[:8]) |
- bug_str = '<br>'.join(bugs) |
- if not bug_str: |
- if (self.projectname == 'kernel-next' or |
- self.commit_email == 'chrome-bot@chromium.org'): |
- bug_str = 'not needed' |
- else: |
- bug_str = '<font color="red">none</font>' |
- |
- cols = [ |
- cgi.escape(self.projectname), |
- str(self.commit_date), |
- commit_desc, |
- cgi.escape(self.commit_email), |
- bug_str, |
- cgi.escape(self.subject[:100]), |
- ] |
- return '<tr><td>%s</td></tr>' % ('</td><td>'.join(cols)) |
- |
- def __cmp__(self, other): |
- """Compare two Commit objects first by project name, then by date.""" |
- return (cmp(self.projectname, other.projectname) or |
- cmp(self.commit_date, other.commit_date)) |
- |
- |
-def _GrabChanges(path, tag1, tag2, tracker_acc): |
- """Return list of commits to path between tag1 and tag2. |
- |
- Args: |
- path: One of the directories managed by repo. |
- tag1: The first of the two tags to pass to git log. |
- tag2: The second of the two tags to pass to git log. |
- tracker_acc: A tracker_access.TrackerAccess object. |
- |
- Returns: |
- A list of "Commit" objects. |
- """ |
- |
- cmd = 'cd %s && git config --get remote.cros.projectname' % path |
- projectname = _GrabOutput(cmd).strip() |
- log_fmt = '%x00%H\t%ce\t%cd\t%s\t%b' |
- cmd_fmt = 'cd %s && git log --format="%s" --date=local "%s..%s"' |
- cmd = cmd_fmt % (path, log_fmt, tag1, tag2) |
- output = _GrabOutput(cmd) |
- commits = [] |
- for log_data in output.split('\0')[1:]: |
- commit, commit_email, commit_date, subject, body = log_data.split('\t', 4) |
- change = Commit(commit, projectname, commit_email, commit_date, subject, |
- body, tracker_acc) |
- commits.append(change) |
- return commits |
- |
- |
-def _ParseArgs(): |
- """Parse command-line arguments. |
- |
- Returns: |
- An optparse.OptionParser object. |
- """ |
- parser = optparse.OptionParser() |
- parser.add_option( |
- '--sort-by-date', dest='sort_by_date', default=False, |
- action='store_true', help='Sort commits by date.') |
- parser.add_option( |
- '--tracker-user', dest='tracker_user', default=None, |
- help='Specify a username to login to code.google.com.') |
- parser.add_option( |
- '--tracker-pass', dest='tracker_pass', default=None, |
- help='Specify a password to go w/ user.') |
- parser.add_option( |
- '--tracker-passfile', dest='tracker_passfile', default=None, |
- help='Specify a file containing a password to go w/ user.') |
- return parser.parse_args() |
- |
- |
-def main(): |
- tags = _GrabTags() |
- tag1 = None |
- options, args = _ParseArgs() |
- if len(args) == 2: |
- tag1, tag2 = args |
- elif len(args) == 1: |
- tag2, = args |
- if tag2 in tags: |
- tag2_index = tags.index(tag2) |
- if tag2_index == len(tags) - 1: |
- print >>sys.stderr, 'No previous tag for %s' % tag2 |
- sys.exit(1) |
- tag1 = tags[tag2_index + 1] |
- else: |
- print >>sys.stderr, 'Unrecognized tag: %s' % tag2 |
- sys.exit(1) |
- else: |
- print >>sys.stderr, 'Usage: %s [tag1] tag2' % sys.argv[0] |
- print >>sys.stderr, 'If only one tag is specified, we view the differences' |
- print >>sys.stderr, 'between that tag and the previous tag. You can also' |
- print >>sys.stderr, 'specify cros/master to show differences with' |
- print >>sys.stderr, 'tip-of-tree.' |
- print >>sys.stderr, 'E.g. %s %s cros/master' % (sys.argv[0], tags[0]) |
- sys.exit(1) |
- |
- if options.tracker_user is not None: |
- # TODO(dianders): Once we install GData automatically, move the import |
- # to the top of the file where it belongs. It's only here to allow |
- # people to run the script without GData. |
- try: |
- import tracker_access |
- except ImportError: |
- print >>sys.stderr, INSTRS_FOR_GDATA |
- sys.exit(1) |
- if options.tracker_passfile is not None: |
- options.tracker_pass = open(options.tracker_passfile, 'r').read().strip() |
- tracker_acc = tracker_access.TrackerAccess(options.tracker_user, |
- options.tracker_pass) |
- else: |
- tracker_acc = None |
- |
- print >>sys.stderr, 'Finding differences between %s and %s' % (tag1, tag2) |
- paths = _GrabDirs() |
- changes = [] |
- for path in paths: |
- changes.extend(_GrabChanges(path, tag1, tag2, tracker_acc)) |
- |
- title = 'Changelog for %s to %s' % (tag1, tag2) |
- print '<html>' |
- print '<head><title>%s</title></head>' % title |
- print '<h1>%s</h1>' % title |
- cols = ['Project', 'Date', 'Commit', 'Committer', 'Bugs', 'Subject'] |
- print '<table border="1" cellpadding="4">' |
- print '<tr><th>%s</th>' % ('</th><th>'.join(cols)) |
- if options.sort_by_date: |
- changes.sort(key=operator.attrgetter('commit_date')) |
- else: |
- changes.sort() |
- for change in changes: |
- print change.AsHTMLTableRow() |
- print '</table>' |
- print '</html>' |
- |
- |
-if __name__ == '__main__': |
- main() |