##// END OF EJS Templates
py3: factor out bytechr() function...
Yuya Nishihara -
r31253:64596338 default
parent child Browse files
Show More
@@ -1,294 +1,299 b''
1 # pycompat.py - portability shim for python 3
1 # pycompat.py - portability shim for python 3
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """Mercurial portability shim for python 3.
6 """Mercurial portability shim for python 3.
7
7
8 This contains aliases to hide python version-specific details from the core.
8 This contains aliases to hide python version-specific details from the core.
9 """
9 """
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import getopt
13 import getopt
14 import os
14 import os
15 import shlex
15 import shlex
16 import sys
16 import sys
17
17
18 ispy3 = (sys.version_info[0] >= 3)
18 ispy3 = (sys.version_info[0] >= 3)
19
19
20 if not ispy3:
20 if not ispy3:
21 import cPickle as pickle
21 import cPickle as pickle
22 import cStringIO as io
22 import cStringIO as io
23 import httplib
23 import httplib
24 import Queue as _queue
24 import Queue as _queue
25 import SocketServer as socketserver
25 import SocketServer as socketserver
26 import urlparse
26 import urlparse
27 urlunquote = urlparse.unquote
27 urlunquote = urlparse.unquote
28 import xmlrpclib
28 import xmlrpclib
29 else:
29 else:
30 import http.client as httplib
30 import http.client as httplib
31 import io
31 import io
32 import pickle
32 import pickle
33 import queue as _queue
33 import queue as _queue
34 import socketserver
34 import socketserver
35 import urllib.parse as urlparse
35 import urllib.parse as urlparse
36 urlunquote = urlparse.unquote_to_bytes
36 urlunquote = urlparse.unquote_to_bytes
37 import xmlrpc.client as xmlrpclib
37 import xmlrpc.client as xmlrpclib
38
38
39 if ispy3:
39 if ispy3:
40 import builtins
40 import builtins
41 import functools
41 import functools
42 fsencode = os.fsencode
42 fsencode = os.fsencode
43 fsdecode = os.fsdecode
43 fsdecode = os.fsdecode
44 # A bytes version of os.name.
44 # A bytes version of os.name.
45 osname = os.name.encode('ascii')
45 osname = os.name.encode('ascii')
46 ospathsep = os.pathsep.encode('ascii')
46 ospathsep = os.pathsep.encode('ascii')
47 ossep = os.sep.encode('ascii')
47 ossep = os.sep.encode('ascii')
48 osaltsep = os.altsep
48 osaltsep = os.altsep
49 if osaltsep:
49 if osaltsep:
50 osaltsep = osaltsep.encode('ascii')
50 osaltsep = osaltsep.encode('ascii')
51 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
51 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
52 # returns bytes.
52 # returns bytes.
53 getcwd = os.getcwdb
53 getcwd = os.getcwdb
54 sysplatform = sys.platform.encode('ascii')
54 sysplatform = sys.platform.encode('ascii')
55 sysexecutable = sys.executable
55 sysexecutable = sys.executable
56 if sysexecutable:
56 if sysexecutable:
57 sysexecutable = os.fsencode(sysexecutable)
57 sysexecutable = os.fsencode(sysexecutable)
58
58
59 # TODO: .buffer might not exist if std streams were replaced; we'll need
59 # TODO: .buffer might not exist if std streams were replaced; we'll need
60 # a silly wrapper to make a bytes stream backed by a unicode one.
60 # a silly wrapper to make a bytes stream backed by a unicode one.
61 stdin = sys.stdin.buffer
61 stdin = sys.stdin.buffer
62 stdout = sys.stdout.buffer
62 stdout = sys.stdout.buffer
63 stderr = sys.stderr.buffer
63 stderr = sys.stderr.buffer
64
64
65 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
65 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
66 # we can use os.fsencode() to get back bytes argv.
66 # we can use os.fsencode() to get back bytes argv.
67 #
67 #
68 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
68 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
69 #
69 #
70 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
70 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
71 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
71 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
72 sysargv = list(map(os.fsencode, sys.argv))
72 sysargv = list(map(os.fsencode, sys.argv))
73
73
74 def bytechr(i):
75 return bytes([i])
76
74 def sysstr(s):
77 def sysstr(s):
75 """Return a keyword str to be passed to Python functions such as
78 """Return a keyword str to be passed to Python functions such as
76 getattr() and str.encode()
79 getattr() and str.encode()
77
80
78 This never raises UnicodeDecodeError. Non-ascii characters are
81 This never raises UnicodeDecodeError. Non-ascii characters are
79 considered invalid and mapped to arbitrary but unique code points
82 considered invalid and mapped to arbitrary but unique code points
80 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
83 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
81 """
84 """
82 if isinstance(s, builtins.str):
85 if isinstance(s, builtins.str):
83 return s
86 return s
84 return s.decode(u'latin-1')
87 return s.decode(u'latin-1')
85
88
86 def _wrapattrfunc(f):
89 def _wrapattrfunc(f):
87 @functools.wraps(f)
90 @functools.wraps(f)
88 def w(object, name, *args):
91 def w(object, name, *args):
89 return f(object, sysstr(name), *args)
92 return f(object, sysstr(name), *args)
90 return w
93 return w
91
94
92 # these wrappers are automagically imported by hgloader
95 # these wrappers are automagically imported by hgloader
93 delattr = _wrapattrfunc(builtins.delattr)
96 delattr = _wrapattrfunc(builtins.delattr)
94 getattr = _wrapattrfunc(builtins.getattr)
97 getattr = _wrapattrfunc(builtins.getattr)
95 hasattr = _wrapattrfunc(builtins.hasattr)
98 hasattr = _wrapattrfunc(builtins.hasattr)
96 setattr = _wrapattrfunc(builtins.setattr)
99 setattr = _wrapattrfunc(builtins.setattr)
97 xrange = builtins.range
100 xrange = builtins.range
98
101
99 def open(name, mode='r', buffering=-1):
102 def open(name, mode='r', buffering=-1):
100 return builtins.open(name, sysstr(mode), buffering)
103 return builtins.open(name, sysstr(mode), buffering)
101
104
102 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
105 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
103 # pass bytes there. Passing unicodes will result in unicodes as return
106 # pass bytes there. Passing unicodes will result in unicodes as return
104 # values which we need to convert again to bytes.
107 # values which we need to convert again to bytes.
105 def getoptb(args, shortlist, namelist):
108 def getoptb(args, shortlist, namelist):
106 args = [a.decode('latin-1') for a in args]
109 args = [a.decode('latin-1') for a in args]
107 shortlist = shortlist.decode('latin-1')
110 shortlist = shortlist.decode('latin-1')
108 namelist = [a.decode('latin-1') for a in namelist]
111 namelist = [a.decode('latin-1') for a in namelist]
109 opts, args = getopt.getopt(args, shortlist, namelist)
112 opts, args = getopt.getopt(args, shortlist, namelist)
110 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
113 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
111 for a in opts]
114 for a in opts]
112 args = [a.encode('latin-1') for a in args]
115 args = [a.encode('latin-1') for a in args]
113 return opts, args
116 return opts, args
114
117
115 # keys of keyword arguments in Python need to be strings which are unicodes
118 # keys of keyword arguments in Python need to be strings which are unicodes
116 # Python 3. This function takes keyword arguments, convert the keys to str.
119 # Python 3. This function takes keyword arguments, convert the keys to str.
117 def strkwargs(dic):
120 def strkwargs(dic):
118 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
121 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
119 return dic
122 return dic
120
123
121 # keys of keyword arguments need to be unicode while passing into
124 # keys of keyword arguments need to be unicode while passing into
122 # a function. This function helps us to convert those keys back to bytes
125 # a function. This function helps us to convert those keys back to bytes
123 # again as we need to deal with bytes.
126 # again as we need to deal with bytes.
124 def byteskwargs(dic):
127 def byteskwargs(dic):
125 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
128 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
126 return dic
129 return dic
127
130
128 # shlex.split() accepts unicodes on Python 3. This function takes bytes
131 # shlex.split() accepts unicodes on Python 3. This function takes bytes
129 # argument, convert it into unicodes, pass into shlex.split(), convert the
132 # argument, convert it into unicodes, pass into shlex.split(), convert the
130 # returned value to bytes and return that.
133 # returned value to bytes and return that.
131 # TODO: handle shlex.shlex().
134 # TODO: handle shlex.shlex().
132 def shlexsplit(s):
135 def shlexsplit(s):
133 ret = shlex.split(s.decode('latin-1'))
136 ret = shlex.split(s.decode('latin-1'))
134 return [a.encode('latin-1') for a in ret]
137 return [a.encode('latin-1') for a in ret]
135
138
136 else:
139 else:
140 bytechr = chr
141
137 def sysstr(s):
142 def sysstr(s):
138 return s
143 return s
139
144
140 # Partial backport from os.py in Python 3, which only accepts bytes.
145 # Partial backport from os.py in Python 3, which only accepts bytes.
141 # In Python 2, our paths should only ever be bytes, a unicode path
146 # In Python 2, our paths should only ever be bytes, a unicode path
142 # indicates a bug.
147 # indicates a bug.
143 def fsencode(filename):
148 def fsencode(filename):
144 if isinstance(filename, str):
149 if isinstance(filename, str):
145 return filename
150 return filename
146 else:
151 else:
147 raise TypeError(
152 raise TypeError(
148 "expect str, not %s" % type(filename).__name__)
153 "expect str, not %s" % type(filename).__name__)
149
154
150 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
155 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
151 # better not to touch Python 2 part as it's already working fine.
156 # better not to touch Python 2 part as it's already working fine.
152 def fsdecode(filename):
157 def fsdecode(filename):
153 return filename
158 return filename
154
159
155 def getoptb(args, shortlist, namelist):
160 def getoptb(args, shortlist, namelist):
156 return getopt.getopt(args, shortlist, namelist)
161 return getopt.getopt(args, shortlist, namelist)
157
162
158 def strkwargs(dic):
163 def strkwargs(dic):
159 return dic
164 return dic
160
165
161 def byteskwargs(dic):
166 def byteskwargs(dic):
162 return dic
167 return dic
163
168
164 osname = os.name
169 osname = os.name
165 ospathsep = os.pathsep
170 ospathsep = os.pathsep
166 ossep = os.sep
171 ossep = os.sep
167 osaltsep = os.altsep
172 osaltsep = os.altsep
168 stdin = sys.stdin
173 stdin = sys.stdin
169 stdout = sys.stdout
174 stdout = sys.stdout
170 stderr = sys.stderr
175 stderr = sys.stderr
171 sysargv = sys.argv
176 sysargv = sys.argv
172 sysplatform = sys.platform
177 sysplatform = sys.platform
173 getcwd = os.getcwd
178 getcwd = os.getcwd
174 sysexecutable = sys.executable
179 sysexecutable = sys.executable
175 shlexsplit = shlex.split
180 shlexsplit = shlex.split
176
181
177 stringio = io.StringIO
182 stringio = io.StringIO
178 empty = _queue.Empty
183 empty = _queue.Empty
179 queue = _queue.Queue
184 queue = _queue.Queue
180
185
181 class _pycompatstub(object):
186 class _pycompatstub(object):
182 def __init__(self):
187 def __init__(self):
183 self._aliases = {}
188 self._aliases = {}
184
189
185 def _registeraliases(self, origin, items):
190 def _registeraliases(self, origin, items):
186 """Add items that will be populated at the first access"""
191 """Add items that will be populated at the first access"""
187 items = map(sysstr, items)
192 items = map(sysstr, items)
188 self._aliases.update(
193 self._aliases.update(
189 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
194 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
190 for item in items)
195 for item in items)
191
196
192 def __getattr__(self, name):
197 def __getattr__(self, name):
193 try:
198 try:
194 origin, item = self._aliases[name]
199 origin, item = self._aliases[name]
195 except KeyError:
200 except KeyError:
196 raise AttributeError(name)
201 raise AttributeError(name)
197 self.__dict__[name] = obj = getattr(origin, item)
202 self.__dict__[name] = obj = getattr(origin, item)
198 return obj
203 return obj
199
204
200 httpserver = _pycompatstub()
205 httpserver = _pycompatstub()
201 urlreq = _pycompatstub()
206 urlreq = _pycompatstub()
202 urlerr = _pycompatstub()
207 urlerr = _pycompatstub()
203 if not ispy3:
208 if not ispy3:
204 import BaseHTTPServer
209 import BaseHTTPServer
205 import CGIHTTPServer
210 import CGIHTTPServer
206 import SimpleHTTPServer
211 import SimpleHTTPServer
207 import urllib2
212 import urllib2
208 import urllib
213 import urllib
209 urlreq._registeraliases(urllib, (
214 urlreq._registeraliases(urllib, (
210 "addclosehook",
215 "addclosehook",
211 "addinfourl",
216 "addinfourl",
212 "ftpwrapper",
217 "ftpwrapper",
213 "pathname2url",
218 "pathname2url",
214 "quote",
219 "quote",
215 "splitattr",
220 "splitattr",
216 "splitpasswd",
221 "splitpasswd",
217 "splitport",
222 "splitport",
218 "splituser",
223 "splituser",
219 "unquote",
224 "unquote",
220 "url2pathname",
225 "url2pathname",
221 "urlencode",
226 "urlencode",
222 ))
227 ))
223 urlreq._registeraliases(urllib2, (
228 urlreq._registeraliases(urllib2, (
224 "AbstractHTTPHandler",
229 "AbstractHTTPHandler",
225 "BaseHandler",
230 "BaseHandler",
226 "build_opener",
231 "build_opener",
227 "FileHandler",
232 "FileHandler",
228 "FTPHandler",
233 "FTPHandler",
229 "HTTPBasicAuthHandler",
234 "HTTPBasicAuthHandler",
230 "HTTPDigestAuthHandler",
235 "HTTPDigestAuthHandler",
231 "HTTPHandler",
236 "HTTPHandler",
232 "HTTPPasswordMgrWithDefaultRealm",
237 "HTTPPasswordMgrWithDefaultRealm",
233 "HTTPSHandler",
238 "HTTPSHandler",
234 "install_opener",
239 "install_opener",
235 "ProxyHandler",
240 "ProxyHandler",
236 "Request",
241 "Request",
237 "urlopen",
242 "urlopen",
238 ))
243 ))
239 urlerr._registeraliases(urllib2, (
244 urlerr._registeraliases(urllib2, (
240 "HTTPError",
245 "HTTPError",
241 "URLError",
246 "URLError",
242 ))
247 ))
243 httpserver._registeraliases(BaseHTTPServer, (
248 httpserver._registeraliases(BaseHTTPServer, (
244 "HTTPServer",
249 "HTTPServer",
245 "BaseHTTPRequestHandler",
250 "BaseHTTPRequestHandler",
246 ))
251 ))
247 httpserver._registeraliases(SimpleHTTPServer, (
252 httpserver._registeraliases(SimpleHTTPServer, (
248 "SimpleHTTPRequestHandler",
253 "SimpleHTTPRequestHandler",
249 ))
254 ))
250 httpserver._registeraliases(CGIHTTPServer, (
255 httpserver._registeraliases(CGIHTTPServer, (
251 "CGIHTTPRequestHandler",
256 "CGIHTTPRequestHandler",
252 ))
257 ))
253
258
254 else:
259 else:
255 import urllib.request
260 import urllib.request
256 urlreq._registeraliases(urllib.request, (
261 urlreq._registeraliases(urllib.request, (
257 "AbstractHTTPHandler",
262 "AbstractHTTPHandler",
258 "addclosehook",
263 "addclosehook",
259 "addinfourl",
264 "addinfourl",
260 "BaseHandler",
265 "BaseHandler",
261 "build_opener",
266 "build_opener",
262 "FileHandler",
267 "FileHandler",
263 "FTPHandler",
268 "FTPHandler",
264 "ftpwrapper",
269 "ftpwrapper",
265 "HTTPHandler",
270 "HTTPHandler",
266 "HTTPSHandler",
271 "HTTPSHandler",
267 "install_opener",
272 "install_opener",
268 "pathname2url",
273 "pathname2url",
269 "HTTPBasicAuthHandler",
274 "HTTPBasicAuthHandler",
270 "HTTPDigestAuthHandler",
275 "HTTPDigestAuthHandler",
271 "HTTPPasswordMgrWithDefaultRealm",
276 "HTTPPasswordMgrWithDefaultRealm",
272 "ProxyHandler",
277 "ProxyHandler",
273 "quote",
278 "quote",
274 "Request",
279 "Request",
275 "splitattr",
280 "splitattr",
276 "splitpasswd",
281 "splitpasswd",
277 "splitport",
282 "splitport",
278 "splituser",
283 "splituser",
279 "unquote",
284 "unquote",
280 "url2pathname",
285 "url2pathname",
281 "urlopen",
286 "urlopen",
282 ))
287 ))
283 import urllib.error
288 import urllib.error
284 urlerr._registeraliases(urllib.error, (
289 urlerr._registeraliases(urllib.error, (
285 "HTTPError",
290 "HTTPError",
286 "URLError",
291 "URLError",
287 ))
292 ))
288 import http.server
293 import http.server
289 httpserver._registeraliases(http.server, (
294 httpserver._registeraliases(http.server, (
290 "HTTPServer",
295 "HTTPServer",
291 "BaseHTTPRequestHandler",
296 "BaseHTTPRequestHandler",
292 "SimpleHTTPRequestHandler",
297 "SimpleHTTPRequestHandler",
293 "CGIHTTPRequestHandler",
298 "CGIHTTPRequestHandler",
294 ))
299 ))
@@ -1,577 +1,573 b''
1 # store.py - repository store handling for Mercurial
1 # store.py - repository store handling for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import os
12 import os
13 import stat
13 import stat
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 parsers,
18 parsers,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 vfs as vfsmod,
21 vfs as vfsmod,
22 )
22 )
23
23
24 # This avoids a collision between a file named foo and a dir named
24 # This avoids a collision between a file named foo and a dir named
25 # foo.i or foo.d
25 # foo.i or foo.d
26 def _encodedir(path):
26 def _encodedir(path):
27 '''
27 '''
28 >>> _encodedir('data/foo.i')
28 >>> _encodedir('data/foo.i')
29 'data/foo.i'
29 'data/foo.i'
30 >>> _encodedir('data/foo.i/bla.i')
30 >>> _encodedir('data/foo.i/bla.i')
31 'data/foo.i.hg/bla.i'
31 'data/foo.i.hg/bla.i'
32 >>> _encodedir('data/foo.i.hg/bla.i')
32 >>> _encodedir('data/foo.i.hg/bla.i')
33 'data/foo.i.hg.hg/bla.i'
33 'data/foo.i.hg.hg/bla.i'
34 >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
34 >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
35 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
35 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
36 '''
36 '''
37 return (path
37 return (path
38 .replace(".hg/", ".hg.hg/")
38 .replace(".hg/", ".hg.hg/")
39 .replace(".i/", ".i.hg/")
39 .replace(".i/", ".i.hg/")
40 .replace(".d/", ".d.hg/"))
40 .replace(".d/", ".d.hg/"))
41
41
42 encodedir = getattr(parsers, 'encodedir', _encodedir)
42 encodedir = getattr(parsers, 'encodedir', _encodedir)
43
43
44 def decodedir(path):
44 def decodedir(path):
45 '''
45 '''
46 >>> decodedir('data/foo.i')
46 >>> decodedir('data/foo.i')
47 'data/foo.i'
47 'data/foo.i'
48 >>> decodedir('data/foo.i.hg/bla.i')
48 >>> decodedir('data/foo.i.hg/bla.i')
49 'data/foo.i/bla.i'
49 'data/foo.i/bla.i'
50 >>> decodedir('data/foo.i.hg.hg/bla.i')
50 >>> decodedir('data/foo.i.hg.hg/bla.i')
51 'data/foo.i.hg/bla.i'
51 'data/foo.i.hg/bla.i'
52 '''
52 '''
53 if ".hg/" not in path:
53 if ".hg/" not in path:
54 return path
54 return path
55 return (path
55 return (path
56 .replace(".d.hg/", ".d/")
56 .replace(".d.hg/", ".d/")
57 .replace(".i.hg/", ".i/")
57 .replace(".i.hg/", ".i/")
58 .replace(".hg.hg/", ".hg/"))
58 .replace(".hg.hg/", ".hg/"))
59
59
60 def _reserved():
60 def _reserved():
61 ''' characters that are problematic for filesystems
61 ''' characters that are problematic for filesystems
62
62
63 * ascii escapes (0..31)
63 * ascii escapes (0..31)
64 * ascii hi (126..255)
64 * ascii hi (126..255)
65 * windows specials
65 * windows specials
66
66
67 these characters will be escaped by encodefunctions
67 these characters will be escaped by encodefunctions
68 '''
68 '''
69 winreserved = [ord(x) for x in u'\\:*?"<>|']
69 winreserved = [ord(x) for x in u'\\:*?"<>|']
70 for x in range(32):
70 for x in range(32):
71 yield x
71 yield x
72 for x in range(126, 256):
72 for x in range(126, 256):
73 yield x
73 yield x
74 for x in winreserved:
74 for x in winreserved:
75 yield x
75 yield x
76
76
77 def _buildencodefun():
77 def _buildencodefun():
78 '''
78 '''
79 >>> enc, dec = _buildencodefun()
79 >>> enc, dec = _buildencodefun()
80
80
81 >>> enc('nothing/special.txt')
81 >>> enc('nothing/special.txt')
82 'nothing/special.txt'
82 'nothing/special.txt'
83 >>> dec('nothing/special.txt')
83 >>> dec('nothing/special.txt')
84 'nothing/special.txt'
84 'nothing/special.txt'
85
85
86 >>> enc('HELLO')
86 >>> enc('HELLO')
87 '_h_e_l_l_o'
87 '_h_e_l_l_o'
88 >>> dec('_h_e_l_l_o')
88 >>> dec('_h_e_l_l_o')
89 'HELLO'
89 'HELLO'
90
90
91 >>> enc('hello:world?')
91 >>> enc('hello:world?')
92 'hello~3aworld~3f'
92 'hello~3aworld~3f'
93 >>> dec('hello~3aworld~3f')
93 >>> dec('hello~3aworld~3f')
94 'hello:world?'
94 'hello:world?'
95
95
96 >>> enc('the\x07quick\xADshot')
96 >>> enc('the\x07quick\xADshot')
97 'the~07quick~adshot'
97 'the~07quick~adshot'
98 >>> dec('the~07quick~adshot')
98 >>> dec('the~07quick~adshot')
99 'the\\x07quick\\xadshot'
99 'the\\x07quick\\xadshot'
100 '''
100 '''
101 e = '_'
101 e = '_'
102 if pycompat.ispy3:
102 xchr = pycompat.bytechr
103 xchr = lambda x: bytes([x])
103 asciistr = list(map(xchr, range(127)))
104 asciistr = [bytes([a]) for a in range(127)]
105 else:
106 xchr = chr
107 asciistr = map(chr, xrange(127))
108 capitals = list(range(ord("A"), ord("Z") + 1))
104 capitals = list(range(ord("A"), ord("Z") + 1))
109
105
110 cmap = dict((x, x) for x in asciistr)
106 cmap = dict((x, x) for x in asciistr)
111 for x in _reserved():
107 for x in _reserved():
112 cmap[xchr(x)] = "~%02x" % x
108 cmap[xchr(x)] = "~%02x" % x
113 for x in capitals + [ord(e)]:
109 for x in capitals + [ord(e)]:
114 cmap[xchr(x)] = e + xchr(x).lower()
110 cmap[xchr(x)] = e + xchr(x).lower()
115
111
116 dmap = {}
112 dmap = {}
117 for k, v in cmap.iteritems():
113 for k, v in cmap.iteritems():
118 dmap[v] = k
114 dmap[v] = k
119 def decode(s):
115 def decode(s):
120 i = 0
116 i = 0
121 while i < len(s):
117 while i < len(s):
122 for l in xrange(1, 4):
118 for l in xrange(1, 4):
123 try:
119 try:
124 yield dmap[s[i:i + l]]
120 yield dmap[s[i:i + l]]
125 i += l
121 i += l
126 break
122 break
127 except KeyError:
123 except KeyError:
128 pass
124 pass
129 else:
125 else:
130 raise KeyError
126 raise KeyError
131 return (lambda s: ''.join([cmap[s[c:c + 1]] for c in xrange(len(s))]),
127 return (lambda s: ''.join([cmap[s[c:c + 1]] for c in xrange(len(s))]),
132 lambda s: ''.join(list(decode(s))))
128 lambda s: ''.join(list(decode(s))))
133
129
134 _encodefname, _decodefname = _buildencodefun()
130 _encodefname, _decodefname = _buildencodefun()
135
131
136 def encodefilename(s):
132 def encodefilename(s):
137 '''
133 '''
138 >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
134 >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
139 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
135 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
140 '''
136 '''
141 return _encodefname(encodedir(s))
137 return _encodefname(encodedir(s))
142
138
143 def decodefilename(s):
139 def decodefilename(s):
144 '''
140 '''
145 >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
141 >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
146 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
142 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
147 '''
143 '''
148 return decodedir(_decodefname(s))
144 return decodedir(_decodefname(s))
149
145
150 def _buildlowerencodefun():
146 def _buildlowerencodefun():
151 '''
147 '''
152 >>> f = _buildlowerencodefun()
148 >>> f = _buildlowerencodefun()
153 >>> f('nothing/special.txt')
149 >>> f('nothing/special.txt')
154 'nothing/special.txt'
150 'nothing/special.txt'
155 >>> f('HELLO')
151 >>> f('HELLO')
156 'hello'
152 'hello'
157 >>> f('hello:world?')
153 >>> f('hello:world?')
158 'hello~3aworld~3f'
154 'hello~3aworld~3f'
159 >>> f('the\x07quick\xADshot')
155 >>> f('the\x07quick\xADshot')
160 'the~07quick~adshot'
156 'the~07quick~adshot'
161 '''
157 '''
162 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
158 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
163 for x in _reserved():
159 for x in _reserved():
164 cmap[chr(x)] = "~%02x" % x
160 cmap[chr(x)] = "~%02x" % x
165 for x in range(ord("A"), ord("Z") + 1):
161 for x in range(ord("A"), ord("Z") + 1):
166 cmap[chr(x)] = chr(x).lower()
162 cmap[chr(x)] = chr(x).lower()
167 return lambda s: "".join([cmap[c] for c in s])
163 return lambda s: "".join([cmap[c] for c in s])
168
164
169 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
165 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
170
166
171 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
167 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
172 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
168 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
173 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
169 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
174 def _auxencode(path, dotencode):
170 def _auxencode(path, dotencode):
175 '''
171 '''
176 Encodes filenames containing names reserved by Windows or which end in
172 Encodes filenames containing names reserved by Windows or which end in
177 period or space. Does not touch other single reserved characters c.
173 period or space. Does not touch other single reserved characters c.
178 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
174 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
179 Additionally encodes space or period at the beginning, if dotencode is
175 Additionally encodes space or period at the beginning, if dotencode is
180 True. Parameter path is assumed to be all lowercase.
176 True. Parameter path is assumed to be all lowercase.
181 A segment only needs encoding if a reserved name appears as a
177 A segment only needs encoding if a reserved name appears as a
182 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
178 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
183 doesn't need encoding.
179 doesn't need encoding.
184
180
185 >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
181 >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
186 >>> _auxencode(s.split('/'), True)
182 >>> _auxencode(s.split('/'), True)
187 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
183 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
188 >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
184 >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
189 >>> _auxencode(s.split('/'), False)
185 >>> _auxencode(s.split('/'), False)
190 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
186 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
191 >>> _auxencode(['foo. '], True)
187 >>> _auxencode(['foo. '], True)
192 ['foo.~20']
188 ['foo.~20']
193 >>> _auxencode([' .foo'], True)
189 >>> _auxencode([' .foo'], True)
194 ['~20.foo']
190 ['~20.foo']
195 '''
191 '''
196 for i, n in enumerate(path):
192 for i, n in enumerate(path):
197 if not n:
193 if not n:
198 continue
194 continue
199 if dotencode and n[0] in '. ':
195 if dotencode and n[0] in '. ':
200 n = "~%02x" % ord(n[0]) + n[1:]
196 n = "~%02x" % ord(n[0]) + n[1:]
201 path[i] = n
197 path[i] = n
202 else:
198 else:
203 l = n.find('.')
199 l = n.find('.')
204 if l == -1:
200 if l == -1:
205 l = len(n)
201 l = len(n)
206 if ((l == 3 and n[:3] in _winres3) or
202 if ((l == 3 and n[:3] in _winres3) or
207 (l == 4 and n[3] <= '9' and n[3] >= '1'
203 (l == 4 and n[3] <= '9' and n[3] >= '1'
208 and n[:3] in _winres4)):
204 and n[:3] in _winres4)):
209 # encode third letter ('aux' -> 'au~78')
205 # encode third letter ('aux' -> 'au~78')
210 ec = "~%02x" % ord(n[2])
206 ec = "~%02x" % ord(n[2])
211 n = n[0:2] + ec + n[3:]
207 n = n[0:2] + ec + n[3:]
212 path[i] = n
208 path[i] = n
213 if n[-1] in '. ':
209 if n[-1] in '. ':
214 # encode last period or space ('foo...' -> 'foo..~2e')
210 # encode last period or space ('foo...' -> 'foo..~2e')
215 path[i] = n[:-1] + "~%02x" % ord(n[-1])
211 path[i] = n[:-1] + "~%02x" % ord(n[-1])
216 return path
212 return path
217
213
218 _maxstorepathlen = 120
214 _maxstorepathlen = 120
219 _dirprefixlen = 8
215 _dirprefixlen = 8
220 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
216 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
221
217
222 def _hashencode(path, dotencode):
218 def _hashencode(path, dotencode):
223 digest = hashlib.sha1(path).hexdigest()
219 digest = hashlib.sha1(path).hexdigest()
224 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
220 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
225 parts = _auxencode(le, dotencode)
221 parts = _auxencode(le, dotencode)
226 basename = parts[-1]
222 basename = parts[-1]
227 _root, ext = os.path.splitext(basename)
223 _root, ext = os.path.splitext(basename)
228 sdirs = []
224 sdirs = []
229 sdirslen = 0
225 sdirslen = 0
230 for p in parts[:-1]:
226 for p in parts[:-1]:
231 d = p[:_dirprefixlen]
227 d = p[:_dirprefixlen]
232 if d[-1] in '. ':
228 if d[-1] in '. ':
233 # Windows can't access dirs ending in period or space
229 # Windows can't access dirs ending in period or space
234 d = d[:-1] + '_'
230 d = d[:-1] + '_'
235 if sdirslen == 0:
231 if sdirslen == 0:
236 t = len(d)
232 t = len(d)
237 else:
233 else:
238 t = sdirslen + 1 + len(d)
234 t = sdirslen + 1 + len(d)
239 if t > _maxshortdirslen:
235 if t > _maxshortdirslen:
240 break
236 break
241 sdirs.append(d)
237 sdirs.append(d)
242 sdirslen = t
238 sdirslen = t
243 dirs = '/'.join(sdirs)
239 dirs = '/'.join(sdirs)
244 if len(dirs) > 0:
240 if len(dirs) > 0:
245 dirs += '/'
241 dirs += '/'
246 res = 'dh/' + dirs + digest + ext
242 res = 'dh/' + dirs + digest + ext
247 spaceleft = _maxstorepathlen - len(res)
243 spaceleft = _maxstorepathlen - len(res)
248 if spaceleft > 0:
244 if spaceleft > 0:
249 filler = basename[:spaceleft]
245 filler = basename[:spaceleft]
250 res = 'dh/' + dirs + filler + digest + ext
246 res = 'dh/' + dirs + filler + digest + ext
251 return res
247 return res
252
248
253 def _hybridencode(path, dotencode):
249 def _hybridencode(path, dotencode):
254 '''encodes path with a length limit
250 '''encodes path with a length limit
255
251
256 Encodes all paths that begin with 'data/', according to the following.
252 Encodes all paths that begin with 'data/', according to the following.
257
253
258 Default encoding (reversible):
254 Default encoding (reversible):
259
255
260 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
256 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
261 characters are encoded as '~xx', where xx is the two digit hex code
257 characters are encoded as '~xx', where xx is the two digit hex code
262 of the character (see encodefilename).
258 of the character (see encodefilename).
263 Relevant path components consisting of Windows reserved filenames are
259 Relevant path components consisting of Windows reserved filenames are
264 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
260 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
265
261
266 Hashed encoding (not reversible):
262 Hashed encoding (not reversible):
267
263
268 If the default-encoded path is longer than _maxstorepathlen, a
264 If the default-encoded path is longer than _maxstorepathlen, a
269 non-reversible hybrid hashing of the path is done instead.
265 non-reversible hybrid hashing of the path is done instead.
270 This encoding uses up to _dirprefixlen characters of all directory
266 This encoding uses up to _dirprefixlen characters of all directory
271 levels of the lowerencoded path, but not more levels than can fit into
267 levels of the lowerencoded path, but not more levels than can fit into
272 _maxshortdirslen.
268 _maxshortdirslen.
273 Then follows the filler followed by the sha digest of the full path.
269 Then follows the filler followed by the sha digest of the full path.
274 The filler is the beginning of the basename of the lowerencoded path
270 The filler is the beginning of the basename of the lowerencoded path
275 (the basename is everything after the last path separator). The filler
271 (the basename is everything after the last path separator). The filler
276 is as long as possible, filling in characters from the basename until
272 is as long as possible, filling in characters from the basename until
277 the encoded path has _maxstorepathlen characters (or all chars of the
273 the encoded path has _maxstorepathlen characters (or all chars of the
278 basename have been taken).
274 basename have been taken).
279 The extension (e.g. '.i' or '.d') is preserved.
275 The extension (e.g. '.i' or '.d') is preserved.
280
276
281 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
277 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
282 encoding was used.
278 encoding was used.
283 '''
279 '''
284 path = encodedir(path)
280 path = encodedir(path)
285 ef = _encodefname(path).split('/')
281 ef = _encodefname(path).split('/')
286 res = '/'.join(_auxencode(ef, dotencode))
282 res = '/'.join(_auxencode(ef, dotencode))
287 if len(res) > _maxstorepathlen:
283 if len(res) > _maxstorepathlen:
288 res = _hashencode(path, dotencode)
284 res = _hashencode(path, dotencode)
289 return res
285 return res
290
286
291 def _pathencode(path):
287 def _pathencode(path):
292 de = encodedir(path)
288 de = encodedir(path)
293 if len(path) > _maxstorepathlen:
289 if len(path) > _maxstorepathlen:
294 return _hashencode(de, True)
290 return _hashencode(de, True)
295 ef = _encodefname(de).split('/')
291 ef = _encodefname(de).split('/')
296 res = '/'.join(_auxencode(ef, True))
292 res = '/'.join(_auxencode(ef, True))
297 if len(res) > _maxstorepathlen:
293 if len(res) > _maxstorepathlen:
298 return _hashencode(de, True)
294 return _hashencode(de, True)
299 return res
295 return res
300
296
301 _pathencode = getattr(parsers, 'pathencode', _pathencode)
297 _pathencode = getattr(parsers, 'pathencode', _pathencode)
302
298
303 def _plainhybridencode(f):
299 def _plainhybridencode(f):
304 return _hybridencode(f, False)
300 return _hybridencode(f, False)
305
301
306 def _calcmode(vfs):
302 def _calcmode(vfs):
307 try:
303 try:
308 # files in .hg/ will be created using this mode
304 # files in .hg/ will be created using this mode
309 mode = vfs.stat().st_mode
305 mode = vfs.stat().st_mode
310 # avoid some useless chmods
306 # avoid some useless chmods
311 if (0o777 & ~util.umask) == (0o777 & mode):
307 if (0o777 & ~util.umask) == (0o777 & mode):
312 mode = None
308 mode = None
313 except OSError:
309 except OSError:
314 mode = None
310 mode = None
315 return mode
311 return mode
316
312
317 _data = ('data meta 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
313 _data = ('data meta 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
318 ' phaseroots obsstore')
314 ' phaseroots obsstore')
319
315
320 class basicstore(object):
316 class basicstore(object):
321 '''base class for local repository stores'''
317 '''base class for local repository stores'''
322 def __init__(self, path, vfstype):
318 def __init__(self, path, vfstype):
323 vfs = vfstype(path)
319 vfs = vfstype(path)
324 self.path = vfs.base
320 self.path = vfs.base
325 self.createmode = _calcmode(vfs)
321 self.createmode = _calcmode(vfs)
326 vfs.createmode = self.createmode
322 vfs.createmode = self.createmode
327 self.rawvfs = vfs
323 self.rawvfs = vfs
328 self.vfs = vfsmod.filtervfs(vfs, encodedir)
324 self.vfs = vfsmod.filtervfs(vfs, encodedir)
329 self.opener = self.vfs
325 self.opener = self.vfs
330
326
331 def join(self, f):
327 def join(self, f):
332 return self.path + '/' + encodedir(f)
328 return self.path + '/' + encodedir(f)
333
329
334 def _walk(self, relpath, recurse):
330 def _walk(self, relpath, recurse):
335 '''yields (unencoded, encoded, size)'''
331 '''yields (unencoded, encoded, size)'''
336 path = self.path
332 path = self.path
337 if relpath:
333 if relpath:
338 path += '/' + relpath
334 path += '/' + relpath
339 striplen = len(self.path) + 1
335 striplen = len(self.path) + 1
340 l = []
336 l = []
341 if self.rawvfs.isdir(path):
337 if self.rawvfs.isdir(path):
342 visit = [path]
338 visit = [path]
343 readdir = self.rawvfs.readdir
339 readdir = self.rawvfs.readdir
344 while visit:
340 while visit:
345 p = visit.pop()
341 p = visit.pop()
346 for f, kind, st in readdir(p, stat=True):
342 for f, kind, st in readdir(p, stat=True):
347 fp = p + '/' + f
343 fp = p + '/' + f
348 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
344 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
349 n = util.pconvert(fp[striplen:])
345 n = util.pconvert(fp[striplen:])
350 l.append((decodedir(n), n, st.st_size))
346 l.append((decodedir(n), n, st.st_size))
351 elif kind == stat.S_IFDIR and recurse:
347 elif kind == stat.S_IFDIR and recurse:
352 visit.append(fp)
348 visit.append(fp)
353 l.sort()
349 l.sort()
354 return l
350 return l
355
351
356 def datafiles(self):
352 def datafiles(self):
357 return self._walk('data', True) + self._walk('meta', True)
353 return self._walk('data', True) + self._walk('meta', True)
358
354
359 def topfiles(self):
355 def topfiles(self):
360 # yield manifest before changelog
356 # yield manifest before changelog
361 return reversed(self._walk('', False))
357 return reversed(self._walk('', False))
362
358
363 def walk(self):
359 def walk(self):
364 '''yields (unencoded, encoded, size)'''
360 '''yields (unencoded, encoded, size)'''
365 # yield data files first
361 # yield data files first
366 for x in self.datafiles():
362 for x in self.datafiles():
367 yield x
363 yield x
368 for x in self.topfiles():
364 for x in self.topfiles():
369 yield x
365 yield x
370
366
371 def copylist(self):
367 def copylist(self):
372 return ['requires'] + _data.split()
368 return ['requires'] + _data.split()
373
369
374 def write(self, tr):
370 def write(self, tr):
375 pass
371 pass
376
372
377 def invalidatecaches(self):
373 def invalidatecaches(self):
378 pass
374 pass
379
375
380 def markremoved(self, fn):
376 def markremoved(self, fn):
381 pass
377 pass
382
378
383 def __contains__(self, path):
379 def __contains__(self, path):
384 '''Checks if the store contains path'''
380 '''Checks if the store contains path'''
385 path = "/".join(("data", path))
381 path = "/".join(("data", path))
386 # file?
382 # file?
387 if self.vfs.exists(path + ".i"):
383 if self.vfs.exists(path + ".i"):
388 return True
384 return True
389 # dir?
385 # dir?
390 if not path.endswith("/"):
386 if not path.endswith("/"):
391 path = path + "/"
387 path = path + "/"
392 return self.vfs.exists(path)
388 return self.vfs.exists(path)
393
389
394 class encodedstore(basicstore):
390 class encodedstore(basicstore):
395 def __init__(self, path, vfstype):
391 def __init__(self, path, vfstype):
396 vfs = vfstype(path + '/store')
392 vfs = vfstype(path + '/store')
397 self.path = vfs.base
393 self.path = vfs.base
398 self.createmode = _calcmode(vfs)
394 self.createmode = _calcmode(vfs)
399 vfs.createmode = self.createmode
395 vfs.createmode = self.createmode
400 self.rawvfs = vfs
396 self.rawvfs = vfs
401 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
397 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
402 self.opener = self.vfs
398 self.opener = self.vfs
403
399
404 def datafiles(self):
400 def datafiles(self):
405 for a, b, size in super(encodedstore, self).datafiles():
401 for a, b, size in super(encodedstore, self).datafiles():
406 try:
402 try:
407 a = decodefilename(a)
403 a = decodefilename(a)
408 except KeyError:
404 except KeyError:
409 a = None
405 a = None
410 yield a, b, size
406 yield a, b, size
411
407
412 def join(self, f):
408 def join(self, f):
413 return self.path + '/' + encodefilename(f)
409 return self.path + '/' + encodefilename(f)
414
410
415 def copylist(self):
411 def copylist(self):
416 return (['requires', '00changelog.i'] +
412 return (['requires', '00changelog.i'] +
417 ['store/' + f for f in _data.split()])
413 ['store/' + f for f in _data.split()])
418
414
419 class fncache(object):
415 class fncache(object):
420 # the filename used to be partially encoded
416 # the filename used to be partially encoded
421 # hence the encodedir/decodedir dance
417 # hence the encodedir/decodedir dance
422 def __init__(self, vfs):
418 def __init__(self, vfs):
423 self.vfs = vfs
419 self.vfs = vfs
424 self.entries = None
420 self.entries = None
425 self._dirty = False
421 self._dirty = False
426
422
427 def _load(self):
423 def _load(self):
428 '''fill the entries from the fncache file'''
424 '''fill the entries from the fncache file'''
429 self._dirty = False
425 self._dirty = False
430 try:
426 try:
431 fp = self.vfs('fncache', mode='rb')
427 fp = self.vfs('fncache', mode='rb')
432 except IOError:
428 except IOError:
433 # skip nonexistent file
429 # skip nonexistent file
434 self.entries = set()
430 self.entries = set()
435 return
431 return
436 self.entries = set(decodedir(fp.read()).splitlines())
432 self.entries = set(decodedir(fp.read()).splitlines())
437 if '' in self.entries:
433 if '' in self.entries:
438 fp.seek(0)
434 fp.seek(0)
439 for n, line in enumerate(util.iterfile(fp)):
435 for n, line in enumerate(util.iterfile(fp)):
440 if not line.rstrip('\n'):
436 if not line.rstrip('\n'):
441 t = _('invalid entry in fncache, line %d') % (n + 1)
437 t = _('invalid entry in fncache, line %d') % (n + 1)
442 raise error.Abort(t)
438 raise error.Abort(t)
443 fp.close()
439 fp.close()
444
440
445 def write(self, tr):
441 def write(self, tr):
446 if self._dirty:
442 if self._dirty:
447 tr.addbackup('fncache')
443 tr.addbackup('fncache')
448 fp = self.vfs('fncache', mode='wb', atomictemp=True)
444 fp = self.vfs('fncache', mode='wb', atomictemp=True)
449 if self.entries:
445 if self.entries:
450 fp.write(encodedir('\n'.join(self.entries) + '\n'))
446 fp.write(encodedir('\n'.join(self.entries) + '\n'))
451 fp.close()
447 fp.close()
452 self._dirty = False
448 self._dirty = False
453
449
454 def add(self, fn):
450 def add(self, fn):
455 if self.entries is None:
451 if self.entries is None:
456 self._load()
452 self._load()
457 if fn not in self.entries:
453 if fn not in self.entries:
458 self._dirty = True
454 self._dirty = True
459 self.entries.add(fn)
455 self.entries.add(fn)
460
456
461 def remove(self, fn):
457 def remove(self, fn):
462 if self.entries is None:
458 if self.entries is None:
463 self._load()
459 self._load()
464 try:
460 try:
465 self.entries.remove(fn)
461 self.entries.remove(fn)
466 self._dirty = True
462 self._dirty = True
467 except KeyError:
463 except KeyError:
468 pass
464 pass
469
465
470 def __contains__(self, fn):
466 def __contains__(self, fn):
471 if self.entries is None:
467 if self.entries is None:
472 self._load()
468 self._load()
473 return fn in self.entries
469 return fn in self.entries
474
470
475 def __iter__(self):
471 def __iter__(self):
476 if self.entries is None:
472 if self.entries is None:
477 self._load()
473 self._load()
478 return iter(self.entries)
474 return iter(self.entries)
479
475
480 class _fncachevfs(vfsmod.abstractvfs, vfsmod.auditvfs):
476 class _fncachevfs(vfsmod.abstractvfs, vfsmod.auditvfs):
481 def __init__(self, vfs, fnc, encode):
477 def __init__(self, vfs, fnc, encode):
482 vfsmod.auditvfs.__init__(self, vfs)
478 vfsmod.auditvfs.__init__(self, vfs)
483 self.fncache = fnc
479 self.fncache = fnc
484 self.encode = encode
480 self.encode = encode
485
481
486 def __call__(self, path, mode='r', *args, **kw):
482 def __call__(self, path, mode='r', *args, **kw):
487 if mode not in ('r', 'rb') and (path.startswith('data/') or
483 if mode not in ('r', 'rb') and (path.startswith('data/') or
488 path.startswith('meta/')):
484 path.startswith('meta/')):
489 self.fncache.add(path)
485 self.fncache.add(path)
490 return self.vfs(self.encode(path), mode, *args, **kw)
486 return self.vfs(self.encode(path), mode, *args, **kw)
491
487
492 def join(self, path):
488 def join(self, path):
493 if path:
489 if path:
494 return self.vfs.join(self.encode(path))
490 return self.vfs.join(self.encode(path))
495 else:
491 else:
496 return self.vfs.join(path)
492 return self.vfs.join(path)
497
493
498 class fncachestore(basicstore):
494 class fncachestore(basicstore):
499 def __init__(self, path, vfstype, dotencode):
495 def __init__(self, path, vfstype, dotencode):
500 if dotencode:
496 if dotencode:
501 encode = _pathencode
497 encode = _pathencode
502 else:
498 else:
503 encode = _plainhybridencode
499 encode = _plainhybridencode
504 self.encode = encode
500 self.encode = encode
505 vfs = vfstype(path + '/store')
501 vfs = vfstype(path + '/store')
506 self.path = vfs.base
502 self.path = vfs.base
507 self.pathsep = self.path + '/'
503 self.pathsep = self.path + '/'
508 self.createmode = _calcmode(vfs)
504 self.createmode = _calcmode(vfs)
509 vfs.createmode = self.createmode
505 vfs.createmode = self.createmode
510 self.rawvfs = vfs
506 self.rawvfs = vfs
511 fnc = fncache(vfs)
507 fnc = fncache(vfs)
512 self.fncache = fnc
508 self.fncache = fnc
513 self.vfs = _fncachevfs(vfs, fnc, encode)
509 self.vfs = _fncachevfs(vfs, fnc, encode)
514 self.opener = self.vfs
510 self.opener = self.vfs
515
511
516 def join(self, f):
512 def join(self, f):
517 return self.pathsep + self.encode(f)
513 return self.pathsep + self.encode(f)
518
514
519 def getsize(self, path):
515 def getsize(self, path):
520 return self.rawvfs.stat(path).st_size
516 return self.rawvfs.stat(path).st_size
521
517
522 def datafiles(self):
518 def datafiles(self):
523 for f in sorted(self.fncache):
519 for f in sorted(self.fncache):
524 ef = self.encode(f)
520 ef = self.encode(f)
525 try:
521 try:
526 yield f, ef, self.getsize(ef)
522 yield f, ef, self.getsize(ef)
527 except OSError as err:
523 except OSError as err:
528 if err.errno != errno.ENOENT:
524 if err.errno != errno.ENOENT:
529 raise
525 raise
530
526
531 def copylist(self):
527 def copylist(self):
532 d = ('data meta dh fncache phaseroots obsstore'
528 d = ('data meta dh fncache phaseroots obsstore'
533 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
529 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
534 return (['requires', '00changelog.i'] +
530 return (['requires', '00changelog.i'] +
535 ['store/' + f for f in d.split()])
531 ['store/' + f for f in d.split()])
536
532
537 def write(self, tr):
533 def write(self, tr):
538 self.fncache.write(tr)
534 self.fncache.write(tr)
539
535
540 def invalidatecaches(self):
536 def invalidatecaches(self):
541 self.fncache.entries = None
537 self.fncache.entries = None
542
538
543 def markremoved(self, fn):
539 def markremoved(self, fn):
544 self.fncache.remove(fn)
540 self.fncache.remove(fn)
545
541
546 def _exists(self, f):
542 def _exists(self, f):
547 ef = self.encode(f)
543 ef = self.encode(f)
548 try:
544 try:
549 self.getsize(ef)
545 self.getsize(ef)
550 return True
546 return True
551 except OSError as err:
547 except OSError as err:
552 if err.errno != errno.ENOENT:
548 if err.errno != errno.ENOENT:
553 raise
549 raise
554 # nonexistent entry
550 # nonexistent entry
555 return False
551 return False
556
552
557 def __contains__(self, path):
553 def __contains__(self, path):
558 '''Checks if the store contains path'''
554 '''Checks if the store contains path'''
559 path = "/".join(("data", path))
555 path = "/".join(("data", path))
560 # check for files (exact match)
556 # check for files (exact match)
561 e = path + '.i'
557 e = path + '.i'
562 if e in self.fncache and self._exists(e):
558 if e in self.fncache and self._exists(e):
563 return True
559 return True
564 # now check for directories (prefix match)
560 # now check for directories (prefix match)
565 if not path.endswith('/'):
561 if not path.endswith('/'):
566 path += '/'
562 path += '/'
567 for e in self.fncache:
563 for e in self.fncache:
568 if e.startswith(path) and self._exists(e):
564 if e.startswith(path) and self._exists(e):
569 return True
565 return True
570 return False
566 return False
571
567
572 def store(requirements, path, vfstype):
568 def store(requirements, path, vfstype):
573 if 'store' in requirements:
569 if 'store' in requirements:
574 if 'fncache' in requirements:
570 if 'fncache' in requirements:
575 return fncachestore(path, vfstype, 'dotencode' in requirements)
571 return fncachestore(path, vfstype, 'dotencode' in requirements)
576 return encodedstore(path, vfstype)
572 return encodedstore(path, vfstype)
577 return basicstore(path, vfstype)
573 return basicstore(path, vfstype)
@@ -1,1674 +1,1670 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import atexit
10 import atexit
11 import collections
11 import collections
12 import contextlib
12 import contextlib
13 import errno
13 import errno
14 import getpass
14 import getpass
15 import inspect
15 import inspect
16 import os
16 import os
17 import re
17 import re
18 import signal
18 import signal
19 import socket
19 import socket
20 import subprocess
20 import subprocess
21 import sys
21 import sys
22 import tempfile
22 import tempfile
23 import traceback
23 import traceback
24
24
25 from .i18n import _
25 from .i18n import _
26 from .node import hex
26 from .node import hex
27
27
28 from . import (
28 from . import (
29 color,
29 color,
30 config,
30 config,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39
39
40 urlreq = util.urlreq
40 urlreq = util.urlreq
41
41
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 if pycompat.ispy3:
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 _bytes = [bytes([c]) for c in range(256)]
44 if not c.isalnum())
45 _notalnum = [s for s in _bytes if not s.isalnum()]
46 else:
47 _notalnum = [c for c in map(chr, range(256)) if not c.isalnum()]
48 _keepalnum = ''.join(_notalnum)
49
45
50 samplehgrcs = {
46 samplehgrcs = {
51 'user':
47 'user':
52 """# example user config (see 'hg help config' for more info)
48 """# example user config (see 'hg help config' for more info)
53 [ui]
49 [ui]
54 # name and email, e.g.
50 # name and email, e.g.
55 # username = Jane Doe <jdoe@example.com>
51 # username = Jane Doe <jdoe@example.com>
56 username =
52 username =
57
53
58 # uncomment to colorize command output
54 # uncomment to colorize command output
59 # color = auto
55 # color = auto
60
56
61 [extensions]
57 [extensions]
62 # uncomment these lines to enable some popular extensions
58 # uncomment these lines to enable some popular extensions
63 # (see 'hg help extensions' for more info)
59 # (see 'hg help extensions' for more info)
64 #
60 #
65 # pager =""",
61 # pager =""",
66
62
67 'cloned':
63 'cloned':
68 """# example repository config (see 'hg help config' for more info)
64 """# example repository config (see 'hg help config' for more info)
69 [paths]
65 [paths]
70 default = %s
66 default = %s
71
67
72 # path aliases to other clones of this repo in URLs or filesystem paths
68 # path aliases to other clones of this repo in URLs or filesystem paths
73 # (see 'hg help config.paths' for more info)
69 # (see 'hg help config.paths' for more info)
74 #
70 #
75 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
71 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
76 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
77 # my-clone = /home/jdoe/jdoes-clone
73 # my-clone = /home/jdoe/jdoes-clone
78
74
79 [ui]
75 [ui]
80 # name and email (local to this repository, optional), e.g.
76 # name and email (local to this repository, optional), e.g.
81 # username = Jane Doe <jdoe@example.com>
77 # username = Jane Doe <jdoe@example.com>
82 """,
78 """,
83
79
84 'local':
80 'local':
85 """# example repository config (see 'hg help config' for more info)
81 """# example repository config (see 'hg help config' for more info)
86 [paths]
82 [paths]
87 # path aliases to other clones of this repo in URLs or filesystem paths
83 # path aliases to other clones of this repo in URLs or filesystem paths
88 # (see 'hg help config.paths' for more info)
84 # (see 'hg help config.paths' for more info)
89 #
85 #
90 # default = http://example.com/hg/example-repo
86 # default = http://example.com/hg/example-repo
91 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
87 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
92 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
88 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
93 # my-clone = /home/jdoe/jdoes-clone
89 # my-clone = /home/jdoe/jdoes-clone
94
90
95 [ui]
91 [ui]
96 # name and email (local to this repository, optional), e.g.
92 # name and email (local to this repository, optional), e.g.
97 # username = Jane Doe <jdoe@example.com>
93 # username = Jane Doe <jdoe@example.com>
98 """,
94 """,
99
95
100 'global':
96 'global':
101 """# example system-wide hg config (see 'hg help config' for more info)
97 """# example system-wide hg config (see 'hg help config' for more info)
102
98
103 [ui]
99 [ui]
104 # uncomment to colorize command output
100 # uncomment to colorize command output
105 # color = auto
101 # color = auto
106
102
107 [extensions]
103 [extensions]
108 # uncomment these lines to enable some popular extensions
104 # uncomment these lines to enable some popular extensions
109 # (see 'hg help extensions' for more info)
105 # (see 'hg help extensions' for more info)
110 #
106 #
111 # blackbox =
107 # blackbox =
112 # pager =""",
108 # pager =""",
113 }
109 }
114
110
115
111
116 class httppasswordmgrdbproxy(object):
112 class httppasswordmgrdbproxy(object):
117 """Delays loading urllib2 until it's needed."""
113 """Delays loading urllib2 until it's needed."""
118 def __init__(self):
114 def __init__(self):
119 self._mgr = None
115 self._mgr = None
120
116
121 def _get_mgr(self):
117 def _get_mgr(self):
122 if self._mgr is None:
118 if self._mgr is None:
123 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
119 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
124 return self._mgr
120 return self._mgr
125
121
126 def add_password(self, *args, **kwargs):
122 def add_password(self, *args, **kwargs):
127 return self._get_mgr().add_password(*args, **kwargs)
123 return self._get_mgr().add_password(*args, **kwargs)
128
124
129 def find_user_password(self, *args, **kwargs):
125 def find_user_password(self, *args, **kwargs):
130 return self._get_mgr().find_user_password(*args, **kwargs)
126 return self._get_mgr().find_user_password(*args, **kwargs)
131
127
132 def _catchterm(*args):
128 def _catchterm(*args):
133 raise error.SignalInterrupt
129 raise error.SignalInterrupt
134
130
135 class ui(object):
131 class ui(object):
136 def __init__(self, src=None):
132 def __init__(self, src=None):
137 """Create a fresh new ui object if no src given
133 """Create a fresh new ui object if no src given
138
134
139 Use uimod.ui.load() to create a ui which knows global and user configs.
135 Use uimod.ui.load() to create a ui which knows global and user configs.
140 In most cases, you should use ui.copy() to create a copy of an existing
136 In most cases, you should use ui.copy() to create a copy of an existing
141 ui object.
137 ui object.
142 """
138 """
143 # _buffers: used for temporary capture of output
139 # _buffers: used for temporary capture of output
144 self._buffers = []
140 self._buffers = []
145 # 3-tuple describing how each buffer in the stack behaves.
141 # 3-tuple describing how each buffer in the stack behaves.
146 # Values are (capture stderr, capture subprocesses, apply labels).
142 # Values are (capture stderr, capture subprocesses, apply labels).
147 self._bufferstates = []
143 self._bufferstates = []
148 # When a buffer is active, defines whether we are expanding labels.
144 # When a buffer is active, defines whether we are expanding labels.
149 # This exists to prevent an extra list lookup.
145 # This exists to prevent an extra list lookup.
150 self._bufferapplylabels = None
146 self._bufferapplylabels = None
151 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
147 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
152 self._reportuntrusted = True
148 self._reportuntrusted = True
153 self._ocfg = config.config() # overlay
149 self._ocfg = config.config() # overlay
154 self._tcfg = config.config() # trusted
150 self._tcfg = config.config() # trusted
155 self._ucfg = config.config() # untrusted
151 self._ucfg = config.config() # untrusted
156 self._trustusers = set()
152 self._trustusers = set()
157 self._trustgroups = set()
153 self._trustgroups = set()
158 self.callhooks = True
154 self.callhooks = True
159 # Insecure server connections requested.
155 # Insecure server connections requested.
160 self.insecureconnections = False
156 self.insecureconnections = False
161 # Blocked time
157 # Blocked time
162 self.logblockedtimes = False
158 self.logblockedtimes = False
163 # color mode: see mercurial/color.py for possible value
159 # color mode: see mercurial/color.py for possible value
164 self._colormode = None
160 self._colormode = None
165 self._terminfoparams = {}
161 self._terminfoparams = {}
166 self._styles = {}
162 self._styles = {}
167
163
168 if src:
164 if src:
169 self.fout = src.fout
165 self.fout = src.fout
170 self.ferr = src.ferr
166 self.ferr = src.ferr
171 self.fin = src.fin
167 self.fin = src.fin
172 self.pageractive = src.pageractive
168 self.pageractive = src.pageractive
173 self._disablepager = src._disablepager
169 self._disablepager = src._disablepager
174
170
175 self._tcfg = src._tcfg.copy()
171 self._tcfg = src._tcfg.copy()
176 self._ucfg = src._ucfg.copy()
172 self._ucfg = src._ucfg.copy()
177 self._ocfg = src._ocfg.copy()
173 self._ocfg = src._ocfg.copy()
178 self._trustusers = src._trustusers.copy()
174 self._trustusers = src._trustusers.copy()
179 self._trustgroups = src._trustgroups.copy()
175 self._trustgroups = src._trustgroups.copy()
180 self.environ = src.environ
176 self.environ = src.environ
181 self.callhooks = src.callhooks
177 self.callhooks = src.callhooks
182 self.insecureconnections = src.insecureconnections
178 self.insecureconnections = src.insecureconnections
183 self._colormode = src._colormode
179 self._colormode = src._colormode
184 self._terminfoparams = src._terminfoparams.copy()
180 self._terminfoparams = src._terminfoparams.copy()
185 self._styles = src._styles.copy()
181 self._styles = src._styles.copy()
186
182
187 self.fixconfig()
183 self.fixconfig()
188
184
189 self.httppasswordmgrdb = src.httppasswordmgrdb
185 self.httppasswordmgrdb = src.httppasswordmgrdb
190 self._blockedtimes = src._blockedtimes
186 self._blockedtimes = src._blockedtimes
191 else:
187 else:
192 self.fout = util.stdout
188 self.fout = util.stdout
193 self.ferr = util.stderr
189 self.ferr = util.stderr
194 self.fin = util.stdin
190 self.fin = util.stdin
195 self.pageractive = False
191 self.pageractive = False
196 self._disablepager = False
192 self._disablepager = False
197
193
198 # shared read-only environment
194 # shared read-only environment
199 self.environ = encoding.environ
195 self.environ = encoding.environ
200
196
201 self.httppasswordmgrdb = httppasswordmgrdbproxy()
197 self.httppasswordmgrdb = httppasswordmgrdbproxy()
202 self._blockedtimes = collections.defaultdict(int)
198 self._blockedtimes = collections.defaultdict(int)
203
199
204 allowed = self.configlist('experimental', 'exportableenviron')
200 allowed = self.configlist('experimental', 'exportableenviron')
205 if '*' in allowed:
201 if '*' in allowed:
206 self._exportableenviron = self.environ
202 self._exportableenviron = self.environ
207 else:
203 else:
208 self._exportableenviron = {}
204 self._exportableenviron = {}
209 for k in allowed:
205 for k in allowed:
210 if k in self.environ:
206 if k in self.environ:
211 self._exportableenviron[k] = self.environ[k]
207 self._exportableenviron[k] = self.environ[k]
212
208
213 @classmethod
209 @classmethod
214 def load(cls):
210 def load(cls):
215 """Create a ui and load global and user configs"""
211 """Create a ui and load global and user configs"""
216 u = cls()
212 u = cls()
217 # we always trust global config files
213 # we always trust global config files
218 for f in scmutil.rcpath():
214 for f in scmutil.rcpath():
219 u.readconfig(f, trust=True)
215 u.readconfig(f, trust=True)
220 return u
216 return u
221
217
222 def copy(self):
218 def copy(self):
223 return self.__class__(self)
219 return self.__class__(self)
224
220
225 def resetstate(self):
221 def resetstate(self):
226 """Clear internal state that shouldn't persist across commands"""
222 """Clear internal state that shouldn't persist across commands"""
227 if self._progbar:
223 if self._progbar:
228 self._progbar.resetstate() # reset last-print time of progress bar
224 self._progbar.resetstate() # reset last-print time of progress bar
229 self.httppasswordmgrdb = httppasswordmgrdbproxy()
225 self.httppasswordmgrdb = httppasswordmgrdbproxy()
230
226
231 @contextlib.contextmanager
227 @contextlib.contextmanager
232 def timeblockedsection(self, key):
228 def timeblockedsection(self, key):
233 # this is open-coded below - search for timeblockedsection to find them
229 # this is open-coded below - search for timeblockedsection to find them
234 starttime = util.timer()
230 starttime = util.timer()
235 try:
231 try:
236 yield
232 yield
237 finally:
233 finally:
238 self._blockedtimes[key + '_blocked'] += \
234 self._blockedtimes[key + '_blocked'] += \
239 (util.timer() - starttime) * 1000
235 (util.timer() - starttime) * 1000
240
236
241 def formatter(self, topic, opts):
237 def formatter(self, topic, opts):
242 return formatter.formatter(self, topic, opts)
238 return formatter.formatter(self, topic, opts)
243
239
244 def _trusted(self, fp, f):
240 def _trusted(self, fp, f):
245 st = util.fstat(fp)
241 st = util.fstat(fp)
246 if util.isowner(st):
242 if util.isowner(st):
247 return True
243 return True
248
244
249 tusers, tgroups = self._trustusers, self._trustgroups
245 tusers, tgroups = self._trustusers, self._trustgroups
250 if '*' in tusers or '*' in tgroups:
246 if '*' in tusers or '*' in tgroups:
251 return True
247 return True
252
248
253 user = util.username(st.st_uid)
249 user = util.username(st.st_uid)
254 group = util.groupname(st.st_gid)
250 group = util.groupname(st.st_gid)
255 if user in tusers or group in tgroups or user == util.username():
251 if user in tusers or group in tgroups or user == util.username():
256 return True
252 return True
257
253
258 if self._reportuntrusted:
254 if self._reportuntrusted:
259 self.warn(_('not trusting file %s from untrusted '
255 self.warn(_('not trusting file %s from untrusted '
260 'user %s, group %s\n') % (f, user, group))
256 'user %s, group %s\n') % (f, user, group))
261 return False
257 return False
262
258
263 def readconfig(self, filename, root=None, trust=False,
259 def readconfig(self, filename, root=None, trust=False,
264 sections=None, remap=None):
260 sections=None, remap=None):
265 try:
261 try:
266 fp = open(filename, u'rb')
262 fp = open(filename, u'rb')
267 except IOError:
263 except IOError:
268 if not sections: # ignore unless we were looking for something
264 if not sections: # ignore unless we were looking for something
269 return
265 return
270 raise
266 raise
271
267
272 cfg = config.config()
268 cfg = config.config()
273 trusted = sections or trust or self._trusted(fp, filename)
269 trusted = sections or trust or self._trusted(fp, filename)
274
270
275 try:
271 try:
276 cfg.read(filename, fp, sections=sections, remap=remap)
272 cfg.read(filename, fp, sections=sections, remap=remap)
277 fp.close()
273 fp.close()
278 except error.ConfigError as inst:
274 except error.ConfigError as inst:
279 if trusted:
275 if trusted:
280 raise
276 raise
281 self.warn(_("ignored: %s\n") % str(inst))
277 self.warn(_("ignored: %s\n") % str(inst))
282
278
283 if self.plain():
279 if self.plain():
284 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
280 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
285 'logtemplate', 'statuscopies', 'style',
281 'logtemplate', 'statuscopies', 'style',
286 'traceback', 'verbose'):
282 'traceback', 'verbose'):
287 if k in cfg['ui']:
283 if k in cfg['ui']:
288 del cfg['ui'][k]
284 del cfg['ui'][k]
289 for k, v in cfg.items('defaults'):
285 for k, v in cfg.items('defaults'):
290 del cfg['defaults'][k]
286 del cfg['defaults'][k]
291 # Don't remove aliases from the configuration if in the exceptionlist
287 # Don't remove aliases from the configuration if in the exceptionlist
292 if self.plain('alias'):
288 if self.plain('alias'):
293 for k, v in cfg.items('alias'):
289 for k, v in cfg.items('alias'):
294 del cfg['alias'][k]
290 del cfg['alias'][k]
295 if self.plain('revsetalias'):
291 if self.plain('revsetalias'):
296 for k, v in cfg.items('revsetalias'):
292 for k, v in cfg.items('revsetalias'):
297 del cfg['revsetalias'][k]
293 del cfg['revsetalias'][k]
298 if self.plain('templatealias'):
294 if self.plain('templatealias'):
299 for k, v in cfg.items('templatealias'):
295 for k, v in cfg.items('templatealias'):
300 del cfg['templatealias'][k]
296 del cfg['templatealias'][k]
301
297
302 if trusted:
298 if trusted:
303 self._tcfg.update(cfg)
299 self._tcfg.update(cfg)
304 self._tcfg.update(self._ocfg)
300 self._tcfg.update(self._ocfg)
305 self._ucfg.update(cfg)
301 self._ucfg.update(cfg)
306 self._ucfg.update(self._ocfg)
302 self._ucfg.update(self._ocfg)
307
303
308 if root is None:
304 if root is None:
309 root = os.path.expanduser('~')
305 root = os.path.expanduser('~')
310 self.fixconfig(root=root)
306 self.fixconfig(root=root)
311
307
312 def fixconfig(self, root=None, section=None):
308 def fixconfig(self, root=None, section=None):
313 if section in (None, 'paths'):
309 if section in (None, 'paths'):
314 # expand vars and ~
310 # expand vars and ~
315 # translate paths relative to root (or home) into absolute paths
311 # translate paths relative to root (or home) into absolute paths
316 root = root or pycompat.getcwd()
312 root = root or pycompat.getcwd()
317 for c in self._tcfg, self._ucfg, self._ocfg:
313 for c in self._tcfg, self._ucfg, self._ocfg:
318 for n, p in c.items('paths'):
314 for n, p in c.items('paths'):
319 # Ignore sub-options.
315 # Ignore sub-options.
320 if ':' in n:
316 if ':' in n:
321 continue
317 continue
322 if not p:
318 if not p:
323 continue
319 continue
324 if '%%' in p:
320 if '%%' in p:
325 s = self.configsource('paths', n) or 'none'
321 s = self.configsource('paths', n) or 'none'
326 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
322 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
327 % (n, p, s))
323 % (n, p, s))
328 p = p.replace('%%', '%')
324 p = p.replace('%%', '%')
329 p = util.expandpath(p)
325 p = util.expandpath(p)
330 if not util.hasscheme(p) and not os.path.isabs(p):
326 if not util.hasscheme(p) and not os.path.isabs(p):
331 p = os.path.normpath(os.path.join(root, p))
327 p = os.path.normpath(os.path.join(root, p))
332 c.set("paths", n, p)
328 c.set("paths", n, p)
333
329
334 if section in (None, 'ui'):
330 if section in (None, 'ui'):
335 # update ui options
331 # update ui options
336 self.debugflag = self.configbool('ui', 'debug')
332 self.debugflag = self.configbool('ui', 'debug')
337 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
333 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
338 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
334 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
339 if self.verbose and self.quiet:
335 if self.verbose and self.quiet:
340 self.quiet = self.verbose = False
336 self.quiet = self.verbose = False
341 self._reportuntrusted = self.debugflag or self.configbool("ui",
337 self._reportuntrusted = self.debugflag or self.configbool("ui",
342 "report_untrusted", True)
338 "report_untrusted", True)
343 self.tracebackflag = self.configbool('ui', 'traceback', False)
339 self.tracebackflag = self.configbool('ui', 'traceback', False)
344 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
340 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
345
341
346 if section in (None, 'trusted'):
342 if section in (None, 'trusted'):
347 # update trust information
343 # update trust information
348 self._trustusers.update(self.configlist('trusted', 'users'))
344 self._trustusers.update(self.configlist('trusted', 'users'))
349 self._trustgroups.update(self.configlist('trusted', 'groups'))
345 self._trustgroups.update(self.configlist('trusted', 'groups'))
350
346
351 def backupconfig(self, section, item):
347 def backupconfig(self, section, item):
352 return (self._ocfg.backup(section, item),
348 return (self._ocfg.backup(section, item),
353 self._tcfg.backup(section, item),
349 self._tcfg.backup(section, item),
354 self._ucfg.backup(section, item),)
350 self._ucfg.backup(section, item),)
355 def restoreconfig(self, data):
351 def restoreconfig(self, data):
356 self._ocfg.restore(data[0])
352 self._ocfg.restore(data[0])
357 self._tcfg.restore(data[1])
353 self._tcfg.restore(data[1])
358 self._ucfg.restore(data[2])
354 self._ucfg.restore(data[2])
359
355
360 def setconfig(self, section, name, value, source=''):
356 def setconfig(self, section, name, value, source=''):
361 for cfg in (self._ocfg, self._tcfg, self._ucfg):
357 for cfg in (self._ocfg, self._tcfg, self._ucfg):
362 cfg.set(section, name, value, source)
358 cfg.set(section, name, value, source)
363 self.fixconfig(section=section)
359 self.fixconfig(section=section)
364
360
365 def _data(self, untrusted):
361 def _data(self, untrusted):
366 return untrusted and self._ucfg or self._tcfg
362 return untrusted and self._ucfg or self._tcfg
367
363
368 def configsource(self, section, name, untrusted=False):
364 def configsource(self, section, name, untrusted=False):
369 return self._data(untrusted).source(section, name)
365 return self._data(untrusted).source(section, name)
370
366
371 def config(self, section, name, default=None, untrusted=False):
367 def config(self, section, name, default=None, untrusted=False):
372 if isinstance(name, list):
368 if isinstance(name, list):
373 alternates = name
369 alternates = name
374 else:
370 else:
375 alternates = [name]
371 alternates = [name]
376
372
377 for n in alternates:
373 for n in alternates:
378 value = self._data(untrusted).get(section, n, None)
374 value = self._data(untrusted).get(section, n, None)
379 if value is not None:
375 if value is not None:
380 name = n
376 name = n
381 break
377 break
382 else:
378 else:
383 value = default
379 value = default
384
380
385 if self.debugflag and not untrusted and self._reportuntrusted:
381 if self.debugflag and not untrusted and self._reportuntrusted:
386 for n in alternates:
382 for n in alternates:
387 uvalue = self._ucfg.get(section, n)
383 uvalue = self._ucfg.get(section, n)
388 if uvalue is not None and uvalue != value:
384 if uvalue is not None and uvalue != value:
389 self.debug("ignoring untrusted configuration option "
385 self.debug("ignoring untrusted configuration option "
390 "%s.%s = %s\n" % (section, n, uvalue))
386 "%s.%s = %s\n" % (section, n, uvalue))
391 return value
387 return value
392
388
393 def configsuboptions(self, section, name, default=None, untrusted=False):
389 def configsuboptions(self, section, name, default=None, untrusted=False):
394 """Get a config option and all sub-options.
390 """Get a config option and all sub-options.
395
391
396 Some config options have sub-options that are declared with the
392 Some config options have sub-options that are declared with the
397 format "key:opt = value". This method is used to return the main
393 format "key:opt = value". This method is used to return the main
398 option and all its declared sub-options.
394 option and all its declared sub-options.
399
395
400 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
396 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
401 is a dict of defined sub-options where keys and values are strings.
397 is a dict of defined sub-options where keys and values are strings.
402 """
398 """
403 data = self._data(untrusted)
399 data = self._data(untrusted)
404 main = data.get(section, name, default)
400 main = data.get(section, name, default)
405 if self.debugflag and not untrusted and self._reportuntrusted:
401 if self.debugflag and not untrusted and self._reportuntrusted:
406 uvalue = self._ucfg.get(section, name)
402 uvalue = self._ucfg.get(section, name)
407 if uvalue is not None and uvalue != main:
403 if uvalue is not None and uvalue != main:
408 self.debug('ignoring untrusted configuration option '
404 self.debug('ignoring untrusted configuration option '
409 '%s.%s = %s\n' % (section, name, uvalue))
405 '%s.%s = %s\n' % (section, name, uvalue))
410
406
411 sub = {}
407 sub = {}
412 prefix = '%s:' % name
408 prefix = '%s:' % name
413 for k, v in data.items(section):
409 for k, v in data.items(section):
414 if k.startswith(prefix):
410 if k.startswith(prefix):
415 sub[k[len(prefix):]] = v
411 sub[k[len(prefix):]] = v
416
412
417 if self.debugflag and not untrusted and self._reportuntrusted:
413 if self.debugflag and not untrusted and self._reportuntrusted:
418 for k, v in sub.items():
414 for k, v in sub.items():
419 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
415 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
420 if uvalue is not None and uvalue != v:
416 if uvalue is not None and uvalue != v:
421 self.debug('ignoring untrusted configuration option '
417 self.debug('ignoring untrusted configuration option '
422 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
418 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
423
419
424 return main, sub
420 return main, sub
425
421
426 def configpath(self, section, name, default=None, untrusted=False):
422 def configpath(self, section, name, default=None, untrusted=False):
427 'get a path config item, expanded relative to repo root or config file'
423 'get a path config item, expanded relative to repo root or config file'
428 v = self.config(section, name, default, untrusted)
424 v = self.config(section, name, default, untrusted)
429 if v is None:
425 if v is None:
430 return None
426 return None
431 if not os.path.isabs(v) or "://" not in v:
427 if not os.path.isabs(v) or "://" not in v:
432 src = self.configsource(section, name, untrusted)
428 src = self.configsource(section, name, untrusted)
433 if ':' in src:
429 if ':' in src:
434 base = os.path.dirname(src.rsplit(':')[0])
430 base = os.path.dirname(src.rsplit(':')[0])
435 v = os.path.join(base, os.path.expanduser(v))
431 v = os.path.join(base, os.path.expanduser(v))
436 return v
432 return v
437
433
438 def configbool(self, section, name, default=False, untrusted=False):
434 def configbool(self, section, name, default=False, untrusted=False):
439 """parse a configuration element as a boolean
435 """parse a configuration element as a boolean
440
436
441 >>> u = ui(); s = 'foo'
437 >>> u = ui(); s = 'foo'
442 >>> u.setconfig(s, 'true', 'yes')
438 >>> u.setconfig(s, 'true', 'yes')
443 >>> u.configbool(s, 'true')
439 >>> u.configbool(s, 'true')
444 True
440 True
445 >>> u.setconfig(s, 'false', 'no')
441 >>> u.setconfig(s, 'false', 'no')
446 >>> u.configbool(s, 'false')
442 >>> u.configbool(s, 'false')
447 False
443 False
448 >>> u.configbool(s, 'unknown')
444 >>> u.configbool(s, 'unknown')
449 False
445 False
450 >>> u.configbool(s, 'unknown', True)
446 >>> u.configbool(s, 'unknown', True)
451 True
447 True
452 >>> u.setconfig(s, 'invalid', 'somevalue')
448 >>> u.setconfig(s, 'invalid', 'somevalue')
453 >>> u.configbool(s, 'invalid')
449 >>> u.configbool(s, 'invalid')
454 Traceback (most recent call last):
450 Traceback (most recent call last):
455 ...
451 ...
456 ConfigError: foo.invalid is not a boolean ('somevalue')
452 ConfigError: foo.invalid is not a boolean ('somevalue')
457 """
453 """
458
454
459 v = self.config(section, name, None, untrusted)
455 v = self.config(section, name, None, untrusted)
460 if v is None:
456 if v is None:
461 return default
457 return default
462 if isinstance(v, bool):
458 if isinstance(v, bool):
463 return v
459 return v
464 b = util.parsebool(v)
460 b = util.parsebool(v)
465 if b is None:
461 if b is None:
466 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
462 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
467 % (section, name, v))
463 % (section, name, v))
468 return b
464 return b
469
465
470 def configwith(self, convert, section, name, default=None,
466 def configwith(self, convert, section, name, default=None,
471 desc=None, untrusted=False):
467 desc=None, untrusted=False):
472 """parse a configuration element with a conversion function
468 """parse a configuration element with a conversion function
473
469
474 >>> u = ui(); s = 'foo'
470 >>> u = ui(); s = 'foo'
475 >>> u.setconfig(s, 'float1', '42')
471 >>> u.setconfig(s, 'float1', '42')
476 >>> u.configwith(float, s, 'float1')
472 >>> u.configwith(float, s, 'float1')
477 42.0
473 42.0
478 >>> u.setconfig(s, 'float2', '-4.25')
474 >>> u.setconfig(s, 'float2', '-4.25')
479 >>> u.configwith(float, s, 'float2')
475 >>> u.configwith(float, s, 'float2')
480 -4.25
476 -4.25
481 >>> u.configwith(float, s, 'unknown', 7)
477 >>> u.configwith(float, s, 'unknown', 7)
482 7
478 7
483 >>> u.setconfig(s, 'invalid', 'somevalue')
479 >>> u.setconfig(s, 'invalid', 'somevalue')
484 >>> u.configwith(float, s, 'invalid')
480 >>> u.configwith(float, s, 'invalid')
485 Traceback (most recent call last):
481 Traceback (most recent call last):
486 ...
482 ...
487 ConfigError: foo.invalid is not a valid float ('somevalue')
483 ConfigError: foo.invalid is not a valid float ('somevalue')
488 >>> u.configwith(float, s, 'invalid', desc='womble')
484 >>> u.configwith(float, s, 'invalid', desc='womble')
489 Traceback (most recent call last):
485 Traceback (most recent call last):
490 ...
486 ...
491 ConfigError: foo.invalid is not a valid womble ('somevalue')
487 ConfigError: foo.invalid is not a valid womble ('somevalue')
492 """
488 """
493
489
494 v = self.config(section, name, None, untrusted)
490 v = self.config(section, name, None, untrusted)
495 if v is None:
491 if v is None:
496 return default
492 return default
497 try:
493 try:
498 return convert(v)
494 return convert(v)
499 except ValueError:
495 except ValueError:
500 if desc is None:
496 if desc is None:
501 desc = convert.__name__
497 desc = convert.__name__
502 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
498 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
503 % (section, name, desc, v))
499 % (section, name, desc, v))
504
500
505 def configint(self, section, name, default=None, untrusted=False):
501 def configint(self, section, name, default=None, untrusted=False):
506 """parse a configuration element as an integer
502 """parse a configuration element as an integer
507
503
508 >>> u = ui(); s = 'foo'
504 >>> u = ui(); s = 'foo'
509 >>> u.setconfig(s, 'int1', '42')
505 >>> u.setconfig(s, 'int1', '42')
510 >>> u.configint(s, 'int1')
506 >>> u.configint(s, 'int1')
511 42
507 42
512 >>> u.setconfig(s, 'int2', '-42')
508 >>> u.setconfig(s, 'int2', '-42')
513 >>> u.configint(s, 'int2')
509 >>> u.configint(s, 'int2')
514 -42
510 -42
515 >>> u.configint(s, 'unknown', 7)
511 >>> u.configint(s, 'unknown', 7)
516 7
512 7
517 >>> u.setconfig(s, 'invalid', 'somevalue')
513 >>> u.setconfig(s, 'invalid', 'somevalue')
518 >>> u.configint(s, 'invalid')
514 >>> u.configint(s, 'invalid')
519 Traceback (most recent call last):
515 Traceback (most recent call last):
520 ...
516 ...
521 ConfigError: foo.invalid is not a valid integer ('somevalue')
517 ConfigError: foo.invalid is not a valid integer ('somevalue')
522 """
518 """
523
519
524 return self.configwith(int, section, name, default, 'integer',
520 return self.configwith(int, section, name, default, 'integer',
525 untrusted)
521 untrusted)
526
522
527 def configbytes(self, section, name, default=0, untrusted=False):
523 def configbytes(self, section, name, default=0, untrusted=False):
528 """parse a configuration element as a quantity in bytes
524 """parse a configuration element as a quantity in bytes
529
525
530 Units can be specified as b (bytes), k or kb (kilobytes), m or
526 Units can be specified as b (bytes), k or kb (kilobytes), m or
531 mb (megabytes), g or gb (gigabytes).
527 mb (megabytes), g or gb (gigabytes).
532
528
533 >>> u = ui(); s = 'foo'
529 >>> u = ui(); s = 'foo'
534 >>> u.setconfig(s, 'val1', '42')
530 >>> u.setconfig(s, 'val1', '42')
535 >>> u.configbytes(s, 'val1')
531 >>> u.configbytes(s, 'val1')
536 42
532 42
537 >>> u.setconfig(s, 'val2', '42.5 kb')
533 >>> u.setconfig(s, 'val2', '42.5 kb')
538 >>> u.configbytes(s, 'val2')
534 >>> u.configbytes(s, 'val2')
539 43520
535 43520
540 >>> u.configbytes(s, 'unknown', '7 MB')
536 >>> u.configbytes(s, 'unknown', '7 MB')
541 7340032
537 7340032
542 >>> u.setconfig(s, 'invalid', 'somevalue')
538 >>> u.setconfig(s, 'invalid', 'somevalue')
543 >>> u.configbytes(s, 'invalid')
539 >>> u.configbytes(s, 'invalid')
544 Traceback (most recent call last):
540 Traceback (most recent call last):
545 ...
541 ...
546 ConfigError: foo.invalid is not a byte quantity ('somevalue')
542 ConfigError: foo.invalid is not a byte quantity ('somevalue')
547 """
543 """
548
544
549 value = self.config(section, name)
545 value = self.config(section, name)
550 if value is None:
546 if value is None:
551 if not isinstance(default, str):
547 if not isinstance(default, str):
552 return default
548 return default
553 value = default
549 value = default
554 try:
550 try:
555 return util.sizetoint(value)
551 return util.sizetoint(value)
556 except error.ParseError:
552 except error.ParseError:
557 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
553 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
558 % (section, name, value))
554 % (section, name, value))
559
555
560 def configlist(self, section, name, default=None, untrusted=False):
556 def configlist(self, section, name, default=None, untrusted=False):
561 """parse a configuration element as a list of comma/space separated
557 """parse a configuration element as a list of comma/space separated
562 strings
558 strings
563
559
564 >>> u = ui(); s = 'foo'
560 >>> u = ui(); s = 'foo'
565 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
561 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
566 >>> u.configlist(s, 'list1')
562 >>> u.configlist(s, 'list1')
567 ['this', 'is', 'a small', 'test']
563 ['this', 'is', 'a small', 'test']
568 """
564 """
569
565
570 def _parse_plain(parts, s, offset):
566 def _parse_plain(parts, s, offset):
571 whitespace = False
567 whitespace = False
572 while offset < len(s) and (s[offset:offset + 1].isspace()
568 while offset < len(s) and (s[offset:offset + 1].isspace()
573 or s[offset:offset + 1] == ','):
569 or s[offset:offset + 1] == ','):
574 whitespace = True
570 whitespace = True
575 offset += 1
571 offset += 1
576 if offset >= len(s):
572 if offset >= len(s):
577 return None, parts, offset
573 return None, parts, offset
578 if whitespace:
574 if whitespace:
579 parts.append('')
575 parts.append('')
580 if s[offset:offset + 1] == '"' and not parts[-1]:
576 if s[offset:offset + 1] == '"' and not parts[-1]:
581 return _parse_quote, parts, offset + 1
577 return _parse_quote, parts, offset + 1
582 elif s[offset:offset + 1] == '"' and parts[-1][-1] == '\\':
578 elif s[offset:offset + 1] == '"' and parts[-1][-1] == '\\':
583 parts[-1] = parts[-1][:-1] + s[offset:offset + 1]
579 parts[-1] = parts[-1][:-1] + s[offset:offset + 1]
584 return _parse_plain, parts, offset + 1
580 return _parse_plain, parts, offset + 1
585 parts[-1] += s[offset:offset + 1]
581 parts[-1] += s[offset:offset + 1]
586 return _parse_plain, parts, offset + 1
582 return _parse_plain, parts, offset + 1
587
583
588 def _parse_quote(parts, s, offset):
584 def _parse_quote(parts, s, offset):
589 if offset < len(s) and s[offset:offset + 1] == '"': # ""
585 if offset < len(s) and s[offset:offset + 1] == '"': # ""
590 parts.append('')
586 parts.append('')
591 offset += 1
587 offset += 1
592 while offset < len(s) and (s[offset:offset + 1].isspace() or
588 while offset < len(s) and (s[offset:offset + 1].isspace() or
593 s[offset:offset + 1] == ','):
589 s[offset:offset + 1] == ','):
594 offset += 1
590 offset += 1
595 return _parse_plain, parts, offset
591 return _parse_plain, parts, offset
596
592
597 while offset < len(s) and s[offset:offset + 1] != '"':
593 while offset < len(s) and s[offset:offset + 1] != '"':
598 if (s[offset:offset + 1] == '\\' and offset + 1 < len(s)
594 if (s[offset:offset + 1] == '\\' and offset + 1 < len(s)
599 and s[offset + 1:offset + 2] == '"'):
595 and s[offset + 1:offset + 2] == '"'):
600 offset += 1
596 offset += 1
601 parts[-1] += '"'
597 parts[-1] += '"'
602 else:
598 else:
603 parts[-1] += s[offset:offset + 1]
599 parts[-1] += s[offset:offset + 1]
604 offset += 1
600 offset += 1
605
601
606 if offset >= len(s):
602 if offset >= len(s):
607 real_parts = _configlist(parts[-1])
603 real_parts = _configlist(parts[-1])
608 if not real_parts:
604 if not real_parts:
609 parts[-1] = '"'
605 parts[-1] = '"'
610 else:
606 else:
611 real_parts[0] = '"' + real_parts[0]
607 real_parts[0] = '"' + real_parts[0]
612 parts = parts[:-1]
608 parts = parts[:-1]
613 parts.extend(real_parts)
609 parts.extend(real_parts)
614 return None, parts, offset
610 return None, parts, offset
615
611
616 offset += 1
612 offset += 1
617 while offset < len(s) and s[offset:offset + 1] in [' ', ',']:
613 while offset < len(s) and s[offset:offset + 1] in [' ', ',']:
618 offset += 1
614 offset += 1
619
615
620 if offset < len(s):
616 if offset < len(s):
621 if offset + 1 == len(s) and s[offset:offset + 1] == '"':
617 if offset + 1 == len(s) and s[offset:offset + 1] == '"':
622 parts[-1] += '"'
618 parts[-1] += '"'
623 offset += 1
619 offset += 1
624 else:
620 else:
625 parts.append('')
621 parts.append('')
626 else:
622 else:
627 return None, parts, offset
623 return None, parts, offset
628
624
629 return _parse_plain, parts, offset
625 return _parse_plain, parts, offset
630
626
631 def _configlist(s):
627 def _configlist(s):
632 s = s.rstrip(' ,')
628 s = s.rstrip(' ,')
633 if not s:
629 if not s:
634 return []
630 return []
635 parser, parts, offset = _parse_plain, [''], 0
631 parser, parts, offset = _parse_plain, [''], 0
636 while parser:
632 while parser:
637 parser, parts, offset = parser(parts, s, offset)
633 parser, parts, offset = parser(parts, s, offset)
638 return parts
634 return parts
639
635
640 result = self.config(section, name, untrusted=untrusted)
636 result = self.config(section, name, untrusted=untrusted)
641 if result is None:
637 if result is None:
642 result = default or []
638 result = default or []
643 if isinstance(result, bytes):
639 if isinstance(result, bytes):
644 result = _configlist(result.lstrip(' ,\n'))
640 result = _configlist(result.lstrip(' ,\n'))
645 if result is None:
641 if result is None:
646 result = default or []
642 result = default or []
647 return result
643 return result
648
644
649 def hasconfig(self, section, name, untrusted=False):
645 def hasconfig(self, section, name, untrusted=False):
650 return self._data(untrusted).hasitem(section, name)
646 return self._data(untrusted).hasitem(section, name)
651
647
652 def has_section(self, section, untrusted=False):
648 def has_section(self, section, untrusted=False):
653 '''tell whether section exists in config.'''
649 '''tell whether section exists in config.'''
654 return section in self._data(untrusted)
650 return section in self._data(untrusted)
655
651
656 def configitems(self, section, untrusted=False, ignoresub=False):
652 def configitems(self, section, untrusted=False, ignoresub=False):
657 items = self._data(untrusted).items(section)
653 items = self._data(untrusted).items(section)
658 if ignoresub:
654 if ignoresub:
659 newitems = {}
655 newitems = {}
660 for k, v in items:
656 for k, v in items:
661 if ':' not in k:
657 if ':' not in k:
662 newitems[k] = v
658 newitems[k] = v
663 items = newitems.items()
659 items = newitems.items()
664 if self.debugflag and not untrusted and self._reportuntrusted:
660 if self.debugflag and not untrusted and self._reportuntrusted:
665 for k, v in self._ucfg.items(section):
661 for k, v in self._ucfg.items(section):
666 if self._tcfg.get(section, k) != v:
662 if self._tcfg.get(section, k) != v:
667 self.debug("ignoring untrusted configuration option "
663 self.debug("ignoring untrusted configuration option "
668 "%s.%s = %s\n" % (section, k, v))
664 "%s.%s = %s\n" % (section, k, v))
669 return items
665 return items
670
666
671 def walkconfig(self, untrusted=False):
667 def walkconfig(self, untrusted=False):
672 cfg = self._data(untrusted)
668 cfg = self._data(untrusted)
673 for section in cfg.sections():
669 for section in cfg.sections():
674 for name, value in self.configitems(section, untrusted):
670 for name, value in self.configitems(section, untrusted):
675 yield section, name, value
671 yield section, name, value
676
672
677 def plain(self, feature=None):
673 def plain(self, feature=None):
678 '''is plain mode active?
674 '''is plain mode active?
679
675
680 Plain mode means that all configuration variables which affect
676 Plain mode means that all configuration variables which affect
681 the behavior and output of Mercurial should be
677 the behavior and output of Mercurial should be
682 ignored. Additionally, the output should be stable,
678 ignored. Additionally, the output should be stable,
683 reproducible and suitable for use in scripts or applications.
679 reproducible and suitable for use in scripts or applications.
684
680
685 The only way to trigger plain mode is by setting either the
681 The only way to trigger plain mode is by setting either the
686 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
682 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
687
683
688 The return value can either be
684 The return value can either be
689 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
685 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
690 - True otherwise
686 - True otherwise
691 '''
687 '''
692 if ('HGPLAIN' not in encoding.environ and
688 if ('HGPLAIN' not in encoding.environ and
693 'HGPLAINEXCEPT' not in encoding.environ):
689 'HGPLAINEXCEPT' not in encoding.environ):
694 return False
690 return False
695 exceptions = encoding.environ.get('HGPLAINEXCEPT',
691 exceptions = encoding.environ.get('HGPLAINEXCEPT',
696 '').strip().split(',')
692 '').strip().split(',')
697 if feature and exceptions:
693 if feature and exceptions:
698 return feature not in exceptions
694 return feature not in exceptions
699 return True
695 return True
700
696
701 def username(self):
697 def username(self):
702 """Return default username to be used in commits.
698 """Return default username to be used in commits.
703
699
704 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
700 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
705 and stop searching if one of these is set.
701 and stop searching if one of these is set.
706 If not found and ui.askusername is True, ask the user, else use
702 If not found and ui.askusername is True, ask the user, else use
707 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
703 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
708 """
704 """
709 user = encoding.environ.get("HGUSER")
705 user = encoding.environ.get("HGUSER")
710 if user is None:
706 if user is None:
711 user = self.config("ui", ["username", "user"])
707 user = self.config("ui", ["username", "user"])
712 if user is not None:
708 if user is not None:
713 user = os.path.expandvars(user)
709 user = os.path.expandvars(user)
714 if user is None:
710 if user is None:
715 user = encoding.environ.get("EMAIL")
711 user = encoding.environ.get("EMAIL")
716 if user is None and self.configbool("ui", "askusername"):
712 if user is None and self.configbool("ui", "askusername"):
717 user = self.prompt(_("enter a commit username:"), default=None)
713 user = self.prompt(_("enter a commit username:"), default=None)
718 if user is None and not self.interactive():
714 if user is None and not self.interactive():
719 try:
715 try:
720 user = '%s@%s' % (util.getuser(), socket.getfqdn())
716 user = '%s@%s' % (util.getuser(), socket.getfqdn())
721 self.warn(_("no username found, using '%s' instead\n") % user)
717 self.warn(_("no username found, using '%s' instead\n") % user)
722 except KeyError:
718 except KeyError:
723 pass
719 pass
724 if not user:
720 if not user:
725 raise error.Abort(_('no username supplied'),
721 raise error.Abort(_('no username supplied'),
726 hint=_("use 'hg config --edit' "
722 hint=_("use 'hg config --edit' "
727 'to set your username'))
723 'to set your username'))
728 if "\n" in user:
724 if "\n" in user:
729 raise error.Abort(_("username %s contains a newline\n")
725 raise error.Abort(_("username %s contains a newline\n")
730 % repr(user))
726 % repr(user))
731 return user
727 return user
732
728
733 def shortuser(self, user):
729 def shortuser(self, user):
734 """Return a short representation of a user name or email address."""
730 """Return a short representation of a user name or email address."""
735 if not self.verbose:
731 if not self.verbose:
736 user = util.shortuser(user)
732 user = util.shortuser(user)
737 return user
733 return user
738
734
739 def expandpath(self, loc, default=None):
735 def expandpath(self, loc, default=None):
740 """Return repository location relative to cwd or from [paths]"""
736 """Return repository location relative to cwd or from [paths]"""
741 try:
737 try:
742 p = self.paths.getpath(loc)
738 p = self.paths.getpath(loc)
743 if p:
739 if p:
744 return p.rawloc
740 return p.rawloc
745 except error.RepoError:
741 except error.RepoError:
746 pass
742 pass
747
743
748 if default:
744 if default:
749 try:
745 try:
750 p = self.paths.getpath(default)
746 p = self.paths.getpath(default)
751 if p:
747 if p:
752 return p.rawloc
748 return p.rawloc
753 except error.RepoError:
749 except error.RepoError:
754 pass
750 pass
755
751
756 return loc
752 return loc
757
753
758 @util.propertycache
754 @util.propertycache
759 def paths(self):
755 def paths(self):
760 return paths(self)
756 return paths(self)
761
757
762 def pushbuffer(self, error=False, subproc=False, labeled=False):
758 def pushbuffer(self, error=False, subproc=False, labeled=False):
763 """install a buffer to capture standard output of the ui object
759 """install a buffer to capture standard output of the ui object
764
760
765 If error is True, the error output will be captured too.
761 If error is True, the error output will be captured too.
766
762
767 If subproc is True, output from subprocesses (typically hooks) will be
763 If subproc is True, output from subprocesses (typically hooks) will be
768 captured too.
764 captured too.
769
765
770 If labeled is True, any labels associated with buffered
766 If labeled is True, any labels associated with buffered
771 output will be handled. By default, this has no effect
767 output will be handled. By default, this has no effect
772 on the output returned, but extensions and GUI tools may
768 on the output returned, but extensions and GUI tools may
773 handle this argument and returned styled output. If output
769 handle this argument and returned styled output. If output
774 is being buffered so it can be captured and parsed or
770 is being buffered so it can be captured and parsed or
775 processed, labeled should not be set to True.
771 processed, labeled should not be set to True.
776 """
772 """
777 self._buffers.append([])
773 self._buffers.append([])
778 self._bufferstates.append((error, subproc, labeled))
774 self._bufferstates.append((error, subproc, labeled))
779 self._bufferapplylabels = labeled
775 self._bufferapplylabels = labeled
780
776
781 def popbuffer(self):
777 def popbuffer(self):
782 '''pop the last buffer and return the buffered output'''
778 '''pop the last buffer and return the buffered output'''
783 self._bufferstates.pop()
779 self._bufferstates.pop()
784 if self._bufferstates:
780 if self._bufferstates:
785 self._bufferapplylabels = self._bufferstates[-1][2]
781 self._bufferapplylabels = self._bufferstates[-1][2]
786 else:
782 else:
787 self._bufferapplylabels = None
783 self._bufferapplylabels = None
788
784
789 return "".join(self._buffers.pop())
785 return "".join(self._buffers.pop())
790
786
791 def write(self, *args, **opts):
787 def write(self, *args, **opts):
792 '''write args to output
788 '''write args to output
793
789
794 By default, this method simply writes to the buffer or stdout.
790 By default, this method simply writes to the buffer or stdout.
795 Color mode can be set on the UI class to have the output decorated
791 Color mode can be set on the UI class to have the output decorated
796 with color modifier before being written to stdout.
792 with color modifier before being written to stdout.
797
793
798 The color used is controlled by an optional keyword argument, "label".
794 The color used is controlled by an optional keyword argument, "label".
799 This should be a string containing label names separated by space.
795 This should be a string containing label names separated by space.
800 Label names take the form of "topic.type". For example, ui.debug()
796 Label names take the form of "topic.type". For example, ui.debug()
801 issues a label of "ui.debug".
797 issues a label of "ui.debug".
802
798
803 When labeling output for a specific command, a label of
799 When labeling output for a specific command, a label of
804 "cmdname.type" is recommended. For example, status issues
800 "cmdname.type" is recommended. For example, status issues
805 a label of "status.modified" for modified files.
801 a label of "status.modified" for modified files.
806 '''
802 '''
807 if self._buffers and not opts.get('prompt', False):
803 if self._buffers and not opts.get('prompt', False):
808 if self._bufferapplylabels:
804 if self._bufferapplylabels:
809 label = opts.get('label', '')
805 label = opts.get('label', '')
810 self._buffers[-1].extend(self.label(a, label) for a in args)
806 self._buffers[-1].extend(self.label(a, label) for a in args)
811 else:
807 else:
812 self._buffers[-1].extend(args)
808 self._buffers[-1].extend(args)
813 elif self._colormode == 'win32':
809 elif self._colormode == 'win32':
814 # windows color printing is its own can of crab, defer to
810 # windows color printing is its own can of crab, defer to
815 # the color module and that is it.
811 # the color module and that is it.
816 color.win32print(self, self._write, *args, **opts)
812 color.win32print(self, self._write, *args, **opts)
817 else:
813 else:
818 msgs = args
814 msgs = args
819 if self._colormode is not None:
815 if self._colormode is not None:
820 label = opts.get('label', '')
816 label = opts.get('label', '')
821 msgs = [self.label(a, label) for a in args]
817 msgs = [self.label(a, label) for a in args]
822 self._write(*msgs, **opts)
818 self._write(*msgs, **opts)
823
819
824 def _write(self, *msgs, **opts):
820 def _write(self, *msgs, **opts):
825 self._progclear()
821 self._progclear()
826 # opencode timeblockedsection because this is a critical path
822 # opencode timeblockedsection because this is a critical path
827 starttime = util.timer()
823 starttime = util.timer()
828 try:
824 try:
829 for a in msgs:
825 for a in msgs:
830 self.fout.write(a)
826 self.fout.write(a)
831 finally:
827 finally:
832 self._blockedtimes['stdio_blocked'] += \
828 self._blockedtimes['stdio_blocked'] += \
833 (util.timer() - starttime) * 1000
829 (util.timer() - starttime) * 1000
834
830
835 def write_err(self, *args, **opts):
831 def write_err(self, *args, **opts):
836 self._progclear()
832 self._progclear()
837 if self._bufferstates and self._bufferstates[-1][0]:
833 if self._bufferstates and self._bufferstates[-1][0]:
838 self.write(*args, **opts)
834 self.write(*args, **opts)
839 elif self._colormode == 'win32':
835 elif self._colormode == 'win32':
840 # windows color printing is its own can of crab, defer to
836 # windows color printing is its own can of crab, defer to
841 # the color module and that is it.
837 # the color module and that is it.
842 color.win32print(self, self._write_err, *args, **opts)
838 color.win32print(self, self._write_err, *args, **opts)
843 else:
839 else:
844 msgs = args
840 msgs = args
845 if self._colormode is not None:
841 if self._colormode is not None:
846 label = opts.get('label', '')
842 label = opts.get('label', '')
847 msgs = [self.label(a, label) for a in args]
843 msgs = [self.label(a, label) for a in args]
848 self._write_err(*msgs, **opts)
844 self._write_err(*msgs, **opts)
849
845
850 def _write_err(self, *msgs, **opts):
846 def _write_err(self, *msgs, **opts):
851 try:
847 try:
852 with self.timeblockedsection('stdio'):
848 with self.timeblockedsection('stdio'):
853 if not getattr(self.fout, 'closed', False):
849 if not getattr(self.fout, 'closed', False):
854 self.fout.flush()
850 self.fout.flush()
855 for a in msgs:
851 for a in msgs:
856 self.ferr.write(a)
852 self.ferr.write(a)
857 # stderr may be buffered under win32 when redirected to files,
853 # stderr may be buffered under win32 when redirected to files,
858 # including stdout.
854 # including stdout.
859 if not getattr(self.ferr, 'closed', False):
855 if not getattr(self.ferr, 'closed', False):
860 self.ferr.flush()
856 self.ferr.flush()
861 except IOError as inst:
857 except IOError as inst:
862 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
858 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
863 raise
859 raise
864
860
865 def flush(self):
861 def flush(self):
866 # opencode timeblockedsection because this is a critical path
862 # opencode timeblockedsection because this is a critical path
867 starttime = util.timer()
863 starttime = util.timer()
868 try:
864 try:
869 try: self.fout.flush()
865 try: self.fout.flush()
870 except (IOError, ValueError): pass
866 except (IOError, ValueError): pass
871 try: self.ferr.flush()
867 try: self.ferr.flush()
872 except (IOError, ValueError): pass
868 except (IOError, ValueError): pass
873 finally:
869 finally:
874 self._blockedtimes['stdio_blocked'] += \
870 self._blockedtimes['stdio_blocked'] += \
875 (util.timer() - starttime) * 1000
871 (util.timer() - starttime) * 1000
876
872
877 def _isatty(self, fh):
873 def _isatty(self, fh):
878 if self.configbool('ui', 'nontty', False):
874 if self.configbool('ui', 'nontty', False):
879 return False
875 return False
880 return util.isatty(fh)
876 return util.isatty(fh)
881
877
882 def disablepager(self):
878 def disablepager(self):
883 self._disablepager = True
879 self._disablepager = True
884
880
885 def pager(self, command):
881 def pager(self, command):
886 """Start a pager for subsequent command output.
882 """Start a pager for subsequent command output.
887
883
888 Commands which produce a long stream of output should call
884 Commands which produce a long stream of output should call
889 this function to activate the user's preferred pagination
885 this function to activate the user's preferred pagination
890 mechanism (which may be no pager). Calling this function
886 mechanism (which may be no pager). Calling this function
891 precludes any future use of interactive functionality, such as
887 precludes any future use of interactive functionality, such as
892 prompting the user or activating curses.
888 prompting the user or activating curses.
893
889
894 Args:
890 Args:
895 command: The full, non-aliased name of the command. That is, "log"
891 command: The full, non-aliased name of the command. That is, "log"
896 not "history, "summary" not "summ", etc.
892 not "history, "summary" not "summ", etc.
897 """
893 """
898 if (self._disablepager
894 if (self._disablepager
899 or self.pageractive
895 or self.pageractive
900 or command in self.configlist('pager', 'ignore')
896 or command in self.configlist('pager', 'ignore')
901 or not self.configbool('pager', 'enable', True)
897 or not self.configbool('pager', 'enable', True)
902 or not self.configbool('pager', 'attend-' + command, True)
898 or not self.configbool('pager', 'attend-' + command, True)
903 # TODO: if we want to allow HGPLAINEXCEPT=pager,
899 # TODO: if we want to allow HGPLAINEXCEPT=pager,
904 # formatted() will need some adjustment.
900 # formatted() will need some adjustment.
905 or not self.formatted()
901 or not self.formatted()
906 or self.plain()
902 or self.plain()
907 # TODO: expose debugger-enabled on the UI object
903 # TODO: expose debugger-enabled on the UI object
908 or '--debugger' in sys.argv):
904 or '--debugger' in sys.argv):
909 # We only want to paginate if the ui appears to be
905 # We only want to paginate if the ui appears to be
910 # interactive, the user didn't say HGPLAIN or
906 # interactive, the user didn't say HGPLAIN or
911 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
907 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
912 return
908 return
913
909
914 # TODO: add a "system defaults" config section so this default
910 # TODO: add a "system defaults" config section so this default
915 # of more(1) can be easily replaced with a global
911 # of more(1) can be easily replaced with a global
916 # configuration file. For example, on OS X the sane default is
912 # configuration file. For example, on OS X the sane default is
917 # less(1), not more(1), and on debian it's
913 # less(1), not more(1), and on debian it's
918 # sensible-pager(1). We should probably also give the system
914 # sensible-pager(1). We should probably also give the system
919 # default editor command similar treatment.
915 # default editor command similar treatment.
920 envpager = encoding.environ.get('PAGER', 'more')
916 envpager = encoding.environ.get('PAGER', 'more')
921 pagercmd = self.config('pager', 'pager', envpager)
917 pagercmd = self.config('pager', 'pager', envpager)
922 if not pagercmd:
918 if not pagercmd:
923 return
919 return
924
920
925 self.debug('starting pager for command %r\n' % command)
921 self.debug('starting pager for command %r\n' % command)
926 self.pageractive = True
922 self.pageractive = True
927 # Preserve the formatted-ness of the UI. This is important
923 # Preserve the formatted-ness of the UI. This is important
928 # because we mess with stdout, which might confuse
924 # because we mess with stdout, which might confuse
929 # auto-detection of things being formatted.
925 # auto-detection of things being formatted.
930 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
926 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
931 self.setconfig('ui', 'interactive', False, 'pager')
927 self.setconfig('ui', 'interactive', False, 'pager')
932 if util.safehasattr(signal, "SIGPIPE"):
928 if util.safehasattr(signal, "SIGPIPE"):
933 signal.signal(signal.SIGPIPE, _catchterm)
929 signal.signal(signal.SIGPIPE, _catchterm)
934 self._runpager(pagercmd)
930 self._runpager(pagercmd)
935
931
936 def _runpager(self, command):
932 def _runpager(self, command):
937 """Actually start the pager and set up file descriptors.
933 """Actually start the pager and set up file descriptors.
938
934
939 This is separate in part so that extensions (like chg) can
935 This is separate in part so that extensions (like chg) can
940 override how a pager is invoked.
936 override how a pager is invoked.
941 """
937 """
942 pager = subprocess.Popen(command, shell=True, bufsize=-1,
938 pager = subprocess.Popen(command, shell=True, bufsize=-1,
943 close_fds=util.closefds, stdin=subprocess.PIPE,
939 close_fds=util.closefds, stdin=subprocess.PIPE,
944 stdout=util.stdout, stderr=util.stderr)
940 stdout=util.stdout, stderr=util.stderr)
945
941
946 # back up original file descriptors
942 # back up original file descriptors
947 stdoutfd = os.dup(util.stdout.fileno())
943 stdoutfd = os.dup(util.stdout.fileno())
948 stderrfd = os.dup(util.stderr.fileno())
944 stderrfd = os.dup(util.stderr.fileno())
949
945
950 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
946 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
951 if self._isatty(util.stderr):
947 if self._isatty(util.stderr):
952 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
948 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
953
949
954 @atexit.register
950 @atexit.register
955 def killpager():
951 def killpager():
956 if util.safehasattr(signal, "SIGINT"):
952 if util.safehasattr(signal, "SIGINT"):
957 signal.signal(signal.SIGINT, signal.SIG_IGN)
953 signal.signal(signal.SIGINT, signal.SIG_IGN)
958 # restore original fds, closing pager.stdin copies in the process
954 # restore original fds, closing pager.stdin copies in the process
959 os.dup2(stdoutfd, util.stdout.fileno())
955 os.dup2(stdoutfd, util.stdout.fileno())
960 os.dup2(stderrfd, util.stderr.fileno())
956 os.dup2(stderrfd, util.stderr.fileno())
961 pager.stdin.close()
957 pager.stdin.close()
962 pager.wait()
958 pager.wait()
963
959
964 def interface(self, feature):
960 def interface(self, feature):
965 """what interface to use for interactive console features?
961 """what interface to use for interactive console features?
966
962
967 The interface is controlled by the value of `ui.interface` but also by
963 The interface is controlled by the value of `ui.interface` but also by
968 the value of feature-specific configuration. For example:
964 the value of feature-specific configuration. For example:
969
965
970 ui.interface.histedit = text
966 ui.interface.histedit = text
971 ui.interface.chunkselector = curses
967 ui.interface.chunkselector = curses
972
968
973 Here the features are "histedit" and "chunkselector".
969 Here the features are "histedit" and "chunkselector".
974
970
975 The configuration above means that the default interfaces for commands
971 The configuration above means that the default interfaces for commands
976 is curses, the interface for histedit is text and the interface for
972 is curses, the interface for histedit is text and the interface for
977 selecting chunk is crecord (the best curses interface available).
973 selecting chunk is crecord (the best curses interface available).
978
974
979 Consider the following example:
975 Consider the following example:
980 ui.interface = curses
976 ui.interface = curses
981 ui.interface.histedit = text
977 ui.interface.histedit = text
982
978
983 Then histedit will use the text interface and chunkselector will use
979 Then histedit will use the text interface and chunkselector will use
984 the default curses interface (crecord at the moment).
980 the default curses interface (crecord at the moment).
985 """
981 """
986 alldefaults = frozenset(["text", "curses"])
982 alldefaults = frozenset(["text", "curses"])
987
983
988 featureinterfaces = {
984 featureinterfaces = {
989 "chunkselector": [
985 "chunkselector": [
990 "text",
986 "text",
991 "curses",
987 "curses",
992 ]
988 ]
993 }
989 }
994
990
995 # Feature-specific interface
991 # Feature-specific interface
996 if feature not in featureinterfaces.keys():
992 if feature not in featureinterfaces.keys():
997 # Programming error, not user error
993 # Programming error, not user error
998 raise ValueError("Unknown feature requested %s" % feature)
994 raise ValueError("Unknown feature requested %s" % feature)
999
995
1000 availableinterfaces = frozenset(featureinterfaces[feature])
996 availableinterfaces = frozenset(featureinterfaces[feature])
1001 if alldefaults > availableinterfaces:
997 if alldefaults > availableinterfaces:
1002 # Programming error, not user error. We need a use case to
998 # Programming error, not user error. We need a use case to
1003 # define the right thing to do here.
999 # define the right thing to do here.
1004 raise ValueError(
1000 raise ValueError(
1005 "Feature %s does not handle all default interfaces" %
1001 "Feature %s does not handle all default interfaces" %
1006 feature)
1002 feature)
1007
1003
1008 if self.plain():
1004 if self.plain():
1009 return "text"
1005 return "text"
1010
1006
1011 # Default interface for all the features
1007 # Default interface for all the features
1012 defaultinterface = "text"
1008 defaultinterface = "text"
1013 i = self.config("ui", "interface", None)
1009 i = self.config("ui", "interface", None)
1014 if i in alldefaults:
1010 if i in alldefaults:
1015 defaultinterface = i
1011 defaultinterface = i
1016
1012
1017 choseninterface = defaultinterface
1013 choseninterface = defaultinterface
1018 f = self.config("ui", "interface.%s" % feature, None)
1014 f = self.config("ui", "interface.%s" % feature, None)
1019 if f in availableinterfaces:
1015 if f in availableinterfaces:
1020 choseninterface = f
1016 choseninterface = f
1021
1017
1022 if i is not None and defaultinterface != i:
1018 if i is not None and defaultinterface != i:
1023 if f is not None:
1019 if f is not None:
1024 self.warn(_("invalid value for ui.interface: %s\n") %
1020 self.warn(_("invalid value for ui.interface: %s\n") %
1025 (i,))
1021 (i,))
1026 else:
1022 else:
1027 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1023 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1028 (i, choseninterface))
1024 (i, choseninterface))
1029 if f is not None and choseninterface != f:
1025 if f is not None and choseninterface != f:
1030 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1026 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1031 (feature, f, choseninterface))
1027 (feature, f, choseninterface))
1032
1028
1033 return choseninterface
1029 return choseninterface
1034
1030
1035 def interactive(self):
1031 def interactive(self):
1036 '''is interactive input allowed?
1032 '''is interactive input allowed?
1037
1033
1038 An interactive session is a session where input can be reasonably read
1034 An interactive session is a session where input can be reasonably read
1039 from `sys.stdin'. If this function returns false, any attempt to read
1035 from `sys.stdin'. If this function returns false, any attempt to read
1040 from stdin should fail with an error, unless a sensible default has been
1036 from stdin should fail with an error, unless a sensible default has been
1041 specified.
1037 specified.
1042
1038
1043 Interactiveness is triggered by the value of the `ui.interactive'
1039 Interactiveness is triggered by the value of the `ui.interactive'
1044 configuration variable or - if it is unset - when `sys.stdin' points
1040 configuration variable or - if it is unset - when `sys.stdin' points
1045 to a terminal device.
1041 to a terminal device.
1046
1042
1047 This function refers to input only; for output, see `ui.formatted()'.
1043 This function refers to input only; for output, see `ui.formatted()'.
1048 '''
1044 '''
1049 i = self.configbool("ui", "interactive", None)
1045 i = self.configbool("ui", "interactive", None)
1050 if i is None:
1046 if i is None:
1051 # some environments replace stdin without implementing isatty
1047 # some environments replace stdin without implementing isatty
1052 # usually those are non-interactive
1048 # usually those are non-interactive
1053 return self._isatty(self.fin)
1049 return self._isatty(self.fin)
1054
1050
1055 return i
1051 return i
1056
1052
1057 def termwidth(self):
1053 def termwidth(self):
1058 '''how wide is the terminal in columns?
1054 '''how wide is the terminal in columns?
1059 '''
1055 '''
1060 if 'COLUMNS' in encoding.environ:
1056 if 'COLUMNS' in encoding.environ:
1061 try:
1057 try:
1062 return int(encoding.environ['COLUMNS'])
1058 return int(encoding.environ['COLUMNS'])
1063 except ValueError:
1059 except ValueError:
1064 pass
1060 pass
1065 return scmutil.termsize(self)[0]
1061 return scmutil.termsize(self)[0]
1066
1062
1067 def formatted(self):
1063 def formatted(self):
1068 '''should formatted output be used?
1064 '''should formatted output be used?
1069
1065
1070 It is often desirable to format the output to suite the output medium.
1066 It is often desirable to format the output to suite the output medium.
1071 Examples of this are truncating long lines or colorizing messages.
1067 Examples of this are truncating long lines or colorizing messages.
1072 However, this is not often not desirable when piping output into other
1068 However, this is not often not desirable when piping output into other
1073 utilities, e.g. `grep'.
1069 utilities, e.g. `grep'.
1074
1070
1075 Formatted output is triggered by the value of the `ui.formatted'
1071 Formatted output is triggered by the value of the `ui.formatted'
1076 configuration variable or - if it is unset - when `sys.stdout' points
1072 configuration variable or - if it is unset - when `sys.stdout' points
1077 to a terminal device. Please note that `ui.formatted' should be
1073 to a terminal device. Please note that `ui.formatted' should be
1078 considered an implementation detail; it is not intended for use outside
1074 considered an implementation detail; it is not intended for use outside
1079 Mercurial or its extensions.
1075 Mercurial or its extensions.
1080
1076
1081 This function refers to output only; for input, see `ui.interactive()'.
1077 This function refers to output only; for input, see `ui.interactive()'.
1082 This function always returns false when in plain mode, see `ui.plain()'.
1078 This function always returns false when in plain mode, see `ui.plain()'.
1083 '''
1079 '''
1084 if self.plain():
1080 if self.plain():
1085 return False
1081 return False
1086
1082
1087 i = self.configbool("ui", "formatted", None)
1083 i = self.configbool("ui", "formatted", None)
1088 if i is None:
1084 if i is None:
1089 # some environments replace stdout without implementing isatty
1085 # some environments replace stdout without implementing isatty
1090 # usually those are non-interactive
1086 # usually those are non-interactive
1091 return self._isatty(self.fout)
1087 return self._isatty(self.fout)
1092
1088
1093 return i
1089 return i
1094
1090
1095 def _readline(self, prompt=''):
1091 def _readline(self, prompt=''):
1096 if self._isatty(self.fin):
1092 if self._isatty(self.fin):
1097 try:
1093 try:
1098 # magically add command line editing support, where
1094 # magically add command line editing support, where
1099 # available
1095 # available
1100 import readline
1096 import readline
1101 # force demandimport to really load the module
1097 # force demandimport to really load the module
1102 readline.read_history_file
1098 readline.read_history_file
1103 # windows sometimes raises something other than ImportError
1099 # windows sometimes raises something other than ImportError
1104 except Exception:
1100 except Exception:
1105 pass
1101 pass
1106
1102
1107 # call write() so output goes through subclassed implementation
1103 # call write() so output goes through subclassed implementation
1108 # e.g. color extension on Windows
1104 # e.g. color extension on Windows
1109 self.write(prompt, prompt=True)
1105 self.write(prompt, prompt=True)
1110
1106
1111 # instead of trying to emulate raw_input, swap (self.fin,
1107 # instead of trying to emulate raw_input, swap (self.fin,
1112 # self.fout) with (sys.stdin, sys.stdout)
1108 # self.fout) with (sys.stdin, sys.stdout)
1113 oldin = sys.stdin
1109 oldin = sys.stdin
1114 oldout = sys.stdout
1110 oldout = sys.stdout
1115 sys.stdin = self.fin
1111 sys.stdin = self.fin
1116 sys.stdout = self.fout
1112 sys.stdout = self.fout
1117 # prompt ' ' must exist; otherwise readline may delete entire line
1113 # prompt ' ' must exist; otherwise readline may delete entire line
1118 # - http://bugs.python.org/issue12833
1114 # - http://bugs.python.org/issue12833
1119 with self.timeblockedsection('stdio'):
1115 with self.timeblockedsection('stdio'):
1120 line = raw_input(' ')
1116 line = raw_input(' ')
1121 sys.stdin = oldin
1117 sys.stdin = oldin
1122 sys.stdout = oldout
1118 sys.stdout = oldout
1123
1119
1124 # When stdin is in binary mode on Windows, it can cause
1120 # When stdin is in binary mode on Windows, it can cause
1125 # raw_input() to emit an extra trailing carriage return
1121 # raw_input() to emit an extra trailing carriage return
1126 if os.linesep == '\r\n' and line and line[-1] == '\r':
1122 if os.linesep == '\r\n' and line and line[-1] == '\r':
1127 line = line[:-1]
1123 line = line[:-1]
1128 return line
1124 return line
1129
1125
1130 def prompt(self, msg, default="y"):
1126 def prompt(self, msg, default="y"):
1131 """Prompt user with msg, read response.
1127 """Prompt user with msg, read response.
1132 If ui is not interactive, the default is returned.
1128 If ui is not interactive, the default is returned.
1133 """
1129 """
1134 if not self.interactive():
1130 if not self.interactive():
1135 self.write(msg, ' ', default or '', "\n")
1131 self.write(msg, ' ', default or '', "\n")
1136 return default
1132 return default
1137 try:
1133 try:
1138 r = self._readline(self.label(msg, 'ui.prompt'))
1134 r = self._readline(self.label(msg, 'ui.prompt'))
1139 if not r:
1135 if not r:
1140 r = default
1136 r = default
1141 if self.configbool('ui', 'promptecho'):
1137 if self.configbool('ui', 'promptecho'):
1142 self.write(r, "\n")
1138 self.write(r, "\n")
1143 return r
1139 return r
1144 except EOFError:
1140 except EOFError:
1145 raise error.ResponseExpected()
1141 raise error.ResponseExpected()
1146
1142
1147 @staticmethod
1143 @staticmethod
1148 def extractchoices(prompt):
1144 def extractchoices(prompt):
1149 """Extract prompt message and list of choices from specified prompt.
1145 """Extract prompt message and list of choices from specified prompt.
1150
1146
1151 This returns tuple "(message, choices)", and "choices" is the
1147 This returns tuple "(message, choices)", and "choices" is the
1152 list of tuple "(response character, text without &)".
1148 list of tuple "(response character, text without &)".
1153
1149
1154 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1150 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1155 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1151 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1156 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1152 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1157 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1153 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1158 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1154 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1159 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1155 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1160 """
1156 """
1161
1157
1162 # Sadly, the prompt string may have been built with a filename
1158 # Sadly, the prompt string may have been built with a filename
1163 # containing "$$" so let's try to find the first valid-looking
1159 # containing "$$" so let's try to find the first valid-looking
1164 # prompt to start parsing. Sadly, we also can't rely on
1160 # prompt to start parsing. Sadly, we also can't rely on
1165 # choices containing spaces, ASCII, or basically anything
1161 # choices containing spaces, ASCII, or basically anything
1166 # except an ampersand followed by a character.
1162 # except an ampersand followed by a character.
1167 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1163 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1168 msg = m.group(1)
1164 msg = m.group(1)
1169 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1165 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1170 return (msg,
1166 return (msg,
1171 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1167 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1172 for s in choices])
1168 for s in choices])
1173
1169
1174 def promptchoice(self, prompt, default=0):
1170 def promptchoice(self, prompt, default=0):
1175 """Prompt user with a message, read response, and ensure it matches
1171 """Prompt user with a message, read response, and ensure it matches
1176 one of the provided choices. The prompt is formatted as follows:
1172 one of the provided choices. The prompt is formatted as follows:
1177
1173
1178 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1174 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1179
1175
1180 The index of the choice is returned. Responses are case
1176 The index of the choice is returned. Responses are case
1181 insensitive. If ui is not interactive, the default is
1177 insensitive. If ui is not interactive, the default is
1182 returned.
1178 returned.
1183 """
1179 """
1184
1180
1185 msg, choices = self.extractchoices(prompt)
1181 msg, choices = self.extractchoices(prompt)
1186 resps = [r for r, t in choices]
1182 resps = [r for r, t in choices]
1187 while True:
1183 while True:
1188 r = self.prompt(msg, resps[default])
1184 r = self.prompt(msg, resps[default])
1189 if r.lower() in resps:
1185 if r.lower() in resps:
1190 return resps.index(r.lower())
1186 return resps.index(r.lower())
1191 self.write(_("unrecognized response\n"))
1187 self.write(_("unrecognized response\n"))
1192
1188
1193 def getpass(self, prompt=None, default=None):
1189 def getpass(self, prompt=None, default=None):
1194 if not self.interactive():
1190 if not self.interactive():
1195 return default
1191 return default
1196 try:
1192 try:
1197 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1193 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1198 # disable getpass() only if explicitly specified. it's still valid
1194 # disable getpass() only if explicitly specified. it's still valid
1199 # to interact with tty even if fin is not a tty.
1195 # to interact with tty even if fin is not a tty.
1200 with self.timeblockedsection('stdio'):
1196 with self.timeblockedsection('stdio'):
1201 if self.configbool('ui', 'nontty'):
1197 if self.configbool('ui', 'nontty'):
1202 l = self.fin.readline()
1198 l = self.fin.readline()
1203 if not l:
1199 if not l:
1204 raise EOFError
1200 raise EOFError
1205 return l.rstrip('\n')
1201 return l.rstrip('\n')
1206 else:
1202 else:
1207 return getpass.getpass('')
1203 return getpass.getpass('')
1208 except EOFError:
1204 except EOFError:
1209 raise error.ResponseExpected()
1205 raise error.ResponseExpected()
1210 def status(self, *msg, **opts):
1206 def status(self, *msg, **opts):
1211 '''write status message to output (if ui.quiet is False)
1207 '''write status message to output (if ui.quiet is False)
1212
1208
1213 This adds an output label of "ui.status".
1209 This adds an output label of "ui.status".
1214 '''
1210 '''
1215 if not self.quiet:
1211 if not self.quiet:
1216 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1212 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1217 self.write(*msg, **opts)
1213 self.write(*msg, **opts)
1218 def warn(self, *msg, **opts):
1214 def warn(self, *msg, **opts):
1219 '''write warning message to output (stderr)
1215 '''write warning message to output (stderr)
1220
1216
1221 This adds an output label of "ui.warning".
1217 This adds an output label of "ui.warning".
1222 '''
1218 '''
1223 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1219 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1224 self.write_err(*msg, **opts)
1220 self.write_err(*msg, **opts)
1225 def note(self, *msg, **opts):
1221 def note(self, *msg, **opts):
1226 '''write note to output (if ui.verbose is True)
1222 '''write note to output (if ui.verbose is True)
1227
1223
1228 This adds an output label of "ui.note".
1224 This adds an output label of "ui.note".
1229 '''
1225 '''
1230 if self.verbose:
1226 if self.verbose:
1231 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1227 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1232 self.write(*msg, **opts)
1228 self.write(*msg, **opts)
1233 def debug(self, *msg, **opts):
1229 def debug(self, *msg, **opts):
1234 '''write debug message to output (if ui.debugflag is True)
1230 '''write debug message to output (if ui.debugflag is True)
1235
1231
1236 This adds an output label of "ui.debug".
1232 This adds an output label of "ui.debug".
1237 '''
1233 '''
1238 if self.debugflag:
1234 if self.debugflag:
1239 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1235 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1240 self.write(*msg, **opts)
1236 self.write(*msg, **opts)
1241
1237
1242 def edit(self, text, user, extra=None, editform=None, pending=None,
1238 def edit(self, text, user, extra=None, editform=None, pending=None,
1243 repopath=None):
1239 repopath=None):
1244 extra_defaults = {
1240 extra_defaults = {
1245 'prefix': 'editor',
1241 'prefix': 'editor',
1246 'suffix': '.txt',
1242 'suffix': '.txt',
1247 }
1243 }
1248 if extra is not None:
1244 if extra is not None:
1249 extra_defaults.update(extra)
1245 extra_defaults.update(extra)
1250 extra = extra_defaults
1246 extra = extra_defaults
1251
1247
1252 rdir = None
1248 rdir = None
1253 if self.configbool('experimental', 'editortmpinhg'):
1249 if self.configbool('experimental', 'editortmpinhg'):
1254 rdir = repopath
1250 rdir = repopath
1255 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1251 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1256 suffix=extra['suffix'], text=True,
1252 suffix=extra['suffix'], text=True,
1257 dir=rdir)
1253 dir=rdir)
1258 try:
1254 try:
1259 f = os.fdopen(fd, pycompat.sysstr("w"))
1255 f = os.fdopen(fd, pycompat.sysstr("w"))
1260 f.write(text)
1256 f.write(text)
1261 f.close()
1257 f.close()
1262
1258
1263 environ = {'HGUSER': user}
1259 environ = {'HGUSER': user}
1264 if 'transplant_source' in extra:
1260 if 'transplant_source' in extra:
1265 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1261 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1266 for label in ('intermediate-source', 'source', 'rebase_source'):
1262 for label in ('intermediate-source', 'source', 'rebase_source'):
1267 if label in extra:
1263 if label in extra:
1268 environ.update({'HGREVISION': extra[label]})
1264 environ.update({'HGREVISION': extra[label]})
1269 break
1265 break
1270 if editform:
1266 if editform:
1271 environ.update({'HGEDITFORM': editform})
1267 environ.update({'HGEDITFORM': editform})
1272 if pending:
1268 if pending:
1273 environ.update({'HG_PENDING': pending})
1269 environ.update({'HG_PENDING': pending})
1274
1270
1275 editor = self.geteditor()
1271 editor = self.geteditor()
1276
1272
1277 self.system("%s \"%s\"" % (editor, name),
1273 self.system("%s \"%s\"" % (editor, name),
1278 environ=environ,
1274 environ=environ,
1279 onerr=error.Abort, errprefix=_("edit failed"),
1275 onerr=error.Abort, errprefix=_("edit failed"),
1280 blockedtag='editor')
1276 blockedtag='editor')
1281
1277
1282 f = open(name)
1278 f = open(name)
1283 t = f.read()
1279 t = f.read()
1284 f.close()
1280 f.close()
1285 finally:
1281 finally:
1286 os.unlink(name)
1282 os.unlink(name)
1287
1283
1288 return t
1284 return t
1289
1285
1290 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1286 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1291 blockedtag=None):
1287 blockedtag=None):
1292 '''execute shell command with appropriate output stream. command
1288 '''execute shell command with appropriate output stream. command
1293 output will be redirected if fout is not stdout.
1289 output will be redirected if fout is not stdout.
1294
1290
1295 if command fails and onerr is None, return status, else raise onerr
1291 if command fails and onerr is None, return status, else raise onerr
1296 object as exception.
1292 object as exception.
1297 '''
1293 '''
1298 if blockedtag is None:
1294 if blockedtag is None:
1299 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1295 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1300 out = self.fout
1296 out = self.fout
1301 if any(s[1] for s in self._bufferstates):
1297 if any(s[1] for s in self._bufferstates):
1302 out = self
1298 out = self
1303 with self.timeblockedsection(blockedtag):
1299 with self.timeblockedsection(blockedtag):
1304 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1300 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1305 if rc and onerr:
1301 if rc and onerr:
1306 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1302 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1307 util.explainexit(rc)[0])
1303 util.explainexit(rc)[0])
1308 if errprefix:
1304 if errprefix:
1309 errmsg = '%s: %s' % (errprefix, errmsg)
1305 errmsg = '%s: %s' % (errprefix, errmsg)
1310 raise onerr(errmsg)
1306 raise onerr(errmsg)
1311 return rc
1307 return rc
1312
1308
1313 def _runsystem(self, cmd, environ, cwd, out):
1309 def _runsystem(self, cmd, environ, cwd, out):
1314 """actually execute the given shell command (can be overridden by
1310 """actually execute the given shell command (can be overridden by
1315 extensions like chg)"""
1311 extensions like chg)"""
1316 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1312 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1317
1313
1318 def traceback(self, exc=None, force=False):
1314 def traceback(self, exc=None, force=False):
1319 '''print exception traceback if traceback printing enabled or forced.
1315 '''print exception traceback if traceback printing enabled or forced.
1320 only to call in exception handler. returns true if traceback
1316 only to call in exception handler. returns true if traceback
1321 printed.'''
1317 printed.'''
1322 if self.tracebackflag or force:
1318 if self.tracebackflag or force:
1323 if exc is None:
1319 if exc is None:
1324 exc = sys.exc_info()
1320 exc = sys.exc_info()
1325 cause = getattr(exc[1], 'cause', None)
1321 cause = getattr(exc[1], 'cause', None)
1326
1322
1327 if cause is not None:
1323 if cause is not None:
1328 causetb = traceback.format_tb(cause[2])
1324 causetb = traceback.format_tb(cause[2])
1329 exctb = traceback.format_tb(exc[2])
1325 exctb = traceback.format_tb(exc[2])
1330 exconly = traceback.format_exception_only(cause[0], cause[1])
1326 exconly = traceback.format_exception_only(cause[0], cause[1])
1331
1327
1332 # exclude frame where 'exc' was chained and rethrown from exctb
1328 # exclude frame where 'exc' was chained and rethrown from exctb
1333 self.write_err('Traceback (most recent call last):\n',
1329 self.write_err('Traceback (most recent call last):\n',
1334 ''.join(exctb[:-1]),
1330 ''.join(exctb[:-1]),
1335 ''.join(causetb),
1331 ''.join(causetb),
1336 ''.join(exconly))
1332 ''.join(exconly))
1337 else:
1333 else:
1338 output = traceback.format_exception(exc[0], exc[1], exc[2])
1334 output = traceback.format_exception(exc[0], exc[1], exc[2])
1339 data = r''.join(output)
1335 data = r''.join(output)
1340 if pycompat.ispy3:
1336 if pycompat.ispy3:
1341 enc = pycompat.sysstr(encoding.encoding)
1337 enc = pycompat.sysstr(encoding.encoding)
1342 data = data.encode(enc, errors=r'replace')
1338 data = data.encode(enc, errors=r'replace')
1343 self.write_err(data)
1339 self.write_err(data)
1344 return self.tracebackflag or force
1340 return self.tracebackflag or force
1345
1341
1346 def geteditor(self):
1342 def geteditor(self):
1347 '''return editor to use'''
1343 '''return editor to use'''
1348 if pycompat.sysplatform == 'plan9':
1344 if pycompat.sysplatform == 'plan9':
1349 # vi is the MIPS instruction simulator on Plan 9. We
1345 # vi is the MIPS instruction simulator on Plan 9. We
1350 # instead default to E to plumb commit messages to
1346 # instead default to E to plumb commit messages to
1351 # avoid confusion.
1347 # avoid confusion.
1352 editor = 'E'
1348 editor = 'E'
1353 else:
1349 else:
1354 editor = 'vi'
1350 editor = 'vi'
1355 return (encoding.environ.get("HGEDITOR") or
1351 return (encoding.environ.get("HGEDITOR") or
1356 self.config("ui", "editor") or
1352 self.config("ui", "editor") or
1357 encoding.environ.get("VISUAL") or
1353 encoding.environ.get("VISUAL") or
1358 encoding.environ.get("EDITOR", editor))
1354 encoding.environ.get("EDITOR", editor))
1359
1355
1360 @util.propertycache
1356 @util.propertycache
1361 def _progbar(self):
1357 def _progbar(self):
1362 """setup the progbar singleton to the ui object"""
1358 """setup the progbar singleton to the ui object"""
1363 if (self.quiet or self.debugflag
1359 if (self.quiet or self.debugflag
1364 or self.configbool('progress', 'disable', False)
1360 or self.configbool('progress', 'disable', False)
1365 or not progress.shouldprint(self)):
1361 or not progress.shouldprint(self)):
1366 return None
1362 return None
1367 return getprogbar(self)
1363 return getprogbar(self)
1368
1364
1369 def _progclear(self):
1365 def _progclear(self):
1370 """clear progress bar output if any. use it before any output"""
1366 """clear progress bar output if any. use it before any output"""
1371 if '_progbar' not in vars(self): # nothing loaded yet
1367 if '_progbar' not in vars(self): # nothing loaded yet
1372 return
1368 return
1373 if self._progbar is not None and self._progbar.printed:
1369 if self._progbar is not None and self._progbar.printed:
1374 self._progbar.clear()
1370 self._progbar.clear()
1375
1371
1376 def progress(self, topic, pos, item="", unit="", total=None):
1372 def progress(self, topic, pos, item="", unit="", total=None):
1377 '''show a progress message
1373 '''show a progress message
1378
1374
1379 By default a textual progress bar will be displayed if an operation
1375 By default a textual progress bar will be displayed if an operation
1380 takes too long. 'topic' is the current operation, 'item' is a
1376 takes too long. 'topic' is the current operation, 'item' is a
1381 non-numeric marker of the current position (i.e. the currently
1377 non-numeric marker of the current position (i.e. the currently
1382 in-process file), 'pos' is the current numeric position (i.e.
1378 in-process file), 'pos' is the current numeric position (i.e.
1383 revision, bytes, etc.), unit is a corresponding unit label,
1379 revision, bytes, etc.), unit is a corresponding unit label,
1384 and total is the highest expected pos.
1380 and total is the highest expected pos.
1385
1381
1386 Multiple nested topics may be active at a time.
1382 Multiple nested topics may be active at a time.
1387
1383
1388 All topics should be marked closed by setting pos to None at
1384 All topics should be marked closed by setting pos to None at
1389 termination.
1385 termination.
1390 '''
1386 '''
1391 if self._progbar is not None:
1387 if self._progbar is not None:
1392 self._progbar.progress(topic, pos, item=item, unit=unit,
1388 self._progbar.progress(topic, pos, item=item, unit=unit,
1393 total=total)
1389 total=total)
1394 if pos is None or not self.configbool('progress', 'debug'):
1390 if pos is None or not self.configbool('progress', 'debug'):
1395 return
1391 return
1396
1392
1397 if unit:
1393 if unit:
1398 unit = ' ' + unit
1394 unit = ' ' + unit
1399 if item:
1395 if item:
1400 item = ' ' + item
1396 item = ' ' + item
1401
1397
1402 if total:
1398 if total:
1403 pct = 100.0 * pos / total
1399 pct = 100.0 * pos / total
1404 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1400 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1405 % (topic, item, pos, total, unit, pct))
1401 % (topic, item, pos, total, unit, pct))
1406 else:
1402 else:
1407 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1403 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1408
1404
1409 def log(self, service, *msg, **opts):
1405 def log(self, service, *msg, **opts):
1410 '''hook for logging facility extensions
1406 '''hook for logging facility extensions
1411
1407
1412 service should be a readily-identifiable subsystem, which will
1408 service should be a readily-identifiable subsystem, which will
1413 allow filtering.
1409 allow filtering.
1414
1410
1415 *msg should be a newline-terminated format string to log, and
1411 *msg should be a newline-terminated format string to log, and
1416 then any values to %-format into that format string.
1412 then any values to %-format into that format string.
1417
1413
1418 **opts currently has no defined meanings.
1414 **opts currently has no defined meanings.
1419 '''
1415 '''
1420
1416
1421 def label(self, msg, label):
1417 def label(self, msg, label):
1422 '''style msg based on supplied label
1418 '''style msg based on supplied label
1423
1419
1424 If some color mode is enabled, this will add the necessary control
1420 If some color mode is enabled, this will add the necessary control
1425 characters to apply such color. In addition, 'debug' color mode adds
1421 characters to apply such color. In addition, 'debug' color mode adds
1426 markup showing which label affects a piece of text.
1422 markup showing which label affects a piece of text.
1427
1423
1428 ui.write(s, 'label') is equivalent to
1424 ui.write(s, 'label') is equivalent to
1429 ui.write(ui.label(s, 'label')).
1425 ui.write(ui.label(s, 'label')).
1430 '''
1426 '''
1431 if self._colormode is not None:
1427 if self._colormode is not None:
1432 return color.colorlabel(self, msg, label)
1428 return color.colorlabel(self, msg, label)
1433 return msg
1429 return msg
1434
1430
1435 def develwarn(self, msg, stacklevel=1, config=None):
1431 def develwarn(self, msg, stacklevel=1, config=None):
1436 """issue a developer warning message
1432 """issue a developer warning message
1437
1433
1438 Use 'stacklevel' to report the offender some layers further up in the
1434 Use 'stacklevel' to report the offender some layers further up in the
1439 stack.
1435 stack.
1440 """
1436 """
1441 if not self.configbool('devel', 'all-warnings'):
1437 if not self.configbool('devel', 'all-warnings'):
1442 if config is not None and not self.configbool('devel', config):
1438 if config is not None and not self.configbool('devel', config):
1443 return
1439 return
1444 msg = 'devel-warn: ' + msg
1440 msg = 'devel-warn: ' + msg
1445 stacklevel += 1 # get in develwarn
1441 stacklevel += 1 # get in develwarn
1446 if self.tracebackflag:
1442 if self.tracebackflag:
1447 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1443 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1448 self.log('develwarn', '%s at:\n%s' %
1444 self.log('develwarn', '%s at:\n%s' %
1449 (msg, ''.join(util.getstackframes(stacklevel))))
1445 (msg, ''.join(util.getstackframes(stacklevel))))
1450 else:
1446 else:
1451 curframe = inspect.currentframe()
1447 curframe = inspect.currentframe()
1452 calframe = inspect.getouterframes(curframe, 2)
1448 calframe = inspect.getouterframes(curframe, 2)
1453 self.write_err('%s at: %s:%s (%s)\n'
1449 self.write_err('%s at: %s:%s (%s)\n'
1454 % ((msg,) + calframe[stacklevel][1:4]))
1450 % ((msg,) + calframe[stacklevel][1:4]))
1455 self.log('develwarn', '%s at: %s:%s (%s)\n',
1451 self.log('develwarn', '%s at: %s:%s (%s)\n',
1456 msg, *calframe[stacklevel][1:4])
1452 msg, *calframe[stacklevel][1:4])
1457 curframe = calframe = None # avoid cycles
1453 curframe = calframe = None # avoid cycles
1458
1454
1459 def deprecwarn(self, msg, version):
1455 def deprecwarn(self, msg, version):
1460 """issue a deprecation warning
1456 """issue a deprecation warning
1461
1457
1462 - msg: message explaining what is deprecated and how to upgrade,
1458 - msg: message explaining what is deprecated and how to upgrade,
1463 - version: last version where the API will be supported,
1459 - version: last version where the API will be supported,
1464 """
1460 """
1465 if not (self.configbool('devel', 'all-warnings')
1461 if not (self.configbool('devel', 'all-warnings')
1466 or self.configbool('devel', 'deprec-warn')):
1462 or self.configbool('devel', 'deprec-warn')):
1467 return
1463 return
1468 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1464 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1469 " update your code.)") % version
1465 " update your code.)") % version
1470 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1466 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1471
1467
1472 def exportableenviron(self):
1468 def exportableenviron(self):
1473 """The environment variables that are safe to export, e.g. through
1469 """The environment variables that are safe to export, e.g. through
1474 hgweb.
1470 hgweb.
1475 """
1471 """
1476 return self._exportableenviron
1472 return self._exportableenviron
1477
1473
1478 @contextlib.contextmanager
1474 @contextlib.contextmanager
1479 def configoverride(self, overrides, source=""):
1475 def configoverride(self, overrides, source=""):
1480 """Context manager for temporary config overrides
1476 """Context manager for temporary config overrides
1481 `overrides` must be a dict of the following structure:
1477 `overrides` must be a dict of the following structure:
1482 {(section, name) : value}"""
1478 {(section, name) : value}"""
1483 backups = {}
1479 backups = {}
1484 try:
1480 try:
1485 for (section, name), value in overrides.items():
1481 for (section, name), value in overrides.items():
1486 backups[(section, name)] = self.backupconfig(section, name)
1482 backups[(section, name)] = self.backupconfig(section, name)
1487 self.setconfig(section, name, value, source)
1483 self.setconfig(section, name, value, source)
1488 yield
1484 yield
1489 finally:
1485 finally:
1490 for __, backup in backups.items():
1486 for __, backup in backups.items():
1491 self.restoreconfig(backup)
1487 self.restoreconfig(backup)
1492 # just restoring ui.quiet config to the previous value is not enough
1488 # just restoring ui.quiet config to the previous value is not enough
1493 # as it does not update ui.quiet class member
1489 # as it does not update ui.quiet class member
1494 if ('ui', 'quiet') in overrides:
1490 if ('ui', 'quiet') in overrides:
1495 self.fixconfig(section='ui')
1491 self.fixconfig(section='ui')
1496
1492
1497 class paths(dict):
1493 class paths(dict):
1498 """Represents a collection of paths and their configs.
1494 """Represents a collection of paths and their configs.
1499
1495
1500 Data is initially derived from ui instances and the config files they have
1496 Data is initially derived from ui instances and the config files they have
1501 loaded.
1497 loaded.
1502 """
1498 """
1503 def __init__(self, ui):
1499 def __init__(self, ui):
1504 dict.__init__(self)
1500 dict.__init__(self)
1505
1501
1506 for name, loc in ui.configitems('paths', ignoresub=True):
1502 for name, loc in ui.configitems('paths', ignoresub=True):
1507 # No location is the same as not existing.
1503 # No location is the same as not existing.
1508 if not loc:
1504 if not loc:
1509 continue
1505 continue
1510 loc, sub = ui.configsuboptions('paths', name)
1506 loc, sub = ui.configsuboptions('paths', name)
1511 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1507 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1512
1508
1513 def getpath(self, name, default=None):
1509 def getpath(self, name, default=None):
1514 """Return a ``path`` from a string, falling back to default.
1510 """Return a ``path`` from a string, falling back to default.
1515
1511
1516 ``name`` can be a named path or locations. Locations are filesystem
1512 ``name`` can be a named path or locations. Locations are filesystem
1517 paths or URIs.
1513 paths or URIs.
1518
1514
1519 Returns None if ``name`` is not a registered path, a URI, or a local
1515 Returns None if ``name`` is not a registered path, a URI, or a local
1520 path to a repo.
1516 path to a repo.
1521 """
1517 """
1522 # Only fall back to default if no path was requested.
1518 # Only fall back to default if no path was requested.
1523 if name is None:
1519 if name is None:
1524 if not default:
1520 if not default:
1525 default = ()
1521 default = ()
1526 elif not isinstance(default, (tuple, list)):
1522 elif not isinstance(default, (tuple, list)):
1527 default = (default,)
1523 default = (default,)
1528 for k in default:
1524 for k in default:
1529 try:
1525 try:
1530 return self[k]
1526 return self[k]
1531 except KeyError:
1527 except KeyError:
1532 continue
1528 continue
1533 return None
1529 return None
1534
1530
1535 # Most likely empty string.
1531 # Most likely empty string.
1536 # This may need to raise in the future.
1532 # This may need to raise in the future.
1537 if not name:
1533 if not name:
1538 return None
1534 return None
1539
1535
1540 try:
1536 try:
1541 return self[name]
1537 return self[name]
1542 except KeyError:
1538 except KeyError:
1543 # Try to resolve as a local path or URI.
1539 # Try to resolve as a local path or URI.
1544 try:
1540 try:
1545 # We don't pass sub-options in, so no need to pass ui instance.
1541 # We don't pass sub-options in, so no need to pass ui instance.
1546 return path(None, None, rawloc=name)
1542 return path(None, None, rawloc=name)
1547 except ValueError:
1543 except ValueError:
1548 raise error.RepoError(_('repository %s does not exist') %
1544 raise error.RepoError(_('repository %s does not exist') %
1549 name)
1545 name)
1550
1546
1551 _pathsuboptions = {}
1547 _pathsuboptions = {}
1552
1548
1553 def pathsuboption(option, attr):
1549 def pathsuboption(option, attr):
1554 """Decorator used to declare a path sub-option.
1550 """Decorator used to declare a path sub-option.
1555
1551
1556 Arguments are the sub-option name and the attribute it should set on
1552 Arguments are the sub-option name and the attribute it should set on
1557 ``path`` instances.
1553 ``path`` instances.
1558
1554
1559 The decorated function will receive as arguments a ``ui`` instance,
1555 The decorated function will receive as arguments a ``ui`` instance,
1560 ``path`` instance, and the string value of this option from the config.
1556 ``path`` instance, and the string value of this option from the config.
1561 The function should return the value that will be set on the ``path``
1557 The function should return the value that will be set on the ``path``
1562 instance.
1558 instance.
1563
1559
1564 This decorator can be used to perform additional verification of
1560 This decorator can be used to perform additional verification of
1565 sub-options and to change the type of sub-options.
1561 sub-options and to change the type of sub-options.
1566 """
1562 """
1567 def register(func):
1563 def register(func):
1568 _pathsuboptions[option] = (attr, func)
1564 _pathsuboptions[option] = (attr, func)
1569 return func
1565 return func
1570 return register
1566 return register
1571
1567
1572 @pathsuboption('pushurl', 'pushloc')
1568 @pathsuboption('pushurl', 'pushloc')
1573 def pushurlpathoption(ui, path, value):
1569 def pushurlpathoption(ui, path, value):
1574 u = util.url(value)
1570 u = util.url(value)
1575 # Actually require a URL.
1571 # Actually require a URL.
1576 if not u.scheme:
1572 if not u.scheme:
1577 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1573 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1578 return None
1574 return None
1579
1575
1580 # Don't support the #foo syntax in the push URL to declare branch to
1576 # Don't support the #foo syntax in the push URL to declare branch to
1581 # push.
1577 # push.
1582 if u.fragment:
1578 if u.fragment:
1583 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1579 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1584 'ignoring)\n') % path.name)
1580 'ignoring)\n') % path.name)
1585 u.fragment = None
1581 u.fragment = None
1586
1582
1587 return str(u)
1583 return str(u)
1588
1584
1589 @pathsuboption('pushrev', 'pushrev')
1585 @pathsuboption('pushrev', 'pushrev')
1590 def pushrevpathoption(ui, path, value):
1586 def pushrevpathoption(ui, path, value):
1591 return value
1587 return value
1592
1588
1593 class path(object):
1589 class path(object):
1594 """Represents an individual path and its configuration."""
1590 """Represents an individual path and its configuration."""
1595
1591
1596 def __init__(self, ui, name, rawloc=None, suboptions=None):
1592 def __init__(self, ui, name, rawloc=None, suboptions=None):
1597 """Construct a path from its config options.
1593 """Construct a path from its config options.
1598
1594
1599 ``ui`` is the ``ui`` instance the path is coming from.
1595 ``ui`` is the ``ui`` instance the path is coming from.
1600 ``name`` is the symbolic name of the path.
1596 ``name`` is the symbolic name of the path.
1601 ``rawloc`` is the raw location, as defined in the config.
1597 ``rawloc`` is the raw location, as defined in the config.
1602 ``pushloc`` is the raw locations pushes should be made to.
1598 ``pushloc`` is the raw locations pushes should be made to.
1603
1599
1604 If ``name`` is not defined, we require that the location be a) a local
1600 If ``name`` is not defined, we require that the location be a) a local
1605 filesystem path with a .hg directory or b) a URL. If not,
1601 filesystem path with a .hg directory or b) a URL. If not,
1606 ``ValueError`` is raised.
1602 ``ValueError`` is raised.
1607 """
1603 """
1608 if not rawloc:
1604 if not rawloc:
1609 raise ValueError('rawloc must be defined')
1605 raise ValueError('rawloc must be defined')
1610
1606
1611 # Locations may define branches via syntax <base>#<branch>.
1607 # Locations may define branches via syntax <base>#<branch>.
1612 u = util.url(rawloc)
1608 u = util.url(rawloc)
1613 branch = None
1609 branch = None
1614 if u.fragment:
1610 if u.fragment:
1615 branch = u.fragment
1611 branch = u.fragment
1616 u.fragment = None
1612 u.fragment = None
1617
1613
1618 self.url = u
1614 self.url = u
1619 self.branch = branch
1615 self.branch = branch
1620
1616
1621 self.name = name
1617 self.name = name
1622 self.rawloc = rawloc
1618 self.rawloc = rawloc
1623 self.loc = str(u)
1619 self.loc = str(u)
1624
1620
1625 # When given a raw location but not a symbolic name, validate the
1621 # When given a raw location but not a symbolic name, validate the
1626 # location is valid.
1622 # location is valid.
1627 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1623 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1628 raise ValueError('location is not a URL or path to a local '
1624 raise ValueError('location is not a URL or path to a local '
1629 'repo: %s' % rawloc)
1625 'repo: %s' % rawloc)
1630
1626
1631 suboptions = suboptions or {}
1627 suboptions = suboptions or {}
1632
1628
1633 # Now process the sub-options. If a sub-option is registered, its
1629 # Now process the sub-options. If a sub-option is registered, its
1634 # attribute will always be present. The value will be None if there
1630 # attribute will always be present. The value will be None if there
1635 # was no valid sub-option.
1631 # was no valid sub-option.
1636 for suboption, (attr, func) in _pathsuboptions.iteritems():
1632 for suboption, (attr, func) in _pathsuboptions.iteritems():
1637 if suboption not in suboptions:
1633 if suboption not in suboptions:
1638 setattr(self, attr, None)
1634 setattr(self, attr, None)
1639 continue
1635 continue
1640
1636
1641 value = func(ui, self, suboptions[suboption])
1637 value = func(ui, self, suboptions[suboption])
1642 setattr(self, attr, value)
1638 setattr(self, attr, value)
1643
1639
1644 def _isvalidlocalpath(self, path):
1640 def _isvalidlocalpath(self, path):
1645 """Returns True if the given path is a potentially valid repository.
1641 """Returns True if the given path is a potentially valid repository.
1646 This is its own function so that extensions can change the definition of
1642 This is its own function so that extensions can change the definition of
1647 'valid' in this case (like when pulling from a git repo into a hg
1643 'valid' in this case (like when pulling from a git repo into a hg
1648 one)."""
1644 one)."""
1649 return os.path.isdir(os.path.join(path, '.hg'))
1645 return os.path.isdir(os.path.join(path, '.hg'))
1650
1646
1651 @property
1647 @property
1652 def suboptions(self):
1648 def suboptions(self):
1653 """Return sub-options and their values for this path.
1649 """Return sub-options and their values for this path.
1654
1650
1655 This is intended to be used for presentation purposes.
1651 This is intended to be used for presentation purposes.
1656 """
1652 """
1657 d = {}
1653 d = {}
1658 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1654 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1659 value = getattr(self, attr)
1655 value = getattr(self, attr)
1660 if value is not None:
1656 if value is not None:
1661 d[subopt] = value
1657 d[subopt] = value
1662 return d
1658 return d
1663
1659
1664 # we instantiate one globally shared progress bar to avoid
1660 # we instantiate one globally shared progress bar to avoid
1665 # competing progress bars when multiple UI objects get created
1661 # competing progress bars when multiple UI objects get created
1666 _progresssingleton = None
1662 _progresssingleton = None
1667
1663
1668 def getprogbar(ui):
1664 def getprogbar(ui):
1669 global _progresssingleton
1665 global _progresssingleton
1670 if _progresssingleton is None:
1666 if _progresssingleton is None:
1671 # passing 'ui' object to the singleton is fishy,
1667 # passing 'ui' object to the singleton is fishy,
1672 # this is how the extension used to work but feel free to rework it.
1668 # this is how the extension used to work but feel free to rework it.
1673 _progresssingleton = progress.progbar(ui)
1669 _progresssingleton = progress.progbar(ui)
1674 return _progresssingleton
1670 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now