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