Goes further down the rabbit hole.

Enable automatic command and one-liner doc. Reformat pydoc accordingly.
Add parser as an argument and parse_args hook in preparation to move parse_args at the right place, inside the CMDxx functions.
Update unit tests

Review URL: http://codereview.chromium.org/2129005

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@47449 0039d316-1c4b-4281-b951-d872f2087c98
experimental/szager/collated-output
maruel@chromium.org 15 years ago
parent a761b60c33
commit ddff62df37

@ -55,7 +55,7 @@ Hooks
] ]
""" """
__version__ = "0.3.5" __version__ = "0.3.7"
import errno import errno
import logging import logging
@ -75,23 +75,10 @@ from third_party.repo.progress import Progress
# default help text # default help text
DEFAULT_USAGE_TEXT = ( DEFAULT_USAGE_TEXT = (
"""usage: %prog <subcommand> [options] [--] [SCM options/args...] """%prog <subcommand> [options] [--] [SCM options/args...]
a wrapper for managing a set of svn client modules and/or git repositories. a wrapper for managing a set of svn client modules and/or git repositories.
Version """ + __version__ + """ Version """ + __version__ + """
subcommands:
cleanup
config
diff
export
pack
revert
status
sync
update
runhooks
revinfo
Options and extra arguments can be passed to invoked SCM commands by Options and extra arguments can be passed to invoked SCM commands by
appending them to the command line. Note that if the first such appending them to the command line. Note that if the first such
appended option starts with a dash (-) then the options must be appended option starts with a dash (-) then the options must be
@ -829,8 +816,9 @@ solutions = [
## gclient commands. ## gclient commands.
def CMDcleanup(options, args): def CMDcleanup(parser, options, args):
"""Clean up all working copies, using 'svn cleanup' for each module. """Clean up all working copies, using 'svn cleanup' for each module.
Additional options and args may be passed to 'svn cleanup'. Additional options and args may be passed to 'svn cleanup'.
usage: cleanup [options] [--] [svn cleanup args/options] usage: cleanup [options] [--] [svn cleanup args/options]
@ -848,9 +836,10 @@ Valid options:
return client.RunOnDeps('cleanup', args) return client.RunOnDeps('cleanup', args)
def CMDconfig(options, args): def CMDconfig(parser, options, args):
"""Create a .gclient file in the current directory; this """Create a .gclient file in the current directory.
specifies the configuration for further commands. After update/sync,
This specifies the configuration for further commands. After update/sync,
top-level DEPS files in each module are read to determine dependent top-level DEPS files in each module are read to determine dependent
modules to operate on as well. If optional [url] parameter is modules to operate on as well. If optional [url] parameter is
provided, then configuration is read from a specified Subversion server provided, then configuration is read from a specified Subversion server
@ -897,9 +886,8 @@ Examples:
return 0 return 0
def CMDexport(options, args): def CMDexport(parser, options, args):
"""Wrapper for svn export for all managed directories """Wrapper for svn export for all managed directories."""
"""
if len(args) != 1: if len(args) != 1:
raise gclient_utils.Error("Need directory name") raise gclient_utils.Error("Need directory name")
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
@ -914,27 +902,9 @@ def CMDexport(options, args):
return client.RunOnDeps('export', args) return client.RunOnDeps('export', args)
def CMDhelp(options, args): def CMDpack(parser, options, args):
"""Describe the usage of this program or its subcommands.
usage: help [options] [subcommand]
Valid options:
--verbose : output additional diagnostics
"""
__pychecker__ = 'unusednames=options'
module = sys.modules[__name__]
commands = [x[3:] for x in dir(module) if x.startswith('CMD')]
if len(args) == 1 and args[0] in commands:
print getattr(module, 'CMD' + args[0]).__doc__
else:
raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
args[0])
return 0
def CMDpack(options, args):
"""Generate a patch which can be applied at the root of the tree. """Generate a patch which can be applied at the root of the tree.
Internally, runs 'svn diff' on each checked out module and Internally, runs 'svn diff' on each checked out module and
dependencies, and performs minimal postprocessing of the output. The dependencies, and performs minimal postprocessing of the output. The
resulting patch is printed to stdout and can be applied to a freshly resulting patch is printed to stdout and can be applied to a freshly
@ -966,9 +936,10 @@ Examples:
return client.RunOnDeps('pack', args) return client.RunOnDeps('pack', args)
def CMDstatus(options, args): def CMDstatus(parser, options, args):
"""Show the status of client and dependent modules, using 'svn diff' """Show the modification status of for every dependencies.
for each module. Additional options and args may be passed to 'svn diff'.
Additional options and args may be passed to 'svn status'.
usage: status [options] [--] [svn diff args/options] usage: status [options] [--] [svn diff args/options]
@ -986,17 +957,17 @@ Valid options:
return client.RunOnDeps('status', args) return client.RunOnDeps('status', args)
def CMDsync(options, args): def CMDsync(parser, options, args):
"""Perform a checkout/update of the modules specified by the gclient """Checkout/update the modules specified by the gclient configuration.
configuration; see 'help config'. Unless --revision is specified,
then the latest revision of the root solutions is checked out, with Unless --revision is specified, then the latest revision of the root solutions
dependent submodule versions updated according to DEPS files. is checked out, with dependent submodule versions updated according to DEPS
If --revision is specified, then the given revision is used in place files. If --revision is specified, then the given revision is used in place
of the latest, either for a single solution or for all solutions. of the latest, either for a single solution or for all solutions.
Unless the --force option is provided, solutions and modules whose Unless the --force option is provided, solutions and modules whose
local revision matches the one to update (i.e., they have not changed local revision matches the one to update (i.e., they have not changed
in the repository) are *not* modified. Unless --nohooks is provided, in the repository) are *not* modified. Unless --nohooks is provided,
the hooks are run. the hooks are run. See 'help config' for more information.
usage: gclient sync [options] [--] [SCM update options/args] usage: gclient sync [options] [--] [SCM update options/args]
@ -1052,14 +1023,14 @@ Examples:
return client.RunOnDeps('update', args) return client.RunOnDeps('update', args)
def CMDupdate(options, args): def CMDupdate(parser, options, args):
"""Alias for the sync command. Deprecated. """Alias for the sync command. Deprecated."""
""" return CMDsync(parser, options, args)
return CMDsync(options, args)
def CMDdiff(options, args): def CMDdiff(parser, options, args):
"""Display the differences between two revisions of modules. """Display the differences between two revisions of modules.
(Does 'svn diff' for each checked out module and dependences.) (Does 'svn diff' for each checked out module and dependences.)
Additional args and options to 'svn diff' can be passed after Additional args and options to 'svn diff' can be passed after
gclient options. gclient options.
@ -1087,18 +1058,18 @@ Examples:
return client.RunOnDeps('diff', args) return client.RunOnDeps('diff', args)
def CMDrevert(options, args): def CMDrevert(parser, options, args):
"""Revert every file in every managed directory in the client view. """Revert every file in every managed directory in the client view."""
"""
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
raise gclient_utils.Error("client not configured; see 'gclient config'") raise gclient_utils.Error("client not configured; see 'gclient config'")
return client.RunOnDeps('revert', args) return client.RunOnDeps('revert', args)
def CMDrunhooks(options, args): def CMDrunhooks(parser, options, args):
"""Runs hooks for files that have been modified in the local working copy, """Runs hooks for files that have been modified in the local working copy.
according to 'svn status'. Implies --force.
Implies --force.
usage: runhooks [options] usage: runhooks [options]
@ -1116,8 +1087,10 @@ Valid options:
return client.RunOnDeps('runhooks', args) return client.RunOnDeps('runhooks', args)
def CMDrevinfo(options, args): def CMDrevinfo(parser, options, args):
"""Outputs source path, server URL and revision information for every """Outputs defails for every dependencies.
This includes source path, server URL and revision information for every
dependency in all solutions. dependency in all solutions.
usage: revinfo [options] usage: revinfo [options]
@ -1130,102 +1103,102 @@ usage: revinfo [options]
return 0 return 0
def DispatchCommand(command, options, args): def CMDhelp(parser, options, args):
"""Dispatches the appropriate subcommand based on command line arguments. """Prints general help or command-specific documentation."""
""" if len(args) == 1:
module = sys.modules[__name__] command = Command(args[0])
command = getattr(module, 'CMD' + command, None)
if command: if command:
return command(options, args) print getattr(sys.modules[__name__], 'CMD' + args[0]).__doc__
else: return 0
raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" % parser.usage = (DEFAULT_USAGE_TEXT + '\nCommands are:\n' + '\n'.join([
command) ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
parser.print_help()
return 0
def Command(command):
return getattr(sys.modules[__name__], 'CMD' + command, CMDhelp)
def Main(argv): def Main(argv):
option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT, parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
version=__version__) version='%prog ' + __version__)
option_parser.add_option("--force", action="store_true", parser.add_option("-v", "--verbose", action="count", default=0,
help="Produces additional output for diagnostics. Can be "
"used up to three times for more logging info.")
parser.add_option("--gclientfile", metavar="FILENAME", dest="config_filename",
default=os.environ.get("GCLIENT_FILE", ".gclient"),
help="Specify an alternate .gclient file")
# The other options will be moved eventually.
parser.add_option("--force", action="store_true",
help="(update/sync only) force update even " help="(update/sync only) force update even "
"for modules which haven't changed") "for modules which haven't changed")
option_parser.add_option("--nohooks", action="store_true", parser.add_option("--nohooks", action="store_true",
help="(update/sync/revert only) prevent the hooks " help="(update/sync/revert only) prevent the hooks "
"from running") "from running")
option_parser.add_option("--revision", action="append", dest="revisions", parser.add_option("--revision", action="append", dest="revisions",
metavar="REV", default=[], metavar="REV", default=[],
help="(update/sync only) sync to a specific " help="(update/sync only) sync to a specific "
"revision, can be used multiple times for " "revision, can be used multiple times for "
"each solution, e.g. --revision=src@123, " "each solution, e.g. --revision=src@123, "
"--revision=internal@32") "--revision=internal@32")
option_parser.add_option("--deps", dest="deps_os", metavar="OS_LIST", parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
help="(update/sync only) sync deps for the " help="(update/sync only) sync deps for the "
"specified (comma-separated) platform(s); " "specified (comma-separated) platform(s); "
"'all' will sync all platforms") "'all' will sync all platforms")
option_parser.add_option("--reset", action="store_true", parser.add_option("--reset", action="store_true",
help="(update/sync only) resets any local changes " help="(update/sync only) resets any local changes "
"before updating (git only)") "before updating (git only)")
option_parser.add_option("--spec", parser.add_option("--spec",
help="(config only) create a gclient file " help="(config only) create a gclient file "
"containing the provided string") "containing the provided string")
option_parser.add_option("-v", "--verbose", action="count", default=0, parser.add_option("--manually_grab_svn_rev", action="store_true",
help="produce additional output for diagnostics")
option_parser.add_option("--manually_grab_svn_rev", action="store_true",
help="Skip svn up whenever possible by requesting " help="Skip svn up whenever possible by requesting "
"actual HEAD revision from the repository") "actual HEAD revision from the repository")
option_parser.add_option("--head", action="store_true", parser.add_option("--head", action="store_true",
help="skips any safesync_urls specified in " help="skips any safesync_urls specified in "
"configured solutions") "configured solutions")
option_parser.add_option("--delete_unversioned_trees", action="store_true", parser.add_option("--delete_unversioned_trees", action="store_true",
help="on update, delete any unexpected " help="on update, delete any unexpected "
"unversioned trees that are in the checkout") "unversioned trees that are in the checkout")
option_parser.add_option("--snapshot", action="store_true", parser.add_option("--snapshot", action="store_true",
help="(revinfo only), create a snapshot file " help="(revinfo only), create a snapshot file "
"of the current version of all repositories") "of the current version of all repositories")
option_parser.add_option("--name", parser.add_option("--name",
help="specify alternate relative solution path") help="specify alternate relative solution path")
option_parser.add_option("--gclientfile", metavar="FILENAME", # Integrate standard options processing.
help="specify an alternate .gclient file") old_parser = parser.parse_args
def Parse(args):
(options, args) = old_parser(args)
if options.verbose == 2:
logging.basicConfig(level=logging.INFO)
elif options.verbose > 2:
logging.basicConfig(level=logging.DEBUG)
options.entries_filename = options.config_filename + "_entries"
return (options, args)
parser.parse_args = Parse
# We don't want wordwrapping in epilog (usually examples)
parser.format_epilog = lambda _: parser.epilog or ''
if len(argv) < 2: if not len(argv):
# Users don't need to be told to use the 'help' command. argv = ['help']
option_parser.print_help()
return 1
# Add manual support for --version as first argument. # Add manual support for --version as first argument.
if argv[1] == '--version': if argv[0] == '--version':
option_parser.print_version() parser.print_version()
return 0 return 0
# Add manual support for --help as first argument. # Add manual support for --help as first argument.
if argv[1] == '--help': if argv[0] == '--help':
argv[1] = 'help' argv[0] = 'help'
options, args = parser.parse_args(argv[1:])
command = argv[1] return Command(argv[0])(parser, options, args)
options, args = option_parser.parse_args(argv[2:])
if len(argv) < 3 and command == "help":
option_parser.print_help()
return 0
if options.verbose > 1:
logging.basicConfig(level=logging.DEBUG)
# Files used for configuration and state saving.
options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
if options.gclientfile:
options.config_filename = options.gclientfile
options.entries_filename = options.config_filename + "_entries"
options.deps_file = "DEPS"
options.platform = sys.platform
return DispatchCommand(command, options, args)
if "__main__" == __name__: if "__main__" == __name__:
try: try:
result = Main(sys.argv) sys.exit(Main(sys.argv[1:]))
except gclient_utils.Error, e: except gclient_utils.Error, e:
print >> sys.stderr, "Error: %s" % str(e) print >> sys.stderr, "Error: %s" % str(e)
result = 1 sys.exit(1)
sys.exit(result)
# vim: ts=2:sw=2:tw=80:et: # vim: ts=2:sw=2:tw=80:et:

