chiark / gitweb /
95e25f8795648d33052b2607ebb73d79ec7999a6
[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 from stgit import utils
27
28 #
29 # The commands map
30 #
31 class Commands(dict):
32     """Commands class. It performs on-demand module loading
33     """
34     def canonical_cmd(self, key):
35         """Return the canonical name for a possibly-shortenned
36         command name.
37         """
38         candidates = [cmd for cmd in self.keys() if cmd.startswith(key)]
39
40         if not candidates:
41             out.error('Unknown command: %s' % key,
42                       'Try "%s help" for a list of supported commands' % prog)
43             sys.exit(utils.STGIT_GENERAL_ERROR)
44         elif len(candidates) > 1:
45             out.error('Ambiguous command: %s' % key,
46                       'Candidates are: %s' % ', '.join(candidates))
47             sys.exit(utils.STGIT_GENERAL_ERROR)
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     '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     'repair':           'repair',
93     'resolved':         'resolved',
94     'rm':               'rm',
95     'series':           'series',
96     'show':             'show',
97     'sink':             'sink',
98     'status':           'status',
99     'sync':             'sync',
100     'top':              'top',
101     'unapplied':        'unapplied',
102     'uncommit':         'uncommit',
103     'unhide':           'unhide'
104     })
105
106 # classification: repository, stack, patch, working copy
107 repocommands = (
108     'clone',
109     'id',
110     )
111 stackcommands = (
112     'applied',
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     'repair',
126     'series',
127     'sink',
128     'top',
129     'unapplied',
130     'uncommit',
131     'unhide',
132     )
133 patchcommands = (
134     'delete',
135     'edit',
136     'export',
137     'files',
138     'fold',
139     'import',
140     'log',
141     'mail',
142     'new',
143     'pick',
144     'refresh',
145     'rename',
146     'show',
147     'sync',
148     )
149 wccommands = (
150     'add',
151     'cp',
152     'diff',
153     'resolved',
154     'rm',
155     'status',
156     )
157
158 def _print_helpstring(cmd):
159     print '  ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
160     
161 def print_help():
162     print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
163     print
164     print 'Generic commands:'
165     print '  help        print the detailed command usage'
166     print '  version     display version information'
167     print '  copyright   display copyright information'
168     # unclassified commands if any
169     cmds = commands.keys()
170     cmds.sort()
171     for cmd in cmds:
172         if not cmd in repocommands and not cmd in stackcommands \
173                and not cmd in patchcommands and not cmd in wccommands:
174             _print_helpstring(cmd)
175     print
176
177     print 'Repository commands:'
178     for cmd in repocommands:
179         _print_helpstring(cmd)
180     print
181     
182     print 'Stack commands:'
183     for cmd in stackcommands:
184         _print_helpstring(cmd)
185     print
186
187     print 'Patch commands:'
188     for cmd in patchcommands:
189         _print_helpstring(cmd)
190     print
191
192     print 'Working-copy commands:'
193     for cmd in wccommands:
194         _print_helpstring(cmd)
195
196 #
197 # The main function (command dispatcher)
198 #
199 def main():
200     """The main function
201     """
202     global prog
203
204     prog = os.path.basename(sys.argv[0])
205
206     if len(sys.argv) < 2:
207         print >> sys.stderr, 'usage: %s <command>' % prog
208         print >> sys.stderr, \
209               '  Try "%s --help" for a list of supported commands' % prog
210         sys.exit(utils.STGIT_GENERAL_ERROR)
211
212     cmd = sys.argv[1]
213
214     if cmd in ['-h', '--help']:
215         if len(sys.argv) >= 3:
216             cmd = commands.canonical_cmd(sys.argv[2])
217             sys.argv[2] = '--help'
218         else:
219             print_help()
220             sys.exit(utils.STGIT_SUCCESS)
221     if cmd == 'help':
222         if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
223             cmd = commands.canonical_cmd(sys.argv[2])
224             if not cmd in commands:
225                 out.error('%s help: "%s" command unknown' % (prog, cmd))
226                 sys.exit(utils.STGIT_GENERAL_ERROR)
227
228             sys.argv[0] += ' %s' % cmd
229             command = commands[cmd]
230             parser = OptionParser(usage = command.usage,
231                                   option_list = command.options)
232             from pydoc import pager
233             pager(parser.format_help())
234         else:
235             print_help()
236         sys.exit(utils.STGIT_SUCCESS)
237     if cmd in ['-v', '--version', 'version']:
238         from stgit.version import version
239         print 'Stacked GIT %s' % version
240         os.system('git --version')
241         print 'Python version %s' % sys.version
242         sys.exit(utils.STGIT_SUCCESS)
243     if cmd in ['copyright']:
244         print __copyright__
245         sys.exit(utils.STGIT_SUCCESS)
246
247     # re-build the command line arguments
248     cmd = commands.canonical_cmd(cmd)
249     sys.argv[0] += ' %s' % cmd
250     del(sys.argv[1])
251
252     command = commands[cmd]
253     usage = command.usage.split('\n')[0].strip()
254     parser = OptionParser(usage = usage, option_list = command.options)
255     options, args = parser.parse_args()
256     directory = command.directory
257
258     # These modules are only used from this point onwards and do not
259     # need to be imported earlier
260     from stgit.exception import StgException
261     from stgit.config import config_setup
262     from ConfigParser import ParsingError, NoSectionError
263     from stgit.stack import Series
264
265     try:
266         debug_level = int(os.environ.get('STGIT_DEBUG_LEVEL', 0))
267     except ValueError:
268         out.error('Invalid STGIT_DEBUG_LEVEL environment variable')
269         sys.exit(utils.STGIT_GENERAL_ERROR)
270
271     try:
272         directory.setup()
273         config_setup()
274
275         # Some commands don't (always) need an initialized series.
276         if directory.needs_current_series:
277             if hasattr(options, 'branch') and options.branch:
278                 command.crt_series = Series(options.branch)
279             else:
280                 command.crt_series = Series()
281
282         command.func(parser, options, args)
283     except (StgException, IOError, ParsingError, NoSectionError), err:
284         out.error(str(err), title = '%s %s' % (prog, cmd))
285         if debug_level > 0:
286             raise
287         else:
288             sys.exit(utils.STGIT_COMMAND_ERROR)
289     except KeyboardInterrupt:
290         sys.exit(utils.STGIT_GENERAL_ERROR)
291
292     sys.exit(utils.STGIT_SUCCESS)