chiark / gitweb /
2c8716b2d850c070597c1b4ca987a299c7b69330
[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 from stgit.out import *
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(1)
43         elif len(candidates) > 1:
44             out.error('Ambiguous command: %s' % key,
45                       'Candidates are: %s' % ', '.join(candidates))
46             sys.exit(1)
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     'add':              'add',
62     'applied':          'applied',
63     'assimilate':       'assimilate',
64     'branch':           'branch',
65     'delete':           'delete',
66     'diff':             'diff',
67     'clean':            'clean',
68     'clone':            'clone',
69     'commit':           'commit',
70     'cp':               'copy',
71     'edit':             'edit',
72     'export':           'export',
73     'files':            'files',
74     'float':            'float',
75     'fold':             'fold',
76     'goto':             'goto',
77     'hide':             'hide',
78     'id':               'id',
79     'import':           'imprt',
80     'init':             'init',
81     'log':              'log',
82     'mail':             'mail',
83     'new':              'new',
84     'patches':          'patches',
85     'pick':             'pick',
86     'pop':              'pop',
87     'pull':             'pull',
88     'push':             'push',
89     'rebase':           'rebase',
90     'refresh':          'refresh',
91     'rename':           'rename',
92     'resolved':         'resolved',
93     'rm':               'rm',
94     'series':           'series',
95     'show':             'show',
96     'sink':             'sink',
97     'status':           'status',
98     'sync':             'sync',
99     'top':              'top',
100     'unapplied':        'unapplied',
101     'uncommit':         'uncommit',
102     'unhide':           'unhide'
103     })
104
105 # classification: repository, stack, patch, working copy
106 repocommands = (
107     'clone',
108     'id',
109     )
110 stackcommands = (
111     'applied',
112     'assimilate',
113     'branch',
114     'clean',
115     'commit',
116     'float',
117     'goto',
118     'hide',
119     'init',
120     'patches',
121     'pop',
122     'pull',
123     'push',
124     'rebase',
125     'series',
126     'sink',
127     'top',
128     'unapplied',
129     'uncommit',
130     'unhide',
131     )
132 patchcommands = (
133     'delete',
134     'edit',
135     'export',
136     'files',
137     'fold',
138     'import',
139     'log',
140     'mail',
141     'new',
142     'pick',
143     'refresh',
144     'rename',
145     'show',
146     'sync',
147     )
148 wccommands = (
149     'add',
150     'cp',
151     'diff',
152     'resolved',
153     'rm',
154     'status',
155     )
156
157 def _print_helpstring(cmd):
158     print '  ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
159     
160 def print_help():
161     print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
162     print
163     print 'Generic commands:'
164     print '  help        print the detailed command usage'
165     print '  version     display version information'
166     print '  copyright   display copyright information'
167     # unclassified commands if any
168     cmds = commands.keys()
169     cmds.sort()
170     for cmd in cmds:
171         if not cmd in repocommands and not cmd in stackcommands \
172                and not cmd in patchcommands and not cmd in wccommands:
173             _print_helpstring(cmd)
174     print
175
176     print 'Repository commands:'
177     for cmd in repocommands:
178         _print_helpstring(cmd)
179     print
180     
181     print 'Stack commands:'
182     for cmd in stackcommands:
183         _print_helpstring(cmd)
184     print
185
186     print 'Patch commands:'
187     for cmd in patchcommands:
188         _print_helpstring(cmd)
189     print
190
191     print 'Working-copy commands:'
192     for cmd in wccommands:
193         _print_helpstring(cmd)
194
195 #
196 # The main function (command dispatcher)
197 #
198 def main():
199     """The main function
200     """
201     global prog
202
203     prog = os.path.basename(sys.argv[0])
204
205     if len(sys.argv) < 2:
206         print >> sys.stderr, 'usage: %s <command>' % prog
207         print >> sys.stderr, \
208               '  Try "%s --help" for a list of supported commands' % prog
209         sys.exit(1)
210
211     cmd = sys.argv[1]
212
213     if cmd in ['-h', '--help']:
214         if len(sys.argv) >= 3:
215             cmd = commands.canonical_cmd(sys.argv[2])
216             sys.argv[2] = '--help'
217         else:
218             print_help()
219             sys.exit(0)
220     if cmd == 'help':
221         if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
222             cmd = commands.canonical_cmd(sys.argv[2])
223             if not cmd in commands:
224                 out.error('%s help: "%s" command unknown' % (prog, cmd))
225                 sys.exit(1)
226
227             sys.argv[0] += ' %s' % cmd
228             command = commands[cmd]
229             parser = OptionParser(usage = command.usage,
230                                   option_list = command.options)
231             from pydoc import pager
232             pager(parser.format_help())
233         else:
234             print_help()
235         sys.exit(0)
236     if cmd in ['-v', '--version', 'version']:
237         from stgit.version import version
238         print 'Stacked GIT %s' % version
239         os.system('git --version')
240         print 'Python version %s' % sys.version
241         sys.exit(0)
242     if cmd in ['copyright']:
243         print __copyright__
244         sys.exit(0)
245
246     # re-build the command line arguments
247     cmd = commands.canonical_cmd(cmd)
248     sys.argv[0] += ' %s' % cmd
249     del(sys.argv[1])
250
251     command = commands[cmd]
252     usage = command.usage.split('\n')[0].strip()
253     parser = OptionParser(usage = usage, option_list = command.options)
254     options, args = parser.parse_args()
255
256     # These modules are only used from this point onwards and do not
257     # need to be imported earlier
258     from stgit.config import config_setup
259     from ConfigParser import ParsingError, NoSectionError
260     from stgit.stack import Series, StackException
261     from stgit.git import GitException
262     from stgit.commands.common import CmdException
263     from stgit.gitmergeonefile import GitMergeException
264     from stgit.utils import EditorException
265
266     try:
267         debug_level = int(os.environ['STGIT_DEBUG_LEVEL'])
268     except KeyError:
269         debug_level = 0
270     except ValueError:
271         out.error('Invalid STGIT_DEBUG_LEVEL environment variable')
272         sys.exit(1)
273
274     try:
275         config_setup()
276
277         # 'clone' doesn't expect an already initialised GIT tree. A Series
278         # object will be created after the GIT tree is cloned
279         if cmd != 'clone':
280             if hasattr(options, 'branch') and options.branch:
281                 command.crt_series = Series(options.branch)
282             else:
283                 command.crt_series = Series()
284             stgit.commands.common.crt_series = command.crt_series
285
286         command.func(parser, options, args)
287     except (IOError, ParsingError, NoSectionError, CmdException,
288             StackException, GitException, GitMergeException,
289             EditorException), err:
290         print >> sys.stderr, '%s %s: %s' % (prog, cmd, err)
291         if debug_level > 0:
292             raise
293         else:
294             sys.exit(2)
295     except KeyboardInterrupt:
296         sys.exit(1)
297
298     sys.exit(0)