Index: gcl.py |
diff --git a/gcl.py b/gcl.py |
index 910a0d9139ce93abdc2558aa5c56fe0d639b8fad..272e8d64803fba3a346a1e7c875022ac8c65f5a2 100755 |
--- a/gcl.py |
+++ b/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__": |