Massive overhaul for gcl help

Automatically generate help and enforce stricter argument parsing.
Stops from throwing an exception when run outside a subversion checkout.
Fixed gcl lint.

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@48386 0039d316-1c4b-4281-b951-d872f2087c98
experimental/szager/collated-output
maruel@chromium.org 16 years ago
parent 6b1d00b0d0
commit 62fd693347

371
gcl.py

@ -3,8 +3,10 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Wrapper script around Rietveld's upload.py that groups files into
changelists."""
"""\
Wrapper script around Rietveld's upload.py that simplifies working with groups
of files.
"""
import getpass
import os
@ -27,7 +29,7 @@ __pychecker__ = ''
from scm import SVN
import gclient_utils
__version__ = '1.1.4'
__version__ = '1.2'
CODEREVIEW_SETTINGS = {
@ -82,14 +84,9 @@ def CheckHomeForFile(filename):
return None
def UnknownFiles(extra_args):
"""Runs svn status and returns unknown files.
Any args in |extra_args| are passed to the tool to support giving alternate
code locations.
"""
return [item[1] for item in SVN.CaptureStatus(extra_args)
if item[0][0] == '?']
def UnknownFiles():
"""Runs svn status and returns unknown files."""
return [item[1] for item in SVN.CaptureStatus([]) if item[0][0] == '?']
def GetRepositoryRoot():
@ -612,7 +609,7 @@ def ListFiles(show_unknown_files):
for filename in files[cl_name]:
print "".join(filename)
if show_unknown_files:
unknown_files = UnknownFiles([])
unknown_files = UnknownFiles()
if (files.get('') or (show_unknown_files and len(unknown_files))):
print "\n--- Not in any changelist:"
for item in files.get('', []):
@ -623,105 +620,6 @@ def ListFiles(show_unknown_files):
return 0
def CMDopened(args):
"""Lists modified files in the current directory down."""
if args:
ErrorExit("Doesn't support arguments")
return ListFiles(False)
def CMDstatus(args):
"""Lists modified and unknown files in the current directory down."""
if args:
ErrorExit("Doesn't support arguments")
return ListFiles(True)
def CMDhelp(args):
"""Prints this help or help for the given command."""
if args and len(args) > 2:
if args[2] == 'try':
return TryChange(None, ['--help'], swallow_exception=False)
if args[2] == 'upload':
upload.RealMain(['upload.py', '--help'])
return 0
print (
"""GCL is a wrapper for Subversion that simplifies working with groups of files.
version """ + __version__ + """
Basic commands:
-----------------------------------------
gcl change change_name
Add/remove files to a changelist. Only scans the current directory and
subdirectories.
gcl upload change_name [-r reviewer1@gmail.com,reviewer2@gmail.com,...]
[--send_mail] [--no_try] [--no_presubmit]
[--no_watchlists]
Uploads the changelist to the server for review.
(You can create the file '.gcl_upload_no_try' in your home dir to
skip the automatic tries.)
gcl commit change_name [--no_presubmit]
Commits the changelist to the repository.
gcl lint change_name
Check all the files in the changelist for possible style violations.
Advanced commands:
-----------------------------------------
gcl delete change_name
Deletes a changelist.
gcl diff change_name
Diffs all files in the changelist.
gcl presubmit change_name
Runs presubmit checks without uploading the changelist.
gcl diff
Diffs all files in the current directory and subdirectories that aren't in
a changelist.
gcl description
Prints the description of the specified change to stdout.
gcl changes
Lists all the the changelists and the files in them.
gcl rename <old-name> <new-name>
Renames an existing change.
gcl nothave [optional directory]
Lists files unknown to Subversion.
gcl opened
Lists modified files in the current directory and subdirectories.
gcl settings
Print the code review settings for this directory.
gcl status
Lists modified and unknown files in the current directory and
subdirectories.
gcl try change_name
Sends the change to the tryserver so a trybot can do a test run on your
code. To send multiple changes as one path, use a comma-separated list
of changenames.
--> Use 'gcl help try' for more information!
gcl deleteempties
Deletes all changelists that have no files associated with them. Careful,
you can lose your descriptions.
gcl help [command]
Print this help menu, or help for the given command if it exists.
""")
return 0
def GetEditor():
editor = os.environ.get("SVN_EDITOR")
if not editor:
@ -746,6 +644,13 @@ def OptionallyDoPresubmitChecks(change_info, committing, args):
return DoPresubmitChecks(change_info, committing, True)
def defer_attributes(a, b):
"""Copy attributes from an object (like a function) to another."""
for x in dir(a):
if not getattr(b, x, None):
setattr(b, x, getattr(a, x))
def need_change(function):
"""Converts args -> change_info."""
def hook(args):
@ -753,13 +658,63 @@ def need_change(function):
ErrorExit("You need to pass a change list name")
change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(), True, True)
return function(change_info)
defer_attributes(function, hook)
hook.need_change = True
hook.no_args = True
return hook
def CMDupload(args):
if not args:
ErrorExit("You need to pass a change list name")
change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
def need_change_and_args(function):
"""Converts args -> change_info."""
def hook(args):
change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
return function(change_info, args)
defer_attributes(function, hook)
hook.need_change = True
return hook
def no_args(function):
"""Make sure no args are passed."""
def hook(args):
if args:
ErrorExit("Doesn't support arguments")
return function()
defer_attributes(function, hook)
hook.no_args = True
return hook
def attrs(**kwargs):
"""Decorate a function with new attributes."""
def decorate(function):
for k in kwargs:
setattr(function, k, kwargs[k])
return function
return decorate
@no_args
def CMDopened():
"""Lists modified files in the current directory down."""
return ListFiles(False)
@no_args
def CMDstatus():
"""Lists modified and unknown files in the current directory down."""
return ListFiles(True)
@need_change_and_args
@attrs(usage='[--no_try] [--no_presubmit] [--clobber]\n'
' [--no_watchlists]')
def CMDupload(change_info, args):
"""Uploads the changelist to the server for review.
(You can create the file '.gcl_upload_no_try' in your home dir to
skip the automatic tries.)
"""
if not change_info.GetFiles():
print "Nothing to upload, changelist is empty."
return 0
@ -768,11 +723,11 @@ def CMDupload(args):
# Might want to support GetInfoDir()/GetRepositoryRoot() like
# CheckHomeForFile() so the skip of tries can be per tree basis instead
# of user global.
no_try = FilterFlag(args, "--no_try") or \
FilterFlag(args, "--no-try") or \
not (CheckHomeForFile(".gcl_upload_no_try") is None)
no_watchlists = FilterFlag(args, "--no_watchlists") or \
FilterFlag(args, "--no-watchlists")
no_try = (FilterFlag(args, "--no_try") or
FilterFlag(args, "--no-try") or
not (CheckHomeForFile(".gcl_upload_no_try") is None))
no_watchlists = (FilterFlag(args, "--no_watchlists") or
FilterFlag(args, "--no-watchlists"))
# Map --send-mail to --send_mail
if FilterFlag(args, "--send-mail"):
@ -877,7 +832,10 @@ def CMDupload(args):
@need_change
def CMDpresubmit(change_info):
"""Runs presubmit checks on the change."""
"""Runs presubmit checks on the change.
The actual presubmit code is implemented in presubmit_support.py and looks
for PRESUBMIT.py files."""
if not change_info.GetFiles():
print "Nothing to presubmit check, changelist is empty."
return 0
@ -917,10 +875,10 @@ def TryChange(change_info, args, swallow_exception):
prog='gcl try')
def CMDcommit(args):
if not args:
ErrorExit("You need to pass a change list name")
change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
@need_change_and_args
@attrs(usage='[--no_presubmit]')
def CMDcommit(change_list, args):
"""Commits the changelist to the repository."""
if not change_info.GetFiles():
print "Nothing to commit, changelist is empty."
return 1
@ -978,7 +936,9 @@ def CMDcommit(args):
def CMDchange(args):
"""Creates/edits a changelist."""
"""Creates or edits a changelist.
Only scans the current directory and subdirectories."""
if len(args) == 0:
# Generate a random changelist name.
changename = GenerateChangeName()
@ -1098,9 +1058,12 @@ def CMDchange(args):
return 0
@need_change
def CMDlint(change_info):
"""Runs cpplint.py on all the files in |change_info|"""
@need_change_and_args
def CMDlint(change_info, args):
"""Runs cpplint.py on all the files in the change list.
Checks all the files in the changelist for possible style violations.
"""
try:
import cpplint
except ImportError:
@ -1158,10 +1121,9 @@ def DoPresubmitChecks(change_info, committing, may_prompt):
return result
def CMDchanges(args):
@no_args
def CMDchanges():
"""Lists all the changelists and their files."""
if args:
ErrorExit("Doesn't support arguments")
for cl in GetCLs():
change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
print "\n--- Changelist " + change_info.name + ":"
@ -1170,10 +1132,9 @@ def CMDchanges(args):
return 0
def CMDdeleteempties(args):
@no_args
def CMDdeleteempties():
"""Delete all changelists that have no files."""
if args:
ErrorExit("Doesn't support arguments")
print "\n--- Deleting:"
for cl in GetCLs():
change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
@ -1183,21 +1144,20 @@ def CMDdeleteempties(args):
return 0
def CMDnothave(args):
@no_args
def CMDnothave():
"""Lists files unknown to Subversion."""
if args:
ErrorExit("Doesn't support arguments")
for filename in UnknownFiles(args):
for filename in UnknownFiles():
print "? " + "".join(filename)
return 0
@attrs(usage='<svn options>')
def CMDdiff(args):
"""Diffs all files in the changelist."""
"""Diffs all files in the changelist or all files that aren't in a CL."""
files = None
if args:
change_info = ChangeInfo.Load(args[0], GetRepositoryRoot(), True, True)
args.pop(0)
change_info = ChangeInfo.Load(args.pop(0), GetRepositoryRoot(), True, True)
files = change_info.GetFileNames()
else:
files = GetFilesNotInCL()
@ -1205,9 +1165,9 @@ def CMDdiff(args):
print_output=True)[1]
def CMDsettings(args):
"""Prints code review settings."""
__pychecker__ = 'unusednames=args'
@no_args
def CMDsettings():
"""Prints code review settings for this checkout."""
# Force load settings
GetCodeReviewSetting("UNKNOWN");
del CODEREVIEW_SETTINGS['__just_initialized']
@ -1231,8 +1191,7 @@ def CMDdelete(change_info):
def CMDtry(args):
"""Sends the change to the tryserver so a trybot can do a test run on your
code.
"""Sends the change to the tryserver to do a test run on your code.
To send multiple changes as one path, use a comma-separated list of
changenames. Use 'gcl help try' for more information!"""
@ -1255,6 +1214,7 @@ def CMDtry(args):
return TryChange(change_info, args, swallow_exception=False)
@attrs(usage='<old-name> <new-name>')
def CMDrename(args):
"""Renames an existing change."""
if len(args) != 2:
@ -1272,9 +1232,10 @@ def CMDrename(args):
def CMDpassthru(args):
# Everything else that is passed into gcl we redirect to svn, after adding
# the files.
# args is guaranteed to be len(args) >= 1
"""Everything else that is passed into gcl we redirect to svn.
It assumes a change list name is passed and is converted with the files names.
"""
args = ["svn", args[0]]
if len(args) > 1:
root = GetRepositoryRoot()
@ -1283,61 +1244,73 @@ def CMDpassthru(args):
return RunShellWithReturnCode(args, print_output=True)[1]
def Command(name):
return getattr(sys.modules[__name__], 'CMD' + name, None)
def GenUsage(command):
"""Modify an OptParse object with the function's documentation."""
obj = Command(command)
display = command
more = getattr(obj, 'usage', '')
if command == 'help':
display = '<command>'
need_change = ''
if getattr(obj, 'need_change', None):
need_change = ' <change_list>'
options = ' [options]'
if getattr(obj, 'no_args', None):
options = ''
res = 'Usage: gcl %s%s%s %s\n\n' % (display, need_change, options, more)
res += re.sub('\n ', '\n', obj.__doc__)
return res
def CMDhelp(args):
"""Prints this help or help for the given command."""
if args and 'CMD' + args[0] in dir(sys.modules[__name__]):
print GenUsage(args[0])
# These commands defer to external tools so give this info too.
if args[0] == 'try':
TryChange(None, ['--help'], swallow_exception=False)
if args[0] == 'upload':
upload.RealMain(['upload.py', '--help'])
return 0
print GenUsage('help')
print sys.modules[__name__].__doc__
print 'version ' + __version__ + '\n'
print('Commands are:\n' + '\n'.join([
' %-12s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
return 0
def main(argv):
__pychecker__ = 'maxreturns=0'
try:
# Create the directories where we store information about changelists if it
# doesn't exist.
if not os.path.exists(GetInfoDir()):
os.mkdir(GetInfoDir())
if not os.path.exists(GetChangesDir()):
os.mkdir(GetChangesDir())
if not os.path.exists(GetCacheDir()):
os.mkdir(GetCacheDir())
GetRepositoryRoot()
except gclient_utils.Error:
# Will throw an exception if not run in a svn checkout.
pass
print('To use gcl, you need to be in a subversion checkout.')
return 1
# Create the directories where we store information about changelists if it
# doesn't exist.
if not os.path.exists(GetInfoDir()):
os.mkdir(GetInfoDir())
if not os.path.exists(GetChangesDir()):
os.mkdir(GetChangesDir())
if not os.path.exists(GetCacheDir()):
os.mkdir(GetCacheDir())
if not argv:
argv = ['help']
# Commands that don't require an argument.
command = argv[0]
if command == "opened":
return CMDopened(argv[1:])
if command == "status":
return CMDstatus(argv[1:])
if command == "nothave":
return CMDnothave(argv[1:])
if command == "changes":
return CMDchanges(argv[1:])
if command == "help":
return CMDhelp(argv[1:])
if command == "diff":
return CMDdiff(argv[1:])
if command == "settings":
return CMDsettings(argv[1:])
if command == "deleteempties":
return CMDdeleteempties(argv[1:])
if command == "rename":
return CMDrename(argv[1:])
elif command == "change":
return CMDchange(argv[1:])
elif command == "description":
return CMDdescription(argv[1:])
elif command == "lint":
return CMDlint(argv[1:])
elif command == "upload":
return CMDupload(argv[1:])
elif command == "presubmit":
return CMDpresubmit(argv[1:])
elif command in ("commit", "submit"):
return CMDcommit(argv[1:])
elif command == "delete":
return CMDdelete(argv[1:])
elif command == "try":
return CMDtry(argv[1:])
else:
return CMDpassthru(argv)
command = Command(argv[0])
if command:
return command(argv[1:])
# Unknown command, try to pass that to svn
return CMDpassthru(argv)
if __name__ == "__main__":

@ -36,9 +36,9 @@ class GclUnittest(GclTestsBase):
'CMDdescription', 'CMDdiff', 'CMDhelp', 'CMDlint', 'CMDnothave',
'CMDopened', 'CMDpassthru', 'CMDpresubmit', 'CMDrename', 'CMDsettings',
'CMDstatus', 'CMDtry', 'CMDupload',
'ChangeInfo', 'DEFAULT_LINT_IGNORE_REGEX',
'DEFAULT_LINT_REGEX', 'CheckHomeForFile',
'DoPresubmitChecks', 'ErrorExit', 'FILES_CACHE', 'FilterFlag',
'ChangeInfo', 'Command', 'DEFAULT_LINT_IGNORE_REGEX',
'DEFAULT_LINT_REGEX', 'CheckHomeForFile', 'DoPresubmitChecks',
'ErrorExit', 'FILES_CACHE', 'FilterFlag', 'GenUsage',
'GenerateChangeName', 'GenerateDiff', 'GetCLs', 'GetCacheDir',
'GetCachedFile', 'GetChangelistInfoFile', 'GetChangesDir',
'GetCodeReviewSetting', 'GetEditor', 'GetFilesNotInCL', 'GetInfoDir',
@ -48,7 +48,8 @@ class GclUnittest(GclTestsBase):
'OptionallyDoPresubmitChecks', 'REPOSITORY_ROOT',
'RunShell', 'RunShellWithReturnCode', 'SVN',
'SendToRietveld', 'TryChange', 'UnknownFiles', 'Warn',
'breakpad', 'gclient_utils', 'getpass', 'main', 'need_change', 'os',
'attrs', 'breakpad', 'defer_attributes', 'gclient_utils', 'getpass',
'main', 'need_change', 'need_change_and_args', 'no_args', 'os',
'random', 're', 'shutil', 'string', 'subprocess', 'sys', 'tempfile',
'time', 'upload', 'urllib2',
]
@ -85,8 +86,7 @@ class GclUnittest(GclTestsBase):
self.assertEquals(gcl.GetRepositoryRoot(), root_path + '.~')
def testHelp(self):
gcl.sys.stdout.write(mox.StrContains('GCL is a wrapper for Subversion'))
gcl.sys.stdout.write('\n')
gcl.sys.stdout.write = lambda x: None
self.mox.ReplayAll()
gcl.CMDhelp([])

Loading…
Cancel
Save