Add the 'sync' command
[stgit] / stgit / main.py
index 3fd11a1..3c8e8f4 100644 (file)
@@ -19,77 +19,143 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
 import sys, os
-from optparse import OptionParser, make_option
-
-from stgit.utils import *
-from stgit import stack, git
-from stgit.version import version
-from stgit.config import config
-from stgit.commands.common import *
-
-# The commands
-import stgit.commands.add
-import stgit.commands.applied
-import stgit.commands.delete
-import stgit.commands.diff
-import stgit.commands.clean
-import stgit.commands.export
-import stgit.commands.files
-import stgit.commands.imprt
-import stgit.commands.init
-import stgit.commands.mail
-import stgit.commands.new
-import stgit.commands.pop
-import stgit.commands.pull
-import stgit.commands.push
-import stgit.commands.refresh
-import stgit.commands.rename
-import stgit.commands.resolved
-import stgit.commands.rm
-import stgit.commands.series
-import stgit.commands.status
-import stgit.commands.top
-import stgit.commands.unapplied
+from optparse import OptionParser
 
+import stgit.commands
 
 #
 # The commands map
 #
-commands = {
-    'add':      stgit.commands.add,
-    'applied':  stgit.commands.applied,
-    'delete':   stgit.commands.delete,
-    'diff':     stgit.commands.diff,
-    'clean':    stgit.commands.clean,
-    'export':   stgit.commands.export,
-    'files':    stgit.commands.files,
-    'import':   stgit.commands.imprt,
-    'init':     stgit.commands.init,
-    'mail':     stgit.commands.mail,
-    'new':      stgit.commands.new,
-    'pop':      stgit.commands.pop,
-    'pull':     stgit.commands.pull,
-    'push':     stgit.commands.push,
-    'refresh':  stgit.commands.refresh,
-    'rename':   stgit.commands.rename,
-    'resolved': stgit.commands.resolved,
-    'rm':       stgit.commands.rm,
-    'series':   stgit.commands.series,
-    'status':   stgit.commands.status,
-    'top':      stgit.commands.top,
-    'unapplied':stgit.commands.unapplied,
-    }
-
+class Commands(dict):
+    """Commands class. It performs on-demand module loading
+    """
+    def __getitem__(self, key):
+        cmd_mod = self.get(key)
+        __import__('stgit.commands.' + cmd_mod)
+        return getattr(stgit.commands, cmd_mod)
+
+commands = Commands({
+    'add':              'add',
+    'applied':          'applied',
+    'assimilate':       'assimilate',
+    'branch':           'branch',
+    'delete':           'delete',
+    'diff':             'diff',
+    'clean':            'clean',
+    'clone':            'clone',
+    'commit':           'commit',
+    'export':           'export',
+    'files':            'files',
+    'float':            'float',
+    'fold':             'fold',
+    'goto':             'goto',
+    'id':               'id',
+    'import':           'imprt',
+    'init':             'init',
+    'log':              'log',
+    'mail':             'mail',
+    'new':              'new',
+    'patches':          'patches',
+    'pick':             'pick',
+    'pop':              'pop',
+    'pull':             'pull',
+    'push':             'push',
+    'refresh':          'refresh',
+    'rename':           'rename',
+    'resolved':         'resolved',
+    'rm':               'rm',
+    'series':           'series',
+    'show':             'show',
+    'status':           'status',
+    'sync':             'sync',
+    'top':              'top',
+    'unapplied':        'unapplied',
+    'uncommit':         'uncommit'
+    })
+
+# classification: repository, stack, patch, working copy
+repocommands = (
+    'branch',
+    'clone',
+    'id',
+    'pull'
+    )
+stackcommands = (
+    'applied',
+    'assimilate',
+    'clean',
+    'commit',
+    'float',
+    'goto',
+    'init',
+    'pop',
+    'push',
+    'series',
+    'top',
+    'unapplied',
+    'uncommit'
+    )
+patchcommands = (
+    'delete',
+    'export',
+    'files',
+    'fold',
+    'import',
+    'log',
+    'mail',
+    'new',
+    'pick',
+    'refresh',
+    'rename',
+    'show',
+    'sync'
+    )
+wccommands = (
+    'add',
+    'diff',
+    'patches',
+    'resolved',
+    'rm',
+    'status'
+    )
+
+def _print_helpstring(cmd):
+    print '  ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
+    
 def print_help():
     print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
     print
-    print 'commands:'
-    print '  help        print this message'
-
+    print 'Generic commands:'
+    print '  help        print the detailed command usage'
+    print '  version     display version information'
+    print '  copyright   display copyright information'
+    # unclassified commands if any
     cmds = commands.keys()
     cmds.sort()
     for cmd in cmds:
