Teach "stg assimilate" to repair patch reachability
[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 'edit': 'edit',
72 'export': 'export',
73 'files': 'files',
74 'float': 'float',
75 'fold': 'fold',
76 'goto': 'goto',
77 'hide': 'hide',
78 'id': 'id',
79 'import': 'imprt',
80 'init': 'init',
81 'log': 'log',
82 'mail': 'mail',
83 'new': 'new',
84 'patches': 'patches',
85 'pick': 'pick',
86 'pop': 'pop',
87 'pull': 'pull',
88 'push': 'push',
89 'rebase': 'rebase',
90 'refresh': 'refresh',
91 'rename': 'rename',
92 'resolved': 'resolved',
93 'rm': 'rm',
94 'series': 'series',
95 'show': 'show',
96 'sink': 'sink',
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 'clean',
115 'commit',
116 'float',
117 'goto',
118 'hide',
119 'init',
120 'patches',
121 'pop',
122 'pull',
123 'push',
124 'rebase',
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 'add',
150 'cp',
151 'diff',
152 'resolved',
153 'rm',
154 'status',
155 )
156
157 def _print_helpstring(cmd):
158 print ' ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
159
160 def print_help():
161 print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
162 print
163 print 'Generic commands:'
164 print ' help print the detailed command usage'
165 print ' version display version information'
166 print ' copyright display copyright information'
167 # unclassified commands if any
168 cmds = commands.keys()
169 cmds.sort()
170 for cmd in cmds:
171 if not cmd in repocommands and not cmd in stackcommands \
172 and not cmd in patchcommands and not cmd in wccommands:
173 _print_helpstring(cmd)
174 print
175
176 print 'Repository commands:'
177 for cmd in repocommands:
178 _print_helpstring(cmd)
179 print
180
181 print 'Stack commands:'
182 for cmd in stackcommands:
183 _print_helpstring(cmd)
184 print
185
186 print 'Patch commands:'
187 for cmd in patchcommands:
188 _print_helpstring(cmd)
189 print
190
191 print 'Working-copy commands:'
192 for cmd in wccommands:
193 _print_helpstring(cmd)
194
195 #
196 # The main function (command dispatcher)
197 #
198 def main():
199 """The main function
200 """
201 global prog
202
203 prog = os.path.basename(sys.argv[0])
204
205 if len(sys.argv) < 2:
206 print >> sys.stderr, 'usage: %s <command>' % prog
207 print >> sys.stderr, \
208 ' Try "%s --help" for a list of supported commands' % prog
209 sys.exit(1)
210
211 cmd = sys.argv[1]
212
213 if cmd in ['-h', '--help']:
214 if len(sys.argv) >= 3:
215 cmd = commands.canonical_cmd(sys.argv[2])
216 sys.argv[2] = '--help'
217 else:
218 print_help()
219 sys.exit(0)
220 if cmd == 'help':
221 if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
222 cmd = commands.canonical_cmd(sys.argv[2])
223 if not cmd in commands:
224 out.error('%s help: "%s" command unknown' % (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 cmd = commands.canonical_cmd(cmd)
248 sys.argv[0] += ' %s' % cmd
249 del(sys.argv[1])
250
251 command = commands[cmd]
252 usage = command.usage.split('\n')[0].strip()
253 parser = OptionParser(usage = usage, option_list = command.options)
254 options, args = parser.parse_args()
255
256 # These modules are only used from this point onwards and do not
257 # need to be imported earlier
258 from stgit.config import config_setup
259 from ConfigParser import ParsingError, NoSectionError
260 from stgit.stack import Series, StackException
261 from stgit.git import GitException
262 from stgit.commands.common import CmdException
263 from stgit.gitmergeonefile import GitMergeException
264 from stgit.utils import EditorException
265
266 try:
267 debug_level = int(os.environ['STGIT_DEBUG_LEVEL'])
268 except KeyError:
269 debug_level = 0
270 except ValueError:
271 out.error('Invalid STGIT_DEBUG_LEVEL environment variable')
272 sys.exit(1)
273
274 try:
275 config_setup()
276
277 # 'clone' doesn't expect an already initialised GIT tree. A Series
278 # object will be created after the GIT tree is cloned
279 if cmd != 'clone':
280 if hasattr(options, 'branch') and options.branch:
281 command.crt_series = Series(options.branch)
282 else:
283 command.crt_series = Series()
284 stgit.commands.common.crt_series = command.crt_series
285
286 command.func(parser, options, args)
287 except (IOError, ParsingError, NoSectionError, CmdException,
288 StackException, GitException, GitMergeException,
289 EditorException), err:
290 print >> sys.stderr, '%s %s: %s' % (prog, cmd, err)
291 if debug_level > 0:
292 raise
293 else:
294 sys.exit(2)
295 except KeyboardInterrupt:
296 sys.exit(1)
297
298 sys.exit(0)