Merge branch 'master' of git://git.denx.de/u-boot-x86

This commit is contained in:
Tom Rini 2013-02-04 17:50:11 -05:00
commit 6e787b7234
9 changed files with 389 additions and 29 deletions

View File

@ -43,6 +43,9 @@ Series-to: fred.blogs@napier.co.nz
in one of your commits, the series will be sent there. in one of your commits, the series will be sent there.
In Linux this will also call get_maintainer.pl on each of your
patches automatically.
How to use this tool How to use this tool
==================== ====================
@ -65,8 +68,12 @@ will get a consistent result each time.
How to configure it How to configure it
=================== ===================
For most cases patman will locate and use the file 'doc/git-mailrc' in For most cases of using patman for U-Boot developement patman will
your U-Boot directory. This contains most of the aliases you will need. locate and use the file 'doc/git-mailrc' in your U-Boot directory.
This contains most of the aliases you will need.
For Linux the 'scripts/get_maintainer.pl' handles figuring out where
to send patches pretty well.
During the first run patman creates a config file for you by taking the default During the first run patman creates a config file for you by taking the default
user name and email address from the global .gitconfig file. user name and email address from the global .gitconfig file.
@ -91,6 +98,35 @@ The checkpatch.pl in the U-Boot tools/ subdirectory will be located and
used. Failing that you can put it into your path or ~/bin/checkpatch.pl used. Failing that you can put it into your path or ~/bin/checkpatch.pl
If you want to change the defaults for patman's command-line arguments,
you can add a [settings] section to your .patman file. This can be used
for any command line option by referring to the "dest" for the option in
patman.py. For reference, the useful ones (at the moment) shown below
(all with the non-default setting):
>>>
[settings]
ignore_errors: True
process_tags: False
verbose: True
<<<
If you want to adjust settings (or aliases) that affect just a single
project you can add a section that looks like [project_settings] or
[project_alias]. If you want to use tags for your linux work, you could
do:
>>>
[linux_settings]
process_tags: True
<<<
How to run it How to run it
============= =============
@ -226,6 +262,9 @@ Date: Mon Nov 7 23:18:44 2011 -0500
will create a patch which is copied to x86, arm, sandbox, mikef, ag and will create a patch which is copied to x86, arm, sandbox, mikef, ag and
afleming. afleming.
If you have a cover letter it will get sent to the union of the CC lists of
all of the other patches.
Example Work Flow Example Work Flow
================= =================

View File

@ -23,13 +23,16 @@ import command
import gitutil import gitutil
import os import os
import re import re
import sys
import terminal import terminal
def FindCheckPatch(): def FindCheckPatch():
top_level = gitutil.GetTopLevel()
try_list = [ try_list = [
os.getcwd(), os.getcwd(),
os.path.join(os.getcwd(), '..', '..'), os.path.join(os.getcwd(), '..', '..'),
os.path.join(gitutil.GetTopLevel(), 'tools'), os.path.join(top_level, 'tools'),
os.path.join(top_level, 'scripts'),
'%s/bin' % os.getenv('HOME'), '%s/bin' % os.getenv('HOME'),
] ]
# Look in current dir # Look in current dir
@ -45,8 +48,10 @@ def FindCheckPatch():
if os.path.isfile(fname): if os.path.isfile(fname):
return fname return fname
path = os.path.dirname(path) path = os.path.dirname(path)
print 'Could not find checkpatch.pl'
return None print >> sys.stderr, ('Cannot find checkpatch.pl - please put it in your ' +
'~/bin directory or use --no-check')
sys.exit(1)
def CheckPatch(fname, verbose=False): def CheckPatch(fname, verbose=False):
"""Run checkpatch.pl on a file. """Run checkpatch.pl on a file.
@ -65,9 +70,6 @@ def CheckPatch(fname, verbose=False):
error_count, warning_count, lines = 0, 0, 0 error_count, warning_count, lines = 0, 0, 0
problems = [] problems = []
chk = FindCheckPatch() chk = FindCheckPatch()
if not chk:
raise OSError, ('Cannot find checkpatch.pl - please put it in your ' +
'~/bin directory')
item = {} item = {}
stdout = command.Output(chk, '--no-tree', fname) stdout = command.Output(chk, '--no-tree', fname)
#pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) #pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE)

View File