@ -97,7 +97,8 @@ class TestCMDconfig(GclientTestCase):
exception_msg = "required argument missing; see 'gclient help config'" exception_msg = "required argument missing; see 'gclient help config'"
self.mox.ReplayAll() self.mox.ReplayAll()
self.assertRaisesError(exception_msg, gclient.CMDconfig, self.Options(), ()) self.assertRaisesError(exception_msg, gclient.CMDconfig, None,
self.Options(), ())
def testExistingClientFile(self): def testExistingClientFile(self):
options = self.Options() options = self.Options()
@ -106,7 +107,8 @@ class TestCMDconfig(GclientTestCase):
gclient.os.path.exists(options.config_filename).AndReturn(True) gclient.os.path.exists(options.config_filename).AndReturn(True)
self.mox.ReplayAll() self.mox.ReplayAll()
self.assertRaisesError(exception_msg, gclient.CMDconfig, options, (1,)) self.assertRaisesError(exception_msg, gclient.CMDconfig, None, options,
(1,))
def testFromText(self): def testFromText(self):
options = self.Options(spec='config_source_content') options = self.Options(spec='config_source_content')
@ -116,7 +118,7 @@ class TestCMDconfig(GclientTestCase):
gclient.GClient.SaveConfig() gclient.GClient.SaveConfig()
self.mox.ReplayAll() self.mox.ReplayAll()
gclient.CMDconfig(options, (1,),) gclient.CMDconfig(None, options, (1,),)
def testCreateClientFile(self): def testCreateClientFile(self):
options = self.Options() options = self.Options()
@ -127,7 +129,7 @@ class TestCMDconfig(GclientTestCase):
gclient.GClient.SaveConfig() gclient.GClient.SaveConfig()
self.mox.ReplayAll() self.mox.ReplayAll()
gclient.CMDconfig(options, gclient.CMDconfig(None, options,
('http://svn/url/the_name', 'other', 'args', 'ignored')) ('http://svn/url/the_name', 'other', 'args', 'ignored'))
@ -138,7 +140,7 @@ class GenericCommandTestCase(GclientTestCase):
gclient.GClient.RunOnDeps(command, self.args).AndReturn(return_value) gclient.GClient.RunOnDeps(command, self.args).AndReturn(return_value)
self.mox.ReplayAll() self.mox.ReplayAll()
result = function(options, self.args) result = function(None, options, self.args)
self.assertEquals(result, return_value) self.assertEquals(result, return_value)
def BadClient(self, function): def BadClient(self, function):
@ -148,7 +150,7 @@ class GenericCommandTestCase(GclientTestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
self.assertRaisesError( self.assertRaisesError(
"client not configured; see 'gclient config'", "client not configured; see 'gclient config'",
function, options, self.args) function, None, options, self.args)
def Verbose(self, command, function): def Verbose(self, command, function):
options = self.Options(verbose=True) options = self.Options(verbose=True)
@ -159,7 +161,7 @@ class GenericCommandTestCase(GclientTestCase):
gclient.GClient.RunOnDeps(command, self.args).AndReturn(0) gclient.GClient.RunOnDeps(command, self.args).AndReturn(0)
self.mox.ReplayAll() self.mox.ReplayAll()
result = function(options, self.args) result = function(None, options, self.args)
self.assertEquals(result, 0) self.assertEquals(result, 0)
@ -201,7 +203,7 @@ class TestCMDupdate(GenericCommandTestCase):
gclient.GClient.RunOnDeps(command, self.args).AndReturn(return_value) gclient.GClient.RunOnDeps(command, self.args).AndReturn(return_value)
self.mox.ReplayAll() self.mox.ReplayAll()
result = function(options, self.args) result = function(None, options, self.args)
self.assertEquals(result, return_value) self.assertEquals(result, return_value)
def Verbose(self, command, function): def Verbose(self, command, function):
@ -214,7 +216,7 @@ class TestCMDupdate(GenericCommandTestCase):
gclient.GClient.RunOnDeps(command, self.args).AndReturn(0) gclient.GClient.RunOnDeps(command, self.args).AndReturn(0)
self.mox.ReplayAll() self.mox.ReplayAll()
result = function(options, self.args) result = function(None, options, self.args)
self.assertEquals(result, 0) self.assertEquals(result, 0)
def Options(self, verbose=False, *args, **kwargs): def Options(self, verbose=False, *args, **kwargs):

Loading…
Cancel
Save