Name the exit codes to improve legibility
[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)