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