##// END OF EJS Templates
Merge with stable
Martin Geisler -
r11601:4d9b4725 merge default
parent child Browse files
Show More
@@ -1,238 +1,238 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import re, glob
10 import re, glob
11 import optparse
11 import optparse
12
12
13 def repquote(m):
13 def repquote(m):
14 t = re.sub(r"\w", "x", m.group('text'))
14 t = re.sub(r"\w", "x", m.group('text'))
15 t = re.sub(r"[^\sx]", "o", t)
15 t = re.sub(r"[^\sx]", "o", t)
16 return m.group('quote') + t + m.group('quote')
16 return m.group('quote') + t + m.group('quote')
17
17
18 def reppython(m):
18 def reppython(m):
19 comment = m.group('comment')
19 comment = m.group('comment')
20 if comment:
20 if comment:
21 return "#" * len(comment)
21 return "#" * len(comment)
22 return repquote(m)
22 return repquote(m)
23
23
24 def repcomment(m):
24 def repcomment(m):
25 return m.group(1) + "#" * len(m.group(2))
25 return m.group(1) + "#" * len(m.group(2))
26
26
27 def repccomment(m):
27 def repccomment(m):
28 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
28 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
29 return m.group(1) + t + "*/"
29 return m.group(1) + t + "*/"
30
30
31 def repcallspaces(m):
31 def repcallspaces(m):
32 t = re.sub(r"\n\s+", "\n", m.group(2))
32 t = re.sub(r"\n\s+", "\n", m.group(2))
33 return m.group(1) + t
33 return m.group(1) + t
34
34
35 def repinclude(m):
35 def repinclude(m):
36 return m.group(1) + "<foo>"
36 return m.group(1) + "<foo>"
37
37
38 def rephere(m):
38 def rephere(m):
39 t = re.sub(r"\S", "x", m.group(2))
39 t = re.sub(r"\S", "x", m.group(2))
40 return m.group(1) + t
40 return m.group(1) + t
41
41
42
42
43 testpats = [
43 testpats = [
44 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
44 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
45 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
45 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
46 (r'^function', "don't use 'function', use old style"),
46 (r'^function', "don't use 'function', use old style"),
47 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
47 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
48 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
48 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
49 (r'^diff.*-\w*N', "don't use 'diff -N'"),
49 (r'^diff.*-\w*N', "don't use 'diff -N'"),
50 (r'(^| )wc[^|]*$', "filter wc output"),
50 (r'(^| )wc[^|]*$', "filter wc output"),
51 (r'head -c', "don't use 'head -c', use 'dd'"),
51 (r'head -c', "don't use 'head -c', use 'dd'"),
52 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
52 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
53 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
53 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
54 (r'printf.*\\x', "don't use printf \\x, use Python"),
54 (r'printf.*\\x', "don't use printf \\x, use Python"),
55 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
55 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
56 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
56 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
57 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
57 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
58 "use egrep for extended grep syntax"),
58 "use egrep for extended grep syntax"),
59 (r'/bin/', "don't use explicit paths for tools"),
59 (r'/bin/', "don't use explicit paths for tools"),
60 (r'\$PWD', "don't use $PWD, use `pwd`"),
60 (r'\$PWD', "don't use $PWD, use `pwd`"),
61 (r'[^\n]\Z', "no trailing newline"),
61 (r'[^\n]\Z', "no trailing newline"),
62 (r'export.*=', "don't export and assign at once"),
62 (r'export.*=', "don't export and assign at once"),
63 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
63 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
64 (r'^source\b', "don't use 'source', use '.'"),
64 (r'^source\b', "don't use 'source', use '.'"),
65 ]
65 ]
66
66
67 testfilters = [
67 testfilters = [
68 (r"( *)(#([^\n]*\S)?)", repcomment),
68 (r"( *)(#([^\n]*\S)?)", repcomment),
69 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
69 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
70 ]
70 ]
71
71
72 pypats = [
72 pypats = [
73 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
73 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
74 "tuple parameter unpacking not available in Python 3+"),
74 "tuple parameter unpacking not available in Python 3+"),
75 (r'lambda\s*\(.*,.*\)',
75 (r'lambda\s*\(.*,.*\)',
76 "tuple parameter unpacking not available in Python 3+"),
76 "tuple parameter unpacking not available in Python 3+"),
77 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
77 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
78 (r'^\s*\t', "don't use tabs"),
78 (r'^\s*\t', "don't use tabs"),
79 (r'\S;\s*\n', "semicolon"),
79 (r'\S;\s*\n', "semicolon"),
80 (r'\w,\w', "missing whitespace after ,"),
80 (r'\w,\w', "missing whitespace after ,"),
81 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
81 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
82 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
82 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
83 (r'.{85}', "line too long"),
83 (r'.{85}', "line too long"),
84 (r'[^\n]\Z', "no trailing newline"),
84 (r'[^\n]\Z', "no trailing newline"),
85 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
85 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
86 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
86 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
87 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
87 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
88 "linebreak after :"),
88 "linebreak after :"),
89 (r'class\s[^(]:', "old-style class, use class foo(object)"),
89 (r'class\s[^(]:', "old-style class, use class foo(object)"),
90 (r'^\s+del\(', "del isn't a function"),
90 (r'^\s+del\(', "del isn't a function"),
91 (r'^\s+except\(', "except isn't a function"),
91 (r'^\s+except\(', "except isn't a function"),
92 (r',]', "unneeded trailing ',' in list"),
92 (r',]', "unneeded trailing ',' in list"),
93 # (r'class\s[A-Z][^\(]*\((?!Exception)',
93 # (r'class\s[A-Z][^\(]*\((?!Exception)',
94 # "don't capitalize non-exception classes"),
94 # "don't capitalize non-exception classes"),
95 # (r'in range\(', "use xrange"),
95 # (r'in range\(', "use xrange"),
96 # (r'^\s*print\s+', "avoid using print in core and extensions"),
96 # (r'^\s*print\s+', "avoid using print in core and extensions"),
97 (r'[\x80-\xff]', "non-ASCII character literal"),
97 (r'[\x80-\xff]', "non-ASCII character literal"),
98 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
98 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
99 (r'^\s*with\s+', "with not available in Python 2.4"),
99 (r'^\s*with\s+', "with not available in Python 2.4"),
100 (r'(?<!def)\s+(any|all|format)\(',
100 (r'(?<!def)\s+(any|all|format)\(',
101 "any/all/format not available in Python 2.4"),
101 "any/all/format not available in Python 2.4"),
102 (r'(?<!def)\s+(callable)\(',
102 (r'(?<!def)\s+(callable)\(',
103 "callable not available in Python 3, use hasattr(f, '__call__')"),
103 "callable not available in Python 3, use hasattr(f, '__call__')"),
104 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
104 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
105 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
105 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
106 # (r'\s\s=', "gratuitous whitespace before ="),
106 # (r'\s\s=', "gratuitous whitespace before ="),
107 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
107 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
108 "missing whitespace around operator"),
108 "missing whitespace around operator"),
109 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
109 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
110 "missing whitespace around operator"),
110 "missing whitespace around operator"),
111 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
111 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
112 "missing whitespace around operator"),
112 "missing whitespace around operator"),
113 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
113 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
114 "wrong whitespace around ="),
114 "wrong whitespace around ="),
115 (r'raise Exception', "don't raise generic exceptions"),
115 (r'raise Exception', "don't raise generic exceptions"),
116 (r'ui\.(status|progress|write|note)\([\'\"]x',
116 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
117 "warning: unwrapped ui message"),
117 "warning: unwrapped ui message"),
118 ]
118 ]
119
119
120 pyfilters = [
120 pyfilters = [
121 (r"""(?msx)(?P<comment>\#.*?$)|
121 (r"""(?msx)(?P<comment>\#.*?$)|
122 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
122 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
123 (?P<text>(([^\\]|\\.)*?))
123 (?P<text>(([^\\]|\\.)*?))
124 (?P=quote))""", reppython),
124 (?P=quote))""", reppython),
125 ]
125 ]
126
126
127 cpats = [
127 cpats = [
128 (r'//', "don't use //-style comments"),
128 (r'//', "don't use //-style comments"),
129 (r'^ ', "don't use spaces to indent"),
129 (r'^ ', "don't use spaces to indent"),
130 (r'\S\t', "don't use tabs except for indent"),
130 (r'\S\t', "don't use tabs except for indent"),
131 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
131 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
132 (r'.{85}', "line too long"),
132 (r'.{85}', "line too long"),
133 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
133 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
134 (r'return\(', "return is not a function"),
134 (r'return\(', "return is not a function"),
135 (r' ;', "no space before ;"),
135 (r' ;', "no space before ;"),
136 (r'\w+\* \w+', "use int *foo, not int* foo"),
136 (r'\w+\* \w+', "use int *foo, not int* foo"),
137 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
137 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
138 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
138 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
139 (r'\w,\w', "missing whitespace after ,"),
139 (r'\w,\w', "missing whitespace after ,"),
140 (r'\w[+/*]\w', "missing whitespace in expression"),
140 (r'\w[+/*]\w', "missing whitespace in expression"),
141 (r'^#\s+\w', "use #foo, not # foo"),
141 (r'^#\s+\w', "use #foo, not # foo"),
142 (r'[^\n]\Z', "no trailing newline"),
142 (r'[^\n]\Z', "no trailing newline"),
143 ]
143 ]
144
144
145 cfilters = [
145 cfilters = [
146 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
146 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
147 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
147 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
148 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
148 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
149 (r'(\()([^)]+\))', repcallspaces),
149 (r'(\()([^)]+\))', repcallspaces),
150 ]
150 ]
151
151
152 checks = [
152 checks = [
153 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
153 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
154 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
154 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
155 ('c', r'.*\.c$', cfilters, cpats),
155 ('c', r'.*\.c$', cfilters, cpats),
156 ]
156 ]
157
157
158 class norepeatlogger(object):
158 class norepeatlogger(object):
159 def __init__(self):
159 def __init__(self):
160 self._lastseen = None
160 self._lastseen = None
161
161
162 def log(self, fname, lineno, line, msg):
162 def log(self, fname, lineno, line, msg):
163 """print error related a to given line of a given file.
163 """print error related a to given line of a given file.
164
164
165 The faulty line will also be printed but only once in the case
165 The faulty line will also be printed but only once in the case
166 of multiple errors.
166 of multiple errors.
167
167
168 :fname: filename
168 :fname: filename
169 :lineno: line number
169 :lineno: line number
170 :line: actual content of the line
170 :line: actual content of the line
171 :msg: error message
171 :msg: error message
172 """
172 """
173 msgid = fname, lineno, line
173 msgid = fname, lineno, line
174 if msgid != self._lastseen:
174 if msgid != self._lastseen:
175 print "%s:%d:" % (fname, lineno)
175 print "%s:%d:" % (fname, lineno)
176 print " > %s" % line
176 print " > %s" % line
177 self._lastseen = msgid
177 self._lastseen = msgid
178 print " " + msg
178 print " " + msg
179
179
180 _defaultlogger = norepeatlogger()
180 _defaultlogger = norepeatlogger()
181
181
182 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False):
182 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False):
183 """checks style and portability of a given file
183 """checks style and portability of a given file
184
184
185 :f: filepath
185 :f: filepath
186 :logfunc: function used to report error
186 :logfunc: function used to report error
187 logfunc(filename, linenumber, linecontent, errormessage)
187 logfunc(filename, linenumber, linecontent, errormessage)
188 :maxerr: number of error to display before arborting.
188 :maxerr: number of error to display before arborting.
189 Set to None (default) to report all errors
189 Set to None (default) to report all errors
190
190
191 return True if no error is found, False otherwise.
191 return True if no error is found, False otherwise.
192 """
192 """
193 result = True
193 result = True
194 for name, match, filters, pats in checks:
194 for name, match, filters, pats in checks:
195 fc = 0
195 fc = 0
196 if not re.match(match, f):
196 if not re.match(match, f):
197 continue
197 continue
198 pre = post = open(f).read()
198 pre = post = open(f).read()
199 if "no-" + "check-code" in pre:
199 if "no-" + "check-code" in pre:
200 break
200 break
201 for p, r in filters:
201 for p, r in filters:
202 post = re.sub(p, r, post)
202 post = re.sub(p, r, post)
203 # print post # uncomment to show filtered version
203 # print post # uncomment to show filtered version
204 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
204 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
205 for n, l in z:
205 for n, l in z:
206 if "check-code" + "-ignore" in l[0]:
206 if "check-code" + "-ignore" in l[0]:
207 continue
207 continue
208 for p, msg in pats:
208 for p, msg in pats:
209 if not warnings and msg.startswith("warning"):
209 if not warnings and msg.startswith("warning"):
210 continue
210 continue
211 if re.search(p, l[1]):
211 if re.search(p, l[1]):
212 logfunc(f, n + 1, l[0], msg)
212 logfunc(f, n + 1, l[0], msg)
213 fc += 1
213 fc += 1
214 result = False
214 result = False
215 if maxerr is not None and fc >= maxerr:
215 if maxerr is not None and fc >= maxerr:
216 print " (too many errors, giving up)"
216 print " (too many errors, giving up)"
217 break
217 break
218 break
218 break
219 return result
219 return result
220
220
221
221
222 if __name__ == "__main__":
222 if __name__ == "__main__":
223 parser = optparse.OptionParser("%prog [options] [files]")
223 parser = optparse.OptionParser("%prog [options] [files]")
224 parser.add_option("-w", "--warnings", action="store_true",
224 parser.add_option("-w", "--warnings", action="store_true",
225 help="include warning-level checks")
225 help="include warning-level checks")
226 parser.add_option("-p", "--per-file", type="int",
226 parser.add_option("-p", "--per-file", type="int",
227 help="max warnings per file")
227 help="max warnings per file")
228
228
229 parser.set_defaults(per_file=15, warnings=False)
229 parser.set_defaults(per_file=15, warnings=False)
230 (options, args) = parser.parse_args()
230 (options, args) = parser.parse_args()
231
231
232 if len(args) == 0:
232 if len(args) == 0:
233 check = glob.glob("*")
233 check = glob.glob("*")
234 else:
234 else:
235 check = args
235 check = args
236
236
237 for f in check:
237 for f in check:
238 checkfile(f, maxerr=options.per_file, warnings=options.warnings)
238 checkfile(f, maxerr=options.per_file, warnings=options.warnings)
@@ -1,550 +1,550 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback
10 import util, commands, hg, fancyopts, extensions, hook, error
10 import util, commands, hg, fancyopts, extensions, hook, error
11 import cmdutil, encoding
11 import cmdutil, encoding
12 import ui as uimod
12 import ui as uimod
13
13
14 def run():
14 def run():
15 "run the command in sys.argv"
15 "run the command in sys.argv"
16 sys.exit(dispatch(sys.argv[1:]))
16 sys.exit(dispatch(sys.argv[1:]))
17
17
18 def dispatch(args):
18 def dispatch(args):
19 "run the command specified in args"
19 "run the command specified in args"
20 try:
20 try:
21 u = uimod.ui()
21 u = uimod.ui()
22 if '--traceback' in args:
22 if '--traceback' in args:
23 u.setconfig('ui', 'traceback', 'on')
23 u.setconfig('ui', 'traceback', 'on')
24 except util.Abort, inst:
24 except util.Abort, inst:
25 sys.stderr.write(_("abort: %s\n") % inst)
25 sys.stderr.write(_("abort: %s\n") % inst)
26 if inst.hint:
26 if inst.hint:
27 sys.stdout.write(_("(%s)\n") % inst.hint)
27 sys.stdout.write(_("(%s)\n") % inst.hint)
28 return -1
28 return -1
29 except error.ParseError, inst:
29 except error.ParseError, inst:
30 if len(inst.args) > 1:
30 if len(inst.args) > 1:
31 sys.stderr.write(_("hg: parse error at %s: %s\n") %
31 sys.stderr.write(_("hg: parse error at %s: %s\n") %
32 (inst.args[1], inst.args[0]))
32 (inst.args[1], inst.args[0]))
33 else:
33 else:
34 sys.stderr.write(_("hg: parse error: %s\n") % inst.args[0])
34 sys.stderr.write(_("hg: parse error: %s\n") % inst.args[0])
35 return -1
35 return -1
36 return _runcatch(u, args)
36 return _runcatch(u, args)
37
37
38 def _runcatch(ui, args):
38 def _runcatch(ui, args):
39 def catchterm(*args):
39 def catchterm(*args):
40 raise error.SignalInterrupt
40 raise error.SignalInterrupt
41
41
42 try:
42 try:
43 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
43 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
44 num = getattr(signal, name, None)
44 num = getattr(signal, name, None)
45 if num:
45 if num:
46 signal.signal(num, catchterm)
46 signal.signal(num, catchterm)
47 except ValueError:
47 except ValueError:
48 pass # happens if called in a thread
48 pass # happens if called in a thread
49
49
50 try:
50 try:
51 try:
51 try:
52 # enter the debugger before command execution
52 # enter the debugger before command execution
53 if '--debugger' in args:
53 if '--debugger' in args:
54 ui.warn(_("entering debugger - "
54 ui.warn(_("entering debugger - "
55 "type c to continue starting hg or h for help\n"))
55 "type c to continue starting hg or h for help\n"))
56 pdb.set_trace()
56 pdb.set_trace()
57 try:
57 try:
58 return _dispatch(ui, args)
58 return _dispatch(ui, args)
59 finally:
59 finally:
60 ui.flush()
60 ui.flush()
61 except:
61 except:
62 # enter the debugger when we hit an exception
62 # enter the debugger when we hit an exception
63 if '--debugger' in args:
63 if '--debugger' in args:
64 traceback.print_exc()
64 traceback.print_exc()
65 pdb.post_mortem(sys.exc_info()[2])
65 pdb.post_mortem(sys.exc_info()[2])
66 ui.traceback()
66 ui.traceback()
67 raise
67 raise
68
68
69 # Global exception handling, alphabetically
69 # Global exception handling, alphabetically
70 # Mercurial-specific first, followed by built-in and library exceptions
70 # Mercurial-specific first, followed by built-in and library exceptions
71 except error.AmbiguousCommand, inst:
71 except error.AmbiguousCommand, inst:
72 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
72 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
73 (inst.args[0], " ".join(inst.args[1])))
73 (inst.args[0], " ".join(inst.args[1])))
74 except error.ParseError, inst:
74 except error.ParseError, inst:
75 if len(inst.args) > 1:
75 if len(inst.args) > 1:
76 ui.warn(_("hg: parse error at %s: %s\n") %
76 ui.warn(_("hg: parse error at %s: %s\n") %
77 (inst.args[1], inst.args[0]))
77 (inst.args[1], inst.args[0]))
78 else:
78 else:
79 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
79 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
80 return -1
80 return -1
81 except error.LockHeld, inst:
81 except error.LockHeld, inst:
82 if inst.errno == errno.ETIMEDOUT:
82 if inst.errno == errno.ETIMEDOUT:
83 reason = _('timed out waiting for lock held by %s') % inst.locker
83 reason = _('timed out waiting for lock held by %s') % inst.locker
84 else:
84 else:
85 reason = _('lock held by %s') % inst.locker
85 reason = _('lock held by %s') % inst.locker
86 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
86 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
87 except error.LockUnavailable, inst:
87 except error.LockUnavailable, inst:
88 ui.warn(_("abort: could not lock %s: %s\n") %
88 ui.warn(_("abort: could not lock %s: %s\n") %
89 (inst.desc or inst.filename, inst.strerror))
89 (inst.desc or inst.filename, inst.strerror))
90 except error.CommandError, inst:
90 except error.CommandError, inst:
91 if inst.args[0]:
91 if inst.args[0]:
92 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
92 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
93 commands.help_(ui, inst.args[0])
93 commands.help_(ui, inst.args[0])
94 else:
94 else:
95 ui.warn(_("hg: %s\n") % inst.args[1])
95 ui.warn(_("hg: %s\n") % inst.args[1])
96 commands.help_(ui, 'shortlist')
96 commands.help_(ui, 'shortlist')
97 except error.RepoError, inst:
97 except error.RepoError, inst:
98 ui.warn(_("abort: %s!\n") % inst)
98 ui.warn(_("abort: %s!\n") % inst)
99 except error.ResponseError, inst:
99 except error.ResponseError, inst:
100 ui.warn(_("abort: %s") % inst.args[0])
100 ui.warn(_("abort: %s") % inst.args[0])
101 if not isinstance(inst.args[1], basestring):
101 if not isinstance(inst.args[1], basestring):
102 ui.warn(" %r\n" % (inst.args[1],))
102 ui.warn(" %r\n" % (inst.args[1],))
103 elif not inst.args[1]:
103 elif not inst.args[1]:
104 ui.warn(_(" empty string\n"))
104 ui.warn(_(" empty string\n"))
105 else:
105 else:
106 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
106 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
107 except error.RevlogError, inst:
107 except error.RevlogError, inst:
108 ui.warn(_("abort: %s!\n") % inst)
108 ui.warn(_("abort: %s!\n") % inst)
109 except error.SignalInterrupt:
109 except error.SignalInterrupt:
110 ui.warn(_("killed!\n"))
110 ui.warn(_("killed!\n"))
111 except error.UnknownCommand, inst:
111 except error.UnknownCommand, inst:
112 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
112 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
113 try:
113 try:
114 # check if the command is in a disabled extension
114 # check if the command is in a disabled extension
115 # (but don't check for extensions themselves)
115 # (but don't check for extensions themselves)
116 commands.help_(ui, inst.args[0], unknowncmd=True)
116 commands.help_(ui, inst.args[0], unknowncmd=True)
117 except error.UnknownCommand:
117 except error.UnknownCommand:
118 commands.help_(ui, 'shortlist')
118 commands.help_(ui, 'shortlist')
119 except util.Abort, inst:
119 except util.Abort, inst:
120 ui.warn(_("abort: %s\n") % inst)
120 ui.warn(_("abort: %s\n") % inst)
121 if inst.hint:
121 if inst.hint:
122 ui.status(_("(%s)\n") % inst.hint)
122 ui.status(_("(%s)\n") % inst.hint)
123 except ImportError, inst:
123 except ImportError, inst:
124 ui.warn(_("abort: %s!\n") % inst)
124 ui.warn(_("abort: %s!\n") % inst)
125 m = str(inst).split()[-1]
125 m = str(inst).split()[-1]
126 if m in "mpatch bdiff".split():
126 if m in "mpatch bdiff".split():
127 ui.warn(_("(did you forget to compile extensions?)\n"))
127 ui.warn(_("(did you forget to compile extensions?)\n"))
128 elif m in "zlib".split():
128 elif m in "zlib".split():
129 ui.warn(_("(is your Python install correct?)\n"))
129 ui.warn(_("(is your Python install correct?)\n"))
130 except IOError, inst:
130 except IOError, inst:
131 if hasattr(inst, "code"):
131 if hasattr(inst, "code"):
132 ui.warn(_("abort: %s\n") % inst)
132 ui.warn(_("abort: %s\n") % inst)
133 elif hasattr(inst, "reason"):
133 elif hasattr(inst, "reason"):
134 try: # usually it is in the form (errno, strerror)
134 try: # usually it is in the form (errno, strerror)
135 reason = inst.reason.args[1]
135 reason = inst.reason.args[1]
136 except: # it might be anything, for example a string
136 except: # it might be anything, for example a string
137 reason = inst.reason
137 reason = inst.reason
138 ui.warn(_("abort: error: %s\n") % reason)
138 ui.warn(_("abort: error: %s\n") % reason)
139 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
139 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
140 if ui.debugflag:
140 if ui.debugflag:
141 ui.warn(_("broken pipe\n"))
141 ui.warn(_("broken pipe\n"))
142 elif getattr(inst, "strerror", None):
142 elif getattr(inst, "strerror", None):
143 if getattr(inst, "filename", None):
143 if getattr(inst, "filename", None):
144 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
144 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
145 else:
145 else:
146 ui.warn(_("abort: %s\n") % inst.strerror)
146 ui.warn(_("abort: %s\n") % inst.strerror)
147 else:
147 else:
148 raise
148 raise
149 except OSError, inst:
149 except OSError, inst:
150 if getattr(inst, "filename", None):
150 if getattr(inst, "filename", None):
151 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
151 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
152 else:
152 else:
153 ui.warn(_("abort: %s\n") % inst.strerror)
153 ui.warn(_("abort: %s\n") % inst.strerror)
154 except KeyboardInterrupt:
154 except KeyboardInterrupt:
155 try:
155 try:
156 ui.warn(_("interrupted!\n"))
156 ui.warn(_("interrupted!\n"))
157 except IOError, inst:
157 except IOError, inst:
158 if inst.errno == errno.EPIPE:
158 if inst.errno == errno.EPIPE:
159 if ui.debugflag:
159 if ui.debugflag:
160 ui.warn(_("\nbroken pipe\n"))
160 ui.warn(_("\nbroken pipe\n"))
161 else:
161 else:
162 raise
162 raise
163 except MemoryError:
163 except MemoryError:
164 ui.warn(_("abort: out of memory\n"))
164 ui.warn(_("abort: out of memory\n"))
165 except SystemExit, inst:
165 except SystemExit, inst:
166 # Commands shouldn't sys.exit directly, but give a return code.
166 # Commands shouldn't sys.exit directly, but give a return code.
167 # Just in case catch this and and pass exit code to caller.
167 # Just in case catch this and and pass exit code to caller.
168 return inst.code
168 return inst.code
169 except socket.error, inst:
169 except socket.error, inst:
170 ui.warn(_("abort: %s\n") % inst.args[-1])
170 ui.warn(_("abort: %s\n") % inst.args[-1])
171 except:
171 except:
172 ui.warn(_("** unknown exception encountered, details follow\n"))
172 ui.warn(_("** unknown exception encountered, details follow\n"))
173 ui.warn(_("** report bug details to "
173 ui.warn(_("** report bug details to "
174 "http://mercurial.selenic.com/bts/\n"))
174 "http://mercurial.selenic.com/bts/\n"))
175 ui.warn(_("** or mercurial@selenic.com\n"))
175 ui.warn(_("** or mercurial@selenic.com\n"))
176 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
176 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
177 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
177 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
178 % util.version())
178 % util.version())
179 ui.warn(_("** Extensions loaded: %s\n")
179 ui.warn(_("** Extensions loaded: %s\n")
180 % ", ".join([x[0] for x in extensions.extensions()]))
180 % ", ".join([x[0] for x in extensions.extensions()]))
181 raise
181 raise
182
182
183 return -1
183 return -1
184
184
185 def aliasargs(fn):
185 def aliasargs(fn):
186 if hasattr(fn, 'args'):
186 if hasattr(fn, 'args'):
187 return fn.args
187 return fn.args
188 return []
188 return []
189
189
190 class cmdalias(object):
190 class cmdalias(object):
191 def __init__(self, name, definition, cmdtable):
191 def __init__(self, name, definition, cmdtable):
192 self.name = name
192 self.name = name
193 self.definition = definition
193 self.definition = definition
194 self.args = []
194 self.args = []
195 self.opts = []
195 self.opts = []
196 self.help = ''
196 self.help = ''
197 self.norepo = True
197 self.norepo = True
198 self.badalias = False
198 self.badalias = False
199
199
200 try:
200 try:
201 cmdutil.findcmd(self.name, cmdtable, True)
201 cmdutil.findcmd(self.name, cmdtable, True)
202 self.shadows = True
202 self.shadows = True
203 except error.UnknownCommand:
203 except error.UnknownCommand:
204 self.shadows = False
204 self.shadows = False
205
205
206 if not self.definition:
206 if not self.definition:
207 def fn(ui, *args):
207 def fn(ui, *args):
208 ui.warn(_("no definition for alias '%s'\n") % self.name)
208 ui.warn(_("no definition for alias '%s'\n") % self.name)
209 return 1
209 return 1
210 self.fn = fn
210 self.fn = fn
211 self.badalias = True
211 self.badalias = True
212
212
213 return
213 return
214
214
215 if self.definition.startswith('!'):
215 if self.definition.startswith('!'):
216 def fn(ui, *args):
216 def fn(ui, *args):
217 cmd = '%s %s' % (self.definition[1:], ' '.join(args))
217 cmd = '%s %s' % (self.definition[1:], ' '.join(args))
218 return util.system(cmd)
218 return util.system(cmd)
219 self.fn = fn
219 self.fn = fn
220 return
220 return
221
221
222 args = shlex.split(self.definition)
222 args = shlex.split(self.definition)
223 cmd = args.pop(0)
223 cmd = args.pop(0)
224 args = map(util.expandpath, args)
224 args = map(util.expandpath, args)
225
225
226 try:
226 try:
227 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
227 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
228 if len(tableentry) > 2:
228 if len(tableentry) > 2:
229 self.fn, self.opts, self.help = tableentry
229 self.fn, self.opts, self.help = tableentry
230 else:
230 else:
231 self.fn, self.opts = tableentry
231 self.fn, self.opts = tableentry
232
232
233 self.args = aliasargs(self.fn) + args
233 self.args = aliasargs(self.fn) + args
234 if cmd not in commands.norepo.split(' '):
234 if cmd not in commands.norepo.split(' '):
235 self.norepo = False
235 self.norepo = False
236 if self.help.startswith("hg " + cmd):
236 if self.help.startswith("hg " + cmd):
237 # drop prefix in old-style help lines so hg shows the alias
237 # drop prefix in old-style help lines so hg shows the alias
238 self.help = self.help[4 + len(cmd):]
238 self.help = self.help[4 + len(cmd):]
239 self.__doc__ = self.fn.__doc__
239 self.__doc__ = self.fn.__doc__
240
240
241 except error.UnknownCommand:
241 except error.UnknownCommand:
242 def fn(ui, *args):
242 def fn(ui, *args):
243 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
243 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
244 % (self.name, cmd))
244 % (self.name, cmd))
245 try:
245 try:
246 # check if the command is in a disabled extension
246 # check if the command is in a disabled extension
247 commands.help_(ui, cmd, unknowncmd=True)
247 commands.help_(ui, cmd, unknowncmd=True)
248 except error.UnknownCommand:
248 except error.UnknownCommand:
249 pass
249 pass
250 return 1
250 return 1
251 self.fn = fn
251 self.fn = fn
252 self.badalias = True
252 self.badalias = True
253 except error.AmbiguousCommand:
253 except error.AmbiguousCommand:
254 def fn(ui, *args):
254 def fn(ui, *args):
255 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
255 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
256 % (self.name, cmd))
256 % (self.name, cmd))
257 return 1
257 return 1
258 self.fn = fn
258 self.fn = fn
259 self.badalias = True
259 self.badalias = True
260
260
261 def __call__(self, ui, *args, **opts):
261 def __call__(self, ui, *args, **opts):
262 if self.shadows:
262 if self.shadows:
263 ui.debug("alias '%s' shadows command\n" % self.name)
263 ui.debug("alias '%s' shadows command\n" % self.name)
264
264
265 return self.fn(ui, *args, **opts)
265 return self.fn(ui, *args, **opts)
266
266
267 def addaliases(ui, cmdtable):
267 def addaliases(ui, cmdtable):
268 # aliases are processed after extensions have been loaded, so they
268 # aliases are processed after extensions have been loaded, so they
269 # may use extension commands. Aliases can also use other alias definitions,
269 # may use extension commands. Aliases can also use other alias definitions,
270 # but only if they have been defined prior to the current definition.
270 # but only if they have been defined prior to the current definition.
271 for alias, definition in ui.configitems('alias'):
271 for alias, definition in ui.configitems('alias'):
272 aliasdef = cmdalias(alias, definition, cmdtable)
272 aliasdef = cmdalias(alias, definition, cmdtable)
273 cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
273 cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
274 if aliasdef.norepo:
274 if aliasdef.norepo:
275 commands.norepo += ' %s' % alias
275 commands.norepo += ' %s' % alias
276
276
277 def _parse(ui, args):
277 def _parse(ui, args):
278 options = {}
278 options = {}
279 cmdoptions = {}
279 cmdoptions = {}
280
280
281 try:
281 try:
282 args = fancyopts.fancyopts(args, commands.globalopts, options)
282 args = fancyopts.fancyopts(args, commands.globalopts, options)
283 except fancyopts.getopt.GetoptError, inst:
283 except fancyopts.getopt.GetoptError, inst:
284 raise error.CommandError(None, inst)
284 raise error.CommandError(None, inst)
285
285
286 if args:
286 if args:
287 cmd, args = args[0], args[1:]
287 cmd, args = args[0], args[1:]
288 aliases, entry = cmdutil.findcmd(cmd, commands.table,
288 aliases, entry = cmdutil.findcmd(cmd, commands.table,
289 ui.config("ui", "strict"))
289 ui.config("ui", "strict"))
290 cmd = aliases[0]
290 cmd = aliases[0]
291 args = aliasargs(entry[0]) + args
291 args = aliasargs(entry[0]) + args
292 defaults = ui.config("defaults", cmd)
292 defaults = ui.config("defaults", cmd)
293 if defaults:
293 if defaults:
294 args = map(util.expandpath, shlex.split(defaults)) + args
294 args = map(util.expandpath, shlex.split(defaults)) + args
295 c = list(entry[1])
295 c = list(entry[1])
296 else:
296 else:
297 cmd = None
297 cmd = None
298 c = []
298 c = []
299
299
300 # combine global options into local
300 # combine global options into local
301 for o in commands.globalopts:
301 for o in commands.globalopts:
302 c.append((o[0], o[1], options[o[1]], o[3]))
302 c.append((o[0], o[1], options[o[1]], o[3]))
303
303
304 try:
304 try:
305 args = fancyopts.fancyopts(args, c, cmdoptions, True)
305 args = fancyopts.fancyopts(args, c, cmdoptions, True)
306 except fancyopts.getopt.GetoptError, inst:
306 except fancyopts.getopt.GetoptError, inst:
307 raise error.CommandError(cmd, inst)
307 raise error.CommandError(cmd, inst)
308
308
309 # separate global options back out
309 # separate global options back out
310 for o in commands.globalopts:
310 for o in commands.globalopts:
311 n = o[1]
311 n = o[1]
312 options[n] = cmdoptions[n]
312 options[n] = cmdoptions[n]
313 del cmdoptions[n]
313 del cmdoptions[n]
314
314
315 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
315 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
316
316
317 def _parseconfig(ui, config):
317 def _parseconfig(ui, config):
318 """parse the --config options from the command line"""
318 """parse the --config options from the command line"""
319 for cfg in config:
319 for cfg in config:
320 try:
320 try:
321 name, value = cfg.split('=', 1)
321 name, value = cfg.split('=', 1)
322 section, name = name.split('.', 1)
322 section, name = name.split('.', 1)
323 if not section or not name:
323 if not section or not name:
324 raise IndexError
324 raise IndexError
325 ui.setconfig(section, name, value)
325 ui.setconfig(section, name, value)
326 except (IndexError, ValueError):
326 except (IndexError, ValueError):
327 raise util.Abort(_('malformed --config option: %r '
327 raise util.Abort(_('malformed --config option: %r '
328 '(use --config section.name=value)') % cfg)
328 '(use --config section.name=value)') % cfg)
329
329
330 def _earlygetopt(aliases, args):
330 def _earlygetopt(aliases, args):
331 """Return list of values for an option (or aliases).
331 """Return list of values for an option (or aliases).
332
332
333 The values are listed in the order they appear in args.
333 The values are listed in the order they appear in args.
334 The options and values are removed from args.
334 The options and values are removed from args.
335 """
335 """
336 try:
336 try:
337 argcount = args.index("--")
337 argcount = args.index("--")
338 except ValueError:
338 except ValueError:
339 argcount = len(args)
339 argcount = len(args)
340 shortopts = [opt for opt in aliases if len(opt) == 2]
340 shortopts = [opt for opt in aliases if len(opt) == 2]
341 values = []
341 values = []
342 pos = 0
342 pos = 0
343 while pos < argcount:
343 while pos < argcount:
344 if args[pos] in aliases:
344 if args[pos] in aliases:
345 if pos + 1 >= argcount:
345 if pos + 1 >= argcount:
346 # ignore and let getopt report an error if there is no value
346 # ignore and let getopt report an error if there is no value
347 break
347 break
348 del args[pos]
348 del args[pos]
349 values.append(args.pop(pos))
349 values.append(args.pop(pos))
350 argcount -= 2
350 argcount -= 2
351 elif args[pos][:2] in shortopts:
351 elif args[pos][:2] in shortopts:
352 # short option can have no following space, e.g. hg log -Rfoo
352 # short option can have no following space, e.g. hg log -Rfoo
353 values.append(args.pop(pos)[2:])
353 values.append(args.pop(pos)[2:])
354 argcount -= 1
354 argcount -= 1
355 else:
355 else:
356 pos += 1
356 pos += 1
357 return values
357 return values
358
358
359 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
359 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
360 # run pre-hook, and abort if it fails
360 # run pre-hook, and abort if it fails
361 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
361 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
362 pats=cmdpats, opts=cmdoptions)
362 pats=cmdpats, opts=cmdoptions)
363 if ret:
363 if ret:
364 return ret
364 return ret
365 ret = _runcommand(ui, options, cmd, d)
365 ret = _runcommand(ui, options, cmd, d)
366 # run post-hook, passing command result
366 # run post-hook, passing command result
367 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
367 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
368 result=ret, pats=cmdpats, opts=cmdoptions)
368 result=ret, pats=cmdpats, opts=cmdoptions)
369 return ret
369 return ret
370
370
371 _loaded = set()
371 _loaded = set()
372 def _dispatch(ui, args):
372 def _dispatch(ui, args):
373 # read --config before doing anything else
373 # read --config before doing anything else
374 # (e.g. to change trust settings for reading .hg/hgrc)
374 # (e.g. to change trust settings for reading .hg/hgrc)
375 _parseconfig(ui, _earlygetopt(['--config'], args))
375 _parseconfig(ui, _earlygetopt(['--config'], args))
376
376
377 # check for cwd
377 # check for cwd
378 cwd = _earlygetopt(['--cwd'], args)
378 cwd = _earlygetopt(['--cwd'], args)
379 if cwd:
379 if cwd:
380 os.chdir(cwd[-1])
380 os.chdir(cwd[-1])
381
381
382 # read the local repository .hgrc into a local ui object
382 # read the local repository .hgrc into a local ui object
383 path = cmdutil.findrepo(os.getcwd()) or ""
383 path = cmdutil.findrepo(os.getcwd()) or ""
384 if not path:
384 if not path:
385 lui = ui
385 lui = ui
386 else:
386 else:
387 try:
387 try:
388 lui = ui.copy()
388 lui = ui.copy()
389 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
389 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
390 except IOError:
390 except IOError:
391 pass
391 pass
392
392
393 # now we can expand paths, even ones in .hg/hgrc
393 # now we can expand paths, even ones in .hg/hgrc
394 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
394 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
395 if rpath:
395 if rpath:
396 path = lui.expandpath(rpath[-1])
396 path = lui.expandpath(rpath[-1])
397 lui = ui.copy()
397 lui = ui.copy()
398 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
398 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
399
399
400 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
400 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
401 # reposetup. Programs like TortoiseHg will call _dispatch several
401 # reposetup. Programs like TortoiseHg will call _dispatch several
402 # times so we keep track of configured extensions in _loaded.
402 # times so we keep track of configured extensions in _loaded.
403 extensions.loadall(lui)
403 extensions.loadall(lui)
404 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
404 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
405 # Propagate any changes to lui.__class__ by extensions
405 # Propagate any changes to lui.__class__ by extensions
406 ui.__class__ = lui.__class__
406 ui.__class__ = lui.__class__
407
407
408 # (uisetup and extsetup are handled in extensions.loadall)
408 # (uisetup and extsetup are handled in extensions.loadall)
409
409
410 for name, module in exts:
410 for name, module in exts:
411 cmdtable = getattr(module, 'cmdtable', {})
411 cmdtable = getattr(module, 'cmdtable', {})
412 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
412 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
413 if overrides:
413 if overrides:
414 ui.warn(_("extension '%s' overrides commands: %s\n")
414 ui.warn(_("extension '%s' overrides commands: %s\n")
415 % (name, " ".join(overrides)))
415 % (name, " ".join(overrides)))
416 commands.table.update(cmdtable)
416 commands.table.update(cmdtable)
417 _loaded.add(name)
417 _loaded.add(name)
418
418
419 # (reposetup is handled in hg.repository)
419 # (reposetup is handled in hg.repository)
420
420
421 addaliases(lui, commands.table)
421 addaliases(lui, commands.table)
422
422
423 # check for fallback encoding
423 # check for fallback encoding
424 fallback = lui.config('ui', 'fallbackencoding')
424 fallback = lui.config('ui', 'fallbackencoding')
425 if fallback:
425 if fallback:
426 encoding.fallbackencoding = fallback
426 encoding.fallbackencoding = fallback
427
427
428 fullargs = args
428 fullargs = args
429 cmd, func, args, options, cmdoptions = _parse(lui, args)
429 cmd, func, args, options, cmdoptions = _parse(lui, args)
430
430
431 if options["config"]:
431 if options["config"]:
432 raise util.Abort(_("Option --config may not be abbreviated!"))
432 raise util.Abort(_("Option --config may not be abbreviated!"))
433 if options["cwd"]:
433 if options["cwd"]:
434 raise util.Abort(_("Option --cwd may not be abbreviated!"))
434 raise util.Abort(_("Option --cwd may not be abbreviated!"))
435 if options["repository"]:
435 if options["repository"]:
436 raise util.Abort(_(
436 raise util.Abort(_(
437 "Option -R has to be separated from other options (e.g. not -qR) "
437 "Option -R has to be separated from other options (e.g. not -qR) "
438 "and --repository may only be abbreviated as --repo!"))
438 "and --repository may only be abbreviated as --repo!"))
439
439
440 if options["encoding"]:
440 if options["encoding"]:
441 encoding.encoding = options["encoding"]
441 encoding.encoding = options["encoding"]
442 if options["encodingmode"]:
442 if options["encodingmode"]:
443 encoding.encodingmode = options["encodingmode"]
443 encoding.encodingmode = options["encodingmode"]
444 if options["time"]:
444 if options["time"]:
445 def get_times():
445 def get_times():
446 t = os.times()
446 t = os.times()
447 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
447 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
448 t = (t[0], t[1], t[2], t[3], time.clock())
448 t = (t[0], t[1], t[2], t[3], time.clock())
449 return t
449 return t
450 s = get_times()
450 s = get_times()
451 def print_time():
451 def print_time():
452 t = get_times()
452 t = get_times()
453 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
453 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
454 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
454 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
455 atexit.register(print_time)
455 atexit.register(print_time)
456
456
457 if options['verbose'] or options['debug'] or options['quiet']:
457 if options['verbose'] or options['debug'] or options['quiet']:
458 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
458 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
459 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
459 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
460 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
460 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
461 if options['traceback']:
461 if options['traceback']:
462 ui.setconfig('ui', 'traceback', 'on')
462 ui.setconfig('ui', 'traceback', 'on')
463 if options['noninteractive']:
463 if options['noninteractive']:
464 ui.setconfig('ui', 'interactive', 'off')
464 ui.setconfig('ui', 'interactive', 'off')
465
465
466 if options['help']:
466 if options['help']:
467 return commands.help_(ui, cmd, options['version'])
467 return commands.help_(ui, cmd, options['version'])
468 elif options['version']:
468 elif options['version']:
469 return commands.version_(ui)
469 return commands.version_(ui)
470 elif not cmd:
470 elif not cmd:
471 return commands.help_(ui, 'shortlist')
471 return commands.help_(ui, 'shortlist')
472
472
473 repo = None
473 repo = None
474 cmdpats = args[:]
474 cmdpats = args[:]
475 if cmd not in commands.norepo.split():
475 if cmd not in commands.norepo.split():
476 try:
476 try:
477 repo = hg.repository(ui, path=path)
477 repo = hg.repository(ui, path=path)
478 ui = repo.ui
478 ui = repo.ui
479 if not repo.local():
479 if not repo.local():
480 raise util.Abort(_("repository '%s' is not local") % path)
480 raise util.Abort(_("repository '%s' is not local") % path)
481 ui.setconfig("bundle", "mainreporoot", repo.root)
481 ui.setconfig("bundle", "mainreporoot", repo.root)
482 except error.RepoError:
482 except error.RepoError:
483 if cmd not in commands.optionalrepo.split():
483 if cmd not in commands.optionalrepo.split():
484 if args and not path: # try to infer -R from command args
484 if args and not path: # try to infer -R from command args
485 repos = map(cmdutil.findrepo, args)
485 repos = map(cmdutil.findrepo, args)
486 guess = repos[0]
486 guess = repos[0]
487 if guess and repos.count(guess) == len(repos):
487 if guess and repos.count(guess) == len(repos):
488 return _dispatch(ui, ['--repository', guess] + fullargs)
488 return _dispatch(ui, ['--repository', guess] + fullargs)
489 if not path:
489 if not path:
490 raise error.RepoError(_("There is no Mercurial repository"
490 raise error.RepoError(_("There is no Mercurial repository"
491 " here (.hg not found)"))
491 " here (.hg not found)"))
492 raise
492 raise
493 args.insert(0, repo)
493 args.insert(0, repo)
494 elif rpath:
494 elif rpath:
495 ui.warn("warning: --repository ignored\n")
495 ui.warn(_("warning: --repository ignored\n"))
496
496
497 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
497 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
498 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
498 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
499 cmdpats, cmdoptions)
499 cmdpats, cmdoptions)
500
500
501 def _runcommand(ui, options, cmd, cmdfunc):
501 def _runcommand(ui, options, cmd, cmdfunc):
502 def checkargs():
502 def checkargs():
503 try:
503 try:
504 return cmdfunc()
504 return cmdfunc()
505 except error.SignatureError:
505 except error.SignatureError:
506 raise error.CommandError(cmd, _("invalid arguments"))
506 raise error.CommandError(cmd, _("invalid arguments"))
507
507
508 if options['profile']:
508 if options['profile']:
509 format = ui.config('profiling', 'format', default='text')
509 format = ui.config('profiling', 'format', default='text')
510
510
511 if not format in ['text', 'kcachegrind']:
511 if not format in ['text', 'kcachegrind']:
512 ui.warn(_("unrecognized profiling format '%s'"
512 ui.warn(_("unrecognized profiling format '%s'"
513 " - Ignored\n") % format)
513 " - Ignored\n") % format)
514 format = 'text'
514 format = 'text'
515
515
516 output = ui.config('profiling', 'output')
516 output = ui.config('profiling', 'output')
517
517
518 if output:
518 if output:
519 path = ui.expandpath(output)
519 path = ui.expandpath(output)
520 ostream = open(path, 'wb')
520 ostream = open(path, 'wb')
521 else:
521 else:
522 ostream = sys.stderr
522 ostream = sys.stderr
523
523
524 try:
524 try:
525 from mercurial import lsprof
525 from mercurial import lsprof
526 except ImportError:
526 except ImportError:
527 raise util.Abort(_(
527 raise util.Abort(_(
528 'lsprof not available - install from '
528 'lsprof not available - install from '
529 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
529 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
530 p = lsprof.Profiler()
530 p = lsprof.Profiler()
531 p.enable(subcalls=True)
531 p.enable(subcalls=True)
532 try:
532 try:
533 return checkargs()
533 return checkargs()
534 finally:
534 finally:
535 p.disable()
535 p.disable()
536
536
537 if format == 'kcachegrind':
537 if format == 'kcachegrind':
538 import lsprofcalltree
538 import lsprofcalltree
539 calltree = lsprofcalltree.KCacheGrind(p)
539 calltree = lsprofcalltree.KCacheGrind(p)
540 calltree.output(ostream)
540 calltree.output(ostream)
541 else:
541 else:
542 # format == 'text'
542 # format == 'text'
543 stats = lsprof.Stats(p.getstats())
543 stats = lsprof.Stats(p.getstats())
544 stats.sort()
544 stats.sort()
545 stats.pprint(top=10, file=ostream, climit=5)
545 stats.pprint(top=10, file=ostream, climit=5)
546
546
547 if output:
547 if output:
548 ostream.close()
548 ostream.close()
549 else:
549 else:
550 return checkargs()
550 return checkargs()
@@ -1,159 +1,160 b''
1 # repair.py - functions for repository repair for mercurial
1 # repair.py - functions for repository repair for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 # Copyright 2007 Matt Mackall
4 # Copyright 2007 Matt Mackall
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import changegroup
9 import changegroup
10 from node import nullrev, short
10 from node import nullrev, short
11 from i18n import _
11 from i18n import _
12 import os
12 import os
13
13
14 def _bundle(repo, bases, heads, node, suffix, extranodes=None):
14 def _bundle(repo, bases, heads, node, suffix, extranodes=None):
15 """create a bundle with the specified revisions as a backup"""
15 """create a bundle with the specified revisions as a backup"""
16 cg = repo.changegroupsubset(bases, heads, 'strip', extranodes)
16 cg = repo.changegroupsubset(bases, heads, 'strip', extranodes)
17 backupdir = repo.join("strip-backup")
17 backupdir = repo.join("strip-backup")
18 if not os.path.isdir(backupdir):
18 if not os.path.isdir(backupdir):
19 os.mkdir(backupdir)
19 os.mkdir(backupdir)
20 name = os.path.join(backupdir, "%s-%s.hg" % (short(node), suffix))
20 name = os.path.join(backupdir, "%s-%s.hg" % (short(node), suffix))
21 return changegroup.writebundle(cg, name, "HG10BZ")
21 return changegroup.writebundle(cg, name, "HG10BZ")
22
22
23 def _collectfiles(repo, striprev):
23 def _collectfiles(repo, striprev):
24 """find out the filelogs affected by the strip"""
24 """find out the filelogs affected by the strip"""
25 files = set()
25 files = set()
26
26
27 for x in xrange(striprev, len(repo)):
27 for x in xrange(striprev, len(repo)):
28 files.update(repo[x].files())
28 files.update(repo[x].files())
29
29
30 return sorted(files)
30 return sorted(files)
31
31
32 def _collectextranodes(repo, files, link):
32 def _collectextranodes(repo, files, link):
33 """return the nodes that have to be saved before the strip"""
33 """return the nodes that have to be saved before the strip"""
34 def collectone(revlog):
34 def collectone(revlog):
35 extra = []
35 extra = []
36 startrev = count = len(revlog)
36 startrev = count = len(revlog)
37 # find the truncation point of the revlog
37 # find the truncation point of the revlog
38 for i in xrange(count):
38 for i in xrange(count):
39 lrev = revlog.linkrev(i)
39 lrev = revlog.linkrev(i)
40 if lrev >= link:
40 if lrev >= link:
41 startrev = i + 1
41 startrev = i + 1
42 break
42 break
43
43
44 # see if any revision after that point has a linkrev less than link
44 # see if any revision after that point has a linkrev less than link
45 # (we have to manually save these guys)
45 # (we have to manually save these guys)
46 for i in xrange(startrev, count):
46 for i in xrange(startrev, count):
47 node = revlog.node(i)
47 node = revlog.node(i)
48 lrev = revlog.linkrev(i)
48 lrev = revlog.linkrev(i)
49 if lrev < link:
49 if lrev < link:
50 extra.append((node, cl.node(lrev)))
50 extra.append((node, cl.node(lrev)))
51
51
52 return extra
52 return extra
53
53
54 extranodes = {}
54 extranodes = {}
55 cl = repo.changelog
55 cl = repo.changelog
56 extra = collectone(repo.manifest)
56 extra = collectone(repo.manifest)
57 if extra:
57 if extra:
58 extranodes[1] = extra
58 extranodes[1] = extra
59 for fname in files:
59 for fname in files:
60 f = repo.file(fname)
60 f = repo.file(fname)
61 extra = collectone(f)
61 extra = collectone(f)
62 if extra:
62 if extra:
63 extranodes[fname] = extra
63 extranodes[fname] = extra
64
64
65 return extranodes
65 return extranodes
66
66
67 def strip(ui, repo, node, backup="all"):
67 def strip(ui, repo, node, backup="all"):
68 cl = repo.changelog
68 cl = repo.changelog
69 # TODO delete the undo files, and handle undo of merge sets
69 # TODO delete the undo files, and handle undo of merge sets
70 striprev = cl.rev(node)
70 striprev = cl.rev(node)
71
71
72 # Some revisions with rev > striprev may not be descendants of striprev.
72 # Some revisions with rev > striprev may not be descendants of striprev.
73 # We have to find these revisions and put them in a bundle, so that
73 # We have to find these revisions and put them in a bundle, so that
74 # we can restore them after the truncations.
74 # we can restore them after the truncations.
75 # To create the bundle we use repo.changegroupsubset which requires
75 # To create the bundle we use repo.changegroupsubset which requires
76 # the list of heads and bases of the set of interesting revisions.
76 # the list of heads and bases of the set of interesting revisions.
77 # (head = revision in the set that has no descendant in the set;
77 # (head = revision in the set that has no descendant in the set;
78 # base = revision in the set that has no ancestor in the set)
78 # base = revision in the set that has no ancestor in the set)
79 tostrip = set((striprev,))
79 tostrip = set((striprev,))
80 saveheads = set()
80 saveheads = set()
81 savebases = []
81 savebases = []
82 for r in xrange(striprev + 1, len(cl)):
82 for r in xrange(striprev + 1, len(cl)):
83 parents = cl.parentrevs(r)
83 parents = cl.parentrevs(r)
84 if parents[0] in tostrip or parents[1] in tostrip:
84 if parents[0] in tostrip or parents[1] in tostrip:
85 # r is a descendant of striprev
85 # r is a descendant of striprev
86 tostrip.add(r)
86 tostrip.add(r)
87 # if this is a merge and one of the parents does not descend
87 # if this is a merge and one of the parents does not descend
88 # from striprev, mark that parent as a savehead.
88 # from striprev, mark that parent as a savehead.
89 if parents[1] != nullrev:
89 if parents[1] != nullrev:
90 for p in parents:
90 for p in parents:
91 if p not in tostrip and p > striprev:
91 if p not in tostrip and p > striprev:
92 saveheads.add(p)
92 saveheads.add(p)
93 else:
93 else:
94 # if no parents of this revision will be stripped, mark it as
94 # if no parents of this revision will be stripped, mark it as
95 # a savebase
95 # a savebase
96 if parents[0] < striprev and parents[1] < striprev:
96 if parents[0] < striprev and parents[1] < striprev:
97 savebases.append(cl.node(r))
97 savebases.append(cl.node(r))
98
98
99 saveheads.difference_update(parents)
99 saveheads.difference_update(parents)
100 saveheads.add(r)
100 saveheads.add(r)
101
101
102 saveheads = [cl.node(r) for r in saveheads]
102 saveheads = [cl.node(r) for r in saveheads]
103 files = _collectfiles(repo, striprev)
103 files = _collectfiles(repo, striprev)
104
104
105 extranodes = _collectextranodes(repo, files, striprev)
105 extranodes = _collectextranodes(repo, files, striprev)
106
106
107 # create a changegroup for all the branches we need to keep
107 # create a changegroup for all the branches we need to keep
108 backupfile = None
108 backupfile = None
109 if backup == "all":
109 if backup == "all":
110 backupfile = _bundle(repo, [node], cl.heads(), node, 'backup')
110 backupfile = _bundle(repo, [node], cl.heads(), node, 'backup')
111 repo.ui.status(_("saved backup bundle to %s\n") % backupfile)
111 repo.ui.status(_("saved backup bundle to %s\n") % backupfile)
112 if saveheads or extranodes:
112 if saveheads or extranodes:
113 chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
113 chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
114 extranodes)
114 extranodes)
115
115
116 mfst = repo.manifest
116 mfst = repo.manifest
117
117
118 tr = repo.transaction("strip")
118 tr = repo.transaction("strip")
119 offset = len(tr.entries)
119 offset = len(tr.entries)
120
120
121 try:
121 try:
122 tr.startgroup()
122 tr.startgroup()
123 cl.strip(striprev, tr)
123 cl.strip(striprev, tr)
124 mfst.strip(striprev, tr)
124 mfst.strip(striprev, tr)
125 for fn in files:
125 for fn in files:
126 repo.file(fn).strip(striprev, tr)
126 repo.file(fn).strip(striprev, tr)
127 tr.endgroup()
127 tr.endgroup()
128
128
129 try:
129 try:
130 for i in xrange(offset, len(tr.entries)):
130 for i in xrange(offset, len(tr.entries)):
131 file, troffset, ignore = tr.entries[i]
131 file, troffset, ignore = tr.entries[i]
132 repo.sopener(file, 'a').truncate(troffset)
132 repo.sopener(file, 'a').truncate(troffset)
133 tr.close()
133 tr.close()
134 except:
134 except:
135 tr.abort()
135 tr.abort()
136 raise
136 raise
137
137
138 if saveheads or extranodes:
138 if saveheads or extranodes:
139 ui.note(_("adding branch\n"))
139 ui.note(_("adding branch\n"))
140 f = open(chgrpfile, "rb")
140 f = open(chgrpfile, "rb")
141 gen = changegroup.readbundle(f, chgrpfile)
141 gen = changegroup.readbundle(f, chgrpfile)
142 if not repo.ui.verbose:
142 if not repo.ui.verbose:
143 # silence internal shuffling chatter
143 # silence internal shuffling chatter
144 repo.ui.pushbuffer()
144 repo.ui.pushbuffer()
145 repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True)
145 repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True)
146 if not repo.ui.verbose:
146 if not repo.ui.verbose:
147 repo.ui.popbuffer()
147 repo.ui.popbuffer()
148 f.close()
148 f.close()
149 if backup != "strip":
149 if backup != "strip":
150 os.unlink(chgrpfile)
150 os.unlink(chgrpfile)
151 except:
151 except:
152 if backupfile:
152 if backupfile:
153 ui.warn("strip failed, full bundle stored in '%s'\n" % backupfile)
153 ui.warn(_("strip failed, full bundle stored in '%s'\n")
154 % backupfile)
154 elif saveheads:
155 elif saveheads:
155 ui.warn("strip failed, partial bundle stored in '%s'\n"
156 ui.warn(_("strip failed, partial bundle stored in '%s'\n")
156 % chgrpfile)
157 % chgrpfile)
157 raise
158 raise
158
159
159 repo.destroyed()
160 repo.destroyed()
@@ -1,604 +1,604 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, util, error
10 import config, util, error
11
11
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 '0': False, 'no': False, 'false': False, 'off': False}
13 '0': False, 'no': False, 'false': False, 'off': False}
14
14
15 class ui(object):
15 class ui(object):
16 def __init__(self, src=None):
16 def __init__(self, src=None):
17 self._buffers = []
17 self._buffers = []
18 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
18 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
19 self._reportuntrusted = True
19 self._reportuntrusted = True
20 self._ocfg = config.config() # overlay
20 self._ocfg = config.config() # overlay
21 self._tcfg = config.config() # trusted
21 self._tcfg = config.config() # trusted
22 self._ucfg = config.config() # untrusted
22 self._ucfg = config.config() # untrusted
23 self._trustusers = set()
23 self._trustusers = set()
24 self._trustgroups = set()
24 self._trustgroups = set()
25
25
26 if src:
26 if src:
27 self._tcfg = src._tcfg.copy()
27 self._tcfg = src._tcfg.copy()
28 self._ucfg = src._ucfg.copy()
28 self._ucfg = src._ucfg.copy()
29 self._ocfg = src._ocfg.copy()
29 self._ocfg = src._ocfg.copy()
30 self._trustusers = src._trustusers.copy()
30 self._trustusers = src._trustusers.copy()
31 self._trustgroups = src._trustgroups.copy()
31 self._trustgroups = src._trustgroups.copy()
32 self.environ = src.environ
32 self.environ = src.environ
33 self.fixconfig()
33 self.fixconfig()
34 else:
34 else:
35 # shared read-only environment
35 # shared read-only environment
36 self.environ = os.environ
36 self.environ = os.environ
37 # we always trust global config files
37 # we always trust global config files
38 for f in util.rcpath():
38 for f in util.rcpath():
39 self.readconfig(f, trust=True)
39 self.readconfig(f, trust=True)
40
40
41 def copy(self):
41 def copy(self):
42 return self.__class__(self)
42 return self.__class__(self)
43
43
44 def _is_trusted(self, fp, f):
44 def _is_trusted(self, fp, f):
45 st = util.fstat(fp)
45 st = util.fstat(fp)
46 if util.isowner(st):
46 if util.isowner(st):
47 return True
47 return True
48
48
49 tusers, tgroups = self._trustusers, self._trustgroups
49 tusers, tgroups = self._trustusers, self._trustgroups
50 if '*' in tusers or '*' in tgroups:
50 if '*' in tusers or '*' in tgroups:
51 return True
51 return True
52
52
53 user = util.username(st.st_uid)
53 user = util.username(st.st_uid)
54 group = util.groupname(st.st_gid)
54 group = util.groupname(st.st_gid)
55 if user in tusers or group in tgroups or user == util.username():
55 if user in tusers or group in tgroups or user == util.username():
56 return True
56 return True
57
57
58 if self._reportuntrusted:
58 if self._reportuntrusted:
59 self.warn(_('Not trusting file %s from untrusted '
59 self.warn(_('Not trusting file %s from untrusted '
60 'user %s, group %s\n') % (f, user, group))
60 'user %s, group %s\n') % (f, user, group))
61 return False
61 return False
62
62
63 def readconfig(self, filename, root=None, trust=False,
63 def readconfig(self, filename, root=None, trust=False,
64 sections=None, remap=None):
64 sections=None, remap=None):
65 try:
65 try:
66 fp = open(filename)
66 fp = open(filename)
67 except IOError:
67 except IOError:
68 if not sections: # ignore unless we were looking for something
68 if not sections: # ignore unless we were looking for something
69 return
69 return
70 raise
70 raise
71
71
72 cfg = config.config()
72 cfg = config.config()
73 trusted = sections or trust or self._is_trusted(fp, filename)
73 trusted = sections or trust or self._is_trusted(fp, filename)
74
74
75 try:
75 try:
76 cfg.read(filename, fp, sections=sections, remap=remap)
76 cfg.read(filename, fp, sections=sections, remap=remap)
77 except error.ConfigError, inst:
77 except error.ConfigError, inst:
78 if trusted:
78 if trusted:
79 raise
79 raise
80 self.warn(_("Ignored: %s\n") % str(inst))
80 self.warn(_("Ignored: %s\n") % str(inst))
81
81
82 if self.plain():
82 if self.plain():
83 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
83 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
84 'logtemplate', 'style',
84 'logtemplate', 'style',
85 'traceback', 'verbose'):
85 'traceback', 'verbose'):
86 if k in cfg['ui']:
86 if k in cfg['ui']:
87 del cfg['ui'][k]
87 del cfg['ui'][k]
88 for k, v in cfg.items('alias'):
88 for k, v in cfg.items('alias'):
89 del cfg['alias'][k]
89 del cfg['alias'][k]
90 for k, v in cfg.items('defaults'):
90 for k, v in cfg.items('defaults'):
91 del cfg['defaults'][k]
91 del cfg['defaults'][k]
92
92
93 if trusted:
93 if trusted:
94 self._tcfg.update(cfg)
94 self._tcfg.update(cfg)
95 self._tcfg.update(self._ocfg)
95 self._tcfg.update(self._ocfg)
96 self._ucfg.update(cfg)
96 self._ucfg.update(cfg)
97 self._ucfg.update(self._ocfg)
97 self._ucfg.update(self._ocfg)
98
98
99 if root is None:
99 if root is None:
100 root = os.path.expanduser('~')
100 root = os.path.expanduser('~')
101 self.fixconfig(root=root)
101 self.fixconfig(root=root)
102
102
103 def fixconfig(self, root=None):
103 def fixconfig(self, root=None):
104 # translate paths relative to root (or home) into absolute paths
104 # translate paths relative to root (or home) into absolute paths
105 root = root or os.getcwd()
105 root = root or os.getcwd()
106 for c in self._tcfg, self._ucfg, self._ocfg:
106 for c in self._tcfg, self._ucfg, self._ocfg:
107 for n, p in c.items('paths'):
107 for n, p in c.items('paths'):
108 if p and "://" not in p and not os.path.isabs(p):
108 if p and "://" not in p and not os.path.isabs(p):
109 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
109 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
110
110
111 # update ui options
111 # update ui options
112 self.debugflag = self.configbool('ui', 'debug')
112 self.debugflag = self.configbool('ui', 'debug')
113 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
113 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
114 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
114 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
115 if self.verbose and self.quiet:
115 if self.verbose and self.quiet:
116 self.quiet = self.verbose = False
116 self.quiet = self.verbose = False
117 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
117 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
118 self.tracebackflag = self.configbool('ui', 'traceback', False)
118 self.tracebackflag = self.configbool('ui', 'traceback', False)
119
119
120 # update trust information
120 # update trust information
121 self._trustusers.update(self.configlist('trusted', 'users'))
121 self._trustusers.update(self.configlist('trusted', 'users'))
122 self._trustgroups.update(self.configlist('trusted', 'groups'))
122 self._trustgroups.update(self.configlist('trusted', 'groups'))
123
123
124 def setconfig(self, section, name, value):
124 def setconfig(self, section, name, value):
125 for cfg in (self._ocfg, self._tcfg, self._ucfg):
125 for cfg in (self._ocfg, self._tcfg, self._ucfg):
126 cfg.set(section, name, value)
126 cfg.set(section, name, value)
127 self.fixconfig()
127 self.fixconfig()
128
128
129 def _data(self, untrusted):
129 def _data(self, untrusted):
130 return untrusted and self._ucfg or self._tcfg
130 return untrusted and self._ucfg or self._tcfg
131
131
132 def configsource(self, section, name, untrusted=False):
132 def configsource(self, section, name, untrusted=False):
133 return self._data(untrusted).source(section, name) or 'none'
133 return self._data(untrusted).source(section, name) or 'none'
134
134
135 def config(self, section, name, default=None, untrusted=False):
135 def config(self, section, name, default=None, untrusted=False):
136 value = self._data(untrusted).get(section, name, default)
136 value = self._data(untrusted).get(section, name, default)
137 if self.debugflag and not untrusted and self._reportuntrusted:
137 if self.debugflag and not untrusted and self._reportuntrusted:
138 uvalue = self._ucfg.get(section, name)
138 uvalue = self._ucfg.get(section, name)
139 if uvalue is not None and uvalue != value:
139 if uvalue is not None and uvalue != value:
140 self.debug(_("ignoring untrusted configuration option "
140 self.debug(_("ignoring untrusted configuration option "
141 "%s.%s = %s\n") % (section, name, uvalue))
141 "%s.%s = %s\n") % (section, name, uvalue))
142 return value
142 return value
143
143
144 def configbool(self, section, name, default=False, untrusted=False):
144 def configbool(self, section, name, default=False, untrusted=False):
145 v = self.config(section, name, None, untrusted)
145 v = self.config(section, name, None, untrusted)
146 if v is None:
146 if v is None:
147 return default
147 return default
148 if isinstance(v, bool):
148 if isinstance(v, bool):
149 return v
149 return v
150 if v.lower() not in _booleans:
150 if v.lower() not in _booleans:
151 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
151 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
152 % (section, name, v))
152 % (section, name, v))
153 return _booleans[v.lower()]
153 return _booleans[v.lower()]
154
154
155 def configlist(self, section, name, default=None, untrusted=False):
155 def configlist(self, section, name, default=None, untrusted=False):
156 """Return a list of comma/space separated strings"""
156 """Return a list of comma/space separated strings"""
157
157
158 def _parse_plain(parts, s, offset):
158 def _parse_plain(parts, s, offset):
159 whitespace = False
159 whitespace = False
160 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
160 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
161 whitespace = True
161 whitespace = True
162 offset += 1
162 offset += 1
163 if offset >= len(s):
163 if offset >= len(s):
164 return None, parts, offset
164 return None, parts, offset
165 if whitespace:
165 if whitespace:
166 parts.append('')
166 parts.append('')
167 if s[offset] == '"' and not parts[-1]:
167 if s[offset] == '"' and not parts[-1]:
168 return _parse_quote, parts, offset + 1
168 return _parse_quote, parts, offset + 1
169 elif s[offset] == '"' and parts[-1][-1] == '\\':
169 elif s[offset] == '"' and parts[-1][-1] == '\\':
170 parts[-1] = parts[-1][:-1] + s[offset]
170 parts[-1] = parts[-1][:-1] + s[offset]
171 return _parse_plain, parts, offset + 1
171 return _parse_plain, parts, offset + 1
172 parts[-1] += s[offset]
172 parts[-1] += s[offset]
173 return _parse_plain, parts, offset + 1
173 return _parse_plain, parts, offset + 1
174
174
175 def _parse_quote(parts, s, offset):
175 def _parse_quote(parts, s, offset):
176 if offset < len(s) and s[offset] == '"': # ""
176 if offset < len(s) and s[offset] == '"': # ""
177 parts.append('')
177 parts.append('')
178 offset += 1
178 offset += 1
179 while offset < len(s) and (s[offset].isspace() or
179 while offset < len(s) and (s[offset].isspace() or
180 s[offset] == ','):
180 s[offset] == ','):
181 offset += 1
181 offset += 1
182 return _parse_plain, parts, offset
182 return _parse_plain, parts, offset
183
183
184 while offset < len(s) and s[offset] != '"':
184 while offset < len(s) and s[offset] != '"':
185 if (s[offset] == '\\' and offset + 1 < len(s)
185 if (s[offset] == '\\' and offset + 1 < len(s)
186 and s[offset + 1] == '"'):
186 and s[offset + 1] == '"'):
187 offset += 1
187 offset += 1
188 parts[-1] += '"'
188 parts[-1] += '"'
189 else:
189 else:
190 parts[-1] += s[offset]
190 parts[-1] += s[offset]
191 offset += 1
191 offset += 1
192
192
193 if offset >= len(s):
193 if offset >= len(s):
194 real_parts = _configlist(parts[-1])
194 real_parts = _configlist(parts[-1])
195 if not real_parts:
195 if not real_parts:
196 parts[-1] = '"'
196 parts[-1] = '"'
197 else:
197 else:
198 real_parts[0] = '"' + real_parts[0]
198 real_parts[0] = '"' + real_parts[0]
199 parts = parts[:-1]
199 parts = parts[:-1]
200 parts.extend(real_parts)
200 parts.extend(real_parts)
201 return None, parts, offset
201 return None, parts, offset
202
202
203 offset += 1
203 offset += 1
204 while offset < len(s) and s[offset] in [' ', ',']:
204 while offset < len(s) and s[offset] in [' ', ',']:
205 offset += 1
205 offset += 1
206
206
207 if offset < len(s):
207 if offset < len(s):
208 if offset + 1 == len(s) and s[offset] == '"':
208 if offset + 1 == len(s) and s[offset] == '"':
209 parts[-1] += '"'
209 parts[-1] += '"'
210 offset += 1
210 offset += 1
211 else:
211 else:
212 parts.append('')
212 parts.append('')
213 else:
213 else:
214 return None, parts, offset
214 return None, parts, offset
215
215
216 return _parse_plain, parts, offset
216 return _parse_plain, parts, offset
217
217
218 def _configlist(s):
218 def _configlist(s):
219 s = s.rstrip(' ,')
219 s = s.rstrip(' ,')
220 if not s:
220 if not s:
221 return None
221 return None
222 parser, parts, offset = _parse_plain, [''], 0
222 parser, parts, offset = _parse_plain, [''], 0
223 while parser:
223 while parser:
224 parser, parts, offset = parser(parts, s, offset)
224 parser, parts, offset = parser(parts, s, offset)
225 return parts
225 return parts
226
226
227 result = self.config(section, name, untrusted=untrusted)
227 result = self.config(section, name, untrusted=untrusted)
228 if result is None:
228 if result is None:
229 result = default or []
229 result = default or []
230 if isinstance(result, basestring):
230 if isinstance(result, basestring):
231 result = _configlist(result.lstrip(' ,\n'))
231 result = _configlist(result.lstrip(' ,\n'))
232 if result is None:
232 if result is None:
233 result = default or []
233 result = default or []
234 return result
234 return result
235
235
236 def has_section(self, section, untrusted=False):
236 def has_section(self, section, untrusted=False):
237 '''tell whether section exists in config.'''
237 '''tell whether section exists in config.'''
238 return section in self._data(untrusted)
238 return section in self._data(untrusted)
239
239
240 def configitems(self, section, untrusted=False):
240 def configitems(self, section, untrusted=False):
241 items = self._data(untrusted).items(section)
241 items = self._data(untrusted).items(section)
242 if self.debugflag and not untrusted and self._reportuntrusted:
242 if self.debugflag and not untrusted and self._reportuntrusted:
243 for k, v in self._ucfg.items(section):
243 for k, v in self._ucfg.items(section):
244 if self._tcfg.get(section, k) != v:
244 if self._tcfg.get(section, k) != v:
245 self.debug(_("ignoring untrusted configuration option "
245 self.debug(_("ignoring untrusted configuration option "
246 "%s.%s = %s\n") % (section, k, v))
246 "%s.%s = %s\n") % (section, k, v))
247 return items
247 return items
248
248
249 def walkconfig(self, untrusted=False):
249 def walkconfig(self, untrusted=False):
250 cfg = self._data(untrusted)
250 cfg = self._data(untrusted)
251 for section in cfg.sections():
251 for section in cfg.sections():
252 for name, value in self.configitems(section, untrusted):
252 for name, value in self.configitems(section, untrusted):
253 yield section, name, str(value).replace('\n', '\\n')
253 yield section, name, str(value).replace('\n', '\\n')
254
254
255 def plain(self):
255 def plain(self):
256 '''is plain mode active?
256 '''is plain mode active?
257
257
258 Plain mode means that all configuration variables which affect the
258 Plain mode means that all configuration variables which affect the
259 behavior and output of Mercurial should be ignored. Additionally, the
259 behavior and output of Mercurial should be ignored. Additionally, the
260 output should be stable, reproducible and suitable for use in scripts or
260 output should be stable, reproducible and suitable for use in scripts or
261 applications.
261 applications.
262
262
263 The only way to trigger plain mode is by setting the `HGPLAIN'
263 The only way to trigger plain mode is by setting the `HGPLAIN'
264 environment variable.
264 environment variable.
265 '''
265 '''
266 return 'HGPLAIN' in os.environ
266 return 'HGPLAIN' in os.environ
267
267
268 def username(self):
268 def username(self):
269 """Return default username to be used in commits.
269 """Return default username to be used in commits.
270
270
271 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
271 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
272 and stop searching if one of these is set.
272 and stop searching if one of these is set.
273 If not found and ui.askusername is True, ask the user, else use
273 If not found and ui.askusername is True, ask the user, else use
274 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
274 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
275 """
275 """
276 user = os.environ.get("HGUSER")
276 user = os.environ.get("HGUSER")
277 if user is None:
277 if user is None:
278 user = self.config("ui", "username")
278 user = self.config("ui", "username")
279 if user is not None:
279 if user is not None:
280 user = os.path.expandvars(user)
280 user = os.path.expandvars(user)
281 if user is None:
281 if user is None:
282 user = os.environ.get("EMAIL")
282 user = os.environ.get("EMAIL")
283 if user is None and self.configbool("ui", "askusername"):
283 if user is None and self.configbool("ui", "askusername"):
284 user = self.prompt(_("enter a commit username:"), default=None)
284 user = self.prompt(_("enter a commit username:"), default=None)
285 if user is None and not self.interactive():
285 if user is None and not self.interactive():
286 try:
286 try:
287 user = '%s@%s' % (util.getuser(), socket.getfqdn())
287 user = '%s@%s' % (util.getuser(), socket.getfqdn())
288 self.warn(_("No username found, using '%s' instead\n") % user)
288 self.warn(_("No username found, using '%s' instead\n") % user)
289 except KeyError:
289 except KeyError:
290 pass
290 pass
291 if not user:
291 if not user:
292 raise util.Abort(_('no username supplied (see "hg help config")'))
292 raise util.Abort(_('no username supplied (see "hg help config")'))
293 if "\n" in user:
293 if "\n" in user:
294 raise util.Abort(_("username %s contains a newline\n") % repr(user))
294 raise util.Abort(_("username %s contains a newline\n") % repr(user))
295 return user
295 return user
296
296
297 def shortuser(self, user):
297 def shortuser(self, user):
298 """Return a short representation of a user name or email address."""
298 """Return a short representation of a user name or email address."""
299 if not self.verbose:
299 if not self.verbose:
300 user = util.shortuser(user)
300 user = util.shortuser(user)
301 return user
301 return user
302
302
303 def _path(self, loc):
303 def _path(self, loc):
304 p = self.config('paths', loc)
304 p = self.config('paths', loc)
305 if p:
305 if p:
306 if '%%' in p:
306 if '%%' in p:
307 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
307 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n") %
308 (loc, p, self.configsource('paths', loc)))
308 (loc, p, self.configsource('paths', loc)))
309 p = p.replace('%%', '%')
309 p = p.replace('%%', '%')
310 p = util.expandpath(p)
310 p = util.expandpath(p)
311 return p
311 return p
312
312
313 def expandpath(self, loc, default=None):
313 def expandpath(self, loc, default=None):
314 """Return repository location relative to cwd or from [paths]"""
314 """Return repository location relative to cwd or from [paths]"""
315 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
315 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
316 return loc
316 return loc
317
317
318 path = self._path(loc)
318 path = self._path(loc)
319 if not path and default is not None:
319 if not path and default is not None:
320 path = self._path(default)
320 path = self._path(default)
321 return path or loc
321 return path or loc
322
322
323 def pushbuffer(self):
323 def pushbuffer(self):
324 self._buffers.append([])
324 self._buffers.append([])
325
325
326 def popbuffer(self, labeled=False):
326 def popbuffer(self, labeled=False):
327 '''pop the last buffer and return the buffered output
327 '''pop the last buffer and return the buffered output
328
328
329 If labeled is True, any labels associated with buffered
329 If labeled is True, any labels associated with buffered
330 output will be handled. By default, this has no effect
330 output will be handled. By default, this has no effect
331 on the output returned, but extensions and GUI tools may
331 on the output returned, but extensions and GUI tools may
332 handle this argument and returned styled output. If output
332 handle this argument and returned styled output. If output
333 is being buffered so it can be captured and parsed or
333 is being buffered so it can be captured and parsed or
334 processed, labeled should not be set to True.
334 processed, labeled should not be set to True.
335 '''
335 '''
336 return "".join(self._buffers.pop())
336 return "".join(self._buffers.pop())
337
337
338 def write(self, *args, **opts):
338 def write(self, *args, **opts):
339 '''write args to output
339 '''write args to output
340
340
341 By default, this method simply writes to the buffer or stdout,
341 By default, this method simply writes to the buffer or stdout,
342 but extensions or GUI tools may override this method,
342 but extensions or GUI tools may override this method,
343 write_err(), popbuffer(), and label() to style output from
343 write_err(), popbuffer(), and label() to style output from
344 various parts of hg.
344 various parts of hg.
345
345
346 An optional keyword argument, "label", can be passed in.
346 An optional keyword argument, "label", can be passed in.
347 This should be a string containing label names separated by
347 This should be a string containing label names separated by
348 space. Label names take the form of "topic.type". For example,
348 space. Label names take the form of "topic.type". For example,
349 ui.debug() issues a label of "ui.debug".
349 ui.debug() issues a label of "ui.debug".
350
350
351 When labeling output for a specific command, a label of
351 When labeling output for a specific command, a label of
352 "cmdname.type" is recommended. For example, status issues
352 "cmdname.type" is recommended. For example, status issues
353 a label of "status.modified" for modified files.
353 a label of "status.modified" for modified files.
354 '''
354 '''
355 if self._buffers:
355 if self._buffers:
356 self._buffers[-1].extend([str(a) for a in args])
356 self._buffers[-1].extend([str(a) for a in args])
357 else:
357 else:
358 for a in args:
358 for a in args:
359 sys.stdout.write(str(a))
359 sys.stdout.write(str(a))
360
360
361 def write_err(self, *args, **opts):
361 def write_err(self, *args, **opts):
362 try:
362 try:
363 if not getattr(sys.stdout, 'closed', False):
363 if not getattr(sys.stdout, 'closed', False):
364 sys.stdout.flush()
364 sys.stdout.flush()
365 for a in args:
365 for a in args:
366 sys.stderr.write(str(a))
366 sys.stderr.write(str(a))
367 # stderr may be buffered under win32 when redirected to files,
367 # stderr may be buffered under win32 when redirected to files,
368 # including stdout.
368 # including stdout.
369 if not getattr(sys.stderr, 'closed', False):
369 if not getattr(sys.stderr, 'closed', False):
370 sys.stderr.flush()
370 sys.stderr.flush()
371 except IOError, inst:
371 except IOError, inst:
372 if inst.errno not in (errno.EPIPE, errno.EIO):
372 if inst.errno not in (errno.EPIPE, errno.EIO):
373 raise
373 raise
374
374
375 def flush(self):
375 def flush(self):
376 try: sys.stdout.flush()
376 try: sys.stdout.flush()
377 except: pass
377 except: pass
378 try: sys.stderr.flush()
378 try: sys.stderr.flush()
379 except: pass
379 except: pass
380
380
381 def interactive(self):
381 def interactive(self):
382 '''is interactive input allowed?
382 '''is interactive input allowed?
383
383
384 An interactive session is a session where input can be reasonably read
384 An interactive session is a session where input can be reasonably read
385 from `sys.stdin'. If this function returns false, any attempt to read
385 from `sys.stdin'. If this function returns false, any attempt to read
386 from stdin should fail with an error, unless a sensible default has been
386 from stdin should fail with an error, unless a sensible default has been
387 specified.
387 specified.
388
388
389 Interactiveness is triggered by the value of the `ui.interactive'
389 Interactiveness is triggered by the value of the `ui.interactive'
390 configuration variable or - if it is unset - when `sys.stdin' points
390 configuration variable or - if it is unset - when `sys.stdin' points
391 to a terminal device.
391 to a terminal device.
392
392
393 This function refers to input only; for output, see `ui.formatted()'.
393 This function refers to input only; for output, see `ui.formatted()'.
394 '''
394 '''
395 i = self.configbool("ui", "interactive", None)
395 i = self.configbool("ui", "interactive", None)
396 if i is None:
396 if i is None:
397 try:
397 try:
398 return sys.stdin.isatty()
398 return sys.stdin.isatty()
399 except AttributeError:
399 except AttributeError:
400 # some environments replace stdin without implementing isatty
400 # some environments replace stdin without implementing isatty
401 # usually those are non-interactive
401 # usually those are non-interactive
402 return False
402 return False
403
403
404 return i
404 return i
405
405
406 def formatted(self):
406 def formatted(self):
407 '''should formatted output be used?
407 '''should formatted output be used?
408
408
409 It is often desirable to format the output to suite the output medium.
409 It is often desirable to format the output to suite the output medium.
410 Examples of this are truncating long lines or colorizing messages.
410 Examples of this are truncating long lines or colorizing messages.
411 However, this is not often not desirable when piping output into other
411 However, this is not often not desirable when piping output into other
412 utilities, e.g. `grep'.
412 utilities, e.g. `grep'.
413
413
414 Formatted output is triggered by the value of the `ui.formatted'
414 Formatted output is triggered by the value of the `ui.formatted'
415 configuration variable or - if it is unset - when `sys.stdout' points
415 configuration variable or - if it is unset - when `sys.stdout' points
416 to a terminal device. Please note that `ui.formatted' should be
416 to a terminal device. Please note that `ui.formatted' should be
417 considered an implementation detail; it is not intended for use outside
417 considered an implementation detail; it is not intended for use outside
418 Mercurial or its extensions.
418 Mercurial or its extensions.
419
419
420 This function refers to output only; for input, see `ui.interactive()'.
420 This function refers to output only; for input, see `ui.interactive()'.
421 This function always returns false when in plain mode, see `ui.plain()'.
421 This function always returns false when in plain mode, see `ui.plain()'.
422 '''
422 '''
423 if self.plain():
423 if self.plain():
424 return False
424 return False
425
425
426 i = self.configbool("ui", "formatted", None)
426 i = self.configbool("ui", "formatted", None)
427 if i is None:
427 if i is None:
428 try:
428 try:
429 return sys.stdout.isatty()
429 return sys.stdout.isatty()
430 except AttributeError:
430 except AttributeError:
431 # some environments replace stdout without implementing isatty
431 # some environments replace stdout without implementing isatty
432 # usually those are non-interactive
432 # usually those are non-interactive
433 return False
433 return False
434
434
435 return i
435 return i
436
436
437 def _readline(self, prompt=''):
437 def _readline(self, prompt=''):
438 if sys.stdin.isatty():
438 if sys.stdin.isatty():
439 try:
439 try:
440 # magically add command line editing support, where
440 # magically add command line editing support, where
441 # available
441 # available
442 import readline
442 import readline
443 # force demandimport to really load the module
443 # force demandimport to really load the module
444 readline.read_history_file
444 readline.read_history_file
445 # windows sometimes raises something other than ImportError
445 # windows sometimes raises something other than ImportError
446 except Exception:
446 except Exception:
447 pass
447 pass
448 line = raw_input(prompt)
448 line = raw_input(prompt)
449 # When stdin is in binary mode on Windows, it can cause
449 # When stdin is in binary mode on Windows, it can cause
450 # raw_input() to emit an extra trailing carriage return
450 # raw_input() to emit an extra trailing carriage return
451 if os.linesep == '\r\n' and line and line[-1] == '\r':
451 if os.linesep == '\r\n' and line and line[-1] == '\r':
452 line = line[:-1]
452 line = line[:-1]
453 return line
453 return line
454
454
455 def prompt(self, msg, default="y"):
455 def prompt(self, msg, default="y"):
456 """Prompt user with msg, read response.
456 """Prompt user with msg, read response.
457 If ui is not interactive, the default is returned.
457 If ui is not interactive, the default is returned.
458 """
458 """
459 if not self.interactive():
459 if not self.interactive():
460 self.write(msg, ' ', default, "\n")
460 self.write(msg, ' ', default, "\n")
461 return default
461 return default
462 try:
462 try:
463 r = self._readline(msg + ' ')
463 r = self._readline(msg + ' ')
464 if not r:
464 if not r:
465 return default
465 return default
466 return r
466 return r
467 except EOFError:
467 except EOFError:
468 raise util.Abort(_('response expected'))
468 raise util.Abort(_('response expected'))
469
469
470 def promptchoice(self, msg, choices, default=0):
470 def promptchoice(self, msg, choices, default=0):
471 """Prompt user with msg, read response, and ensure it matches
471 """Prompt user with msg, read response, and ensure it matches
472 one of the provided choices. The index of the choice is returned.
472 one of the provided choices. The index of the choice is returned.
473 choices is a sequence of acceptable responses with the format:
473 choices is a sequence of acceptable responses with the format:
474 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
474 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
475 If ui is not interactive, the default is returned.
475 If ui is not interactive, the default is returned.
476 """
476 """
477 resps = [s[s.index('&')+1].lower() for s in choices]
477 resps = [s[s.index('&')+1].lower() for s in choices]
478 while True:
478 while True:
479 r = self.prompt(msg, resps[default])
479 r = self.prompt(msg, resps[default])
480 if r.lower() in resps:
480 if r.lower() in resps:
481 return resps.index(r.lower())
481 return resps.index(r.lower())
482 self.write(_("unrecognized response\n"))
482 self.write(_("unrecognized response\n"))
483
483
484 def getpass(self, prompt=None, default=None):
484 def getpass(self, prompt=None, default=None):
485 if not self.interactive():
485 if not self.interactive():
486 return default
486 return default
487 try:
487 try:
488 return getpass.getpass(prompt or _('password: '))
488 return getpass.getpass(prompt or _('password: '))
489 except EOFError:
489 except EOFError:
490 raise util.Abort(_('response expected'))
490 raise util.Abort(_('response expected'))
491 def status(self, *msg, **opts):
491 def status(self, *msg, **opts):
492 '''write status message to output (if ui.quiet is False)
492 '''write status message to output (if ui.quiet is False)
493
493
494 This adds an output label of "ui.status".
494 This adds an output label of "ui.status".
495 '''
495 '''
496 if not self.quiet:
496 if not self.quiet:
497 opts['label'] = opts.get('label', '') + ' ui.status'
497 opts['label'] = opts.get('label', '') + ' ui.status'
498 self.write(*msg, **opts)
498 self.write(*msg, **opts)
499 def warn(self, *msg, **opts):
499 def warn(self, *msg, **opts):
500 '''write warning message to output (stderr)
500 '''write warning message to output (stderr)
501
501
502 This adds an output label of "ui.warning".
502 This adds an output label of "ui.warning".
503 '''
503 '''
504 opts['label'] = opts.get('label', '') + ' ui.warning'
504 opts['label'] = opts.get('label', '') + ' ui.warning'
505 self.write_err(*msg, **opts)
505 self.write_err(*msg, **opts)
506 def note(self, *msg, **opts):
506 def note(self, *msg, **opts):
507 '''write note to output (if ui.verbose is True)
507 '''write note to output (if ui.verbose is True)
508
508
509 This adds an output label of "ui.note".
509 This adds an output label of "ui.note".
510 '''
510 '''
511 if self.verbose:
511 if self.verbose:
512 opts['label'] = opts.get('label', '') + ' ui.note'
512 opts['label'] = opts.get('label', '') + ' ui.note'
513 self.write(*msg, **opts)
513 self.write(*msg, **opts)
514 def debug(self, *msg, **opts):
514 def debug(self, *msg, **opts):
515 '''write debug message to output (if ui.debugflag is True)
515 '''write debug message to output (if ui.debugflag is True)
516
516
517 This adds an output label of "ui.debug".
517 This adds an output label of "ui.debug".
518 '''
518 '''
519 if self.debugflag:
519 if self.debugflag:
520 opts['label'] = opts.get('label', '') + ' ui.debug'
520 opts['label'] = opts.get('label', '') + ' ui.debug'
521 self.write(*msg, **opts)
521 self.write(*msg, **opts)
522 def edit(self, text, user):
522 def edit(self, text, user):
523 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
523 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
524 text=True)
524 text=True)
525 try:
525 try:
526 f = os.fdopen(fd, "w")
526 f = os.fdopen(fd, "w")
527 f.write(text)
527 f.write(text)
528 f.close()
528 f.close()
529
529
530 editor = self.geteditor()
530 editor = self.geteditor()
531
531
532 util.system("%s \"%s\"" % (editor, name),
532 util.system("%s \"%s\"" % (editor, name),
533 environ={'HGUSER': user},
533 environ={'HGUSER': user},
534 onerr=util.Abort, errprefix=_("edit failed"))
534 onerr=util.Abort, errprefix=_("edit failed"))
535
535
536 f = open(name)
536 f = open(name)
537 t = f.read()
537 t = f.read()
538 f.close()
538 f.close()
539 finally:
539 finally:
540 os.unlink(name)
540 os.unlink(name)
541
541
542 return t
542 return t
543
543
544 def traceback(self, exc=None):
544 def traceback(self, exc=None):
545 '''print exception traceback if traceback printing enabled.
545 '''print exception traceback if traceback printing enabled.
546 only to call in exception handler. returns true if traceback
546 only to call in exception handler. returns true if traceback
547 printed.'''
547 printed.'''
548 if self.tracebackflag:
548 if self.tracebackflag:
549 if exc:
549 if exc:
550 traceback.print_exception(exc[0], exc[1], exc[2])
550 traceback.print_exception(exc[0], exc[1], exc[2])
551 else:
551 else:
552 traceback.print_exc()
552 traceback.print_exc()
553 return self.tracebackflag
553 return self.tracebackflag
554
554
555 def geteditor(self):
555 def geteditor(self):
556 '''return editor to use'''
556 '''return editor to use'''
557 return (os.environ.get("HGEDITOR") or
557 return (os.environ.get("HGEDITOR") or
558 self.config("ui", "editor") or
558 self.config("ui", "editor") or
559 os.environ.get("VISUAL") or
559 os.environ.get("VISUAL") or
560 os.environ.get("EDITOR", "vi"))
560 os.environ.get("EDITOR", "vi"))
561
561
562 def progress(self, topic, pos, item="", unit="", total=None):
562 def progress(self, topic, pos, item="", unit="", total=None):
563 '''show a progress message
563 '''show a progress message
564
564
565 With stock hg, this is simply a debug message that is hidden
565 With stock hg, this is simply a debug message that is hidden
566 by default, but with extensions or GUI tools it may be
566 by default, but with extensions or GUI tools it may be
567 visible. 'topic' is the current operation, 'item' is a
567 visible. 'topic' is the current operation, 'item' is a
568 non-numeric marker of the current position (ie the currently
568 non-numeric marker of the current position (ie the currently
569 in-process file), 'pos' is the current numeric position (ie
569 in-process file), 'pos' is the current numeric position (ie
570 revision, bytes, etc.), unit is a corresponding unit label,
570 revision, bytes, etc.), unit is a corresponding unit label,
571 and total is the highest expected pos.
571 and total is the highest expected pos.
572
572
573 Multiple nested topics may be active at a time.
573 Multiple nested topics may be active at a time.
574
574
575 All topics should be marked closed by setting pos to None at
575 All topics should be marked closed by setting pos to None at
576 termination.
576 termination.
577 '''
577 '''
578
578
579 if pos == None or not self.debugflag:
579 if pos == None or not self.debugflag:
580 return
580 return
581
581
582 if unit:
582 if unit:
583 unit = ' ' + unit
583 unit = ' ' + unit
584 if item:
584 if item:
585 item = ' ' + item
585 item = ' ' + item
586
586
587 if total:
587 if total:
588 pct = 100.0 * pos / total
588 pct = 100.0 * pos / total
589 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
589 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
590 % (topic, item, pos, total, unit, pct))
590 % (topic, item, pos, total, unit, pct))
591 else:
591 else:
592 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
592 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
593
593
594 def label(self, msg, label):
594 def label(self, msg, label):
595 '''style msg based on supplied label
595 '''style msg based on supplied label
596
596
597 Like ui.write(), this just returns msg unchanged, but extensions
597 Like ui.write(), this just returns msg unchanged, but extensions
598 and GUI tools can override it to allow styling output without
598 and GUI tools can override it to allow styling output without
599 writing it.
599 writing it.
600
600
601 ui.write(s, 'label') is equivalent to
601 ui.write(s, 'label') is equivalent to
602 ui.write(ui.label(s, 'label')).
602 ui.write(ui.label(s, 'label')).
603 '''
603 '''
604 return msg
604 return msg
General Comments 0
You need to be logged in to leave comments. Login now