chiark / gitweb /
9fcbbc2bff88fc0ab0de41b5670ebf659fb99476
[stgit] / stgit / main.py
1 """Basic quilt-like functionality
2 """
3
4 __copyright__ = """
5 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 """
20
21 import sys, os
22 from optparse import OptionParser
23
24 import stgit.commands
25
26 #
27 # The commands map
28 #
29 class Commands(dict):
30     """Commands class. It performs on-demand module loading
31     """
32     def canonical_cmd(self, key):
33         """Return the canonical name for a possibly-shortenned
34         command name.
35         """
36         candidates = [cmd for cmd in self.keys() if cmd.startswith(key)]
37
38         if not candidates:
39             print >> sys.stderr, 'Unknown command: %s' % key
40             print >> sys.stderr, '  Try "%s help" for a list of ' \
41                   'supported commands' % prog
42             sys.exit(1)
43         elif len(candidates) > 1:
44             print >> sys.stderr, 'Ambiguous command: %s' % key
45             print >> sys.stderr, '  Candidates are: %s' \
46                   % ', '.join(candidates)
47             sys.exit(1)
48
49         return candidates[0]
50         
51     def __getitem__(self, key):
52         """Return the command python module name based.
53         """
54         global prog
55
56         cmd_mod = self.get(key) or self.get(self.canonical_cmd(key))
57             
58         __import__('stgit.commands.' + cmd_mod)
59         return getattr(stgit.commands, cmd_mod)
60
61 commands = Commands({
62     'add':              'add',
63     'applied':          'applied',
64     'assimilate':       'assimilate',
65     'branch':           'branch',
66     'delete':           'delete',
67     'diff':             'diff',
68     'clean':            'clean',
69     'clone':            'clone',
70     'commit':           'commit',
71     'export':           'export',
72     'files':            'files',
73     'float':            'float',
74     'fold':             'fold',
75     'goto':             'goto',
76     'hide':             'hide',
77     'id':               'id',
78     'import':           'imprt',
79     'init':             'init',
80     'log':              'log',
81     'mail':             'mail',
82     'new':              'new',
83     'patches':          'patches',
84     'pick':             'pick',
85     'pop':              'pop',
86     'pull':             'pull',
87     'push':             'push',
88     'rebase':           'rebase',
89     'refresh':          'refresh',
90     'rename':           'rename',
91     'resolved':         'resolved',
92     'rm':               'rm',
93     'series':           'series',
94     'show':             'show',
95     'status':           'status',
96     'sync':             'sync',
97     'top':              'top',
98     'unapplied':        'unapplied',
99     'uncommit':         'uncommit',
100     'unhide':           'unhide'
101     })
102
103 # classification: repository, stack, patch, working copy
104 repocommands = (
105     'clone',
106     'id',
107     )
108 stackcommands = (
109     'applied',
110     'assimilate',
111     'branch',
112     'clean',
113     'commit',
114     'float',
115     'goto',
116     'hide',
117     'init',
118     'patches',
119     'pop',
120     'pull',
121     'push',
122     'rebase',
123     'series',
124     'top',
125     'unapplied',
126     'uncommit',
127     'unhide',
128     )
129 patchcommands = (
130     'delete',
131     'export',
132     'files',
133     'fold',
134     'import',
135     'log',
136     'mail',
137     'new',
138     'pick',
139     'refresh',
140     'rename',
141     'show',
142     'sync',
143     )
144 wccommands = (
145     'add',
146     'diff',
147     'resolved',
148     'rm',
149     'status',
150     )
151
152 def _print_helpstring(cmd):
153     print '  ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
154     
155 def print_help():
156     print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
157     print
158     print 'Generic commands:'
159     print '  help        print the detailed command usage'
160     print '  version     display version information'
161     print '  copyright   display copyright information'
162     # unclassified commands if any
163     cmds = commands.keys()
164     cmds.sort()
165     for cmd in cmds:
166         if not cmd in repocommands and not cmd in stackcommands \
167                and not cmd in patchcommands and not cmd in wccommands:
168             _print_helpstring(cmd)
169     print
170
171     print 'Repository commands:'
172     for cmd in repocommands:
173         _print_helpstring(cmd)
174     print
175     
176     print 'Stack commands:'
177     for cmd in stackcommands:
178         _print_helpstring(cmd)
179     print
180
181     print 'Patch commands:'
182     for cmd in patchcommands:
183         _print_helpstring(cmd)
184     print
185
186     print 'Working-copy commands:'
187     for cmd in wccommands:
188         _print_helpstring(cmd)
189
190 #
191 # The main function (command dispatcher)
192 #
193 def main():
194     """The main function
195     """
196     global prog
197
198     prog = os.path.basename(sys.argv[0])
199
200     if len(sys.argv) < 2:
201         print >> sys.stderr, 'usage: %s <command>' % prog
202         print >> sys.stderr, \
203               '  Try "%s --help" for a list of supported commands' % prog
204         sys.exit(1)
205
206     cmd = sys.argv[1]
207
208     if cmd in ['-h', '--help']:
209         if len(sys.argv) >= 3:
210             cmd = commands.canonical_cmd(sys.argv[2])
211             sys.argv[2] = '--help'
212         else:
213             print_help()
214             sys.exit(0)
215     if cmd == 'help':
216         if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
217             cmd = commands.canonical_cmd(sys.argv[2])
218             if not cmd in commands:
219                 print >> sys.stderr, '%s help: "%s" command unknown' \
220                       % (prog, cmd)
221                 sys.exit(1)
222
223             sys.argv[0] += ' %s' % cmd
224             command = commands[cmd]
225             parser = OptionParser(usage = command.usage,
226                                   option_list = command.options)
227             from pydoc import pager
228             pager(parser.format_help())
229         else:
230             print_help()
231         sys.exit(0)
232     if cmd in ['-v', '--version', 'version']:
233         from stgit.version import version
234         print 'Stacked GIT %s' % version
235         os.system('git --version')
236         print 'Python version %s' % sys.version
237         sys.exit(0)
238     if cmd in ['copyright']:
239         print __copyright__
240         sys.exit(0)
241
242     # re-build the command line arguments
243     sys.argv[0] += ' %s' % commands.canonical_cmd(cmd)
244     del(sys.argv[1])
245
246     command = commands[cmd]
247     usage = command.usage.split('\n')[0].strip()
248     parser = OptionParser(usage = usage, option_list = command.options)
249     options, args = parser.parse_args()
250
251     # These modules are only used from this point onwards and do not
252     # need to be imported earlier
253     from stgit.config import config_setup
254     from ConfigParser import ParsingError, NoSectionError
255     from stgit.stack import Series, StackException
256     from stgit.git import GitException
257     from stgit.commands.common import CmdException
258     from stgit.gitmergeonefile import GitMergeException
259     from stgit.utils import EditorException
260
261     try:
262         debug_level = int(os.environ['STGIT_DEBUG_LEVEL'])
263     except KeyError:
264         debug_level = 0
265     except ValueError:
266         print >> sys.stderr, 'Invalid STGIT_DEBUG_LEVEL environment variable'
267         sys.exit(1)
268
269     try:
270         config_setup()
271
272         # 'clone' doesn't expect an already initialised GIT tree. A Series
273         # object will be created after the GIT tree is cloned
274         if cmd != 'clone':
275             if hasattr(options, 'branch') and options.branch:
276                 command.crt_series = Series(options.branch)
277             else:
278                 command.crt_series = Series()
279             stgit.commands.common.crt_series = command.crt_series
280
281         command.func(parser, options, args)
282     except (IOError, ParsingError, NoSectionError, CmdException,
283             StackException, GitException, GitMergeException,
284             EditorException), err:
285         print >> sys.stderr, '%s %s: %s' % (prog, cmd, err)
286         if debug_level:
287             raise
288         else:
289             sys.exit(2)
290     except KeyboardInterrupt:
291         sys.exit(1)
292
293     sys.exit(0)