-        print '  ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
+        if not cmd in repocommands and not cmd in stackcommands \
+               and not cmd in patchcommands and not cmd in wccommands:
+            _print_helpstring(cmd)
+    print
+
+    print 'Repository commands:'
+    for cmd in repocommands:
+        _print_helpstring(cmd)
+    print
+    
+    print 'Stack commands:'
+    for cmd in stackcommands:
+        _print_helpstring(cmd)
+    print
+
+    print 'Patch commands:'
+    for cmd in patchcommands:
+        _print_helpstring(cmd)
+    print
+
+    print 'Working-copy commands:'
+    for cmd in wccommands:
+        _print_helpstring(cmd)
 
 #
 # The main function (command dispatcher)
@@ -100,23 +166,50 @@ def main():
     prog = os.path.basename(sys.argv[0])
 
     if len(sys.argv) < 2:
-        print >> sys.stderr, 'Unknown command'
+        print >> sys.stderr, 'usage: %s <command>' % prog
         print >> sys.stderr, \
-              '  Try "%s help" for a list of supported commands' % prog
+              '  Try "%s --help" for a list of supported commands' % prog
         sys.exit(1)
 
     cmd = sys.argv[1]
 
-    if cmd in ['-h', '--help', 'help']:
-        print_help()
+    if cmd in ['-h', '--help']:
+        if len(sys.argv) >= 3 and sys.argv[2] in commands:
+            cmd = sys.argv[2]
+            sys.argv[2] = '--help'
+        else:
+            print_help()
+            sys.exit(0)
+    if cmd == 'help':
+        if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
+            cmd = sys.argv[2]
+            if not cmd in commands:
+                print >> sys.stderr, '%s help: "%s" command unknown' \
+                      % (prog, cmd)
+                sys.exit(1)
+
+            sys.argv[0] += ' %s' % cmd
+            command = commands[cmd]
+            parser = OptionParser(usage = command.usage,
+                                  option_list = command.options)
+            from pydoc import pager
+            pager(parser.format_help())
+        else:
+            print_help()
         sys.exit(0)
-    if cmd in ['-v', '--version']:
-        print '%s %s' % (prog, version)
+    if cmd in ['-v', '--version', 'version']:
+        from stgit.version import version
+        print 'Stacked GIT %s' % version
+        os.system('git --version')
+        print 'Python version %s' % sys.version
+        sys.exit(0)
+    if cmd in ['copyright']:
+        print __copyright__
         sys.exit(0)
     if not cmd in commands:
         print >> sys.stderr, 'Unknown command: %s' % cmd
-        print >> sys.stderr, '  Try "%s help" for a list of supported commands' \
-              % prog
+        print >> sys.stderr, '  Try "%s help" for a list of supported ' \
+              'commands' % prog
         sys.exit(1)
 
     # re-build the command line arguments
@@ -124,19 +217,37 @@ def main():
     del(sys.argv[1])
 
     command = commands[cmd]
-    parser = OptionParser(usage = command.usage,
-                          option_list = command.options)
+    usage = command.usage.split('\n')[0].strip()
+    parser = OptionParser(usage = usage, option_list = command.options)
     options, args = parser.parse_args()
+
+    # These modules are only used from this point onwards and do not
+    # need to be imported earlier
+    from stgit.config import config_setup
+    from ConfigParser import ParsingError, NoSectionError
+    from stgit.stack import Series, StackException
+    from stgit.git import GitException
+    from stgit.commands.common import CmdException
+    from stgit.gitmergeonefile import GitMergeException
+
     try:
-        # the lines below are a simple way to avoid an exception when
-        # stgit is run outside an initialised tree
-        stgit.commands.common.crt_series = stack.Series()
-        setattr(command, 'crt_series', stgit.commands.common.crt_series)
+        config_setup()
+
+        # 'clone' doesn't expect an already initialised GIT tree. A Series
+        # object will be created after the GIT tree is cloned
+        if cmd != 'clone':
+            if hasattr(options, 'branch') and options.branch:
+                command.crt_series = Series(options.branch)
+            else:
+                command.crt_series = Series()
+            stgit.commands.common.crt_series = command.crt_series
 
         command.func(parser, options, args)
-    except (IOError, CmdException, stack.StackException, git.GitException), \
-               err:
+    except (IOError, ParsingError, NoSectionError, CmdException,
+            StackException, GitException, GitMergeException), err:
         print >> sys.stderr, '%s %s: %s' % (prog, cmd, err)
         sys.exit(2)
+    except KeyboardInterrupt:
+        sys.exit(1)
 
     sys.exit(0)