chiark / gitweb /
Revert 'Changed rebasing safety check to look for reachability of
[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.utils import out
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:
290             raise
291         else:
292             sys.exit(2)
293     except KeyboardInterrupt:
294         sys.exit(1)
295
296     sys.exit(0)