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