##// END OF EJS Templates
py3: use pycompat.bytestr to convert str to bytes...
Pulkit Goyal -
r36246:ddeb7653 default
parent child Browse files
Show More
@@ -1,565 +1,566
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class 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 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 bin,
12 bin,
13 hex,
13 hex,
14 nullid,
14 nullid,
15 )
15 )
16 from .thirdparty import (
16 from .thirdparty import (
17 attr,
17 attr,
18 )
18 )
19
19
20 from . import (
20 from . import (
21 encoding,
21 encoding,
22 error,
22 error,
23 pycompat,
23 revlog,
24 revlog,
24 util,
25 util,
25 )
26 )
26
27
27 _defaultextra = {'branch': 'default'}
28 _defaultextra = {'branch': 'default'}
28
29
29 def _string_escape(text):
30 def _string_escape(text):
30 """
31 """
31 >>> from .pycompat import bytechr as chr
32 >>> from .pycompat import bytechr as chr
32 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
33 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
33 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
34 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
34 >>> s
35 >>> s
35 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
36 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
36 >>> res = _string_escape(s)
37 >>> res = _string_escape(s)
37 >>> s == util.unescapestr(res)
38 >>> s == util.unescapestr(res)
38 True
39 True
39 """
40 """
40 # subset of the string_escape codec
41 # subset of the string_escape codec
41 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
42 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
42 return text.replace('\0', '\\0')
43 return text.replace('\0', '\\0')
43
44
44 def decodeextra(text):
45 def decodeextra(text):
45 """
46 """
46 >>> from .pycompat import bytechr as chr
47 >>> from .pycompat import bytechr as chr
47 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
48 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
48 ... ).items())
49 ... ).items())
49 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
50 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
50 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
51 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
51 ... b'baz': chr(92) + chr(0) + b'2'})
52 ... b'baz': chr(92) + chr(0) + b'2'})
52 ... ).items())
53 ... ).items())
53 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
54 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
54 """
55 """
55 extra = _defaultextra.copy()
56 extra = _defaultextra.copy()
56 for l in text.split('\0'):
57 for l in text.split('\0'):
57 if l:
58 if l:
58 if '\\0' in l:
59 if '\\0' in l:
59 # fix up \0 without getting into trouble with \\0
60 # fix up \0 without getting into trouble with \\0
60 l = l.replace('\\\\', '\\\\\n')
61 l = l.replace('\\\\', '\\\\\n')
61 l = l.replace('\\0', '\0')
62 l = l.replace('\\0', '\0')
62 l = l.replace('\n', '')
63 l = l.replace('\n', '')
63 k, v = util.unescapestr(l).split(':', 1)
64 k, v = util.unescapestr(l).split(':', 1)
64 extra[k] = v
65 extra[k] = v
65 return extra
66 return extra
66
67
67 def encodeextra(d):
68 def encodeextra(d):
68 # keys must be sorted to produce a deterministic changelog entry
69 # keys must be sorted to produce a deterministic changelog entry
69 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
70 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
70 return "\0".join(items)
71 return "\0".join(items)
71
72
72 def stripdesc(desc):
73 def stripdesc(desc):
73 """strip trailing whitespace and leading and trailing empty lines"""
74 """strip trailing whitespace and leading and trailing empty lines"""
74 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
75 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
75
76
76 class appender(object):
77 class appender(object):
77 '''the changelog index must be updated last on disk, so we use this class
78 '''the changelog index must be updated last on disk, so we use this class
78 to delay writes to it'''
79 to delay writes to it'''
79 def __init__(self, vfs, name, mode, buf):
80 def __init__(self, vfs, name, mode, buf):
80 self.data = buf
81 self.data = buf
81 fp = vfs(name, mode)
82 fp = vfs(name, mode)
82 self.fp = fp
83 self.fp = fp
83 self.offset = fp.tell()
84 self.offset = fp.tell()
84 self.size = vfs.fstat(fp).st_size
85 self.size = vfs.fstat(fp).st_size
85 self._end = self.size
86 self._end = self.size
86
87
87 def end(self):
88 def end(self):
88 return self._end
89 return self._end
89 def tell(self):
90 def tell(self):
90 return self.offset
91 return self.offset
91 def flush(self):
92 def flush(self):
92 pass
93 pass
93
94
94 @property
95 @property
95 def closed(self):
96 def closed(self):
96 return self.fp.closed
97 return self.fp.closed
97
98
98 def close(self):
99 def close(self):
99 self.fp.close()
100 self.fp.close()
100
101
101 def seek(self, offset, whence=0):
102 def seek(self, offset, whence=0):
102 '''virtual file offset spans real file and data'''
103 '''virtual file offset spans real file and data'''
103 if whence == 0:
104 if whence == 0:
104 self.offset = offset
105 self.offset = offset
105 elif whence == 1:
106 elif whence == 1:
106 self.offset += offset
107 self.offset += offset
107 elif whence == 2:
108 elif whence == 2:
108 self.offset = self.end() + offset
109 self.offset = self.end() + offset
109 if self.offset < self.size:
110 if self.offset < self.size:
110 self.fp.seek(self.offset)
111 self.fp.seek(self.offset)
111
112
112 def read(self, count=-1):
113 def read(self, count=-1):
113 '''only trick here is reads that span real file and data'''
114 '''only trick here is reads that span real file and data'''
114 ret = ""
115 ret = ""
115 if self.offset < self.size:
116 if self.offset < self.size:
116 s = self.fp.read(count)
117 s = self.fp.read(count)
117 ret = s
118 ret = s
118 self.offset += len(s)
119 self.offset += len(s)
119 if count > 0:
120 if count > 0:
120 count -= len(s)
121 count -= len(s)
121 if count != 0:
122 if count != 0:
122 doff = self.offset - self.size
123 doff = self.offset - self.size
123 self.data.insert(0, "".join(self.data))
124 self.data.insert(0, "".join(self.data))
124 del self.data[1:]
125 del self.data[1:]
125 s = self.data[0][doff:doff + count]
126 s = self.data[0][doff:doff + count]
126 self.offset += len(s)
127 self.offset += len(s)
127 ret += s
128 ret += s
128 return ret
129 return ret
129
130
130 def write(self, s):
131 def write(self, s):
131 self.data.append(bytes(s))
132 self.data.append(bytes(s))
132 self.offset += len(s)
133 self.offset += len(s)
133 self._end += len(s)
134 self._end += len(s)
134
135
135 def __enter__(self):
136 def __enter__(self):
136 self.fp.__enter__()
137 self.fp.__enter__()
137 return self
138 return self
138
139
139 def __exit__(self, *args):
140 def __exit__(self, *args):
140 return self.fp.__exit__(*args)
141 return self.fp.__exit__(*args)
141
142
142 def _divertopener(opener, target):
143 def _divertopener(opener, target):
143 """build an opener that writes in 'target.a' instead of 'target'"""
144 """build an opener that writes in 'target.a' instead of 'target'"""
144 def _divert(name, mode='r', checkambig=False):
145 def _divert(name, mode='r', checkambig=False):
145 if name != target:
146 if name != target:
146 return opener(name, mode)
147 return opener(name, mode)
147 return opener(name + ".a", mode)
148 return opener(name + ".a", mode)
148 return _divert
149 return _divert
149
150
150 def _delayopener(opener, target, buf):
151 def _delayopener(opener, target, buf):
151 """build an opener that stores chunks in 'buf' instead of 'target'"""
152 """build an opener that stores chunks in 'buf' instead of 'target'"""
152 def _delay(name, mode='r', checkambig=False):
153 def _delay(name, mode='r', checkambig=False):
153 if name != target:
154 if name != target:
154 return opener(name, mode)
155 return opener(name, mode)
155 return appender(opener, name, mode, buf)
156 return appender(opener, name, mode, buf)
156 return _delay
157 return _delay
157
158
158 @attr.s
159 @attr.s
159 class _changelogrevision(object):
160 class _changelogrevision(object):
160 # Extensions might modify _defaultextra, so let the constructor below pass
161 # Extensions might modify _defaultextra, so let the constructor below pass
161 # it in
162 # it in
162 extra = attr.ib()
163 extra = attr.ib()
163 manifest = attr.ib(default=nullid)
164 manifest = attr.ib(default=nullid)
164 user = attr.ib(default='')
165 user = attr.ib(default='')
165 date = attr.ib(default=(0, 0))
166 date = attr.ib(default=(0, 0))
166 files = attr.ib(default=attr.Factory(list))
167 files = attr.ib(default=attr.Factory(list))
167 description = attr.ib(default='')
168 description = attr.ib(default='')
168
169
169 class changelogrevision(object):
170 class changelogrevision(object):
170 """Holds results of a parsed changelog revision.
171 """Holds results of a parsed changelog revision.
171
172
172 Changelog revisions consist of multiple pieces of data, including
173 Changelog revisions consist of multiple pieces of data, including
173 the manifest node, user, and date. This object exposes a view into
174 the manifest node, user, and date. This object exposes a view into
174 the parsed object.
175 the parsed object.
175 """
176 """
176
177
177 __slots__ = (
178 __slots__ = (
178 u'_offsets',
179 u'_offsets',
179 u'_text',
180 u'_text',
180 )
181 )
181
182
182 def __new__(cls, text):
183 def __new__(cls, text):
183 if not text:
184 if not text:
184 return _changelogrevision(extra=_defaultextra)
185 return _changelogrevision(extra=_defaultextra)
185
186
186 self = super(changelogrevision, cls).__new__(cls)
187 self = super(changelogrevision, cls).__new__(cls)
187 # We could return here and implement the following as an __init__.
188 # We could return here and implement the following as an __init__.
188 # But doing it here is equivalent and saves an extra function call.
189 # But doing it here is equivalent and saves an extra function call.
189
190
190 # format used:
191 # format used:
191 # nodeid\n : manifest node in ascii
192 # nodeid\n : manifest node in ascii
192 # user\n : user, no \n or \r allowed
193 # user\n : user, no \n or \r allowed
193 # time tz extra\n : date (time is int or float, timezone is int)
194 # time tz extra\n : date (time is int or float, timezone is int)
194 # : extra is metadata, encoded and separated by '\0'
195 # : extra is metadata, encoded and separated by '\0'
195 # : older versions ignore it
196 # : older versions ignore it
196 # files\n\n : files modified by the cset, no \n or \r allowed
197 # files\n\n : files modified by the cset, no \n or \r allowed
197 # (.*) : comment (free text, ideally utf-8)
198 # (.*) : comment (free text, ideally utf-8)
198 #
199 #
199 # changelog v0 doesn't use extra
200 # changelog v0 doesn't use extra
200
201
201 nl1 = text.index('\n')
202 nl1 = text.index('\n')
202 nl2 = text.index('\n', nl1 + 1)
203 nl2 = text.index('\n', nl1 + 1)
203 nl3 = text.index('\n', nl2 + 1)
204 nl3 = text.index('\n', nl2 + 1)
204
205
205 # The list of files may be empty. Which means nl3 is the first of the
206 # The list of files may be empty. Which means nl3 is the first of the
206 # double newline that precedes the description.
207 # double newline that precedes the description.
207 if text[nl3 + 1:nl3 + 2] == '\n':
208 if text[nl3 + 1:nl3 + 2] == '\n':
208 doublenl = nl3
209 doublenl = nl3
209 else:
210 else:
210 doublenl = text.index('\n\n', nl3 + 1)
211 doublenl = text.index('\n\n', nl3 + 1)
211
212
212 self._offsets = (nl1, nl2, nl3, doublenl)
213 self._offsets = (nl1, nl2, nl3, doublenl)
213 self._text = text
214 self._text = text
214
215
215 return self
216 return self
216
217
217 @property
218 @property
218 def manifest(self):
219 def manifest(self):
219 return bin(self._text[0:self._offsets[0]])
220 return bin(self._text[0:self._offsets[0]])
220
221
221 @property
222 @property
222 def user(self):
223 def user(self):
223 off = self._offsets
224 off = self._offsets
224 return encoding.tolocal(self._text[off[0] + 1:off[1]])
225 return encoding.tolocal(self._text[off[0] + 1:off[1]])
225
226
226 @property
227 @property
227 def _rawdate(self):
228 def _rawdate(self):
228 off = self._offsets
229 off = self._offsets
229 dateextra = self._text[off[1] + 1:off[2]]
230 dateextra = self._text[off[1] + 1:off[2]]
230 return dateextra.split(' ', 2)[0:2]
231 return dateextra.split(' ', 2)[0:2]
231
232
232 @property
233 @property
233 def _rawextra(self):
234 def _rawextra(self):
234 off = self._offsets
235 off = self._offsets
235 dateextra = self._text[off[1] + 1:off[2]]
236 dateextra = self._text[off[1] + 1:off[2]]
236 fields = dateextra.split(' ', 2)
237 fields = dateextra.split(' ', 2)
237 if len(fields) != 3:
238 if len(fields) != 3:
238 return None
239 return None
239
240
240 return fields[2]
241 return fields[2]
241
242
242 @property
243 @property
243 def date(self):
244 def date(self):
244 raw = self._rawdate
245 raw = self._rawdate
245 time = float(raw[0])
246 time = float(raw[0])
246 # Various tools did silly things with the timezone.
247 # Various tools did silly things with the timezone.
247 try:
248 try:
248 timezone = int(raw[1])
249 timezone = int(raw[1])
249 except ValueError:
250 except ValueError:
250 timezone = 0
251 timezone = 0
251
252
252 return time, timezone
253 return time, timezone
253
254
254 @property
255 @property
255 def extra(self):
256 def extra(self):
256 raw = self._rawextra
257 raw = self._rawextra
257 if raw is None:
258 if raw is None:
258 return _defaultextra
259 return _defaultextra
259
260
260 return decodeextra(raw)
261 return decodeextra(raw)
261
262
262 @property
263 @property
263 def files(self):
264 def files(self):
264 off = self._offsets
265 off = self._offsets
265 if off[2] == off[3]:
266 if off[2] == off[3]:
266 return []
267 return []
267
268
268 return self._text[off[2] + 1:off[3]].split('\n')
269 return self._text[off[2] + 1:off[3]].split('\n')
269
270
270 @property
271 @property
271 def description(self):
272 def description(self):
272 return encoding.tolocal(self._text[self._offsets[3] + 2:])
273 return encoding.tolocal(self._text[self._offsets[3] + 2:])
273
274
274 class changelog(revlog.revlog):
275 class changelog(revlog.revlog):
275 def __init__(self, opener, trypending=False):
276 def __init__(self, opener, trypending=False):
276 """Load a changelog revlog using an opener.
277 """Load a changelog revlog using an opener.
277
278
278 If ``trypending`` is true, we attempt to load the index from a
279 If ``trypending`` is true, we attempt to load the index from a
279 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
280 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
280 The ``00changelog.i.a`` file contains index (and possibly inline
281 The ``00changelog.i.a`` file contains index (and possibly inline
281 revision) data for a transaction that hasn't been finalized yet.
282 revision) data for a transaction that hasn't been finalized yet.
282 It exists in a separate file to facilitate readers (such as
283 It exists in a separate file to facilitate readers (such as
283 hooks processes) accessing data before a transaction is finalized.
284 hooks processes) accessing data before a transaction is finalized.
284 """
285 """
285 if trypending and opener.exists('00changelog.i.a'):
286 if trypending and opener.exists('00changelog.i.a'):
286 indexfile = '00changelog.i.a'
287 indexfile = '00changelog.i.a'
287 else:
288 else:
288 indexfile = '00changelog.i'
289 indexfile = '00changelog.i'
289
290
290 datafile = '00changelog.d'
291 datafile = '00changelog.d'
291 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
292 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
292 checkambig=True, mmaplargeindex=True)
293 checkambig=True, mmaplargeindex=True)
293
294
294 if self._initempty:
295 if self._initempty:
295 # changelogs don't benefit from generaldelta
296 # changelogs don't benefit from generaldelta
296 self.version &= ~revlog.FLAG_GENERALDELTA
297 self.version &= ~revlog.FLAG_GENERALDELTA
297 self._generaldelta = False
298 self._generaldelta = False
298
299
299 # Delta chains for changelogs tend to be very small because entries
300 # Delta chains for changelogs tend to be very small because entries
300 # tend to be small and don't delta well with each. So disable delta
301 # tend to be small and don't delta well with each. So disable delta
301 # chains.
302 # chains.
302 self.storedeltachains = False
303 self.storedeltachains = False
303
304
304 self._realopener = opener
305 self._realopener = opener
305 self._delayed = False
306 self._delayed = False
306 self._delaybuf = None
307 self._delaybuf = None
307 self._divert = False
308 self._divert = False
308 self.filteredrevs = frozenset()
309 self.filteredrevs = frozenset()
309
310
310 def tiprev(self):
311 def tiprev(self):
311 for i in xrange(len(self) -1, -2, -1):
312 for i in xrange(len(self) -1, -2, -1):
312 if i not in self.filteredrevs:
313 if i not in self.filteredrevs:
313 return i
314 return i
314
315
315 def tip(self):
316 def tip(self):
316 """filtered version of revlog.tip"""
317 """filtered version of revlog.tip"""
317 return self.node(self.tiprev())
318 return self.node(self.tiprev())
318
319
319 def __contains__(self, rev):
320 def __contains__(self, rev):
320 """filtered version of revlog.__contains__"""
321 """filtered version of revlog.__contains__"""
321 return (0 <= rev < len(self)
322 return (0 <= rev < len(self)
322 and rev not in self.filteredrevs)
323 and rev not in self.filteredrevs)
323
324
324 def __iter__(self):
325 def __iter__(self):
325 """filtered version of revlog.__iter__"""
326 """filtered version of revlog.__iter__"""
326 if len(self.filteredrevs) == 0:
327 if len(self.filteredrevs) == 0:
327 return revlog.revlog.__iter__(self)
328 return revlog.revlog.__iter__(self)
328
329
329 def filterediter():
330 def filterediter():
330 for i in xrange(len(self)):
331 for i in xrange(len(self)):
331 if i not in self.filteredrevs:
332 if i not in self.filteredrevs:
332 yield i
333 yield i
333
334
334 return filterediter()
335 return filterediter()
335
336
336 def revs(self, start=0, stop=None):
337 def revs(self, start=0, stop=None):
337 """filtered version of revlog.revs"""
338 """filtered version of revlog.revs"""
338 for i in super(changelog, self).revs(start, stop):
339 for i in super(changelog, self).revs(start, stop):
339 if i not in self.filteredrevs:
340 if i not in self.filteredrevs:
340 yield i
341 yield i
341
342
342 @util.propertycache
343 @util.propertycache
343 def nodemap(self):
344 def nodemap(self):
344 # XXX need filtering too
345 # XXX need filtering too
345 self.rev(self.node(0))
346 self.rev(self.node(0))
346 return self._nodecache
347 return self._nodecache
347
348
348 def reachableroots(self, minroot, heads, roots, includepath=False):
349 def reachableroots(self, minroot, heads, roots, includepath=False):
349 return self.index.reachableroots2(minroot, heads, roots, includepath)
350 return self.index.reachableroots2(minroot, heads, roots, includepath)
350
351
351 def headrevs(self):
352 def headrevs(self):
352 if self.filteredrevs:
353 if self.filteredrevs:
353 try:
354 try:
354 return self.index.headrevsfiltered(self.filteredrevs)
355 return self.index.headrevsfiltered(self.filteredrevs)
355 # AttributeError covers non-c-extension environments and
356 # AttributeError covers non-c-extension environments and
356 # old c extensions without filter handling.
357 # old c extensions without filter handling.
357 except AttributeError:
358 except AttributeError:
358 return self._headrevs()
359 return self._headrevs()
359
360
360 return super(changelog, self).headrevs()
361 return super(changelog, self).headrevs()
361
362
362 def strip(self, *args, **kwargs):
363 def strip(self, *args, **kwargs):
363 # XXX make something better than assert
364 # XXX make something better than assert
364 # We can't expect proper strip behavior if we are filtered.
365 # We can't expect proper strip behavior if we are filtered.
365 assert not self.filteredrevs
366 assert not self.filteredrevs
366 super(changelog, self).strip(*args, **kwargs)
367 super(changelog, self).strip(*args, **kwargs)
367
368
368 def rev(self, node):
369 def rev(self, node):
369 """filtered version of revlog.rev"""
370 """filtered version of revlog.rev"""
370 r = super(changelog, self).rev(node)
371 r = super(changelog, self).rev(node)
371 if r in self.filteredrevs:
372 if r in self.filteredrevs:
372 raise error.FilteredLookupError(hex(node), self.indexfile,
373 raise error.FilteredLookupError(hex(node), self.indexfile,
373 _('filtered node'))
374 _('filtered node'))
374 return r
375 return r
375
376
376 def node(self, rev):
377 def node(self, rev):
377 """filtered version of revlog.node"""
378 """filtered version of revlog.node"""
378 if rev in self.filteredrevs:
379 if rev in self.filteredrevs:
379 raise error.FilteredIndexError(rev)
380 raise error.FilteredIndexError(rev)
380 return super(changelog, self).node(rev)
381 return super(changelog, self).node(rev)
381
382
382 def linkrev(self, rev):
383 def linkrev(self, rev):
383 """filtered version of revlog.linkrev"""
384 """filtered version of revlog.linkrev"""
384 if rev in self.filteredrevs:
385 if rev in self.filteredrevs:
385 raise error.FilteredIndexError(rev)
386 raise error.FilteredIndexError(rev)
386 return super(changelog, self).linkrev(rev)
387 return super(changelog, self).linkrev(rev)
387
388
388 def parentrevs(self, rev):
389 def parentrevs(self, rev):
389 """filtered version of revlog.parentrevs"""
390 """filtered version of revlog.parentrevs"""
390 if rev in self.filteredrevs:
391 if rev in self.filteredrevs:
391 raise error.FilteredIndexError(rev)
392 raise error.FilteredIndexError(rev)
392 return super(changelog, self).parentrevs(rev)
393 return super(changelog, self).parentrevs(rev)
393
394
394 def flags(self, rev):
395 def flags(self, rev):
395 """filtered version of revlog.flags"""
396 """filtered version of revlog.flags"""
396 if rev in self.filteredrevs:
397 if rev in self.filteredrevs:
397 raise error.FilteredIndexError(rev)
398 raise error.FilteredIndexError(rev)
398 return super(changelog, self).flags(rev)
399 return super(changelog, self).flags(rev)
399
400
400 def delayupdate(self, tr):
401 def delayupdate(self, tr):
401 "delay visibility of index updates to other readers"
402 "delay visibility of index updates to other readers"
402
403
403 if not self._delayed:
404 if not self._delayed:
404 if len(self) == 0:
405 if len(self) == 0:
405 self._divert = True
406 self._divert = True
406 if self._realopener.exists(self.indexfile + '.a'):
407 if self._realopener.exists(self.indexfile + '.a'):
407 self._realopener.unlink(self.indexfile + '.a')
408 self._realopener.unlink(self.indexfile + '.a')
408 self.opener = _divertopener(self._realopener, self.indexfile)
409 self.opener = _divertopener(self._realopener, self.indexfile)
409 else:
410 else:
410 self._delaybuf = []
411 self._delaybuf = []
411 self.opener = _delayopener(self._realopener, self.indexfile,
412 self.opener = _delayopener(self._realopener, self.indexfile,
412 self._delaybuf)
413 self._delaybuf)
413 self._delayed = True
414 self._delayed = True
414 tr.addpending('cl-%i' % id(self), self._writepending)
415 tr.addpending('cl-%i' % id(self), self._writepending)
415 tr.addfinalize('cl-%i' % id(self), self._finalize)
416 tr.addfinalize('cl-%i' % id(self), self._finalize)
416
417
417 def _finalize(self, tr):
418 def _finalize(self, tr):
418 "finalize index updates"
419 "finalize index updates"
419 self._delayed = False
420 self._delayed = False
420 self.opener = self._realopener
421 self.opener = self._realopener
421 # move redirected index data back into place
422 # move redirected index data back into place
422 if self._divert:
423 if self._divert:
423 assert not self._delaybuf
424 assert not self._delaybuf
424 tmpname = self.indexfile + ".a"
425 tmpname = self.indexfile + ".a"
425 nfile = self.opener.open(tmpname)
426 nfile = self.opener.open(tmpname)
426 nfile.close()
427 nfile.close()
427 self.opener.rename(tmpname, self.indexfile, checkambig=True)
428 self.opener.rename(tmpname, self.indexfile, checkambig=True)
428 elif self._delaybuf:
429 elif self._delaybuf:
429 fp = self.opener(self.indexfile, 'a', checkambig=True)
430 fp = self.opener(self.indexfile, 'a', checkambig=True)
430 fp.write("".join(self._delaybuf))
431 fp.write("".join(self._delaybuf))
431 fp.close()
432 fp.close()
432 self._delaybuf = None
433 self._delaybuf = None
433 self._divert = False
434 self._divert = False
434 # split when we're done
435 # split when we're done
435 self._enforceinlinesize(tr)
436 self._enforceinlinesize(tr)
436
437
437 def _writepending(self, tr):
438 def _writepending(self, tr):
438 "create a file containing the unfinalized state for pretxnchangegroup"
439 "create a file containing the unfinalized state for pretxnchangegroup"
439 if self._delaybuf:
440 if self._delaybuf:
440 # make a temporary copy of the index
441 # make a temporary copy of the index
441 fp1 = self._realopener(self.indexfile)
442 fp1 = self._realopener(self.indexfile)
442 pendingfilename = self.indexfile + ".a"
443 pendingfilename = self.indexfile + ".a"
443 # register as a temp file to ensure cleanup on failure
444 # register as a temp file to ensure cleanup on failure
444 tr.registertmp(pendingfilename)
445 tr.registertmp(pendingfilename)
445 # write existing data
446 # write existing data
446 fp2 = self._realopener(pendingfilename, "w")
447 fp2 = self._realopener(pendingfilename, "w")
447 fp2.write(fp1.read())
448 fp2.write(fp1.read())
448 # add pending data
449 # add pending data
449 fp2.write("".join(self._delaybuf))
450 fp2.write("".join(self._delaybuf))
450 fp2.close()
451 fp2.close()
451 # switch modes so finalize can simply rename
452 # switch modes so finalize can simply rename
452 self._delaybuf = None
453 self._delaybuf = None
453 self._divert = True
454 self._divert = True
454 self.opener = _divertopener(self._realopener, self.indexfile)
455 self.opener = _divertopener(self._realopener, self.indexfile)
455
456
456 if self._divert:
457 if self._divert:
457 return True
458 return True
458
459
459 return False
460 return False
460
461
461 def _enforceinlinesize(self, tr, fp=None):
462 def _enforceinlinesize(self, tr, fp=None):
462 if not self._delayed:
463 if not self._delayed:
463 revlog.revlog._enforceinlinesize(self, tr, fp)
464 revlog.revlog._enforceinlinesize(self, tr, fp)
464
465
465 def read(self, node):
466 def read(self, node):
466 """Obtain data from a parsed changelog revision.
467 """Obtain data from a parsed changelog revision.
467
468
468 Returns a 6-tuple of:
469 Returns a 6-tuple of:
469
470
470 - manifest node in binary
471 - manifest node in binary
471 - author/user as a localstr
472 - author/user as a localstr
472 - date as a 2-tuple of (time, timezone)
473 - date as a 2-tuple of (time, timezone)
473 - list of files
474 - list of files
474 - commit message as a localstr
475 - commit message as a localstr
475 - dict of extra metadata
476 - dict of extra metadata
476
477
477 Unless you need to access all fields, consider calling
478 Unless you need to access all fields, consider calling
478 ``changelogrevision`` instead, as it is faster for partial object
479 ``changelogrevision`` instead, as it is faster for partial object
479 access.
480 access.
480 """
481 """
481 c = changelogrevision(self.revision(node))
482 c = changelogrevision(self.revision(node))
482 return (
483 return (
483 c.manifest,
484 c.manifest,
484 c.user,
485 c.user,
485 c.date,
486 c.date,
486 c.files,
487 c.files,
487 c.description,
488 c.description,
488 c.extra
489 c.extra
489 )
490 )
490
491
491 def changelogrevision(self, nodeorrev):
492 def changelogrevision(self, nodeorrev):
492 """Obtain a ``changelogrevision`` for a node or revision."""
493 """Obtain a ``changelogrevision`` for a node or revision."""
493 return changelogrevision(self.revision(nodeorrev))
494 return changelogrevision(self.revision(nodeorrev))
494
495
495 def readfiles(self, node):
496 def readfiles(self, node):
496 """
497 """
497 short version of read that only returns the files modified by the cset
498 short version of read that only returns the files modified by the cset
498 """
499 """
499 text = self.revision(node)
500 text = self.revision(node)
500 if not text:
501 if not text:
501 return []
502 return []
502 last = text.index("\n\n")
503 last = text.index("\n\n")
503 l = text[:last].split('\n')
504 l = text[:last].split('\n')
504 return l[3:]
505 return l[3:]
505
506
506 def add(self, manifest, files, desc, transaction, p1, p2,
507 def add(self, manifest, files, desc, transaction, p1, p2,
507 user, date=None, extra=None):
508 user, date=None, extra=None):
508 # Convert to UTF-8 encoded bytestrings as the very first
509 # Convert to UTF-8 encoded bytestrings as the very first
509 # thing: calling any method on a localstr object will turn it
510 # thing: calling any method on a localstr object will turn it
510 # into a str object and the cached UTF-8 string is thus lost.
511 # into a str object and the cached UTF-8 string is thus lost.
511 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
512 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
512
513
513 user = user.strip()
514 user = user.strip()
514 # An empty username or a username with a "\n" will make the
515 # An empty username or a username with a "\n" will make the
515 # revision text contain two "\n\n" sequences -> corrupt
516 # revision text contain two "\n\n" sequences -> corrupt
516 # repository since read cannot unpack the revision.
517 # repository since read cannot unpack the revision.
517 if not user:
518 if not user:
518 raise error.RevlogError(_("empty username"))
519 raise error.RevlogError(_("empty username"))
519 if "\n" in user:
520 if "\n" in user:
520 raise error.RevlogError(_("username %s contains a newline")
521 raise error.RevlogError(_("username %r contains a newline")
521 % repr(user))
522 % pycompat.bytestr(user))
522
523
523 desc = stripdesc(desc)
524 desc = stripdesc(desc)
524
525
525 if date:
526 if date:
526 parseddate = "%d %d" % util.parsedate(date)
527 parseddate = "%d %d" % util.parsedate(date)
527 else:
528 else:
528 parseddate = "%d %d" % util.makedate()
529 parseddate = "%d %d" % util.makedate()
529 if extra:
530 if extra:
530 branch = extra.get("branch")
531 branch = extra.get("branch")
531 if branch in ("default", ""):
532 if branch in ("default", ""):
532 del extra["branch"]
533 del extra["branch"]
533 elif branch in (".", "null", "tip"):
534 elif branch in (".", "null", "tip"):
534 raise error.RevlogError(_('the name \'%s\' is reserved')
535 raise error.RevlogError(_('the name \'%s\' is reserved')
535 % branch)
536 % branch)
536 if extra:
537 if extra:
537 extra = encodeextra(extra)
538 extra = encodeextra(extra)
538 parseddate = "%s %s" % (parseddate, extra)
539 parseddate = "%s %s" % (parseddate, extra)
539 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
540 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
540 text = "\n".join(l)
541 text = "\n".join(l)
541 return self.addrevision(text, transaction, len(self), p1, p2)
542 return self.addrevision(text, transaction, len(self), p1, p2)
542
543
543 def branchinfo(self, rev):
544 def branchinfo(self, rev):
544 """return the branch name and open/close state of a revision
545 """return the branch name and open/close state of a revision
545
546
546 This function exists because creating a changectx object
547 This function exists because creating a changectx object
547 just to access this is costly."""
548 just to access this is costly."""
548 extra = self.read(rev)[5]
549 extra = self.read(rev)[5]
549 return encoding.tolocal(extra.get("branch")), 'close' in extra
550 return encoding.tolocal(extra.get("branch")), 'close' in extra
550
551
551 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
552 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
552 # overlay over the standard revlog._addrevision to track the new
553 # overlay over the standard revlog._addrevision to track the new
553 # revision on the transaction.
554 # revision on the transaction.
554 rev = len(self)
555 rev = len(self)
555 node = super(changelog, self)._addrevision(node, rawtext, transaction,
556 node = super(changelog, self)._addrevision(node, rawtext, transaction,
556 *args, **kwargs)
557 *args, **kwargs)
557 revs = transaction.changes.get('revs')
558 revs = transaction.changes.get('revs')
558 if revs is not None:
559 if revs is not None:
559 if revs:
560 if revs:
560 assert revs[-1] + 1 == rev
561 assert revs[-1] + 1 == rev
561 revs = xrange(revs[0], rev + 1)
562 revs = xrange(revs[0], rev + 1)
562 else:
563 else:
563 revs = xrange(rev, rev + 1)
564 revs = xrange(rev, rev + 1)
564 transaction.changes['revs'] = revs
565 transaction.changes['revs'] = revs
565 return node
566 return node
@@ -1,1849 +1,1849
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 collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40
40
41 urlreq = util.urlreq
41 urlreq = util.urlreq
42
42
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 if not c.isalnum())
45 if not c.isalnum())
46
46
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 tweakrc = b"""
48 tweakrc = b"""
49 [ui]
49 [ui]
50 # The rollback command is dangerous. As a rule, don't use it.
50 # The rollback command is dangerous. As a rule, don't use it.
51 rollback = False
51 rollback = False
52 # Make `hg status` report copy information
52 # Make `hg status` report copy information
53 statuscopies = yes
53 statuscopies = yes
54 # Prefer curses UIs when available. Revert to plain-text with `text`.
54 # Prefer curses UIs when available. Revert to plain-text with `text`.
55 interface = curses
55 interface = curses
56
56
57 [commands]
57 [commands]
58 # Make `hg status` emit cwd-relative paths by default.
58 # Make `hg status` emit cwd-relative paths by default.
59 status.relative = yes
59 status.relative = yes
60 # Refuse to perform an `hg update` that would cause a file content merge
60 # Refuse to perform an `hg update` that would cause a file content merge
61 update.check = noconflict
61 update.check = noconflict
62
62
63 [diff]
63 [diff]
64 git = 1
64 git = 1
65 showfunc = 1
65 showfunc = 1
66 """
66 """
67
67
68 samplehgrcs = {
68 samplehgrcs = {
69 'user':
69 'user':
70 b"""# example user config (see 'hg help config' for more info)
70 b"""# example user config (see 'hg help config' for more info)
71 [ui]
71 [ui]
72 # name and email, e.g.
72 # name and email, e.g.
73 # username = Jane Doe <jdoe@example.com>
73 # username = Jane Doe <jdoe@example.com>
74 username =
74 username =
75
75
76 # We recommend enabling tweakdefaults to get slight improvements to
76 # We recommend enabling tweakdefaults to get slight improvements to
77 # the UI over time. Make sure to set HGPLAIN in the environment when
77 # the UI over time. Make sure to set HGPLAIN in the environment when
78 # writing scripts!
78 # writing scripts!
79 # tweakdefaults = True
79 # tweakdefaults = True
80
80
81 # uncomment to disable color in command output
81 # uncomment to disable color in command output
82 # (see 'hg help color' for details)
82 # (see 'hg help color' for details)
83 # color = never
83 # color = never
84
84
85 # uncomment to disable command output pagination
85 # uncomment to disable command output pagination
86 # (see 'hg help pager' for details)
86 # (see 'hg help pager' for details)
87 # paginate = never
87 # paginate = never
88
88
89 [extensions]
89 [extensions]
90 # uncomment these lines to enable some popular extensions
90 # uncomment these lines to enable some popular extensions
91 # (see 'hg help extensions' for more info)
91 # (see 'hg help extensions' for more info)
92 #
92 #
93 # churn =
93 # churn =
94 """,
94 """,
95
95
96 'cloned':
96 'cloned':
97 b"""# example repository config (see 'hg help config' for more info)
97 b"""# example repository config (see 'hg help config' for more info)
98 [paths]
98 [paths]
99 default = %s
99 default = %s
100
100
101 # path aliases to other clones of this repo in URLs or filesystem paths
101 # path aliases to other clones of this repo in URLs or filesystem paths
102 # (see 'hg help config.paths' for more info)
102 # (see 'hg help config.paths' for more info)
103 #
103 #
104 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
104 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
105 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
105 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
106 # my-clone = /home/jdoe/jdoes-clone
106 # my-clone = /home/jdoe/jdoes-clone
107
107
108 [ui]
108 [ui]
109 # name and email (local to this repository, optional), e.g.
109 # name and email (local to this repository, optional), e.g.
110 # username = Jane Doe <jdoe@example.com>
110 # username = Jane Doe <jdoe@example.com>
111 """,
111 """,
112
112
113 'local':
113 'local':
114 b"""# example repository config (see 'hg help config' for more info)
114 b"""# example repository config (see 'hg help config' for more info)
115 [paths]
115 [paths]
116 # path aliases to other clones of this repo in URLs or filesystem paths
116 # path aliases to other clones of this repo in URLs or filesystem paths
117 # (see 'hg help config.paths' for more info)
117 # (see 'hg help config.paths' for more info)
118 #
118 #
119 # default = http://example.com/hg/example-repo
119 # default = http://example.com/hg/example-repo
120 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
120 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
121 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
121 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
122 # my-clone = /home/jdoe/jdoes-clone
122 # my-clone = /home/jdoe/jdoes-clone
123
123
124 [ui]
124 [ui]
125 # name and email (local to this repository, optional), e.g.
125 # name and email (local to this repository, optional), e.g.
126 # username = Jane Doe <jdoe@example.com>
126 # username = Jane Doe <jdoe@example.com>
127 """,
127 """,
128
128
129 'global':
129 'global':
130 b"""# example system-wide hg config (see 'hg help config' for more info)
130 b"""# example system-wide hg config (see 'hg help config' for more info)
131
131
132 [ui]
132 [ui]
133 # uncomment to disable color in command output
133 # uncomment to disable color in command output
134 # (see 'hg help color' for details)
134 # (see 'hg help color' for details)
135 # color = never
135 # color = never
136
136
137 # uncomment to disable command output pagination
137 # uncomment to disable command output pagination
138 # (see 'hg help pager' for details)
138 # (see 'hg help pager' for details)
139 # paginate = never
139 # paginate = never
140
140
141 [extensions]
141 [extensions]
142 # uncomment these lines to enable some popular extensions
142 # uncomment these lines to enable some popular extensions
143 # (see 'hg help extensions' for more info)
143 # (see 'hg help extensions' for more info)
144 #
144 #
145 # blackbox =
145 # blackbox =
146 # churn =
146 # churn =
147 """,
147 """,
148 }
148 }
149
149
150 def _maybestrurl(maybebytes):
150 def _maybestrurl(maybebytes):
151 return util.rapply(pycompat.strurl, maybebytes)
151 return util.rapply(pycompat.strurl, maybebytes)
152
152
153 def _maybebytesurl(maybestr):
153 def _maybebytesurl(maybestr):
154 return util.rapply(pycompat.bytesurl, maybestr)
154 return util.rapply(pycompat.bytesurl, maybestr)
155
155
156 class httppasswordmgrdbproxy(object):
156 class httppasswordmgrdbproxy(object):
157 """Delays loading urllib2 until it's needed."""
157 """Delays loading urllib2 until it's needed."""
158 def __init__(self):
158 def __init__(self):
159 self._mgr = None
159 self._mgr = None
160
160
161 def _get_mgr(self):
161 def _get_mgr(self):
162 if self._mgr is None:
162 if self._mgr is None:
163 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
163 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
164 return self._mgr
164 return self._mgr
165
165
166 def add_password(self, realm, uris, user, passwd):
166 def add_password(self, realm, uris, user, passwd):
167 return self._get_mgr().add_password(
167 return self._get_mgr().add_password(
168 _maybestrurl(realm), _maybestrurl(uris),
168 _maybestrurl(realm), _maybestrurl(uris),
169 _maybestrurl(user), _maybestrurl(passwd))
169 _maybestrurl(user), _maybestrurl(passwd))
170
170
171 def find_user_password(self, realm, uri):
171 def find_user_password(self, realm, uri):
172 mgr = self._get_mgr()
172 mgr = self._get_mgr()
173 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
173 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
174 _maybestrurl(uri)))
174 _maybestrurl(uri)))
175
175
176 def _catchterm(*args):
176 def _catchterm(*args):
177 raise error.SignalInterrupt
177 raise error.SignalInterrupt
178
178
179 # unique object used to detect no default value has been provided when
179 # unique object used to detect no default value has been provided when
180 # retrieving configuration value.
180 # retrieving configuration value.
181 _unset = object()
181 _unset = object()
182
182
183 # _reqexithandlers: callbacks run at the end of a request
183 # _reqexithandlers: callbacks run at the end of a request
184 _reqexithandlers = []
184 _reqexithandlers = []
185
185
186 class ui(object):
186 class ui(object):
187 def __init__(self, src=None):
187 def __init__(self, src=None):
188 """Create a fresh new ui object if no src given
188 """Create a fresh new ui object if no src given
189
189
190 Use uimod.ui.load() to create a ui which knows global and user configs.
190 Use uimod.ui.load() to create a ui which knows global and user configs.
191 In most cases, you should use ui.copy() to create a copy of an existing
191 In most cases, you should use ui.copy() to create a copy of an existing
192 ui object.
192 ui object.
193 """
193 """
194 # _buffers: used for temporary capture of output
194 # _buffers: used for temporary capture of output
195 self._buffers = []
195 self._buffers = []
196 # 3-tuple describing how each buffer in the stack behaves.
196 # 3-tuple describing how each buffer in the stack behaves.
197 # Values are (capture stderr, capture subprocesses, apply labels).
197 # Values are (capture stderr, capture subprocesses, apply labels).
198 self._bufferstates = []
198 self._bufferstates = []
199 # When a buffer is active, defines whether we are expanding labels.
199 # When a buffer is active, defines whether we are expanding labels.
200 # This exists to prevent an extra list lookup.
200 # This exists to prevent an extra list lookup.
201 self._bufferapplylabels = None
201 self._bufferapplylabels = None
202 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
202 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
203 self._reportuntrusted = True
203 self._reportuntrusted = True
204 self._knownconfig = configitems.coreitems
204 self._knownconfig = configitems.coreitems
205 self._ocfg = config.config() # overlay
205 self._ocfg = config.config() # overlay
206 self._tcfg = config.config() # trusted
206 self._tcfg = config.config() # trusted
207 self._ucfg = config.config() # untrusted
207 self._ucfg = config.config() # untrusted
208 self._trustusers = set()
208 self._trustusers = set()
209 self._trustgroups = set()
209 self._trustgroups = set()
210 self.callhooks = True
210 self.callhooks = True
211 # Insecure server connections requested.
211 # Insecure server connections requested.
212 self.insecureconnections = False
212 self.insecureconnections = False
213 # Blocked time
213 # Blocked time
214 self.logblockedtimes = False
214 self.logblockedtimes = False
215 # color mode: see mercurial/color.py for possible value
215 # color mode: see mercurial/color.py for possible value
216 self._colormode = None
216 self._colormode = None
217 self._terminfoparams = {}
217 self._terminfoparams = {}
218 self._styles = {}
218 self._styles = {}
219
219
220 if src:
220 if src:
221 self.fout = src.fout
221 self.fout = src.fout
222 self.ferr = src.ferr
222 self.ferr = src.ferr
223 self.fin = src.fin
223 self.fin = src.fin
224 self.pageractive = src.pageractive
224 self.pageractive = src.pageractive
225 self._disablepager = src._disablepager
225 self._disablepager = src._disablepager
226 self._tweaked = src._tweaked
226 self._tweaked = src._tweaked
227
227
228 self._tcfg = src._tcfg.copy()
228 self._tcfg = src._tcfg.copy()
229 self._ucfg = src._ucfg.copy()
229 self._ucfg = src._ucfg.copy()
230 self._ocfg = src._ocfg.copy()
230 self._ocfg = src._ocfg.copy()
231 self._trustusers = src._trustusers.copy()
231 self._trustusers = src._trustusers.copy()
232 self._trustgroups = src._trustgroups.copy()
232 self._trustgroups = src._trustgroups.copy()
233 self.environ = src.environ
233 self.environ = src.environ
234 self.callhooks = src.callhooks
234 self.callhooks = src.callhooks
235 self.insecureconnections = src.insecureconnections
235 self.insecureconnections = src.insecureconnections
236 self._colormode = src._colormode
236 self._colormode = src._colormode
237 self._terminfoparams = src._terminfoparams.copy()
237 self._terminfoparams = src._terminfoparams.copy()
238 self._styles = src._styles.copy()
238 self._styles = src._styles.copy()
239
239
240 self.fixconfig()
240 self.fixconfig()
241
241
242 self.httppasswordmgrdb = src.httppasswordmgrdb
242 self.httppasswordmgrdb = src.httppasswordmgrdb
243 self._blockedtimes = src._blockedtimes
243 self._blockedtimes = src._blockedtimes
244 else:
244 else:
245 self.fout = util.stdout
245 self.fout = util.stdout
246 self.ferr = util.stderr
246 self.ferr = util.stderr
247 self.fin = util.stdin
247 self.fin = util.stdin
248 self.pageractive = False
248 self.pageractive = False
249 self._disablepager = False
249 self._disablepager = False
250 self._tweaked = False
250 self._tweaked = False
251
251
252 # shared read-only environment
252 # shared read-only environment
253 self.environ = encoding.environ
253 self.environ = encoding.environ
254
254
255 self.httppasswordmgrdb = httppasswordmgrdbproxy()
255 self.httppasswordmgrdb = httppasswordmgrdbproxy()
256 self._blockedtimes = collections.defaultdict(int)
256 self._blockedtimes = collections.defaultdict(int)
257
257
258 allowed = self.configlist('experimental', 'exportableenviron')
258 allowed = self.configlist('experimental', 'exportableenviron')
259 if '*' in allowed:
259 if '*' in allowed:
260 self._exportableenviron = self.environ
260 self._exportableenviron = self.environ
261 else:
261 else:
262 self._exportableenviron = {}
262 self._exportableenviron = {}
263 for k in allowed:
263 for k in allowed:
264 if k in self.environ:
264 if k in self.environ:
265 self._exportableenviron[k] = self.environ[k]
265 self._exportableenviron[k] = self.environ[k]
266
266
267 @classmethod
267 @classmethod
268 def load(cls):
268 def load(cls):
269 """Create a ui and load global and user configs"""
269 """Create a ui and load global and user configs"""
270 u = cls()
270 u = cls()
271 # we always trust global config files and environment variables
271 # we always trust global config files and environment variables
272 for t, f in rcutil.rccomponents():
272 for t, f in rcutil.rccomponents():
273 if t == 'path':
273 if t == 'path':
274 u.readconfig(f, trust=True)
274 u.readconfig(f, trust=True)
275 elif t == 'items':
275 elif t == 'items':
276 sections = set()
276 sections = set()
277 for section, name, value, source in f:
277 for section, name, value, source in f:
278 # do not set u._ocfg
278 # do not set u._ocfg
279 # XXX clean this up once immutable config object is a thing
279 # XXX clean this up once immutable config object is a thing
280 u._tcfg.set(section, name, value, source)
280 u._tcfg.set(section, name, value, source)
281 u._ucfg.set(section, name, value, source)
281 u._ucfg.set(section, name, value, source)
282 sections.add(section)
282 sections.add(section)
283 for section in sections:
283 for section in sections:
284 u.fixconfig(section=section)
284 u.fixconfig(section=section)
285 else:
285 else:
286 raise error.ProgrammingError('unknown rctype: %s' % t)
286 raise error.ProgrammingError('unknown rctype: %s' % t)
287 u._maybetweakdefaults()
287 u._maybetweakdefaults()
288 return u
288 return u
289
289
290 def _maybetweakdefaults(self):
290 def _maybetweakdefaults(self):
291 if not self.configbool('ui', 'tweakdefaults'):
291 if not self.configbool('ui', 'tweakdefaults'):
292 return
292 return
293 if self._tweaked or self.plain('tweakdefaults'):
293 if self._tweaked or self.plain('tweakdefaults'):
294 return
294 return
295
295
296 # Note: it is SUPER IMPORTANT that you set self._tweaked to
296 # Note: it is SUPER IMPORTANT that you set self._tweaked to
297 # True *before* any calls to setconfig(), otherwise you'll get
297 # True *before* any calls to setconfig(), otherwise you'll get
298 # infinite recursion between setconfig and this method.
298 # infinite recursion between setconfig and this method.
299 #
299 #
300 # TODO: We should extract an inner method in setconfig() to
300 # TODO: We should extract an inner method in setconfig() to
301 # avoid this weirdness.
301 # avoid this weirdness.
302 self._tweaked = True
302 self._tweaked = True
303 tmpcfg = config.config()
303 tmpcfg = config.config()
304 tmpcfg.parse('<tweakdefaults>', tweakrc)
304 tmpcfg.parse('<tweakdefaults>', tweakrc)
305 for section in tmpcfg:
305 for section in tmpcfg:
306 for name, value in tmpcfg.items(section):
306 for name, value in tmpcfg.items(section):
307 if not self.hasconfig(section, name):
307 if not self.hasconfig(section, name):
308 self.setconfig(section, name, value, "<tweakdefaults>")
308 self.setconfig(section, name, value, "<tweakdefaults>")
309
309
310 def copy(self):
310 def copy(self):
311 return self.__class__(self)
311 return self.__class__(self)
312
312
313 def resetstate(self):
313 def resetstate(self):
314 """Clear internal state that shouldn't persist across commands"""
314 """Clear internal state that shouldn't persist across commands"""
315 if self._progbar:
315 if self._progbar:
316 self._progbar.resetstate() # reset last-print time of progress bar
316 self._progbar.resetstate() # reset last-print time of progress bar
317 self.httppasswordmgrdb = httppasswordmgrdbproxy()
317 self.httppasswordmgrdb = httppasswordmgrdbproxy()
318
318
319 @contextlib.contextmanager
319 @contextlib.contextmanager
320 def timeblockedsection(self, key):
320 def timeblockedsection(self, key):
321 # this is open-coded below - search for timeblockedsection to find them
321 # this is open-coded below - search for timeblockedsection to find them
322 starttime = util.timer()
322 starttime = util.timer()
323 try:
323 try:
324 yield
324 yield
325 finally:
325 finally:
326 self._blockedtimes[key + '_blocked'] += \
326 self._blockedtimes[key + '_blocked'] += \
327 (util.timer() - starttime) * 1000
327 (util.timer() - starttime) * 1000
328
328
329 def formatter(self, topic, opts):
329 def formatter(self, topic, opts):
330 return formatter.formatter(self, self, topic, opts)
330 return formatter.formatter(self, self, topic, opts)
331
331
332 def _trusted(self, fp, f):
332 def _trusted(self, fp, f):
333 st = util.fstat(fp)
333 st = util.fstat(fp)
334 if util.isowner(st):
334 if util.isowner(st):
335 return True
335 return True
336
336
337 tusers, tgroups = self._trustusers, self._trustgroups
337 tusers, tgroups = self._trustusers, self._trustgroups
338 if '*' in tusers or '*' in tgroups:
338 if '*' in tusers or '*' in tgroups:
339 return True
339 return True
340
340
341 user = util.username(st.st_uid)
341 user = util.username(st.st_uid)
342 group = util.groupname(st.st_gid)
342 group = util.groupname(st.st_gid)
343 if user in tusers or group in tgroups or user == util.username():
343 if user in tusers or group in tgroups or user == util.username():
344 return True
344 return True
345
345
346 if self._reportuntrusted:
346 if self._reportuntrusted:
347 self.warn(_('not trusting file %s from untrusted '
347 self.warn(_('not trusting file %s from untrusted '
348 'user %s, group %s\n') % (f, user, group))
348 'user %s, group %s\n') % (f, user, group))
349 return False
349 return False
350
350
351 def readconfig(self, filename, root=None, trust=False,
351 def readconfig(self, filename, root=None, trust=False,
352 sections=None, remap=None):
352 sections=None, remap=None):
353 try:
353 try:
354 fp = open(filename, u'rb')
354 fp = open(filename, u'rb')
355 except IOError:
355 except IOError:
356 if not sections: # ignore unless we were looking for something
356 if not sections: # ignore unless we were looking for something
357 return
357 return
358 raise
358 raise
359
359
360 cfg = config.config()
360 cfg = config.config()
361 trusted = sections or trust or self._trusted(fp, filename)
361 trusted = sections or trust or self._trusted(fp, filename)
362
362
363 try:
363 try:
364 cfg.read(filename, fp, sections=sections, remap=remap)
364 cfg.read(filename, fp, sections=sections, remap=remap)
365 fp.close()
365 fp.close()
366 except error.ConfigError as inst:
366 except error.ConfigError as inst:
367 if trusted:
367 if trusted:
368 raise
368 raise
369 self.warn(_("ignored: %s\n") % str(inst))
369 self.warn(_("ignored: %s\n") % str(inst))
370
370
371 if self.plain():
371 if self.plain():
372 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
372 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
373 'logtemplate', 'statuscopies', 'style',
373 'logtemplate', 'statuscopies', 'style',
374 'traceback', 'verbose'):
374 'traceback', 'verbose'):
375 if k in cfg['ui']:
375 if k in cfg['ui']:
376 del cfg['ui'][k]
376 del cfg['ui'][k]
377 for k, v in cfg.items('defaults'):
377 for k, v in cfg.items('defaults'):
378 del cfg['defaults'][k]
378 del cfg['defaults'][k]
379 for k, v in cfg.items('commands'):
379 for k, v in cfg.items('commands'):
380 del cfg['commands'][k]
380 del cfg['commands'][k]
381 # Don't remove aliases from the configuration if in the exceptionlist
381 # Don't remove aliases from the configuration if in the exceptionlist
382 if self.plain('alias'):
382 if self.plain('alias'):
383 for k, v in cfg.items('alias'):
383 for k, v in cfg.items('alias'):
384 del cfg['alias'][k]
384 del cfg['alias'][k]
385 if self.plain('revsetalias'):
385 if self.plain('revsetalias'):
386 for k, v in cfg.items('revsetalias'):
386 for k, v in cfg.items('revsetalias'):
387 del cfg['revsetalias'][k]
387 del cfg['revsetalias'][k]
388 if self.plain('templatealias'):
388 if self.plain('templatealias'):
389 for k, v in cfg.items('templatealias'):
389 for k, v in cfg.items('templatealias'):
390 del cfg['templatealias'][k]
390 del cfg['templatealias'][k]
391
391
392 if trusted:
392 if trusted:
393 self._tcfg.update(cfg)
393 self._tcfg.update(cfg)
394 self._tcfg.update(self._ocfg)
394 self._tcfg.update(self._ocfg)
395 self._ucfg.update(cfg)
395 self._ucfg.update(cfg)
396 self._ucfg.update(self._ocfg)
396 self._ucfg.update(self._ocfg)
397
397
398 if root is None:
398 if root is None:
399 root = os.path.expanduser('~')
399 root = os.path.expanduser('~')
400 self.fixconfig(root=root)
400 self.fixconfig(root=root)
401
401
402 def fixconfig(self, root=None, section=None):
402 def fixconfig(self, root=None, section=None):
403 if section in (None, 'paths'):
403 if section in (None, 'paths'):
404 # expand vars and ~
404 # expand vars and ~
405 # translate paths relative to root (or home) into absolute paths
405 # translate paths relative to root (or home) into absolute paths
406 root = root or pycompat.getcwd()
406 root = root or pycompat.getcwd()
407 for c in self._tcfg, self._ucfg, self._ocfg:
407 for c in self._tcfg, self._ucfg, self._ocfg:
408 for n, p in c.items('paths'):
408 for n, p in c.items('paths'):
409 # Ignore sub-options.
409 # Ignore sub-options.
410 if ':' in n:
410 if ':' in n:
411 continue
411 continue
412 if not p:
412 if not p:
413 continue
413 continue
414 if '%%' in p:
414 if '%%' in p:
415 s = self.configsource('paths', n) or 'none'
415 s = self.configsource('paths', n) or 'none'
416 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
416 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
417 % (n, p, s))
417 % (n, p, s))
418 p = p.replace('%%', '%')
418 p = p.replace('%%', '%')
419 p = util.expandpath(p)
419 p = util.expandpath(p)
420 if not util.hasscheme(p) and not os.path.isabs(p):
420 if not util.hasscheme(p) and not os.path.isabs(p):
421 p = os.path.normpath(os.path.join(root, p))
421 p = os.path.normpath(os.path.join(root, p))
422 c.set("paths", n, p)
422 c.set("paths", n, p)
423
423
424 if section in (None, 'ui'):
424 if section in (None, 'ui'):
425 # update ui options
425 # update ui options
426 self.debugflag = self.configbool('ui', 'debug')
426 self.debugflag = self.configbool('ui', 'debug')
427 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
427 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
428 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
428 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
429 if self.verbose and self.quiet:
429 if self.verbose and self.quiet:
430 self.quiet = self.verbose = False
430 self.quiet = self.verbose = False
431 self._reportuntrusted = self.debugflag or self.configbool("ui",
431 self._reportuntrusted = self.debugflag or self.configbool("ui",
432 "report_untrusted")
432 "report_untrusted")
433 self.tracebackflag = self.configbool('ui', 'traceback')
433 self.tracebackflag = self.configbool('ui', 'traceback')
434 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
434 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
435
435
436 if section in (None, 'trusted'):
436 if section in (None, 'trusted'):
437 # update trust information
437 # update trust information
438 self._trustusers.update(self.configlist('trusted', 'users'))
438 self._trustusers.update(self.configlist('trusted', 'users'))
439 self._trustgroups.update(self.configlist('trusted', 'groups'))
439 self._trustgroups.update(self.configlist('trusted', 'groups'))
440
440
441 def backupconfig(self, section, item):
441 def backupconfig(self, section, item):
442 return (self._ocfg.backup(section, item),
442 return (self._ocfg.backup(section, item),
443 self._tcfg.backup(section, item),
443 self._tcfg.backup(section, item),
444 self._ucfg.backup(section, item),)
444 self._ucfg.backup(section, item),)
445 def restoreconfig(self, data):
445 def restoreconfig(self, data):
446 self._ocfg.restore(data[0])
446 self._ocfg.restore(data[0])
447 self._tcfg.restore(data[1])
447 self._tcfg.restore(data[1])
448 self._ucfg.restore(data[2])
448 self._ucfg.restore(data[2])
449
449
450 def setconfig(self, section, name, value, source=''):
450 def setconfig(self, section, name, value, source=''):
451 for cfg in (self._ocfg, self._tcfg, self._ucfg):
451 for cfg in (self._ocfg, self._tcfg, self._ucfg):
452 cfg.set(section, name, value, source)
452 cfg.set(section, name, value, source)
453 self.fixconfig(section=section)
453 self.fixconfig(section=section)
454 self._maybetweakdefaults()
454 self._maybetweakdefaults()
455
455
456 def _data(self, untrusted):
456 def _data(self, untrusted):
457 return untrusted and self._ucfg or self._tcfg
457 return untrusted and self._ucfg or self._tcfg
458
458
459 def configsource(self, section, name, untrusted=False):
459 def configsource(self, section, name, untrusted=False):
460 return self._data(untrusted).source(section, name)
460 return self._data(untrusted).source(section, name)
461
461
462 def config(self, section, name, default=_unset, untrusted=False):
462 def config(self, section, name, default=_unset, untrusted=False):
463 """return the plain string version of a config"""
463 """return the plain string version of a config"""
464 value = self._config(section, name, default=default,
464 value = self._config(section, name, default=default,
465 untrusted=untrusted)
465 untrusted=untrusted)
466 if value is _unset:
466 if value is _unset:
467 return None
467 return None
468 return value
468 return value
469
469
470 def _config(self, section, name, default=_unset, untrusted=False):
470 def _config(self, section, name, default=_unset, untrusted=False):
471 value = itemdefault = default
471 value = itemdefault = default
472 item = self._knownconfig.get(section, {}).get(name)
472 item = self._knownconfig.get(section, {}).get(name)
473 alternates = [(section, name)]
473 alternates = [(section, name)]
474
474
475 if item is not None:
475 if item is not None:
476 alternates.extend(item.alias)
476 alternates.extend(item.alias)
477 if callable(item.default):
477 if callable(item.default):
478 itemdefault = item.default()
478 itemdefault = item.default()
479 else:
479 else:
480 itemdefault = item.default
480 itemdefault = item.default
481 else:
481 else:
482 msg = ("accessing unregistered config item: '%s.%s'")
482 msg = ("accessing unregistered config item: '%s.%s'")
483 msg %= (section, name)
483 msg %= (section, name)
484 self.develwarn(msg, 2, 'warn-config-unknown')
484 self.develwarn(msg, 2, 'warn-config-unknown')
485
485
486 if default is _unset:
486 if default is _unset:
487 if item is None:
487 if item is None:
488 value = default
488 value = default
489 elif item.default is configitems.dynamicdefault:
489 elif item.default is configitems.dynamicdefault:
490 value = None
490 value = None
491 msg = "config item requires an explicit default value: '%s.%s'"
491 msg = "config item requires an explicit default value: '%s.%s'"
492 msg %= (section, name)
492 msg %= (section, name)
493 self.develwarn(msg, 2, 'warn-config-default')
493 self.develwarn(msg, 2, 'warn-config-default')
494 else:
494 else:
495 value = itemdefault
495 value = itemdefault
496 elif (item is not None
496 elif (item is not None
497 and item.default is not configitems.dynamicdefault
497 and item.default is not configitems.dynamicdefault
498 and default != itemdefault):
498 and default != itemdefault):
499 msg = ("specifying a mismatched default value for a registered "
499 msg = ("specifying a mismatched default value for a registered "
500 "config item: '%s.%s' '%s'")
500 "config item: '%s.%s' '%s'")
501 msg %= (section, name, pycompat.bytestr(default))
501 msg %= (section, name, pycompat.bytestr(default))
502 self.develwarn(msg, 2, 'warn-config-default')
502 self.develwarn(msg, 2, 'warn-config-default')
503
503
504 for s, n in alternates:
504 for s, n in alternates:
505 candidate = self._data(untrusted).get(s, n, None)
505 candidate = self._data(untrusted).get(s, n, None)
506 if candidate is not None:
506 if candidate is not None:
507 value = candidate
507 value = candidate
508 section = s
508 section = s
509 name = n
509 name = n
510 break
510 break
511
511
512 if self.debugflag and not untrusted and self._reportuntrusted:
512 if self.debugflag and not untrusted and self._reportuntrusted:
513 for s, n in alternates:
513 for s, n in alternates:
514 uvalue = self._ucfg.get(s, n)
514 uvalue = self._ucfg.get(s, n)
515 if uvalue is not None and uvalue != value:
515 if uvalue is not None and uvalue != value:
516 self.debug("ignoring untrusted configuration option "
516 self.debug("ignoring untrusted configuration option "
517 "%s.%s = %s\n" % (s, n, uvalue))
517 "%s.%s = %s\n" % (s, n, uvalue))
518 return value
518 return value
519
519
520 def configsuboptions(self, section, name, default=_unset, untrusted=False):
520 def configsuboptions(self, section, name, default=_unset, untrusted=False):
521 """Get a config option and all sub-options.
521 """Get a config option and all sub-options.
522
522
523 Some config options have sub-options that are declared with the
523 Some config options have sub-options that are declared with the
524 format "key:opt = value". This method is used to return the main
524 format "key:opt = value". This method is used to return the main
525 option and all its declared sub-options.
525 option and all its declared sub-options.
526
526
527 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
527 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
528 is a dict of defined sub-options where keys and values are strings.
528 is a dict of defined sub-options where keys and values are strings.
529 """
529 """
530 main = self.config(section, name, default, untrusted=untrusted)
530 main = self.config(section, name, default, untrusted=untrusted)
531 data = self._data(untrusted)
531 data = self._data(untrusted)
532 sub = {}
532 sub = {}
533 prefix = '%s:' % name
533 prefix = '%s:' % name
534 for k, v in data.items(section):
534 for k, v in data.items(section):
535 if k.startswith(prefix):
535 if k.startswith(prefix):
536 sub[k[len(prefix):]] = v
536 sub[k[len(prefix):]] = v
537
537
538 if self.debugflag and not untrusted and self._reportuntrusted:
538 if self.debugflag and not untrusted and self._reportuntrusted:
539 for k, v in sub.items():
539 for k, v in sub.items():
540 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
540 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
541 if uvalue is not None and uvalue != v:
541 if uvalue is not None and uvalue != v:
542 self.debug('ignoring untrusted configuration option '
542 self.debug('ignoring untrusted configuration option '
543 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
543 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
544
544
545 return main, sub
545 return main, sub
546
546
547 def configpath(self, section, name, default=_unset, untrusted=False):
547 def configpath(self, section, name, default=_unset, untrusted=False):
548 'get a path config item, expanded relative to repo root or config file'
548 'get a path config item, expanded relative to repo root or config file'
549 v = self.config(section, name, default, untrusted)
549 v = self.config(section, name, default, untrusted)
550 if v is None:
550 if v is None:
551 return None
551 return None
552 if not os.path.isabs(v) or "://" not in v:
552 if not os.path.isabs(v) or "://" not in v:
553 src = self.configsource(section, name, untrusted)
553 src = self.configsource(section, name, untrusted)
554 if ':' in src:
554 if ':' in src:
555 base = os.path.dirname(src.rsplit(':')[0])
555 base = os.path.dirname(src.rsplit(':')[0])
556 v = os.path.join(base, os.path.expanduser(v))
556 v = os.path.join(base, os.path.expanduser(v))
557 return v
557 return v
558
558
559 def configbool(self, section, name, default=_unset, untrusted=False):
559 def configbool(self, section, name, default=_unset, untrusted=False):
560 """parse a configuration element as a boolean
560 """parse a configuration element as a boolean
561
561
562 >>> u = ui(); s = b'foo'
562 >>> u = ui(); s = b'foo'
563 >>> u.setconfig(s, b'true', b'yes')
563 >>> u.setconfig(s, b'true', b'yes')
564 >>> u.configbool(s, b'true')
564 >>> u.configbool(s, b'true')
565 True
565 True
566 >>> u.setconfig(s, b'false', b'no')
566 >>> u.setconfig(s, b'false', b'no')
567 >>> u.configbool(s, b'false')
567 >>> u.configbool(s, b'false')
568 False
568 False
569 >>> u.configbool(s, b'unknown')
569 >>> u.configbool(s, b'unknown')
570 False
570 False
571 >>> u.configbool(s, b'unknown', True)
571 >>> u.configbool(s, b'unknown', True)
572 True
572 True
573 >>> u.setconfig(s, b'invalid', b'somevalue')
573 >>> u.setconfig(s, b'invalid', b'somevalue')
574 >>> u.configbool(s, b'invalid')
574 >>> u.configbool(s, b'invalid')
575 Traceback (most recent call last):
575 Traceback (most recent call last):
576 ...
576 ...
577 ConfigError: foo.invalid is not a boolean ('somevalue')
577 ConfigError: foo.invalid is not a boolean ('somevalue')
578 """
578 """
579
579
580 v = self._config(section, name, default, untrusted=untrusted)
580 v = self._config(section, name, default, untrusted=untrusted)
581 if v is None:
581 if v is None:
582 return v
582 return v
583 if v is _unset:
583 if v is _unset:
584 if default is _unset:
584 if default is _unset:
585 return False
585 return False
586 return default
586 return default
587 if isinstance(v, bool):
587 if isinstance(v, bool):
588 return v
588 return v
589 b = util.parsebool(v)
589 b = util.parsebool(v)
590 if b is None:
590 if b is None:
591 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
591 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
592 % (section, name, v))
592 % (section, name, v))
593 return b
593 return b
594
594
595 def configwith(self, convert, section, name, default=_unset,
595 def configwith(self, convert, section, name, default=_unset,
596 desc=None, untrusted=False):
596 desc=None, untrusted=False):
597 """parse a configuration element with a conversion function
597 """parse a configuration element with a conversion function
598
598
599 >>> u = ui(); s = b'foo'
599 >>> u = ui(); s = b'foo'
600 >>> u.setconfig(s, b'float1', b'42')
600 >>> u.setconfig(s, b'float1', b'42')
601 >>> u.configwith(float, s, b'float1')
601 >>> u.configwith(float, s, b'float1')
602 42.0
602 42.0
603 >>> u.setconfig(s, b'float2', b'-4.25')
603 >>> u.setconfig(s, b'float2', b'-4.25')
604 >>> u.configwith(float, s, b'float2')
604 >>> u.configwith(float, s, b'float2')
605 -4.25
605 -4.25
606 >>> u.configwith(float, s, b'unknown', 7)
606 >>> u.configwith(float, s, b'unknown', 7)
607 7.0
607 7.0
608 >>> u.setconfig(s, b'invalid', b'somevalue')
608 >>> u.setconfig(s, b'invalid', b'somevalue')
609 >>> u.configwith(float, s, b'invalid')
609 >>> u.configwith(float, s, b'invalid')
610 Traceback (most recent call last):
610 Traceback (most recent call last):
611 ...
611 ...
612 ConfigError: foo.invalid is not a valid float ('somevalue')
612 ConfigError: foo.invalid is not a valid float ('somevalue')
613 >>> u.configwith(float, s, b'invalid', desc=b'womble')
613 >>> u.configwith(float, s, b'invalid', desc=b'womble')
614 Traceback (most recent call last):
614 Traceback (most recent call last):
615 ...
615 ...
616 ConfigError: foo.invalid is not a valid womble ('somevalue')
616 ConfigError: foo.invalid is not a valid womble ('somevalue')
617 """
617 """
618
618
619 v = self.config(section, name, default, untrusted)
619 v = self.config(section, name, default, untrusted)
620 if v is None:
620 if v is None:
621 return v # do not attempt to convert None
621 return v # do not attempt to convert None
622 try:
622 try:
623 return convert(v)
623 return convert(v)
624 except (ValueError, error.ParseError):
624 except (ValueError, error.ParseError):
625 if desc is None:
625 if desc is None:
626 desc = pycompat.sysbytes(convert.__name__)
626 desc = pycompat.sysbytes(convert.__name__)
627 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
627 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
628 % (section, name, desc, v))
628 % (section, name, desc, v))
629
629
630 def configint(self, section, name, default=_unset, untrusted=False):
630 def configint(self, section, name, default=_unset, untrusted=False):
631 """parse a configuration element as an integer
631 """parse a configuration element as an integer
632
632
633 >>> u = ui(); s = b'foo'
633 >>> u = ui(); s = b'foo'
634 >>> u.setconfig(s, b'int1', b'42')
634 >>> u.setconfig(s, b'int1', b'42')
635 >>> u.configint(s, b'int1')
635 >>> u.configint(s, b'int1')
636 42
636 42
637 >>> u.setconfig(s, b'int2', b'-42')
637 >>> u.setconfig(s, b'int2', b'-42')
638 >>> u.configint(s, b'int2')
638 >>> u.configint(s, b'int2')
639 -42
639 -42
640 >>> u.configint(s, b'unknown', 7)
640 >>> u.configint(s, b'unknown', 7)
641 7
641 7
642 >>> u.setconfig(s, b'invalid', b'somevalue')
642 >>> u.setconfig(s, b'invalid', b'somevalue')
643 >>> u.configint(s, b'invalid')
643 >>> u.configint(s, b'invalid')
644 Traceback (most recent call last):
644 Traceback (most recent call last):
645 ...
645 ...
646 ConfigError: foo.invalid is not a valid integer ('somevalue')
646 ConfigError: foo.invalid is not a valid integer ('somevalue')
647 """
647 """
648
648
649 return self.configwith(int, section, name, default, 'integer',
649 return self.configwith(int, section, name, default, 'integer',
650 untrusted)
650 untrusted)
651
651
652 def configbytes(self, section, name, default=_unset, untrusted=False):
652 def configbytes(self, section, name, default=_unset, untrusted=False):
653 """parse a configuration element as a quantity in bytes
653 """parse a configuration element as a quantity in bytes
654
654
655 Units can be specified as b (bytes), k or kb (kilobytes), m or
655 Units can be specified as b (bytes), k or kb (kilobytes), m or
656 mb (megabytes), g or gb (gigabytes).
656 mb (megabytes), g or gb (gigabytes).
657
657
658 >>> u = ui(); s = b'foo'
658 >>> u = ui(); s = b'foo'
659 >>> u.setconfig(s, b'val1', b'42')
659 >>> u.setconfig(s, b'val1', b'42')
660 >>> u.configbytes(s, b'val1')
660 >>> u.configbytes(s, b'val1')
661 42
661 42
662 >>> u.setconfig(s, b'val2', b'42.5 kb')
662 >>> u.setconfig(s, b'val2', b'42.5 kb')
663 >>> u.configbytes(s, b'val2')
663 >>> u.configbytes(s, b'val2')
664 43520
664 43520
665 >>> u.configbytes(s, b'unknown', b'7 MB')
665 >>> u.configbytes(s, b'unknown', b'7 MB')
666 7340032
666 7340032
667 >>> u.setconfig(s, b'invalid', b'somevalue')
667 >>> u.setconfig(s, b'invalid', b'somevalue')
668 >>> u.configbytes(s, b'invalid')
668 >>> u.configbytes(s, b'invalid')
669 Traceback (most recent call last):
669 Traceback (most recent call last):
670 ...
670 ...
671 ConfigError: foo.invalid is not a byte quantity ('somevalue')
671 ConfigError: foo.invalid is not a byte quantity ('somevalue')
672 """
672 """
673
673
674 value = self._config(section, name, default, untrusted)
674 value = self._config(section, name, default, untrusted)
675 if value is _unset:
675 if value is _unset:
676 if default is _unset:
676 if default is _unset:
677 default = 0
677 default = 0
678 value = default
678 value = default
679 if not isinstance(value, bytes):
679 if not isinstance(value, bytes):
680 return value
680 return value
681 try:
681 try:
682 return util.sizetoint(value)
682 return util.sizetoint(value)
683 except error.ParseError:
683 except error.ParseError:
684 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
684 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
685 % (section, name, value))
685 % (section, name, value))
686
686
687 def configlist(self, section, name, default=_unset, untrusted=False):
687 def configlist(self, section, name, default=_unset, untrusted=False):
688 """parse a configuration element as a list of comma/space separated
688 """parse a configuration element as a list of comma/space separated
689 strings
689 strings
690
690
691 >>> u = ui(); s = b'foo'
691 >>> u = ui(); s = b'foo'
692 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
692 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
693 >>> u.configlist(s, b'list1')
693 >>> u.configlist(s, b'list1')
694 ['this', 'is', 'a small', 'test']
694 ['this', 'is', 'a small', 'test']
695 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
695 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
696 >>> u.configlist(s, b'list2')
696 >>> u.configlist(s, b'list2')
697 ['this', 'is', 'a small', 'test']
697 ['this', 'is', 'a small', 'test']
698 """
698 """
699 # default is not always a list
699 # default is not always a list
700 v = self.configwith(config.parselist, section, name, default,
700 v = self.configwith(config.parselist, section, name, default,
701 'list', untrusted)
701 'list', untrusted)
702 if isinstance(v, bytes):
702 if isinstance(v, bytes):
703 return config.parselist(v)
703 return config.parselist(v)
704 elif v is None:
704 elif v is None:
705 return []
705 return []
706 return v
706 return v
707
707
708 def configdate(self, section, name, default=_unset, untrusted=False):
708 def configdate(self, section, name, default=_unset, untrusted=False):
709 """parse a configuration element as a tuple of ints
709 """parse a configuration element as a tuple of ints
710
710
711 >>> u = ui(); s = b'foo'
711 >>> u = ui(); s = b'foo'
712 >>> u.setconfig(s, b'date', b'0 0')
712 >>> u.setconfig(s, b'date', b'0 0')
713 >>> u.configdate(s, b'date')
713 >>> u.configdate(s, b'date')
714 (0, 0)
714 (0, 0)
715 """
715 """
716 if self.config(section, name, default, untrusted):
716 if self.config(section, name, default, untrusted):
717 return self.configwith(util.parsedate, section, name, default,
717 return self.configwith(util.parsedate, section, name, default,
718 'date', untrusted)
718 'date', untrusted)
719 if default is _unset:
719 if default is _unset:
720 return None
720 return None
721 return default
721 return default
722
722
723 def hasconfig(self, section, name, untrusted=False):
723 def hasconfig(self, section, name, untrusted=False):
724 return self._data(untrusted).hasitem(section, name)
724 return self._data(untrusted).hasitem(section, name)
725
725
726 def has_section(self, section, untrusted=False):
726 def has_section(self, section, untrusted=False):
727 '''tell whether section exists in config.'''
727 '''tell whether section exists in config.'''
728 return section in self._data(untrusted)
728 return section in self._data(untrusted)
729
729
730 def configitems(self, section, untrusted=False, ignoresub=False):
730 def configitems(self, section, untrusted=False, ignoresub=False):
731 items = self._data(untrusted).items(section)
731 items = self._data(untrusted).items(section)
732 if ignoresub:
732 if ignoresub:
733 newitems = {}
733 newitems = {}
734 for k, v in items:
734 for k, v in items:
735 if ':' not in k:
735 if ':' not in k:
736 newitems[k] = v
736 newitems[k] = v
737 items = newitems.items()
737 items = newitems.items()
738 if self.debugflag and not untrusted and self._reportuntrusted:
738 if self.debugflag and not untrusted and self._reportuntrusted:
739 for k, v in self._ucfg.items(section):
739 for k, v in self._ucfg.items(section):
740 if self._tcfg.get(section, k) != v:
740 if self._tcfg.get(section, k) != v:
741 self.debug("ignoring untrusted configuration option "
741 self.debug("ignoring untrusted configuration option "
742 "%s.%s = %s\n" % (section, k, v))
742 "%s.%s = %s\n" % (section, k, v))
743 return items
743 return items
744
744
745 def walkconfig(self, untrusted=False):
745 def walkconfig(self, untrusted=False):
746 cfg = self._data(untrusted)
746 cfg = self._data(untrusted)
747 for section in cfg.sections():
747 for section in cfg.sections():
748 for name, value in self.configitems(section, untrusted):
748 for name, value in self.configitems(section, untrusted):
749 yield section, name, value
749 yield section, name, value
750
750
751 def plain(self, feature=None):
751 def plain(self, feature=None):
752 '''is plain mode active?
752 '''is plain mode active?
753
753
754 Plain mode means that all configuration variables which affect
754 Plain mode means that all configuration variables which affect
755 the behavior and output of Mercurial should be
755 the behavior and output of Mercurial should be
756 ignored. Additionally, the output should be stable,
756 ignored. Additionally, the output should be stable,
757 reproducible and suitable for use in scripts or applications.
757 reproducible and suitable for use in scripts or applications.
758
758
759 The only way to trigger plain mode is by setting either the
759 The only way to trigger plain mode is by setting either the
760 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
760 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
761
761
762 The return value can either be
762 The return value can either be
763 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
763 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
764 - False if feature is disabled by default and not included in HGPLAIN
764 - False if feature is disabled by default and not included in HGPLAIN
765 - True otherwise
765 - True otherwise
766 '''
766 '''
767 if ('HGPLAIN' not in encoding.environ and
767 if ('HGPLAIN' not in encoding.environ and
768 'HGPLAINEXCEPT' not in encoding.environ):
768 'HGPLAINEXCEPT' not in encoding.environ):
769 return False
769 return False
770 exceptions = encoding.environ.get('HGPLAINEXCEPT',
770 exceptions = encoding.environ.get('HGPLAINEXCEPT',
771 '').strip().split(',')
771 '').strip().split(',')
772 # TODO: add support for HGPLAIN=+feature,-feature syntax
772 # TODO: add support for HGPLAIN=+feature,-feature syntax
773 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
773 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
774 exceptions.append('strictflags')
774 exceptions.append('strictflags')
775 if feature and exceptions:
775 if feature and exceptions:
776 return feature not in exceptions
776 return feature not in exceptions
777 return True
777 return True
778
778
779 def username(self, acceptempty=False):
779 def username(self, acceptempty=False):
780 """Return default username to be used in commits.
780 """Return default username to be used in commits.
781
781
782 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
782 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
783 and stop searching if one of these is set.
783 and stop searching if one of these is set.
784 If not found and acceptempty is True, returns None.
784 If not found and acceptempty is True, returns None.
785 If not found and ui.askusername is True, ask the user, else use
785 If not found and ui.askusername is True, ask the user, else use
786 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
786 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
787 If no username could be found, raise an Abort error.
787 If no username could be found, raise an Abort error.
788 """
788 """
789 user = encoding.environ.get("HGUSER")
789 user = encoding.environ.get("HGUSER")
790 if user is None:
790 if user is None:
791 user = self.config("ui", "username")
791 user = self.config("ui", "username")
792 if user is not None:
792 if user is not None:
793 user = os.path.expandvars(user)
793 user = os.path.expandvars(user)
794 if user is None:
794 if user is None:
795 user = encoding.environ.get("EMAIL")
795 user = encoding.environ.get("EMAIL")
796 if user is None and acceptempty:
796 if user is None and acceptempty:
797 return user
797 return user
798 if user is None and self.configbool("ui", "askusername"):
798 if user is None and self.configbool("ui", "askusername"):
799 user = self.prompt(_("enter a commit username:"), default=None)
799 user = self.prompt(_("enter a commit username:"), default=None)
800 if user is None and not self.interactive():
800 if user is None and not self.interactive():
801 try:
801 try:
802 user = '%s@%s' % (util.getuser(), socket.getfqdn())
802 user = '%s@%s' % (util.getuser(), socket.getfqdn())
803 self.warn(_("no username found, using '%s' instead\n") % user)
803 self.warn(_("no username found, using '%s' instead\n") % user)
804 except KeyError:
804 except KeyError:
805 pass
805 pass
806 if not user:
806 if not user:
807 raise error.Abort(_('no username supplied'),
807 raise error.Abort(_('no username supplied'),
808 hint=_("use 'hg config --edit' "
808 hint=_("use 'hg config --edit' "
809 'to set your username'))
809 'to set your username'))
810 if "\n" in user:
810 if "\n" in user:
811 raise error.Abort(_("username %s contains a newline\n")
811 raise error.Abort(_("username %r contains a newline\n")
812 % repr(user))
812 % pycompat.bytestr(user))
813 return user
813 return user
814
814
815 def shortuser(self, user):
815 def shortuser(self, user):
816 """Return a short representation of a user name or email address."""
816 """Return a short representation of a user name or email address."""
817 if not self.verbose:
817 if not self.verbose:
818 user = util.shortuser(user)
818 user = util.shortuser(user)
819 return user
819 return user
820
820
821 def expandpath(self, loc, default=None):
821 def expandpath(self, loc, default=None):
822 """Return repository location relative to cwd or from [paths]"""
822 """Return repository location relative to cwd or from [paths]"""
823 try:
823 try:
824 p = self.paths.getpath(loc)
824 p = self.paths.getpath(loc)
825 if p:
825 if p:
826 return p.rawloc
826 return p.rawloc
827 except error.RepoError:
827 except error.RepoError:
828 pass
828 pass
829
829
830 if default:
830 if default:
831 try:
831 try:
832 p = self.paths.getpath(default)
832 p = self.paths.getpath(default)
833 if p:
833 if p:
834 return p.rawloc
834 return p.rawloc
835 except error.RepoError:
835 except error.RepoError:
836 pass
836 pass
837
837
838 return loc
838 return loc
839
839
840 @util.propertycache
840 @util.propertycache
841 def paths(self):
841 def paths(self):
842 return paths(self)
842 return paths(self)
843
843
844 def pushbuffer(self, error=False, subproc=False, labeled=False):
844 def pushbuffer(self, error=False, subproc=False, labeled=False):
845 """install a buffer to capture standard output of the ui object
845 """install a buffer to capture standard output of the ui object
846
846
847 If error is True, the error output will be captured too.
847 If error is True, the error output will be captured too.
848
848
849 If subproc is True, output from subprocesses (typically hooks) will be
849 If subproc is True, output from subprocesses (typically hooks) will be
850 captured too.
850 captured too.
851
851
852 If labeled is True, any labels associated with buffered
852 If labeled is True, any labels associated with buffered
853 output will be handled. By default, this has no effect
853 output will be handled. By default, this has no effect
854 on the output returned, but extensions and GUI tools may
854 on the output returned, but extensions and GUI tools may
855 handle this argument and returned styled output. If output
855 handle this argument and returned styled output. If output
856 is being buffered so it can be captured and parsed or
856 is being buffered so it can be captured and parsed or
857 processed, labeled should not be set to True.
857 processed, labeled should not be set to True.
858 """
858 """
859 self._buffers.append([])
859 self._buffers.append([])
860 self._bufferstates.append((error, subproc, labeled))
860 self._bufferstates.append((error, subproc, labeled))
861 self._bufferapplylabels = labeled
861 self._bufferapplylabels = labeled
862
862
863 def popbuffer(self):
863 def popbuffer(self):
864 '''pop the last buffer and return the buffered output'''
864 '''pop the last buffer and return the buffered output'''
865 self._bufferstates.pop()
865 self._bufferstates.pop()
866 if self._bufferstates:
866 if self._bufferstates:
867 self._bufferapplylabels = self._bufferstates[-1][2]
867 self._bufferapplylabels = self._bufferstates[-1][2]
868 else:
868 else:
869 self._bufferapplylabels = None
869 self._bufferapplylabels = None
870
870
871 return "".join(self._buffers.pop())
871 return "".join(self._buffers.pop())
872
872
873 def canwritewithoutlabels(self):
873 def canwritewithoutlabels(self):
874 '''check if write skips the label'''
874 '''check if write skips the label'''
875 if self._buffers and not self._bufferapplylabels:
875 if self._buffers and not self._bufferapplylabels:
876 return True
876 return True
877 return self._colormode is None
877 return self._colormode is None
878
878
879 def canbatchlabeledwrites(self):
879 def canbatchlabeledwrites(self):
880 '''check if write calls with labels are batchable'''
880 '''check if write calls with labels are batchable'''
881 # Windows color printing is special, see ``write``.
881 # Windows color printing is special, see ``write``.
882 return self._colormode != 'win32'
882 return self._colormode != 'win32'
883
883
884 def write(self, *args, **opts):
884 def write(self, *args, **opts):
885 '''write args to output
885 '''write args to output
886
886
887 By default, this method simply writes to the buffer or stdout.
887 By default, this method simply writes to the buffer or stdout.
888 Color mode can be set on the UI class to have the output decorated
888 Color mode can be set on the UI class to have the output decorated
889 with color modifier before being written to stdout.
889 with color modifier before being written to stdout.
890
890
891 The color used is controlled by an optional keyword argument, "label".
891 The color used is controlled by an optional keyword argument, "label".
892 This should be a string containing label names separated by space.
892 This should be a string containing label names separated by space.
893 Label names take the form of "topic.type". For example, ui.debug()
893 Label names take the form of "topic.type". For example, ui.debug()
894 issues a label of "ui.debug".
894 issues a label of "ui.debug".
895
895
896 When labeling output for a specific command, a label of
896 When labeling output for a specific command, a label of
897 "cmdname.type" is recommended. For example, status issues
897 "cmdname.type" is recommended. For example, status issues
898 a label of "status.modified" for modified files.
898 a label of "status.modified" for modified files.
899 '''
899 '''
900 if self._buffers:
900 if self._buffers:
901 if self._bufferapplylabels:
901 if self._bufferapplylabels:
902 label = opts.get(r'label', '')
902 label = opts.get(r'label', '')
903 self._buffers[-1].extend(self.label(a, label) for a in args)
903 self._buffers[-1].extend(self.label(a, label) for a in args)
904 else:
904 else:
905 self._buffers[-1].extend(args)
905 self._buffers[-1].extend(args)
906 else:
906 else:
907 self._writenobuf(*args, **opts)
907 self._writenobuf(*args, **opts)
908
908
909 def _writenobuf(self, *args, **opts):
909 def _writenobuf(self, *args, **opts):
910 if self._colormode == 'win32':
910 if self._colormode == 'win32':
911 # windows color printing is its own can of crab, defer to
911 # windows color printing is its own can of crab, defer to
912 # the color module and that is it.
912 # the color module and that is it.
913 color.win32print(self, self._write, *args, **opts)
913 color.win32print(self, self._write, *args, **opts)
914 else:
914 else:
915 msgs = args
915 msgs = args
916 if self._colormode is not None:
916 if self._colormode is not None:
917 label = opts.get(r'label', '')
917 label = opts.get(r'label', '')
918 msgs = [self.label(a, label) for a in args]
918 msgs = [self.label(a, label) for a in args]
919 self._write(*msgs, **opts)
919 self._write(*msgs, **opts)
920
920
921 def _write(self, *msgs, **opts):
921 def _write(self, *msgs, **opts):
922 self._progclear()
922 self._progclear()
923 # opencode timeblockedsection because this is a critical path
923 # opencode timeblockedsection because this is a critical path
924 starttime = util.timer()
924 starttime = util.timer()
925 try:
925 try:
926 self.fout.write(''.join(msgs))
926 self.fout.write(''.join(msgs))
927 except IOError as err:
927 except IOError as err:
928 raise error.StdioError(err)
928 raise error.StdioError(err)
929 finally:
929 finally:
930 self._blockedtimes['stdio_blocked'] += \
930 self._blockedtimes['stdio_blocked'] += \
931 (util.timer() - starttime) * 1000
931 (util.timer() - starttime) * 1000
932
932
933 def write_err(self, *args, **opts):
933 def write_err(self, *args, **opts):
934 self._progclear()
934 self._progclear()
935 if self._bufferstates and self._bufferstates[-1][0]:
935 if self._bufferstates and self._bufferstates[-1][0]:
936 self.write(*args, **opts)
936 self.write(*args, **opts)
937 elif self._colormode == 'win32':
937 elif self._colormode == 'win32':
938 # windows color printing is its own can of crab, defer to
938 # windows color printing is its own can of crab, defer to
939 # the color module and that is it.
939 # the color module and that is it.
940 color.win32print(self, self._write_err, *args, **opts)
940 color.win32print(self, self._write_err, *args, **opts)
941 else:
941 else:
942 msgs = args
942 msgs = args
943 if self._colormode is not None:
943 if self._colormode is not None:
944 label = opts.get(r'label', '')
944 label = opts.get(r'label', '')
945 msgs = [self.label(a, label) for a in args]
945 msgs = [self.label(a, label) for a in args]
946 self._write_err(*msgs, **opts)
946 self._write_err(*msgs, **opts)
947
947
948 def _write_err(self, *msgs, **opts):
948 def _write_err(self, *msgs, **opts):
949 try:
949 try:
950 with self.timeblockedsection('stdio'):
950 with self.timeblockedsection('stdio'):
951 if not getattr(self.fout, 'closed', False):
951 if not getattr(self.fout, 'closed', False):
952 self.fout.flush()
952 self.fout.flush()
953 for a in msgs:
953 for a in msgs:
954 self.ferr.write(a)
954 self.ferr.write(a)
955 # stderr may be buffered under win32 when redirected to files,
955 # stderr may be buffered under win32 when redirected to files,
956 # including stdout.
956 # including stdout.
957 if not getattr(self.ferr, 'closed', False):
957 if not getattr(self.ferr, 'closed', False):
958 self.ferr.flush()
958 self.ferr.flush()
959 except IOError as inst:
959 except IOError as inst:
960 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
960 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
961 raise error.StdioError(inst)
961 raise error.StdioError(inst)
962
962
963 def flush(self):
963 def flush(self):
964 # opencode timeblockedsection because this is a critical path
964 # opencode timeblockedsection because this is a critical path
965 starttime = util.timer()
965 starttime = util.timer()
966 try:
966 try:
967 try:
967 try:
968 self.fout.flush()
968 self.fout.flush()
969 except IOError as err:
969 except IOError as err:
970 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
970 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
971 raise error.StdioError(err)
971 raise error.StdioError(err)
972 finally:
972 finally:
973 try:
973 try:
974 self.ferr.flush()
974 self.ferr.flush()
975 except IOError as err:
975 except IOError as err:
976 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
976 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
977 raise error.StdioError(err)
977 raise error.StdioError(err)
978 finally:
978 finally:
979 self._blockedtimes['stdio_blocked'] += \
979 self._blockedtimes['stdio_blocked'] += \
980 (util.timer() - starttime) * 1000
980 (util.timer() - starttime) * 1000
981
981
982 def _isatty(self, fh):
982 def _isatty(self, fh):
983 if self.configbool('ui', 'nontty'):
983 if self.configbool('ui', 'nontty'):
984 return False
984 return False
985 return util.isatty(fh)
985 return util.isatty(fh)
986
986
987 def disablepager(self):
987 def disablepager(self):
988 self._disablepager = True
988 self._disablepager = True
989
989
990 def pager(self, command):
990 def pager(self, command):
991 """Start a pager for subsequent command output.
991 """Start a pager for subsequent command output.
992
992
993 Commands which produce a long stream of output should call
993 Commands which produce a long stream of output should call
994 this function to activate the user's preferred pagination
994 this function to activate the user's preferred pagination
995 mechanism (which may be no pager). Calling this function
995 mechanism (which may be no pager). Calling this function
996 precludes any future use of interactive functionality, such as
996 precludes any future use of interactive functionality, such as
997 prompting the user or activating curses.
997 prompting the user or activating curses.
998
998
999 Args:
999 Args:
1000 command: The full, non-aliased name of the command. That is, "log"
1000 command: The full, non-aliased name of the command. That is, "log"
1001 not "history, "summary" not "summ", etc.
1001 not "history, "summary" not "summ", etc.
1002 """
1002 """
1003 if (self._disablepager
1003 if (self._disablepager
1004 or self.pageractive):
1004 or self.pageractive):
1005 # how pager should do is already determined
1005 # how pager should do is already determined
1006 return
1006 return
1007
1007
1008 if not command.startswith('internal-always-') and (
1008 if not command.startswith('internal-always-') and (
1009 # explicit --pager=on (= 'internal-always-' prefix) should
1009 # explicit --pager=on (= 'internal-always-' prefix) should
1010 # take precedence over disabling factors below
1010 # take precedence over disabling factors below
1011 command in self.configlist('pager', 'ignore')
1011 command in self.configlist('pager', 'ignore')
1012 or not self.configbool('ui', 'paginate')
1012 or not self.configbool('ui', 'paginate')
1013 or not self.configbool('pager', 'attend-' + command, True)
1013 or not self.configbool('pager', 'attend-' + command, True)
1014 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1014 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1015 # formatted() will need some adjustment.
1015 # formatted() will need some adjustment.
1016 or not self.formatted()
1016 or not self.formatted()
1017 or self.plain()
1017 or self.plain()
1018 or self._buffers
1018 or self._buffers
1019 # TODO: expose debugger-enabled on the UI object
1019 # TODO: expose debugger-enabled on the UI object
1020 or '--debugger' in pycompat.sysargv):
1020 or '--debugger' in pycompat.sysargv):
1021 # We only want to paginate if the ui appears to be
1021 # We only want to paginate if the ui appears to be
1022 # interactive, the user didn't say HGPLAIN or
1022 # interactive, the user didn't say HGPLAIN or
1023 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1023 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1024 return
1024 return
1025
1025
1026 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1026 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1027 if not pagercmd:
1027 if not pagercmd:
1028 return
1028 return
1029
1029
1030 pagerenv = {}
1030 pagerenv = {}
1031 for name, value in rcutil.defaultpagerenv().items():
1031 for name, value in rcutil.defaultpagerenv().items():
1032 if name not in encoding.environ:
1032 if name not in encoding.environ:
1033 pagerenv[name] = value
1033 pagerenv[name] = value
1034
1034
1035 self.debug('starting pager for command %r\n' % command)
1035 self.debug('starting pager for command %r\n' % command)
1036 self.flush()
1036 self.flush()
1037
1037
1038 wasformatted = self.formatted()
1038 wasformatted = self.formatted()
1039 if util.safehasattr(signal, "SIGPIPE"):
1039 if util.safehasattr(signal, "SIGPIPE"):
1040 signal.signal(signal.SIGPIPE, _catchterm)
1040 signal.signal(signal.SIGPIPE, _catchterm)
1041 if self._runpager(pagercmd, pagerenv):
1041 if self._runpager(pagercmd, pagerenv):
1042 self.pageractive = True
1042 self.pageractive = True
1043 # Preserve the formatted-ness of the UI. This is important
1043 # Preserve the formatted-ness of the UI. This is important
1044 # because we mess with stdout, which might confuse
1044 # because we mess with stdout, which might confuse
1045 # auto-detection of things being formatted.
1045 # auto-detection of things being formatted.
1046 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1046 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1047 self.setconfig('ui', 'interactive', False, 'pager')
1047 self.setconfig('ui', 'interactive', False, 'pager')
1048
1048
1049 # If pagermode differs from color.mode, reconfigure color now that
1049 # If pagermode differs from color.mode, reconfigure color now that
1050 # pageractive is set.
1050 # pageractive is set.
1051 cm = self._colormode
1051 cm = self._colormode
1052 if cm != self.config('color', 'pagermode', cm):
1052 if cm != self.config('color', 'pagermode', cm):
1053 color.setup(self)
1053 color.setup(self)
1054 else:
1054 else:
1055 # If the pager can't be spawned in dispatch when --pager=on is
1055 # If the pager can't be spawned in dispatch when --pager=on is
1056 # given, don't try again when the command runs, to avoid a duplicate
1056 # given, don't try again when the command runs, to avoid a duplicate
1057 # warning about a missing pager command.
1057 # warning about a missing pager command.
1058 self.disablepager()
1058 self.disablepager()
1059
1059
1060 def _runpager(self, command, env=None):
1060 def _runpager(self, command, env=None):
1061 """Actually start the pager and set up file descriptors.
1061 """Actually start the pager and set up file descriptors.
1062
1062
1063 This is separate in part so that extensions (like chg) can
1063 This is separate in part so that extensions (like chg) can
1064 override how a pager is invoked.
1064 override how a pager is invoked.
1065 """
1065 """
1066 if command == 'cat':
1066 if command == 'cat':
1067 # Save ourselves some work.
1067 # Save ourselves some work.
1068 return False
1068 return False
1069 # If the command doesn't contain any of these characters, we
1069 # If the command doesn't contain any of these characters, we
1070 # assume it's a binary and exec it directly. This means for
1070 # assume it's a binary and exec it directly. This means for
1071 # simple pager command configurations, we can degrade
1071 # simple pager command configurations, we can degrade
1072 # gracefully and tell the user about their broken pager.
1072 # gracefully and tell the user about their broken pager.
1073 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1073 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1074
1074
1075 if pycompat.iswindows and not shell:
1075 if pycompat.iswindows and not shell:
1076 # Window's built-in `more` cannot be invoked with shell=False, but
1076 # Window's built-in `more` cannot be invoked with shell=False, but
1077 # its `more.com` can. Hide this implementation detail from the
1077 # its `more.com` can. Hide this implementation detail from the
1078 # user so we can also get sane bad PAGER behavior. MSYS has
1078 # user so we can also get sane bad PAGER behavior. MSYS has
1079 # `more.exe`, so do a cmd.exe style resolution of the executable to
1079 # `more.exe`, so do a cmd.exe style resolution of the executable to
1080 # determine which one to use.
1080 # determine which one to use.
1081 fullcmd = util.findexe(command)
1081 fullcmd = util.findexe(command)
1082 if not fullcmd:
1082 if not fullcmd:
1083 self.warn(_("missing pager command '%s', skipping pager\n")
1083 self.warn(_("missing pager command '%s', skipping pager\n")
1084 % command)
1084 % command)
1085 return False
1085 return False
1086
1086
1087 command = fullcmd
1087 command = fullcmd
1088
1088
1089 try:
1089 try:
1090 pager = subprocess.Popen(
1090 pager = subprocess.Popen(
1091 command, shell=shell, bufsize=-1,
1091 command, shell=shell, bufsize=-1,
1092 close_fds=util.closefds, stdin=subprocess.PIPE,
1092 close_fds=util.closefds, stdin=subprocess.PIPE,
1093 stdout=util.stdout, stderr=util.stderr,
1093 stdout=util.stdout, stderr=util.stderr,
1094 env=util.shellenviron(env))
1094 env=util.shellenviron(env))
1095 except OSError as e:
1095 except OSError as e:
1096 if e.errno == errno.ENOENT and not shell:
1096 if e.errno == errno.ENOENT and not shell:
1097 self.warn(_("missing pager command '%s', skipping pager\n")
1097 self.warn(_("missing pager command '%s', skipping pager\n")
1098 % command)
1098 % command)
1099 return False
1099 return False
1100 raise
1100 raise
1101
1101
1102 # back up original file descriptors
1102 # back up original file descriptors
1103 stdoutfd = os.dup(util.stdout.fileno())
1103 stdoutfd = os.dup(util.stdout.fileno())
1104 stderrfd = os.dup(util.stderr.fileno())
1104 stderrfd = os.dup(util.stderr.fileno())
1105
1105
1106 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1106 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1107 if self._isatty(util.stderr):
1107 if self._isatty(util.stderr):
1108 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1108 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1109
1109
1110 @self.atexit
1110 @self.atexit
1111 def killpager():
1111 def killpager():
1112 if util.safehasattr(signal, "SIGINT"):
1112 if util.safehasattr(signal, "SIGINT"):
1113 signal.signal(signal.SIGINT, signal.SIG_IGN)
1113 signal.signal(signal.SIGINT, signal.SIG_IGN)
1114 # restore original fds, closing pager.stdin copies in the process
1114 # restore original fds, closing pager.stdin copies in the process
1115 os.dup2(stdoutfd, util.stdout.fileno())
1115 os.dup2(stdoutfd, util.stdout.fileno())
1116 os.dup2(stderrfd, util.stderr.fileno())
1116 os.dup2(stderrfd, util.stderr.fileno())
1117 pager.stdin.close()
1117 pager.stdin.close()
1118 pager.wait()
1118 pager.wait()
1119
1119
1120 return True
1120 return True
1121
1121
1122 @property
1122 @property
1123 def _exithandlers(self):
1123 def _exithandlers(self):
1124 return _reqexithandlers
1124 return _reqexithandlers
1125
1125
1126 def atexit(self, func, *args, **kwargs):
1126 def atexit(self, func, *args, **kwargs):
1127 '''register a function to run after dispatching a request
1127 '''register a function to run after dispatching a request
1128
1128
1129 Handlers do not stay registered across request boundaries.'''
1129 Handlers do not stay registered across request boundaries.'''
1130 self._exithandlers.append((func, args, kwargs))
1130 self._exithandlers.append((func, args, kwargs))
1131 return func
1131 return func
1132
1132
1133 def interface(self, feature):
1133 def interface(self, feature):
1134 """what interface to use for interactive console features?
1134 """what interface to use for interactive console features?
1135
1135
1136 The interface is controlled by the value of `ui.interface` but also by
1136 The interface is controlled by the value of `ui.interface` but also by
1137 the value of feature-specific configuration. For example:
1137 the value of feature-specific configuration. For example:
1138
1138
1139 ui.interface.histedit = text
1139 ui.interface.histedit = text
1140 ui.interface.chunkselector = curses
1140 ui.interface.chunkselector = curses
1141
1141
1142 Here the features are "histedit" and "chunkselector".
1142 Here the features are "histedit" and "chunkselector".
1143
1143
1144 The configuration above means that the default interfaces for commands
1144 The configuration above means that the default interfaces for commands
1145 is curses, the interface for histedit is text and the interface for
1145 is curses, the interface for histedit is text and the interface for
1146 selecting chunk is crecord (the best curses interface available).
1146 selecting chunk is crecord (the best curses interface available).
1147
1147
1148 Consider the following example:
1148 Consider the following example:
1149 ui.interface = curses
1149 ui.interface = curses
1150 ui.interface.histedit = text
1150 ui.interface.histedit = text
1151
1151
1152 Then histedit will use the text interface and chunkselector will use
1152 Then histedit will use the text interface and chunkselector will use
1153 the default curses interface (crecord at the moment).
1153 the default curses interface (crecord at the moment).
1154 """
1154 """
1155 alldefaults = frozenset(["text", "curses"])
1155 alldefaults = frozenset(["text", "curses"])
1156
1156
1157 featureinterfaces = {
1157 featureinterfaces = {
1158 "chunkselector": [
1158 "chunkselector": [
1159 "text",
1159 "text",
1160 "curses",
1160 "curses",
1161 ]
1161 ]
1162 }
1162 }
1163
1163
1164 # Feature-specific interface
1164 # Feature-specific interface
1165 if feature not in featureinterfaces.keys():
1165 if feature not in featureinterfaces.keys():
1166 # Programming error, not user error
1166 # Programming error, not user error
1167 raise ValueError("Unknown feature requested %s" % feature)
1167 raise ValueError("Unknown feature requested %s" % feature)
1168
1168
1169 availableinterfaces = frozenset(featureinterfaces[feature])
1169 availableinterfaces = frozenset(featureinterfaces[feature])
1170 if alldefaults > availableinterfaces:
1170 if alldefaults > availableinterfaces:
1171 # Programming error, not user error. We need a use case to
1171 # Programming error, not user error. We need a use case to
1172 # define the right thing to do here.
1172 # define the right thing to do here.
1173 raise ValueError(
1173 raise ValueError(
1174 "Feature %s does not handle all default interfaces" %
1174 "Feature %s does not handle all default interfaces" %
1175 feature)
1175 feature)
1176
1176
1177 if self.plain():
1177 if self.plain():
1178 return "text"
1178 return "text"
1179
1179
1180 # Default interface for all the features
1180 # Default interface for all the features
1181 defaultinterface = "text"
1181 defaultinterface = "text"
1182 i = self.config("ui", "interface")
1182 i = self.config("ui", "interface")
1183 if i in alldefaults:
1183 if i in alldefaults:
1184 defaultinterface = i
1184 defaultinterface = i
1185
1185
1186 choseninterface = defaultinterface
1186 choseninterface = defaultinterface
1187 f = self.config("ui", "interface.%s" % feature)
1187 f = self.config("ui", "interface.%s" % feature)
1188 if f in availableinterfaces:
1188 if f in availableinterfaces:
1189 choseninterface = f
1189 choseninterface = f
1190
1190
1191 if i is not None and defaultinterface != i:
1191 if i is not None and defaultinterface != i:
1192 if f is not None:
1192 if f is not None:
1193 self.warn(_("invalid value for ui.interface: %s\n") %
1193 self.warn(_("invalid value for ui.interface: %s\n") %
1194 (i,))
1194 (i,))
1195 else:
1195 else:
1196 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1196 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1197 (i, choseninterface))
1197 (i, choseninterface))
1198 if f is not None and choseninterface != f:
1198 if f is not None and choseninterface != f:
1199 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1199 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1200 (feature, f, choseninterface))
1200 (feature, f, choseninterface))
1201
1201
1202 return choseninterface
1202 return choseninterface
1203
1203
1204 def interactive(self):
1204 def interactive(self):
1205 '''is interactive input allowed?
1205 '''is interactive input allowed?
1206
1206
1207 An interactive session is a session where input can be reasonably read
1207 An interactive session is a session where input can be reasonably read
1208 from `sys.stdin'. If this function returns false, any attempt to read
1208 from `sys.stdin'. If this function returns false, any attempt to read
1209 from stdin should fail with an error, unless a sensible default has been
1209 from stdin should fail with an error, unless a sensible default has been
1210 specified.
1210 specified.
1211
1211
1212 Interactiveness is triggered by the value of the `ui.interactive'
1212 Interactiveness is triggered by the value of the `ui.interactive'
1213 configuration variable or - if it is unset - when `sys.stdin' points
1213 configuration variable or - if it is unset - when `sys.stdin' points
1214 to a terminal device.
1214 to a terminal device.
1215
1215
1216 This function refers to input only; for output, see `ui.formatted()'.
1216 This function refers to input only; for output, see `ui.formatted()'.
1217 '''
1217 '''
1218 i = self.configbool("ui", "interactive")
1218 i = self.configbool("ui", "interactive")
1219 if i is None:
1219 if i is None:
1220 # some environments replace stdin without implementing isatty
1220 # some environments replace stdin without implementing isatty
1221 # usually those are non-interactive
1221 # usually those are non-interactive
1222 return self._isatty(self.fin)
1222 return self._isatty(self.fin)
1223
1223
1224 return i
1224 return i
1225
1225
1226 def termwidth(self):
1226 def termwidth(self):
1227 '''how wide is the terminal in columns?
1227 '''how wide is the terminal in columns?
1228 '''
1228 '''
1229 if 'COLUMNS' in encoding.environ:
1229 if 'COLUMNS' in encoding.environ:
1230 try:
1230 try:
1231 return int(encoding.environ['COLUMNS'])
1231 return int(encoding.environ['COLUMNS'])
1232 except ValueError:
1232 except ValueError:
1233 pass
1233 pass
1234 return scmutil.termsize(self)[0]
1234 return scmutil.termsize(self)[0]
1235
1235
1236 def formatted(self):
1236 def formatted(self):
1237 '''should formatted output be used?
1237 '''should formatted output be used?
1238
1238
1239 It is often desirable to format the output to suite the output medium.
1239 It is often desirable to format the output to suite the output medium.
1240 Examples of this are truncating long lines or colorizing messages.
1240 Examples of this are truncating long lines or colorizing messages.
1241 However, this is not often not desirable when piping output into other
1241 However, this is not often not desirable when piping output into other
1242 utilities, e.g. `grep'.
1242 utilities, e.g. `grep'.
1243
1243
1244 Formatted output is triggered by the value of the `ui.formatted'
1244 Formatted output is triggered by the value of the `ui.formatted'
1245 configuration variable or - if it is unset - when `sys.stdout' points
1245 configuration variable or - if it is unset - when `sys.stdout' points
1246 to a terminal device. Please note that `ui.formatted' should be
1246 to a terminal device. Please note that `ui.formatted' should be
1247 considered an implementation detail; it is not intended for use outside
1247 considered an implementation detail; it is not intended for use outside
1248 Mercurial or its extensions.
1248 Mercurial or its extensions.
1249
1249
1250 This function refers to output only; for input, see `ui.interactive()'.
1250 This function refers to output only; for input, see `ui.interactive()'.
1251 This function always returns false when in plain mode, see `ui.plain()'.
1251 This function always returns false when in plain mode, see `ui.plain()'.
1252 '''
1252 '''
1253 if self.plain():
1253 if self.plain():
1254 return False
1254 return False
1255
1255
1256 i = self.configbool("ui", "formatted")
1256 i = self.configbool("ui", "formatted")
1257 if i is None:
1257 if i is None:
1258 # some environments replace stdout without implementing isatty
1258 # some environments replace stdout without implementing isatty
1259 # usually those are non-interactive
1259 # usually those are non-interactive
1260 return self._isatty(self.fout)
1260 return self._isatty(self.fout)
1261
1261
1262 return i
1262 return i
1263
1263
1264 def _readline(self):
1264 def _readline(self):
1265 if self._isatty(self.fin):
1265 if self._isatty(self.fin):
1266 try:
1266 try:
1267 # magically add command line editing support, where
1267 # magically add command line editing support, where
1268 # available
1268 # available
1269 import readline
1269 import readline
1270 # force demandimport to really load the module
1270 # force demandimport to really load the module
1271 readline.read_history_file
1271 readline.read_history_file
1272 # windows sometimes raises something other than ImportError
1272 # windows sometimes raises something other than ImportError
1273 except Exception:
1273 except Exception:
1274 pass
1274 pass
1275
1275
1276 # prompt ' ' must exist; otherwise readline may delete entire line
1276 # prompt ' ' must exist; otherwise readline may delete entire line
1277 # - http://bugs.python.org/issue12833
1277 # - http://bugs.python.org/issue12833
1278 with self.timeblockedsection('stdio'):
1278 with self.timeblockedsection('stdio'):
1279 line = util.bytesinput(self.fin, self.fout, r' ')
1279 line = util.bytesinput(self.fin, self.fout, r' ')
1280
1280
1281 # When stdin is in binary mode on Windows, it can cause
1281 # When stdin is in binary mode on Windows, it can cause
1282 # raw_input() to emit an extra trailing carriage return
1282 # raw_input() to emit an extra trailing carriage return
1283 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1283 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1284 line = line[:-1]
1284 line = line[:-1]
1285 return line
1285 return line
1286
1286
1287 def prompt(self, msg, default="y"):
1287 def prompt(self, msg, default="y"):
1288 """Prompt user with msg, read response.
1288 """Prompt user with msg, read response.
1289 If ui is not interactive, the default is returned.
1289 If ui is not interactive, the default is returned.
1290 """
1290 """
1291 if not self.interactive():
1291 if not self.interactive():
1292 self.write(msg, ' ', default or '', "\n")
1292 self.write(msg, ' ', default or '', "\n")
1293 return default
1293 return default
1294 self._writenobuf(msg, label='ui.prompt')
1294 self._writenobuf(msg, label='ui.prompt')
1295 self.flush()
1295 self.flush()
1296 try:
1296 try:
1297 r = self._readline()
1297 r = self._readline()
1298 if not r:
1298 if not r:
1299 r = default
1299 r = default
1300 if self.configbool('ui', 'promptecho'):
1300 if self.configbool('ui', 'promptecho'):
1301 self.write(r, "\n")
1301 self.write(r, "\n")
1302 return r
1302 return r
1303 except EOFError:
1303 except EOFError:
1304 raise error.ResponseExpected()
1304 raise error.ResponseExpected()
1305
1305
1306 @staticmethod
1306 @staticmethod
1307 def extractchoices(prompt):
1307 def extractchoices(prompt):
1308 """Extract prompt message and list of choices from specified prompt.
1308 """Extract prompt message and list of choices from specified prompt.
1309
1309
1310 This returns tuple "(message, choices)", and "choices" is the
1310 This returns tuple "(message, choices)", and "choices" is the
1311 list of tuple "(response character, text without &)".
1311 list of tuple "(response character, text without &)".
1312
1312
1313 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1313 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1314 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1314 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1315 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1315 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1316 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1316 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1317 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1317 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1318 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1318 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1319 """
1319 """
1320
1320
1321 # Sadly, the prompt string may have been built with a filename
1321 # Sadly, the prompt string may have been built with a filename
1322 # containing "$$" so let's try to find the first valid-looking
1322 # containing "$$" so let's try to find the first valid-looking
1323 # prompt to start parsing. Sadly, we also can't rely on
1323 # prompt to start parsing. Sadly, we also can't rely on
1324 # choices containing spaces, ASCII, or basically anything
1324 # choices containing spaces, ASCII, or basically anything
1325 # except an ampersand followed by a character.
1325 # except an ampersand followed by a character.
1326 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1326 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1327 msg = m.group(1)
1327 msg = m.group(1)
1328 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1328 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1329 def choicetuple(s):
1329 def choicetuple(s):
1330 ampidx = s.index('&')
1330 ampidx = s.index('&')
1331 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1331 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1332 return (msg, [choicetuple(s) for s in choices])
1332 return (msg, [choicetuple(s) for s in choices])
1333
1333
1334 def promptchoice(self, prompt, default=0):
1334 def promptchoice(self, prompt, default=0):
1335 """Prompt user with a message, read response, and ensure it matches
1335 """Prompt user with a message, read response, and ensure it matches
1336 one of the provided choices. The prompt is formatted as follows:
1336 one of the provided choices. The prompt is formatted as follows:
1337
1337
1338 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1338 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1339
1339
1340 The index of the choice is returned. Responses are case
1340 The index of the choice is returned. Responses are case
1341 insensitive. If ui is not interactive, the default is
1341 insensitive. If ui is not interactive, the default is
1342 returned.
1342 returned.
1343 """
1343 """
1344
1344
1345 msg, choices = self.extractchoices(prompt)
1345 msg, choices = self.extractchoices(prompt)
1346 resps = [r for r, t in choices]
1346 resps = [r for r, t in choices]
1347 while True:
1347 while True:
1348 r = self.prompt(msg, resps[default])
1348 r = self.prompt(msg, resps[default])
1349 if r.lower() in resps:
1349 if r.lower() in resps:
1350 return resps.index(r.lower())
1350 return resps.index(r.lower())
1351 self.write(_("unrecognized response\n"))
1351 self.write(_("unrecognized response\n"))
1352
1352
1353 def getpass(self, prompt=None, default=None):
1353 def getpass(self, prompt=None, default=None):
1354 if not self.interactive():
1354 if not self.interactive():
1355 return default
1355 return default
1356 try:
1356 try:
1357 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1357 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1358 # disable getpass() only if explicitly specified. it's still valid
1358 # disable getpass() only if explicitly specified. it's still valid
1359 # to interact with tty even if fin is not a tty.
1359 # to interact with tty even if fin is not a tty.
1360 with self.timeblockedsection('stdio'):
1360 with self.timeblockedsection('stdio'):
1361 if self.configbool('ui', 'nontty'):
1361 if self.configbool('ui', 'nontty'):
1362 l = self.fin.readline()
1362 l = self.fin.readline()
1363 if not l:
1363 if not l:
1364 raise EOFError
1364 raise EOFError
1365 return l.rstrip('\n')
1365 return l.rstrip('\n')
1366 else:
1366 else:
1367 return getpass.getpass('')
1367 return getpass.getpass('')
1368 except EOFError:
1368 except EOFError:
1369 raise error.ResponseExpected()
1369 raise error.ResponseExpected()
1370 def status(self, *msg, **opts):
1370 def status(self, *msg, **opts):
1371 '''write status message to output (if ui.quiet is False)
1371 '''write status message to output (if ui.quiet is False)
1372
1372
1373 This adds an output label of "ui.status".
1373 This adds an output label of "ui.status".
1374 '''
1374 '''
1375 if not self.quiet:
1375 if not self.quiet:
1376 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1376 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1377 self.write(*msg, **opts)
1377 self.write(*msg, **opts)
1378 def warn(self, *msg, **opts):
1378 def warn(self, *msg, **opts):
1379 '''write warning message to output (stderr)
1379 '''write warning message to output (stderr)
1380
1380
1381 This adds an output label of "ui.warning".
1381 This adds an output label of "ui.warning".
1382 '''
1382 '''
1383 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1383 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1384 self.write_err(*msg, **opts)
1384 self.write_err(*msg, **opts)
1385 def note(self, *msg, **opts):
1385 def note(self, *msg, **opts):
1386 '''write note to output (if ui.verbose is True)
1386 '''write note to output (if ui.verbose is True)
1387
1387
1388 This adds an output label of "ui.note".
1388 This adds an output label of "ui.note".
1389 '''
1389 '''
1390 if self.verbose:
1390 if self.verbose:
1391 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1391 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1392 self.write(*msg, **opts)
1392 self.write(*msg, **opts)
1393 def debug(self, *msg, **opts):
1393 def debug(self, *msg, **opts):
1394 '''write debug message to output (if ui.debugflag is True)
1394 '''write debug message to output (if ui.debugflag is True)
1395
1395
1396 This adds an output label of "ui.debug".
1396 This adds an output label of "ui.debug".
1397 '''
1397 '''
1398 if self.debugflag:
1398 if self.debugflag:
1399 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1399 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1400 self.write(*msg, **opts)
1400 self.write(*msg, **opts)
1401
1401
1402 def edit(self, text, user, extra=None, editform=None, pending=None,
1402 def edit(self, text, user, extra=None, editform=None, pending=None,
1403 repopath=None, action=None):
1403 repopath=None, action=None):
1404 if action is None:
1404 if action is None:
1405 self.develwarn('action is None but will soon be a required '
1405 self.develwarn('action is None but will soon be a required '
1406 'parameter to ui.edit()')
1406 'parameter to ui.edit()')
1407 extra_defaults = {
1407 extra_defaults = {
1408 'prefix': 'editor',
1408 'prefix': 'editor',
1409 'suffix': '.txt',
1409 'suffix': '.txt',
1410 }
1410 }
1411 if extra is not None:
1411 if extra is not None:
1412 if extra.get('suffix') is not None:
1412 if extra.get('suffix') is not None:
1413 self.develwarn('extra.suffix is not None but will soon be '
1413 self.develwarn('extra.suffix is not None but will soon be '
1414 'ignored by ui.edit()')
1414 'ignored by ui.edit()')
1415 extra_defaults.update(extra)
1415 extra_defaults.update(extra)
1416 extra = extra_defaults
1416 extra = extra_defaults
1417
1417
1418 if action == 'diff':
1418 if action == 'diff':
1419 suffix = '.diff'
1419 suffix = '.diff'
1420 elif action:
1420 elif action:
1421 suffix = '.%s.hg.txt' % action
1421 suffix = '.%s.hg.txt' % action
1422 else:
1422 else:
1423 suffix = extra['suffix']
1423 suffix = extra['suffix']
1424
1424
1425 rdir = None
1425 rdir = None
1426 if self.configbool('experimental', 'editortmpinhg'):
1426 if self.configbool('experimental', 'editortmpinhg'):
1427 rdir = repopath
1427 rdir = repopath
1428 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1428 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1429 suffix=suffix,
1429 suffix=suffix,
1430 dir=rdir)
1430 dir=rdir)
1431 try:
1431 try:
1432 f = os.fdopen(fd, r'wb')
1432 f = os.fdopen(fd, r'wb')
1433 f.write(util.tonativeeol(text))
1433 f.write(util.tonativeeol(text))
1434 f.close()
1434 f.close()
1435
1435
1436 environ = {'HGUSER': user}
1436 environ = {'HGUSER': user}
1437 if 'transplant_source' in extra:
1437 if 'transplant_source' in extra:
1438 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1438 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1439 for label in ('intermediate-source', 'source', 'rebase_source'):
1439 for label in ('intermediate-source', 'source', 'rebase_source'):
1440 if label in extra:
1440 if label in extra:
1441 environ.update({'HGREVISION': extra[label]})
1441 environ.update({'HGREVISION': extra[label]})
1442 break
1442 break
1443 if editform:
1443 if editform:
1444 environ.update({'HGEDITFORM': editform})
1444 environ.update({'HGEDITFORM': editform})
1445 if pending:
1445 if pending:
1446 environ.update({'HG_PENDING': pending})
1446 environ.update({'HG_PENDING': pending})
1447
1447
1448 editor = self.geteditor()
1448 editor = self.geteditor()
1449
1449
1450 self.system("%s \"%s\"" % (editor, name),
1450 self.system("%s \"%s\"" % (editor, name),
1451 environ=environ,
1451 environ=environ,
1452 onerr=error.Abort, errprefix=_("edit failed"),
1452 onerr=error.Abort, errprefix=_("edit failed"),
1453 blockedtag='editor')
1453 blockedtag='editor')
1454
1454
1455 f = open(name, r'rb')
1455 f = open(name, r'rb')
1456 t = util.fromnativeeol(f.read())
1456 t = util.fromnativeeol(f.read())
1457 f.close()
1457 f.close()
1458 finally:
1458 finally:
1459 os.unlink(name)
1459 os.unlink(name)
1460
1460
1461 return t
1461 return t
1462
1462
1463 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1463 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1464 blockedtag=None):
1464 blockedtag=None):
1465 '''execute shell command with appropriate output stream. command
1465 '''execute shell command with appropriate output stream. command
1466 output will be redirected if fout is not stdout.
1466 output will be redirected if fout is not stdout.
1467
1467
1468 if command fails and onerr is None, return status, else raise onerr
1468 if command fails and onerr is None, return status, else raise onerr
1469 object as exception.
1469 object as exception.
1470 '''
1470 '''
1471 if blockedtag is None:
1471 if blockedtag is None:
1472 # Long cmds tend to be because of an absolute path on cmd. Keep
1472 # Long cmds tend to be because of an absolute path on cmd. Keep
1473 # the tail end instead
1473 # the tail end instead
1474 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1474 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1475 blockedtag = 'unknown_system_' + cmdsuffix
1475 blockedtag = 'unknown_system_' + cmdsuffix
1476 out = self.fout
1476 out = self.fout
1477 if any(s[1] for s in self._bufferstates):
1477 if any(s[1] for s in self._bufferstates):
1478 out = self
1478 out = self
1479 with self.timeblockedsection(blockedtag):
1479 with self.timeblockedsection(blockedtag):
1480 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1480 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1481 if rc and onerr:
1481 if rc and onerr:
1482 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1482 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1483 util.explainexit(rc)[0])
1483 util.explainexit(rc)[0])
1484 if errprefix:
1484 if errprefix:
1485 errmsg = '%s: %s' % (errprefix, errmsg)
1485 errmsg = '%s: %s' % (errprefix, errmsg)
1486 raise onerr(errmsg)
1486 raise onerr(errmsg)
1487 return rc
1487 return rc
1488
1488
1489 def _runsystem(self, cmd, environ, cwd, out):
1489 def _runsystem(self, cmd, environ, cwd, out):
1490 """actually execute the given shell command (can be overridden by
1490 """actually execute the given shell command (can be overridden by
1491 extensions like chg)"""
1491 extensions like chg)"""
1492 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1492 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1493
1493
1494 def traceback(self, exc=None, force=False):
1494 def traceback(self, exc=None, force=False):
1495 '''print exception traceback if traceback printing enabled or forced.
1495 '''print exception traceback if traceback printing enabled or forced.
1496 only to call in exception handler. returns true if traceback
1496 only to call in exception handler. returns true if traceback
1497 printed.'''
1497 printed.'''
1498 if self.tracebackflag or force:
1498 if self.tracebackflag or force:
1499 if exc is None:
1499 if exc is None:
1500 exc = sys.exc_info()
1500 exc = sys.exc_info()
1501 cause = getattr(exc[1], 'cause', None)
1501 cause = getattr(exc[1], 'cause', None)
1502
1502
1503 if cause is not None:
1503 if cause is not None:
1504 causetb = traceback.format_tb(cause[2])
1504 causetb = traceback.format_tb(cause[2])
1505 exctb = traceback.format_tb(exc[2])
1505 exctb = traceback.format_tb(exc[2])
1506 exconly = traceback.format_exception_only(cause[0], cause[1])
1506 exconly = traceback.format_exception_only(cause[0], cause[1])
1507
1507
1508 # exclude frame where 'exc' was chained and rethrown from exctb
1508 # exclude frame where 'exc' was chained and rethrown from exctb
1509 self.write_err('Traceback (most recent call last):\n',
1509 self.write_err('Traceback (most recent call last):\n',
1510 ''.join(exctb[:-1]),
1510 ''.join(exctb[:-1]),
1511 ''.join(causetb),
1511 ''.join(causetb),
1512 ''.join(exconly))
1512 ''.join(exconly))
1513 else:
1513 else:
1514 output = traceback.format_exception(exc[0], exc[1], exc[2])
1514 output = traceback.format_exception(exc[0], exc[1], exc[2])
1515 self.write_err(encoding.strtolocal(r''.join(output)))
1515 self.write_err(encoding.strtolocal(r''.join(output)))
1516 return self.tracebackflag or force
1516 return self.tracebackflag or force
1517
1517
1518 def geteditor(self):
1518 def geteditor(self):
1519 '''return editor to use'''
1519 '''return editor to use'''
1520 if pycompat.sysplatform == 'plan9':
1520 if pycompat.sysplatform == 'plan9':
1521 # vi is the MIPS instruction simulator on Plan 9. We
1521 # vi is the MIPS instruction simulator on Plan 9. We
1522 # instead default to E to plumb commit messages to
1522 # instead default to E to plumb commit messages to
1523 # avoid confusion.
1523 # avoid confusion.
1524 editor = 'E'
1524 editor = 'E'
1525 else:
1525 else:
1526 editor = 'vi'
1526 editor = 'vi'
1527 return (encoding.environ.get("HGEDITOR") or
1527 return (encoding.environ.get("HGEDITOR") or
1528 self.config("ui", "editor", editor))
1528 self.config("ui", "editor", editor))
1529
1529
1530 @util.propertycache
1530 @util.propertycache
1531 def _progbar(self):
1531 def _progbar(self):
1532 """setup the progbar singleton to the ui object"""
1532 """setup the progbar singleton to the ui object"""
1533 if (self.quiet or self.debugflag
1533 if (self.quiet or self.debugflag
1534 or self.configbool('progress', 'disable')
1534 or self.configbool('progress', 'disable')
1535 or not progress.shouldprint(self)):
1535 or not progress.shouldprint(self)):
1536 return None
1536 return None
1537 return getprogbar(self)
1537 return getprogbar(self)
1538
1538
1539 def _progclear(self):
1539 def _progclear(self):
1540 """clear progress bar output if any. use it before any output"""
1540 """clear progress bar output if any. use it before any output"""
1541 if not haveprogbar(): # nothing loaded yet
1541 if not haveprogbar(): # nothing loaded yet
1542 return
1542 return
1543 if self._progbar is not None and self._progbar.printed:
1543 if self._progbar is not None and self._progbar.printed:
1544 self._progbar.clear()
1544 self._progbar.clear()
1545
1545
1546 def progress(self, topic, pos, item="", unit="", total=None):
1546 def progress(self, topic, pos, item="", unit="", total=None):
1547 '''show a progress message
1547 '''show a progress message
1548
1548
1549 By default a textual progress bar will be displayed if an operation
1549 By default a textual progress bar will be displayed if an operation
1550 takes too long. 'topic' is the current operation, 'item' is a
1550 takes too long. 'topic' is the current operation, 'item' is a
1551 non-numeric marker of the current position (i.e. the currently
1551 non-numeric marker of the current position (i.e. the currently
1552 in-process file), 'pos' is the current numeric position (i.e.
1552 in-process file), 'pos' is the current numeric position (i.e.
1553 revision, bytes, etc.), unit is a corresponding unit label,
1553 revision, bytes, etc.), unit is a corresponding unit label,
1554 and total is the highest expected pos.
1554 and total is the highest expected pos.
1555
1555
1556 Multiple nested topics may be active at a time.
1556 Multiple nested topics may be active at a time.
1557
1557
1558 All topics should be marked closed by setting pos to None at
1558 All topics should be marked closed by setting pos to None at
1559 termination.
1559 termination.
1560 '''
1560 '''
1561 if self._progbar is not None:
1561 if self._progbar is not None:
1562 self._progbar.progress(topic, pos, item=item, unit=unit,
1562 self._progbar.progress(topic, pos, item=item, unit=unit,
1563 total=total)
1563 total=total)
1564 if pos is None or not self.configbool('progress', 'debug'):
1564 if pos is None or not self.configbool('progress', 'debug'):
1565 return
1565 return
1566
1566
1567 if unit:
1567 if unit:
1568 unit = ' ' + unit
1568 unit = ' ' + unit
1569 if item:
1569 if item:
1570 item = ' ' + item
1570 item = ' ' + item
1571
1571
1572 if total:
1572 if total:
1573 pct = 100.0 * pos / total
1573 pct = 100.0 * pos / total
1574 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1574 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1575 % (topic, item, pos, total, unit, pct))
1575 % (topic, item, pos, total, unit, pct))
1576 else:
1576 else:
1577 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1577 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1578
1578
1579 def log(self, service, *msg, **opts):
1579 def log(self, service, *msg, **opts):
1580 '''hook for logging facility extensions
1580 '''hook for logging facility extensions
1581
1581
1582 service should be a readily-identifiable subsystem, which will
1582 service should be a readily-identifiable subsystem, which will
1583 allow filtering.
1583 allow filtering.
1584
1584
1585 *msg should be a newline-terminated format string to log, and
1585 *msg should be a newline-terminated format string to log, and
1586 then any values to %-format into that format string.
1586 then any values to %-format into that format string.
1587
1587
1588 **opts currently has no defined meanings.
1588 **opts currently has no defined meanings.
1589 '''
1589 '''
1590
1590
1591 def label(self, msg, label):
1591 def label(self, msg, label):
1592 '''style msg based on supplied label
1592 '''style msg based on supplied label
1593
1593
1594 If some color mode is enabled, this will add the necessary control
1594 If some color mode is enabled, this will add the necessary control
1595 characters to apply such color. In addition, 'debug' color mode adds
1595 characters to apply such color. In addition, 'debug' color mode adds
1596 markup showing which label affects a piece of text.
1596 markup showing which label affects a piece of text.
1597
1597
1598 ui.write(s, 'label') is equivalent to
1598 ui.write(s, 'label') is equivalent to
1599 ui.write(ui.label(s, 'label')).
1599 ui.write(ui.label(s, 'label')).
1600 '''
1600 '''
1601 if self._colormode is not None:
1601 if self._colormode is not None:
1602 return color.colorlabel(self, msg, label)
1602 return color.colorlabel(self, msg, label)
1603 return msg
1603 return msg
1604
1604
1605 def develwarn(self, msg, stacklevel=1, config=None):
1605 def develwarn(self, msg, stacklevel=1, config=None):
1606 """issue a developer warning message
1606 """issue a developer warning message
1607
1607
1608 Use 'stacklevel' to report the offender some layers further up in the
1608 Use 'stacklevel' to report the offender some layers further up in the
1609 stack.
1609 stack.
1610 """
1610 """
1611 if not self.configbool('devel', 'all-warnings'):
1611 if not self.configbool('devel', 'all-warnings'):
1612 if config is None or not self.configbool('devel', config):
1612 if config is None or not self.configbool('devel', config):
1613 return
1613 return
1614 msg = 'devel-warn: ' + msg
1614 msg = 'devel-warn: ' + msg
1615 stacklevel += 1 # get in develwarn
1615 stacklevel += 1 # get in develwarn
1616 if self.tracebackflag:
1616 if self.tracebackflag:
1617 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1617 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1618 self.log('develwarn', '%s at:\n%s' %
1618 self.log('develwarn', '%s at:\n%s' %
1619 (msg, ''.join(util.getstackframes(stacklevel))))
1619 (msg, ''.join(util.getstackframes(stacklevel))))
1620 else:
1620 else:
1621 curframe = inspect.currentframe()
1621 curframe = inspect.currentframe()
1622 calframe = inspect.getouterframes(curframe, 2)
1622 calframe = inspect.getouterframes(curframe, 2)
1623 fname, lineno, fmsg = calframe[stacklevel][1:4]
1623 fname, lineno, fmsg = calframe[stacklevel][1:4]
1624 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1624 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1625 self.write_err('%s at: %s:%d (%s)\n'
1625 self.write_err('%s at: %s:%d (%s)\n'
1626 % (msg, fname, lineno, fmsg))
1626 % (msg, fname, lineno, fmsg))
1627 self.log('develwarn', '%s at: %s:%d (%s)\n',
1627 self.log('develwarn', '%s at: %s:%d (%s)\n',
1628 msg, fname, lineno, fmsg)
1628 msg, fname, lineno, fmsg)
1629 curframe = calframe = None # avoid cycles
1629 curframe = calframe = None # avoid cycles
1630
1630
1631 def deprecwarn(self, msg, version, stacklevel=2):
1631 def deprecwarn(self, msg, version, stacklevel=2):
1632 """issue a deprecation warning
1632 """issue a deprecation warning
1633
1633
1634 - msg: message explaining what is deprecated and how to upgrade,
1634 - msg: message explaining what is deprecated and how to upgrade,
1635 - version: last version where the API will be supported,
1635 - version: last version where the API will be supported,
1636 """
1636 """
1637 if not (self.configbool('devel', 'all-warnings')
1637 if not (self.configbool('devel', 'all-warnings')
1638 or self.configbool('devel', 'deprec-warn')):
1638 or self.configbool('devel', 'deprec-warn')):
1639 return
1639 return
1640 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1640 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1641 " update your code.)") % version
1641 " update your code.)") % version
1642 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1642 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1643
1643
1644 def exportableenviron(self):
1644 def exportableenviron(self):
1645 """The environment variables that are safe to export, e.g. through
1645 """The environment variables that are safe to export, e.g. through
1646 hgweb.
1646 hgweb.
1647 """
1647 """
1648 return self._exportableenviron
1648 return self._exportableenviron
1649
1649
1650 @contextlib.contextmanager
1650 @contextlib.contextmanager
1651 def configoverride(self, overrides, source=""):
1651 def configoverride(self, overrides, source=""):
1652 """Context manager for temporary config overrides
1652 """Context manager for temporary config overrides
1653 `overrides` must be a dict of the following structure:
1653 `overrides` must be a dict of the following structure:
1654 {(section, name) : value}"""
1654 {(section, name) : value}"""
1655 backups = {}
1655 backups = {}
1656 try:
1656 try:
1657 for (section, name), value in overrides.items():
1657 for (section, name), value in overrides.items():
1658 backups[(section, name)] = self.backupconfig(section, name)
1658 backups[(section, name)] = self.backupconfig(section, name)
1659 self.setconfig(section, name, value, source)
1659 self.setconfig(section, name, value, source)
1660 yield
1660 yield
1661 finally:
1661 finally:
1662 for __, backup in backups.items():
1662 for __, backup in backups.items():
1663 self.restoreconfig(backup)
1663 self.restoreconfig(backup)
1664 # just restoring ui.quiet config to the previous value is not enough
1664 # just restoring ui.quiet config to the previous value is not enough
1665 # as it does not update ui.quiet class member
1665 # as it does not update ui.quiet class member
1666 if ('ui', 'quiet') in overrides:
1666 if ('ui', 'quiet') in overrides:
1667 self.fixconfig(section='ui')
1667 self.fixconfig(section='ui')
1668
1668
1669 class paths(dict):
1669 class paths(dict):
1670 """Represents a collection of paths and their configs.
1670 """Represents a collection of paths and their configs.
1671
1671
1672 Data is initially derived from ui instances and the config files they have
1672 Data is initially derived from ui instances and the config files they have
1673 loaded.
1673 loaded.
1674 """
1674 """
1675 def __init__(self, ui):
1675 def __init__(self, ui):
1676 dict.__init__(self)
1676 dict.__init__(self)
1677
1677
1678 for name, loc in ui.configitems('paths', ignoresub=True):
1678 for name, loc in ui.configitems('paths', ignoresub=True):
1679 # No location is the same as not existing.
1679 # No location is the same as not existing.
1680 if not loc:
1680 if not loc:
1681 continue
1681 continue
1682 loc, sub = ui.configsuboptions('paths', name)
1682 loc, sub = ui.configsuboptions('paths', name)
1683 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1683 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1684
1684
1685 def getpath(self, name, default=None):
1685 def getpath(self, name, default=None):
1686 """Return a ``path`` from a string, falling back to default.
1686 """Return a ``path`` from a string, falling back to default.
1687
1687
1688 ``name`` can be a named path or locations. Locations are filesystem
1688 ``name`` can be a named path or locations. Locations are filesystem
1689 paths or URIs.
1689 paths or URIs.
1690
1690
1691 Returns None if ``name`` is not a registered path, a URI, or a local
1691 Returns None if ``name`` is not a registered path, a URI, or a local
1692 path to a repo.
1692 path to a repo.
1693 """
1693 """
1694 # Only fall back to default if no path was requested.
1694 # Only fall back to default if no path was requested.
1695 if name is None:
1695 if name is None:
1696 if not default:
1696 if not default:
1697 default = ()
1697 default = ()
1698 elif not isinstance(default, (tuple, list)):
1698 elif not isinstance(default, (tuple, list)):
1699 default = (default,)
1699 default = (default,)
1700 for k in default:
1700 for k in default:
1701 try:
1701 try:
1702 return self[k]
1702 return self[k]
1703 except KeyError:
1703 except KeyError:
1704 continue
1704 continue
1705 return None
1705 return None
1706
1706
1707 # Most likely empty string.
1707 # Most likely empty string.
1708 # This may need to raise in the future.
1708 # This may need to raise in the future.
1709 if not name:
1709 if not name:
1710 return None
1710 return None
1711
1711
1712 try:
1712 try:
1713 return self[name]
1713 return self[name]
1714 except KeyError:
1714 except KeyError:
1715 # Try to resolve as a local path or URI.
1715 # Try to resolve as a local path or URI.
1716 try:
1716 try:
1717 # We don't pass sub-options in, so no need to pass ui instance.
1717 # We don't pass sub-options in, so no need to pass ui instance.
1718 return path(None, None, rawloc=name)
1718 return path(None, None, rawloc=name)
1719 except ValueError:
1719 except ValueError:
1720 raise error.RepoError(_('repository %s does not exist') %
1720 raise error.RepoError(_('repository %s does not exist') %
1721 name)
1721 name)
1722
1722
1723 _pathsuboptions = {}
1723 _pathsuboptions = {}
1724
1724
1725 def pathsuboption(option, attr):
1725 def pathsuboption(option, attr):
1726 """Decorator used to declare a path sub-option.
1726 """Decorator used to declare a path sub-option.
1727
1727
1728 Arguments are the sub-option name and the attribute it should set on
1728 Arguments are the sub-option name and the attribute it should set on
1729 ``path`` instances.
1729 ``path`` instances.
1730
1730
1731 The decorated function will receive as arguments a ``ui`` instance,
1731 The decorated function will receive as arguments a ``ui`` instance,
1732 ``path`` instance, and the string value of this option from the config.
1732 ``path`` instance, and the string value of this option from the config.
1733 The function should return the value that will be set on the ``path``
1733 The function should return the value that will be set on the ``path``
1734 instance.
1734 instance.
1735
1735
1736 This decorator can be used to perform additional verification of
1736 This decorator can be used to perform additional verification of
1737 sub-options and to change the type of sub-options.
1737 sub-options and to change the type of sub-options.
1738 """
1738 """
1739 def register(func):
1739 def register(func):
1740 _pathsuboptions[option] = (attr, func)
1740 _pathsuboptions[option] = (attr, func)
1741 return func
1741 return func
1742 return register
1742 return register
1743
1743
1744 @pathsuboption('pushurl', 'pushloc')
1744 @pathsuboption('pushurl', 'pushloc')
1745 def pushurlpathoption(ui, path, value):
1745 def pushurlpathoption(ui, path, value):
1746 u = util.url(value)
1746 u = util.url(value)
1747 # Actually require a URL.
1747 # Actually require a URL.
1748 if not u.scheme:
1748 if not u.scheme:
1749 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1749 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1750 return None
1750 return None
1751
1751
1752 # Don't support the #foo syntax in the push URL to declare branch to
1752 # Don't support the #foo syntax in the push URL to declare branch to
1753 # push.
1753 # push.
1754 if u.fragment:
1754 if u.fragment:
1755 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1755 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1756 'ignoring)\n') % path.name)
1756 'ignoring)\n') % path.name)
1757 u.fragment = None
1757 u.fragment = None
1758
1758
1759 return str(u)
1759 return str(u)
1760
1760
1761 @pathsuboption('pushrev', 'pushrev')
1761 @pathsuboption('pushrev', 'pushrev')
1762 def pushrevpathoption(ui, path, value):
1762 def pushrevpathoption(ui, path, value):
1763 return value
1763 return value
1764
1764
1765 class path(object):
1765 class path(object):
1766 """Represents an individual path and its configuration."""
1766 """Represents an individual path and its configuration."""
1767
1767
1768 def __init__(self, ui, name, rawloc=None, suboptions=None):
1768 def __init__(self, ui, name, rawloc=None, suboptions=None):
1769 """Construct a path from its config options.
1769 """Construct a path from its config options.
1770
1770
1771 ``ui`` is the ``ui`` instance the path is coming from.
1771 ``ui`` is the ``ui`` instance the path is coming from.
1772 ``name`` is the symbolic name of the path.
1772 ``name`` is the symbolic name of the path.
1773 ``rawloc`` is the raw location, as defined in the config.
1773 ``rawloc`` is the raw location, as defined in the config.
1774 ``pushloc`` is the raw locations pushes should be made to.
1774 ``pushloc`` is the raw locations pushes should be made to.
1775
1775
1776 If ``name`` is not defined, we require that the location be a) a local
1776 If ``name`` is not defined, we require that the location be a) a local
1777 filesystem path with a .hg directory or b) a URL. If not,
1777 filesystem path with a .hg directory or b) a URL. If not,
1778 ``ValueError`` is raised.
1778 ``ValueError`` is raised.
1779 """
1779 """
1780 if not rawloc:
1780 if not rawloc:
1781 raise ValueError('rawloc must be defined')
1781 raise ValueError('rawloc must be defined')
1782
1782
1783 # Locations may define branches via syntax <base>#<branch>.
1783 # Locations may define branches via syntax <base>#<branch>.
1784 u = util.url(rawloc)
1784 u = util.url(rawloc)
1785 branch = None
1785 branch = None
1786 if u.fragment:
1786 if u.fragment:
1787 branch = u.fragment
1787 branch = u.fragment
1788 u.fragment = None
1788 u.fragment = None
1789
1789
1790 self.url = u
1790 self.url = u
1791 self.branch = branch
1791 self.branch = branch
1792
1792
1793 self.name = name
1793 self.name = name
1794 self.rawloc = rawloc
1794 self.rawloc = rawloc
1795 self.loc = '%s' % u
1795 self.loc = '%s' % u
1796
1796
1797 # When given a raw location but not a symbolic name, validate the
1797 # When given a raw location but not a symbolic name, validate the
1798 # location is valid.
1798 # location is valid.
1799 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1799 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1800 raise ValueError('location is not a URL or path to a local '
1800 raise ValueError('location is not a URL or path to a local '
1801 'repo: %s' % rawloc)
1801 'repo: %s' % rawloc)
1802
1802
1803 suboptions = suboptions or {}
1803 suboptions = suboptions or {}
1804
1804
1805 # Now process the sub-options. If a sub-option is registered, its
1805 # Now process the sub-options. If a sub-option is registered, its
1806 # attribute will always be present. The value will be None if there
1806 # attribute will always be present. The value will be None if there
1807 # was no valid sub-option.
1807 # was no valid sub-option.
1808 for suboption, (attr, func) in _pathsuboptions.iteritems():
1808 for suboption, (attr, func) in _pathsuboptions.iteritems():
1809 if suboption not in suboptions:
1809 if suboption not in suboptions:
1810 setattr(self, attr, None)
1810 setattr(self, attr, None)
1811 continue
1811 continue
1812
1812
1813 value = func(ui, self, suboptions[suboption])
1813 value = func(ui, self, suboptions[suboption])
1814 setattr(self, attr, value)
1814 setattr(self, attr, value)
1815
1815
1816 def _isvalidlocalpath(self, path):
1816 def _isvalidlocalpath(self, path):
1817 """Returns True if the given path is a potentially valid repository.
1817 """Returns True if the given path is a potentially valid repository.
1818 This is its own function so that extensions can change the definition of
1818 This is its own function so that extensions can change the definition of
1819 'valid' in this case (like when pulling from a git repo into a hg
1819 'valid' in this case (like when pulling from a git repo into a hg
1820 one)."""
1820 one)."""
1821 return os.path.isdir(os.path.join(path, '.hg'))
1821 return os.path.isdir(os.path.join(path, '.hg'))
1822
1822
1823 @property
1823 @property
1824 def suboptions(self):
1824 def suboptions(self):
1825 """Return sub-options and their values for this path.
1825 """Return sub-options and their values for this path.
1826
1826
1827 This is intended to be used for presentation purposes.
1827 This is intended to be used for presentation purposes.
1828 """
1828 """
1829 d = {}
1829 d = {}
1830 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1830 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1831 value = getattr(self, attr)
1831 value = getattr(self, attr)
1832 if value is not None:
1832 if value is not None:
1833 d[subopt] = value
1833 d[subopt] = value
1834 return d
1834 return d
1835
1835
1836 # we instantiate one globally shared progress bar to avoid
1836 # we instantiate one globally shared progress bar to avoid
1837 # competing progress bars when multiple UI objects get created
1837 # competing progress bars when multiple UI objects get created
1838 _progresssingleton = None
1838 _progresssingleton = None
1839
1839
1840 def getprogbar(ui):
1840 def getprogbar(ui):
1841 global _progresssingleton
1841 global _progresssingleton
1842 if _progresssingleton is None:
1842 if _progresssingleton is None:
1843 # passing 'ui' object to the singleton is fishy,
1843 # passing 'ui' object to the singleton is fishy,
1844 # this is how the extension used to work but feel free to rework it.
1844 # this is how the extension used to work but feel free to rework it.
1845 _progresssingleton = progress.progbar(ui)
1845 _progresssingleton = progress.progbar(ui)
1846 return _progresssingleton
1846 return _progresssingleton
1847
1847
1848 def haveprogbar():
1848 def haveprogbar():
1849 return _progresssingleton is not None
1849 return _progresssingleton is not None
General Comments 0
You need to be logged in to leave comments. Login now