| 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()
|
|
|