aa1f8ef891f4baf31816796cf517a340f0bbde9c
[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 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 'applied': 'applied',
63 'branch': 'branch',
64 'delete': 'delete',
65 'diff': 'diff',
66 'clean': 'clean',
67 'clone': 'clone',
68 'coalesce': 'coalesce',
69 'commit': 'commit',
70 'edit': 'edit',
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 'repair': 'repair',
92 'resolved': 'resolved',
93 'series': 'series',
94 'show': 'show',
95 'sink': 'sink',
96 'status': 'status',
97 'sync': 'sync',
98 'top': 'top',
99 'unapplied': 'unapplied',
100 'uncommit': 'uncommit',
101 'unhide': 'unhide'
102 })
103
104 # classification: repository, stack, patch, working copy
105 repocommands = (
106 'clone',
107 'id',
108 )
109 stackcommands = (
110 'applied',
111 'branch',
112 'clean',
113 'coalesce',
114 'commit',
115 'float',
116 'goto',
117 'hide',
118 'init',
119 'patches',
120 'pop',
121 'pull',
122 'push',
123 'rebase',
124 'repair',
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 'diff',
150 'resolved',
151 'status',
152 )
153
154 def _print_helpstring(cmd):
155 print ' ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
156
157 def print_help():
158 print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
159 print
160 print 'Generic commands:'
161 print ' help print the detailed command usage'
162 print ' version display version information'
163 print ' copyright display copyright information'
164 # unclassified commands if any
165 cmds = commands.keys()
166 cmds.sort()
167 for cmd in cmds:
168 if not cmd in repocommands and not cmd in stackcommands \
169 and not cmd in patchcommands and not cmd in wccommands:
170 _print_helpstring(cmd)
171 print
172
173 print 'Repository commands:'
174 for cmd in repocommands:
175 _print_helpstring(cmd)
176 print
177
178 print 'Stack commands:'
179 for cmd in stackcommands:
180 _print_helpstring(cmd)
181 print
182
183 print 'Patch commands:'
184 for cmd in patchcommands:
185 _print_helpstring(cmd)
186 print
187
188 print 'Working-copy commands:'
189 for cmd in wccommands:
190 _print_helpstring(cmd)
191
192 #
193 # The main function (command dispatcher)
194 #
195 def main():
196 """The main function
197 """
198 global prog
199
200 prog = os.path.basename(sys.argv[0])
201
202 if len(sys.argv) < 2:
203 print >> sys.stderr, 'usage: %s <command>' % prog
204 print >> sys.stderr, \
205 ' Try "%s --help" for a list of supported commands' % prog
206 sys.exit(utils.STGIT_GENERAL_ERROR)
207
208 cmd = sys.argv[1]
209
210 if cmd in ['-h', '--help']:
211 if len(sys.argv) >= 3:
212 cmd = commands.canonical_cmd(sys.argv[2])
213 sys.argv[2] = '--help'
214 else:
215 print_help()
216 sys.exit(utils.STGIT_SUCCESS)
217 if cmd == 'help':
218 if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
219 cmd = commands.canonical_cmd(sys.argv[2])
220 if not cmd in commands:
221 out.error('%s help: "%s" command unknown' % (prog, cmd))
222 sys.exit(utils.STGIT_GENERAL_ERROR)
223
224 sys.argv[0] += ' %s' % cmd
225 command = commands[cmd]
226 parser = OptionParser(usage = command.usage,
227 option_list = command.options)
228 from pydoc import pager
229 pager(parser.format_help())
230 else:
231 print_help()
232 sys.exit(utils.STGIT_SUCCESS)
233 if cmd in ['-v', '--version', 'version']:
234 from stgit.version import version
235 print 'Stacked GIT %s' % version
236 os.system('git --version')
237 print 'Python version %s' % sys.version
238 sys.exit(utils.STGIT_SUCCESS)
239 if cmd in ['copyright']:
240 print __copyright__
241 sys.exit(utils.STGIT_SUCCESS)
242
243 # re-build the command line arguments
244 cmd = commands.canonical_cmd(cmd)
245 sys.argv[0] += ' %s' % cmd
246 del(sys.argv[1])
247
248 command = commands[cmd]
249 usage = command.usage.split('\n')[0].strip()
250 parser = OptionParser(usage = usage, option_list = command.options)
251 options, args = parser.parse_args()
252 directory = command.directory
253
254 # These modules are only used from this point onwards and do not
255 # need to be imported earlier
256 from stgit.exception import StgException
257 from stgit.config import config_setup
258 from ConfigParser import ParsingError, NoSectionError
259 from stgit.stack import Series
260
261 try:
262 debug_level = int(os.environ.get('STGIT_DEBUG_LEVEL', 0))
263 except ValueError:
264 out.error('Invalid STGIT_DEBUG_LEVEL environment variable')
265 sys.exit(utils.STGIT_GENERAL_ERROR)
266
267 try:
268 directory.setup()
269 config_setup()
270
271 # Some commands don't (always) need an initialized series.
272 if directory.needs_current_series:
273 if hasattr(options, 'branch') and options.branch:
274 command.crt_series = Series(options.branch)
275 else:
276 command.crt_series = Series()
277
278 ret = command.func(parser, options, args)
279 except (StgException, IOError, ParsingError, NoSectionError), err:
280 out.error(str(err), title = '%s %s' % (prog, cmd))
281 if debug_level > 0:
282 traceback.print_exc()
283 sys.exit(utils.STGIT_COMMAND_ERROR)
284 except SystemExit:
285 # Triggered by the option parser when it finds bad commandline
286 # parameters.
287 sys.exit(utils.STGIT_COMMAND_ERROR)
288 except KeyboardInterrupt:
289 sys.exit(utils.STGIT_GENERAL_ERROR)
290 except:
291 out.error('Unhandled exception:')
292 traceback.print_exc()
293 sys.exit(utils.STGIT_BUG_ERROR)
294
295 sys.exit(ret or utils.STGIT_SUCCESS)