chiark / gitweb /
55c467c5c4cd23268ed422f484474319358e9110
[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, traceback
22
23 import stgit.commands
24 from stgit.out import *
25 from stgit import argparse, run, utils
26
27 #
28 # The commands map
29 #
30 class Commands(dict):
31     """Commands class. It performs on-demand module loading
32     """
33     def canonical_cmd(self, key):
34         """Return the canonical name for a possibly-shortenned
35         command name.
36         """
37         candidates = [cmd for cmd in self.keys() if cmd.startswith(key)]
38
39         if not candidates:
40             out.error('Unknown command: %s' % key,
41                       'Try "%s help" for a list of supported commands' % prog)
42             sys.exit(utils.STGIT_GENERAL_ERROR)
43         elif len(candidates) > 1:
44             out.error('Ambiguous command: %s' % key,
45                       'Candidates are: %s' % ', '.join(candidates))
46             sys.exit(utils.STGIT_GENERAL_ERROR)
47
48         return candidates[0]
49         
50     def __getitem__(self, key):
51         """Return the command python module name based.
52         """
53         global prog
54
55         cmd_mod = self.get(key) or self.get(self.canonical_cmd(key))
56             
57         __import__('stgit.commands.' + cmd_mod)
58         return getattr(stgit.commands, cmd_mod)
59
60 commands = Commands({
61     'branch':           'branch',
62     'delete':           'delete',
63     'diff':             'diff',
64     'clean':            'clean',
65     'clone':            'clone',
66     'coalesce':         'coalesce',
67     'commit':           'commit',
68     'edit':             'edit',
69     'export':           'export',
70     'files':            'files',
71     'float':            'float',
72     'fold':             'fold',
73     'goto':             'goto',
74     'hide':             'hide',
75     'id':               'id',
76     'import':           'imprt',
77     'init':             'init',
78     'log':              'log',
79     'mail':             'mail',
80     'new':              'new',
81     'patches':          'patches',
82     'pick':             'pick',
83     'pop':              'pop',
84     'pull':             'pull',
85     'push':             'push',
86     'rebase':           'rebase',
87     'refresh':          'refresh',
88     'rename':           'rename',
89     'repair':           'repair',
90     'resolved':         'resolved',
91     'series':           'series',
92     'show':             'show',
93     'sink':             'sink',
94     'status':           'status',
95     'sync':             'sync',
96     'top':              'top',
97     'uncommit':         'uncommit',
98     'unhide':           'unhide'
99     })
100
101 # classification: repository, stack, patch, working copy
102 repocommands = (
103     'clone',
104     'id',
105     )
106 stackcommands = (
107     'branch',
108     'clean',
109     'coalesce',
110     'commit',
111     'float',
112     'goto',
113     'hide',
114     'init',
115     'patches',
116     'pop',
117     'pull',
118     'push',
119     'rebase',
120     'repair',
121     'series',
122     'sink',
123     'top',
124     'uncommit',
125     'unhide',
126     )
127 patchcommands = (
128     'delete',
129     'edit',
130     'export',
131     'files',
132     'fold',
133     'import',
134     'log',
135     'mail',
136     'new',
137     'pick',
138     'refresh',
139     'rename',
140     'show',
141     'sync',
142     )
143 wccommands = (
144     'diff',
145     'resolved',
146     'status',
147     )
148
149 def _print_helpstring(cmd):
150     print '  ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
151     
152 def print_help():
153     print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
154     print
155     print 'Generic commands:'
156     print '  help        print the detailed command usage'
157     print '  version     display version information'
158     print '  copyright   display copyright information'
159     # unclassified commands if any
160     cmds = commands.keys()
161     cmds.sort()
162     for cmd in cmds:
163         if not cmd in repocommands and not cmd in stackcommands \
164                and not cmd in patchcommands and not cmd in wccommands:
165             _print_helpstring(cmd)
166     print
167
168     print 'Repository commands:'
169     for cmd in repocommands:
170         _print_helpstring(cmd)
171     print
172     
173     print 'Stack commands:'
174     for cmd in stackcommands:
175         _print_helpstring(cmd)
176     print
177
178     print 'Patch commands:'
179     for cmd in patchcommands:
180         _print_helpstring(cmd)
181     print
182
183     print 'Working-copy commands:'
184     for cmd in wccommands:
185         _print_helpstring(cmd)
186
187 #
188 # The main function (command dispatcher)
189 #
190 def _main():
191     """The main function
192     """
193     global prog
194
195     prog = os.path.basename(sys.argv[0])
196
197     if len(sys.argv) < 2:
198         print >> sys.stderr, 'usage: %s <command>' % prog
199         print >> sys.stderr, \
200               '  Try "%s --help" for a list of supported commands' % prog
201         sys.exit(utils.STGIT_GENERAL_ERROR)
202
203     cmd = sys.argv[1]
204
205     if cmd in ['-h', '--help']:
206         if len(sys.argv) >= 3:
207             cmd = commands.canonical_cmd(sys.argv[2])
208             sys.argv[2] = '--help'
209         else:
210             print_help()
211             sys.exit(utils.STGIT_SUCCESS)
212     if cmd == 'help':
213         if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
214             cmd = commands.canonical_cmd(sys.argv[2])
215             if not cmd in commands:
216                 out.error('%s help: "%s" command unknown' % (prog, cmd))
217                 sys.exit(utils.STGIT_GENERAL_ERROR)
218
219             sys.argv[0] += ' %s' % cmd
220             command = commands[cmd]
221             parser = argparse.make_option_parser(command)
222             from pydoc import pager
223             pager(parser.format_help())
224         else:
225             print_help()
226         sys.exit(utils.STGIT_SUCCESS)
227     if cmd in ['-v', '--version', 'version']:
228         from stgit.version import version
229         print 'Stacked GIT %s' % version
230         os.system('git --version')
231         print 'Python version %s' % sys.version
232         sys.exit(utils.STGIT_SUCCESS)
233     if cmd in ['copyright']:
234         print __copyright__
235         sys.exit(utils.STGIT_SUCCESS)
236
237     # re-build the command line arguments
238     cmd = commands.canonical_cmd(cmd)
239     sys.argv[0] += ' %s' % cmd
240     del(sys.argv[1])
241
242     command = commands[cmd]
243     parser = argparse.make_option_parser(command)
244     options, args = parser.parse_args()
245     directory = command.directory
246
247     # These modules are only used from this point onwards and do not
248     # need to be imported earlier
249     from stgit.exception import StgException
250     from stgit.config import config_setup
251     from ConfigParser import ParsingError, NoSectionError
252     from stgit.stack import Series
253
254     try:
255         debug_level = int(os.environ.get('STGIT_DEBUG_LEVEL', 0))
256     except ValueError:
257         out.error('Invalid STGIT_DEBUG_LEVEL environment variable')
258         sys.exit(utils.STGIT_GENERAL_ERROR)
259
260     try:
261         directory.setup()
262         config_setup()
263
264         # Some commands don't (always) need an initialized series.
265         if directory.needs_current_series:
266             if hasattr(options, 'branch') and options.branch:
267                 command.crt_series = Series(options.branch)
268             else:
269                 command.crt_series = Series()
270
271         ret = command.func(parser, options, args)
272     except (StgException, IOError, ParsingError, NoSectionError), err:
273         out.error(str(err), title = '%s %s' % (prog, cmd))
274         if debug_level > 0:
275             traceback.print_exc()
276         sys.exit(utils.STGIT_COMMAND_ERROR)
277     except SystemExit:
278         # Triggered by the option parser when it finds bad commandline
279         # parameters.
280         sys.exit(utils.STGIT_COMMAND_ERROR)
281     except KeyboardInterrupt:
282         sys.exit(utils.STGIT_GENERAL_ERROR)
283     except:
284         out.error('Unhandled exception:')
285         traceback.print_exc()
286         sys.exit(utils.STGIT_BUG_ERROR)
287
288     sys.exit(ret or utils.STGIT_SUCCESS)
289
290 def main():
291     try:
292         _main()
293     finally:
294         run.finish_logging()