stg import now extracts Message-ID header
[stgit.git] / stgit / main.py
blob0dc4e54b8303b5d6e45a86864371258d336de6e9
1 """Basic quilt-like functionality"""
3 import os
4 import sys
5 import traceback
7 import stgit.commands
8 from stgit import argparse, get_version, run, utils
9 from stgit.compat import environ_get, fsdecode_utf8
10 from stgit.config import config
11 from stgit.out import out
12 from stgit.pager import pager
14 __copyright__ = """
15 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
17 This program is free software; you can redistribute it and/or modify
18 it under the terms of the GNU General Public License version 2 as
19 published by the Free Software Foundation.
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, see http://www.gnu.org/licenses/.
28 """
31 class CommandAlias:
32 def __init__(self, name, command):
33 self._command = command
34 self.__name__ = name
35 self.usage = ['<arguments>']
36 self.help = 'Alias for "%s <arguments>".' % self._command
37 self.options = []
39 def func(self, args):
40 cmd = self._command.split() + args
41 p = run.Run(*cmd)
42 p.discard_exitcode().run()
43 return p.exitcode
46 def is_cmd_alias(cmd):
47 return isinstance(cmd, CommandAlias)
50 def append_alias_commands(cmd_list):
51 for (name, command) in config.getstartswith('stgit.alias.'):
52 name = utils.strip_prefix('stgit.alias.', name)
53 cmd_list.append((name, CommandAlias(name, command), 'Alias commands', command))
56 class Commands(dict):
57 """Commands class. It performs on-demand module loading"""
59 def canonical_cmd(self, key):
60 """Return the canonical name for a possibly-shortened command name."""
61 candidates = [cmd for cmd in self if cmd.startswith(key)]
63 if not candidates:
64 out.error(
65 'Unknown command: %s' % key,
66 'Try "%s help" for a list of supported commands' % sys.argv[0],
68 sys.exit(utils.STGIT_GENERAL_ERROR)
69 elif len(candidates) == 1:
70 return candidates[0]
71 elif key in candidates:
72 return key
73 else:
74 out.error(
75 'Ambiguous command: %s' % key,
76 'Candidates are: %s' % ', '.join(candidates),
78 sys.exit(utils.STGIT_GENERAL_ERROR)
80 def __getitem__(self, key):
81 cmd_mod = self.get(key) or self.get(self.canonical_cmd(key))
82 if is_cmd_alias(cmd_mod):
83 return cmd_mod
84 else:
85 return stgit.commands.get_command(cmd_mod)
88 cmd_list = stgit.commands.get_commands()
89 append_alias_commands(cmd_list)
90 commands = Commands((cmd, mod) for cmd, mod, _, _ in cmd_list)
93 def print_help():
94 print('usage: %s <command> [options]' % os.path.basename(sys.argv[0]))
95 print()
96 print('Generic commands:')
97 print(' help print the detailed command usage')
98 print(' version display version information')
99 print(' copyright display copyright information')
100 print()
101 stgit.commands.pretty_command_list(cmd_list, sys.stdout)
104 def _main():
105 sys.argv[:] = list(map(fsdecode_utf8, sys.argv))
106 prog = sys.argv[0] = 'stg'
108 if len(sys.argv) < 2:
109 print('usage: %s <command>' % prog, file=sys.stderr)
110 print(
111 ' Try "%s --help" for a list of supported commands' % prog, file=sys.stderr
113 sys.exit(utils.STGIT_GENERAL_ERROR)
115 cmd = sys.argv[1]
117 if cmd in ['-h', '--help']:
118 if len(sys.argv) >= 3:
119 cmd = commands.canonical_cmd(sys.argv[2])
120 sys.argv[2] = '--help'
121 else:
122 print_help()
123 sys.exit(utils.STGIT_SUCCESS)
124 if cmd == 'help':
125 if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
126 cmd = commands.canonical_cmd(sys.argv[2])
127 sys.argv[0] += ' %s' % cmd
128 command = commands[cmd]
129 parser = argparse.make_option_parser(command)
130 if is_cmd_alias(command):
131 parser.remove_option('-h')
132 pager(parser.format_help().encode())
133 else:
134 print_help()
135 sys.exit(utils.STGIT_SUCCESS)
136 if cmd in ['-v', '--version', 'version']:
137 print('Stacked Git %s' % get_version())
138 os.system('git --version')
139 print('Python version %s' % sys.version)
140 sys.exit(utils.STGIT_SUCCESS)
141 if cmd in ['copyright']:
142 print(__copyright__)
143 sys.exit(utils.STGIT_SUCCESS)
145 # re-build the command line arguments
146 cmd = commands.canonical_cmd(cmd)
147 sys.argv[0] += ' %s' % cmd
148 del sys.argv[1]
150 command = commands[cmd]
151 if is_cmd_alias(command):
152 sys.exit(command.func(sys.argv[1:]))
154 parser = argparse.make_option_parser(command)
156 # These modules are only used from this point onwards and do not
157 # need to be imported earlier
158 from configparser import NoSectionError, ParsingError
160 from stgit.config import config_setup
161 from stgit.exception import StgException
162 from stgit.lib.git import MergeConflictException
164 try:
165 debug_level = int(environ_get('STGIT_DEBUG_LEVEL', 0))
166 except ValueError:
167 out.error('Invalid STGIT_DEBUG_LEVEL environment variable')
168 sys.exit(utils.STGIT_GENERAL_ERROR)
170 try:
171 (options, args) = parser.parse_args()
172 command.directory.setup()
173 config_setup()
174 ret = command.func(parser, options, args)
175 except MergeConflictException as err:
176 if debug_level > 1:
177 traceback.print_exc(file=sys.stderr)
178 for conflict in err.conflicts:
179 out.err(conflict)
180 sys.exit(utils.STGIT_CONFLICT)
181 except (StgException, IOError, ParsingError, NoSectionError) as err:
182 if debug_level > 0:
183 traceback.print_exc(file=sys.stderr)
184 out.error(str(err), title='%s %s' % (prog, cmd))
185 sys.exit(utils.STGIT_COMMAND_ERROR)
186 except SystemExit:
187 # Triggered by the option parser when it finds bad commandline
188 # parameters.
189 sys.exit(utils.STGIT_COMMAND_ERROR)
190 except KeyboardInterrupt:
191 sys.exit(utils.STGIT_GENERAL_ERROR)
192 except BaseException:
193 out.error('Unhandled exception:')
194 traceback.print_exc(file=sys.stderr)
195 sys.exit(utils.STGIT_BUG_ERROR)
197 sys.exit(ret or utils.STGIT_SUCCESS)
200 def main():
201 try:
202 _main()
203 finally:
204 run.finish_logging()