chiark / gitweb /
49089e6e61193c0713614fb0dd507c20243b20f5
[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
26 #
27 # The commands map
28 #
29 class Commands(dict):
30     """Commands class. It performs on-demand module loading
31     """
32     def canonical_cmd(self, key):
33         """Return the canonical name for a possibly-shortenned
34         command name.
35         """
36         candidates = [cmd for cmd in self.keys() if cmd.startswith(key)]
37
38         if not candidates:
39             print >> sys.stderr, 'Unknown command: %s' % key
40             print >> sys.stderr, '  Try "%s help" for a list of ' \
41                   'supported commands' % prog
42             sys.exit(1)
43         elif len(candidates) > 1:
44             print >> sys.stderr, 'Ambiguous command: %s' % key
45             print >> sys.stderr, '  Candidates are: %s' \
46                   % ', '.join(candidates)
47             sys.exit(1)
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     'assimilate':       'assimilate',
65     'branch':           'branch',
66     'delete':           'delete',
67     'diff':             'diff',
68     'clean':            'clean',
69     'clone':            'clone',
70     'commit':           'commit',
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     'status':           'status',
96     'sync':             'sync',
97     'top':              'top',
98     'unapplied':        'unapplied',
99     'uncommit':         'uncommit',
100     'unhide':           'unhide'
101     })
102
103 # classification: repository, stack, patch, working copy
104 repocommands = (
105     'branch',
106     'clone',
107     'id',
108     'pull'
109     )
110 stackcommands = (
111     'applied',
112     'assimilate',
113     'clean',
114     'commit',
115     'float',
116     'goto',
117     'hide',
118     'init',
119     'pop',
120     'push',
121     'rebase',
122     'series',
123     'top',
124     'unapplied',
125     'uncommit',
126     'unhide'
127     )
128 patchcommands = (
129     'delete',
130     'export',
131     'files',
132     'fold',
133     'import',
134     'log',
135     'mail',
136     'new',
137     'pick',
138     'refresh',
139     'rename',
140     'show',
141     'sync'
142     )
143 wccommands = (
144     'add',
145     'diff',
146     'patches',
147     'resolved',
148     'rm',
149     'status'
150     )
151
152 def _print_helpstring(cmd):
153     print '  ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
154     
155 def print_help():
156     print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
157     print
158     print 'Generic commands:'
159     print '  help        print the detailed command usage'
160     print '  version     display version information'
161     print '  copyright   display copyright information'
162     # unclassified commands if any
163     cmds = commands.keys()
164     cmds.sort()
165     for cmd in cmds:
166         if not cmd in repocommands and not cmd in stackcommands \
167                and not cmd in patchcommands and not cmd in wccommands:
168             _print_helpstring(cmd)
169     print
170
171     print 'Repository commands:'
172     for cmd in repocommands:
173         _print_helpstring(cmd)
174     print
175     
176     print 'Stack commands:'
177     for cmd in stackcommands:
178         _print_helpstring(cmd)
179     print
180
181     print 'Patch commands:'
182     for cmd in patchcommands:
183         _print_helpstring(cmd)
184     print
185
186     print 'Working-copy commands:'
187     for cmd in wccommands:
188         _print_helpstring(cmd)
189
190 #
191 # The main function (command dispatcher)
192 #
193 def main():
194     """The main function
195     """
196     global prog
197
198     prog = os.path.basename(sys.argv[0])
199
200     if len(sys.argv) < 2:
201         print >> sys.stderr, 'usage: %s <command>' % prog
202         print >> sys.stderr, \
203               '  Try "%s --help" for a list of supported commands' % prog
204         sys.exit(1)
205
206     cmd = sys.argv[1]
207
208     if cmd in ['-h', '--help']:
209         if len(sys.argv) >= 3:
210             cmd = commands.canonical_cmd(sys.argv[2])
211             sys.argv[2] = '--help'
212         else:
213             print_help()
214             sys.exit(0)
215     if cmd == 'help':
216         if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
217             cmd = commands.canonical_cmd(sys.argv[2])
218             if not cmd in commands:
219                 print >> sys.stderr, '%s help: "%s" command unknown' \
220                       % (prog, cmd)
221                 sys.exit(1)
222
223             sys.argv[0] += ' %s' % cmd
224             command = commands[cmd]
225             parser = OptionParser(usage = command.usage,
226                                   option_list = command.options)
227             from pydoc import pager
228             pager(parser.format_help())
229         else:
230             print_help()
231         sys.exit(0)
232     if cmd in ['-v', '--version', 'version']:
233         from stgit.version import version
234         print 'Stacked GIT %s' % version
235         os.system('git --version')
236         print 'Python version %s' % sys.version
237         sys.exit(0)
238     if cmd in ['copyright']:
239         print __copyright__
240         sys.exit(0)
241
242     # re-build the command line arguments
243     sys.argv[0] += ' %s' % commands.canonical_cmd(cmd)
244     del(sys.argv[1])
245
246     command = commands[cmd]
247     usage = command.usage.split('\n')[0].strip()
248     parser = OptionParser(usage = usage, option_list = command.options)
249     options, args = parser.parse_args()
250
251     # These modules are only used from this point onwards and do not
252     # need to be imported earlier
253     from stgit.config import config_setup
254     from ConfigParser import ParsingError, NoSectionError
255     from stgit.stack import Series, StackException
256     from stgit.git import GitException
257     from stgit.commands.common import CmdException
258     from stgit.gitmergeonefile import GitMergeException
259
260     try:
261         config_setup()
262
263         # 'clone' doesn't expect an already initialised GIT tree. A Series
264         # object will be created after the GIT tree is cloned
265         if cmd != 'clone':
266             if hasattr(options, 'branch') and options.branch:
267                 command.crt_series = Series(options.branch)
268             else:
269                 command.crt_series = Series()
270             stgit.commands.common.crt_series = command.crt_series
271
272         command.func(parser, options, args)
273     except (IOError, ParsingError, NoSectionError, CmdException,
274             StackException, GitException, GitMergeException), err:
275         print >> sys.stderr, '%s %s: %s' % (prog, cmd, err)
276         sys.exit(2)
277     except KeyboardInterrupt:
278         sys.exit(1)
279
280     sys.exit(0)