@ -0,0 +1,63 @@
# Copyright (c) 2012 The Chromium OS Authors.
#
# See file CREDITS for list of people who contributed to this
# project.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307 USA
#
import command
import gitutil
import os
def FindGetMaintainer():
"""Look for the get_maintainer.pl script.
Returns:
If the script is found we'll return a path to it; else None.
"""
try_list = [
os.path.join(gitutil.GetTopLevel(), 'scripts'),
]
# Look in the list
for path in try_list:
fname = os.path.join(path, 'get_maintainer.pl')
if os.path.isfile(fname):
return fname
return None
def GetMaintainer(fname, verbose=False):
"""Run get_maintainer.pl on a file if we find it.
We look for get_maintainer.pl in the 'scripts' directory at the top of
git. If we find it we'll run it. If we don't find get_maintainer.pl
then we fail silently.
Args:
fname: Path to the patch file to run get_maintainer.pl on.
Returns:
A list of email addresses to CC to.
"""
get_maintainer = FindGetMaintainer()
if not get_maintainer:
if verbose:
print "WARNING: Couldn't find get_maintainer.pl"
return []
stdout = command.Output(get_maintainer, '--norolestats', fname)
return stdout.splitlines()

View File

@ -217,6 +217,10 @@ def EmailPatches(series, cover_fname, args, dry_run, cc_fname,
Returns: Returns:
Git command that was/would be run Git command that was/would be run
# For the duration of this doctest pretend that we ran patman with ./patman
>>> _old_argv0 = sys.argv[0]
>>> sys.argv[0] = './patman'
>>> alias = {} >>> alias = {}
>>> alias['fred'] = ['f.bloggs@napier.co.nz'] >>> alias['fred'] = ['f.bloggs@napier.co.nz']
>>> alias['john'] = ['j.bloggs@napier.co.nz'] >>> alias['john'] = ['j.bloggs@napier.co.nz']
@ -244,6 +248,9 @@ def EmailPatches(series, cover_fname, args, dry_run, cc_fname,
'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \ "f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \
"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' "m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2'
# Restore argv[0] since we clobbered it.
>>> sys.argv[0] = _old_argv0
""" """
to = BuildEmailList(series.get('to'), '--to', alias) to = BuildEmailList(series.get('to'), '--to', alias)
if not to: if not to:
@ -340,8 +347,8 @@ def GetTopLevel():
This test makes sure that we are running tests in the right subdir This test makes sure that we are running tests in the right subdir
>>> os.path.realpath(os.getcwd()) == \ >>> os.path.realpath(os.path.dirname(__file__)) == \
os.path.join(GetTopLevel(), 'tools', 'scripts', 'patman') os.path.join(GetTopLevel(), 'tools', 'patman')
True True
""" """
return command.OutputOneLine('git', 'rev-parse', '--show-toplevel') return command.OutputOneLine('git', 'rev-parse', '--show-toplevel')
@ -377,8 +384,6 @@ def GetDefaultUserEmail():
def Setup(): def Setup():
"""Set up git utils, by reading the alias files.""" """Set up git utils, by reading the alias files."""
settings.Setup('')
# Check for a git alias file also # Check for a git alias file also
alias_fname = GetAliasFile() alias_fname = GetAliasFile()
if alias_fname: if alias_fname:

View File

@ -34,6 +34,8 @@ import checkpatch
import command import command
import gitutil import gitutil
import patchstream import patchstream
import project
import settings
import terminal import terminal
import test import test
@ -48,6 +50,9 @@ parser.add_option('-i', '--ignore-errors', action='store_true',
help='Send patches email even if patch errors are found') help='Send patches email even if patch errors are found')
parser.add_option('-n', '--dry-run', action='store_true', dest='dry_run', parser.add_option('-n', '--dry-run', action='store_true', dest='dry_run',
default=False, help="Do a try run (create but don't email patches)") default=False, help="Do a try run (create but don't email patches)")
parser.add_option('-p', '--project', default=project.DetectProject(),
help="Project name; affects default option values and "
"aliases [default: %default]")
parser.add_option('-s', '--start', dest='start', type='int', parser.add_option('-s', '--start', dest='start', type='int',
default=0, help='Commit to start creating patches from (0 = HEAD)') default=0, help='Commit to start creating patches from (0 = HEAD)')
parser.add_option('-t', '--test', action='store_true', dest='test', parser.add_option('-t', '--test', action='store_true', dest='test',
@ -56,6 +61,9 @@ parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
default=False, help='Verbose output of errors and warnings') default=False, help='Verbose output of errors and warnings')
parser.add_option('--cc-cmd', dest='cc_cmd', type='string', action='store', parser.add_option('--cc-cmd', dest='cc_cmd', type='string', action='store',
default=None, help='Output cc list for patch file (used by git)') default=None, help='Output cc list for patch file (used by git)')
parser.add_option('--no-check', action='store_false', dest='check_patch',
default=True,
help="Don't check for patch compliance")
parser.add_option('--no-tags', action='store_false', dest='process_tags', parser.add_option('--no-tags', action='store_false', dest='process_tags',
default=True, help="Don't process subject tags as aliaes") default=True, help="Don't process subject tags as aliaes")
@ -64,6 +72,11 @@ parser.usage = """patman [options]
Create patches from commits in a branch, check them and email them as Create patches from commits in a branch, check them and email them as
specified by tags you place in the commits. Use -n to """ specified by tags you place in the commits. Use -n to """
# Parse options twice: first to get the project and second to handle
# defaults properly (which depends on project).
(options, args) = parser.parse_args()
settings.Setup(parser, options.project, '')
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
# Run our meagre tests # Run our meagre tests
@ -75,7 +88,8 @@ if options.test:
result = unittest.TestResult() result = unittest.TestResult()
suite.run(result) suite.run(result)
suite = doctest.DocTestSuite('gitutil') for module in ['gitutil', 'settings']:
suite = doctest.DocTestSuite(module)
suite.run(result) suite.run(result)
# TODO: Surely we can just 'print' result? # TODO: Surely we can just 'print' result?
@ -135,19 +149,24 @@ else:
series.DoChecks() series.DoChecks()
# Check the patches, and run them through 'git am' just to be sure # Check the patches, and run them through 'git am' just to be sure
if options.check_patch:
ok = checkpatch.CheckPatches(options.verbose, args) ok = checkpatch.CheckPatches(options.verbose, args)
else:
ok = True
if not gitutil.ApplyPatches(options.verbose, args, if not gitutil.ApplyPatches(options.verbose, args,
options.count + options.start): options.count + options.start):
ok = False ok = False
cc_file = series.MakeCcFile(options.process_tags, cover_fname)
# Email the patches out (giving the user time to check / cancel) # Email the patches out (giving the user time to check / cancel)
cmd = '' cmd = ''
if ok or options.ignore_errors: if ok or options.ignore_errors:
cc_file = series.MakeCcFile(options.process_tags)
cmd = gitutil.EmailPatches(series, cover_fname, args, cmd = gitutil.EmailPatches(series, cover_fname, args,
options.dry_run, cc_file) options.dry_run, cc_file)
os.remove(cc_file)
# For a dry run, just show our actions as a sanity check # For a dry run, just show our actions as a sanity check
if options.dry_run: if options.dry_run:
series.ShowActions(args, cmd, options.process_tags) series.ShowActions(args, cmd, options.process_tags)
os.remove(cc_file)

43
tools/patman/project.py Normal file
View File

@ -0,0 +1,43 @@
# Copyright (c) 2012 The Chromium OS Authors.
#
# See file CREDITS for list of people who contributed to this
# project.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307 USA
#
import os.path
import gitutil
def DetectProject():
"""Autodetect the name of the current project.
This looks for signature files/directories that are unlikely to exist except
in the given project.
Returns:
The name of the project, like "linux" or "u-boot". Returns "unknown"
if we can't detect the project.
"""
top_level = gitutil.GetTopLevel()
if os.path.exists(os.path.join(top_level, "include", "u-boot")):
return "u-boot"
elif os.path.exists(os.path.join(top_level, "kernel")):
return "linux"
return "unknown"

View File

@ -19,8 +19,10 @@
# MA 02111-1307 USA # MA 02111-1307 USA
# #
import itertools
import os import os
import get_maintainer
import gitutil import gitutil
import terminal import terminal
@ -46,6 +48,11 @@ class Series(dict):
self.notes = [] self.notes = []
self.changes = {} self.changes = {}
# Written in MakeCcFile()
# key: name of patch file
# value: list of email addresses
self._generated_cc = {}
# These make us more like a dictionary # These make us more like a dictionary
def __setattr__(self, name, value): def __setattr__(self, name, value):
self[name] = value self[name] = value
@ -109,10 +116,7 @@ class Series(dict):
for upto in range(len(args)): for upto in range(len(args)):
commit = self.commits[upto] commit = self.commits[upto]
print col.Color(col.GREEN, ' %s' % args[upto]) print col.Color(col.GREEN, ' %s' % args[upto])
cc_list = [] cc_list = list(self._generated_cc[commit.patch])
if process_tags:
cc_list += gitutil.BuildEmailList(commit.tags)
cc_list += gitutil.BuildEmailList(commit.cc_list)
# Skip items in To list # Skip items in To list
if 'to' in self: if 'to' in self:
@ -136,6 +140,9 @@ class Series(dict):
print 'Prefix:\t ', self.get('prefix') print 'Prefix:\t ', self.get('prefix')
if self.cover: if self.cover:
print 'Cover: %d lines' % len(self.cover) print 'Cover: %d lines' % len(self.cover)
all_ccs = itertools.chain(*self._generated_cc.values())
for email in set(all_ccs):
print ' Cc: ',email
if cmd: if cmd:
print 'Git command: %s' % cmd print 'Git command: %s' % cmd
@ -199,23 +206,33 @@ class Series(dict):
str = 'Change log exists, but no version is set' str = 'Change log exists, but no version is set'
print col.Color(col.RED, str) print col.Color(col.RED, str)
def MakeCcFile(self, process_tags): def MakeCcFile(self, process_tags, cover_fname):
"""Make a cc file for us to use for per-commit Cc automation """Make a cc file for us to use for per-commit Cc automation
Also stores in self._generated_cc to make ShowActions() faster.
Args: Args:
process_tags: Process tags as if they were aliases process_tags: Process tags as if they were aliases
cover_fname: If non-None the name of the cover letter.
Return: Return:
Filename of temp file created Filename of temp file created
""" """
# Look for commit tags (of the form 'xxx:' at the start of the subject) # Look for commit tags (of the form 'xxx:' at the start of the subject)
fname = '/tmp/patman.%d' % os.getpid() fname = '/tmp/patman.%d' % os.getpid()
fd = open(fname, 'w') fd = open(fname, 'w')
all_ccs = []
for commit in self.commits: for commit in self.commits:
list = [] list = []
if process_tags: if process_tags:
list += gitutil.BuildEmailList(commit.tags) list += gitutil.BuildEmailList(commit.tags)
list += gitutil.BuildEmailList(commit.cc_list) list += gitutil.BuildEmailList(commit.cc_list)
list += get_maintainer.GetMaintainer(commit.patch)
all_ccs += list
print >>fd, commit.patch, ', '.join(list) print >>fd, commit.patch, ', '.join(list)
self._generated_cc[commit.patch] = list
if cover_fname:
print >>fd, cover_fname, ', '.join(set(all_ccs))
fd.close() fd.close()
return fname return fname

View File

@ -26,6 +26,140 @@ import re
import command import command
import gitutil import gitutil
"""Default settings per-project.
These are used by _ProjectConfigParser. Settings names should match
the "dest" of the option parser from patman.py.
"""
_default_settings = {
"u-boot": {},
"linux": {
"process_tags": "False",
}
}
class _ProjectConfigParser(ConfigParser.SafeConfigParser):
"""ConfigParser that handles projects.
There are two main goals of this class:
- Load project-specific default settings.
- Merge general default settings/aliases with project-specific ones.
# Sample config used for tests below...
>>> import StringIO
>>> sample_config = '''
... [alias]
... me: Peter P. <likesspiders@example.com>
... enemies: Evil <evil@example.com>
...
... [sm_alias]
... enemies: Green G. <ugly@example.com>
...
... [sm2_alias]
... enemies: Doc O. <pus@example.com>
...
... [settings]
... am_hero: True
... '''
# Check to make sure that bogus project gets general alias.
>>> config = _ProjectConfigParser("zzz")
>>> config.readfp(StringIO.StringIO(sample_config))
>>> config.get("alias", "enemies")
'Evil <evil@example.com>'
# Check to make sure that alias gets overridden by project.
>>> config = _ProjectConfigParser("sm")
>>> config.readfp(StringIO.StringIO(sample_config))
>>> config.get("alias", "enemies")
'Green G. <ugly@example.com>'
# Check to make sure that settings get merged with project.
>>> config = _ProjectConfigParser("linux")
>>> config.readfp(StringIO.StringIO(sample_config))
>>> sorted(config.items("settings"))
[('am_hero', 'True'), ('process_tags', 'False')]
# Check to make sure that settings works with unknown project.
>>> config = _ProjectConfigParser("unknown")
>>> config.readfp(StringIO.StringIO(sample_config))
>>> sorted(config.items("settings"))
[('am_hero', 'True')]
"""
def __init__(self, project_name):
"""Construct _ProjectConfigParser.
In addition to standard SafeConfigParser initialization, this also loads
project defaults.
Args:
project_name: The name of the project.
"""
self._project_name = project_name
ConfigParser.SafeConfigParser.__init__(self)
# Update the project settings in the config based on
# the _default_settings global.
project_settings = "%s_settings" % project_name
if not self.has_section(project_settings):
self.add_section(project_settings)
project_defaults = _default_settings.get(project_name, {})
for setting_name, setting_value in project_defaults.iteritems():
self.set(project_settings, setting_name, setting_value)
def get(self, section, option, *args, **kwargs):
"""Extend SafeConfigParser to try project_section before section.
Args:
See SafeConfigParser.
Returns:
See SafeConfigParser.
"""
try:
return ConfigParser.SafeConfigParser.get(
self, "%s_%s" % (self._project_name, section), option,
*args, **kwargs
)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
return ConfigParser.SafeConfigParser.get(
self, section, option, *args, **kwargs
)
def items(self, section, *args, **kwargs):
"""Extend SafeConfigParser to add project_section to section.
Args:
See SafeConfigParser.
Returns:
See SafeConfigParser.
"""
project_items = []
has_project_section = False
top_items = []
# Get items from the project section
try:
project_items = ConfigParser.SafeConfigParser.items(
self, "%s_%s" % (self._project_name, section), *args, **kwargs
)
has_project_section = True
except ConfigParser.NoSectionError:
pass
# Get top-level items
try:
top_items = ConfigParser.SafeConfigParser.items(
self, section, *args, **kwargs
)
except ConfigParser.NoSectionError:
# If neither section exists raise the error on...
if not has_project_section:
raise
item_dict = dict(top_items)
item_dict.update(project_items)
return item_dict.items()
def ReadGitAliases(fname): def ReadGitAliases(fname):
"""Read a git alias file. This is in the form used by git: """Read a git alias file. This is in the form used by git:
@ -88,13 +222,45 @@ def CreatePatmanConfigFile(config_fname):
print >>f, "[alias]\nme: %s <%s>" % (name, email) print >>f, "[alias]\nme: %s <%s>" % (name, email)
f.close(); f.close();
def Setup(config_fname=''): def _UpdateDefaults(parser, config):
"""Update the given OptionParser defaults based on config.
We'll walk through all of the settings from the parser
For each setting we'll look for a default in the option parser.
If it's found we'll update the option parser default.
The idea here is that the .patman file should be able to update
defaults but that command line flags should still have the final
say.
Args:
parser: An instance of an OptionParser whose defaults will be
updated.
config: An instance of _ProjectConfigParser that we will query
for settings.
"""
defaults = parser.get_default_values()
for name, val in config.items('settings'):
if hasattr(defaults, name):
default_val = getattr(defaults, name)
if isinstance(default_val, bool):
val = config.getboolean('settings', name)
elif isinstance(default_val, int):
val = config.getint('settings', name)
parser.set_default(name, val)
else:
print "WARNING: Unknown setting %s" % name
def Setup(parser, project_name, config_fname=''):
"""Set up the settings module by reading config files. """Set up the settings module by reading config files.
Args: Args:
parser: The parser to update
project_name: Name of project that we're working on; we'll look
for sections named "project_section" as well.
config_fname: Config filename to read ('' for default) config_fname: Config filename to read ('' for default)
""" """
settings = ConfigParser.SafeConfigParser() config = _ProjectConfigParser(project_name)
if config_fname == '': if config_fname == '':
config_fname = '%s/.patman' % os.getenv('HOME') config_fname = '%s/.patman' % os.getenv('HOME')
@ -102,11 +268,17 @@ def Setup(config_fname=''):
print "No config file found ~/.patman\nCreating one...\n" print "No config file found ~/.patman\nCreating one...\n"
CreatePatmanConfigFile(config_fname) CreatePatmanConfigFile(config_fname)
settings.read(config_fname) config.read(config_fname)
for name, value in settings.items('alias'): for name, value in config.items('alias'):
alias[name] = value.split(',') alias[name] = value.split(',')
_UpdateDefaults(parser, config)
# These are the aliases we understand, indexed by alias. Each member is a list. # These are the aliases we understand, indexed by alias. Each member is a list.
alias = {} alias = {}
if __name__ == "__main__":
import doctest
doctest.testmod()