##// END OF EJS Templates
progress: write ui.progress() in terms of ui.makeprogress()...
Martin von Zweigbergk -
r41178:8cf92ca9 default
parent child Browse files
Show More
@@ -1,1810 +1,1845 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16 import subprocess
16 import subprocess
17 import weakref
17 import weakref
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import (
20 from .node import (
21 bin,
21 bin,
22 hex,
22 hex,
23 nullid,
23 nullid,
24 nullrev,
24 nullrev,
25 short,
25 short,
26 wdirid,
26 wdirid,
27 wdirrev,
27 wdirrev,
28 )
28 )
29
29
30 from . import (
30 from . import (
31 encoding,
31 encoding,
32 error,
32 error,
33 match as matchmod,
33 match as matchmod,
34 obsolete,
34 obsolete,
35 obsutil,
35 obsutil,
36 pathutil,
36 pathutil,
37 phases,
37 phases,
38 policy,
38 policy,
39 pycompat,
39 pycompat,
40 revsetlang,
40 revsetlang,
41 similar,
41 similar,
42 smartset,
42 smartset,
43 url,
43 url,
44 util,
44 util,
45 vfs,
45 vfs,
46 )
46 )
47
47
48 from .utils import (
48 from .utils import (
49 procutil,
49 procutil,
50 stringutil,
50 stringutil,
51 )
51 )
52
52
53 if pycompat.iswindows:
53 if pycompat.iswindows:
54 from . import scmwindows as scmplatform
54 from . import scmwindows as scmplatform
55 else:
55 else:
56 from . import scmposix as scmplatform
56 from . import scmposix as scmplatform
57
57
58 parsers = policy.importmod(r'parsers')
58 parsers = policy.importmod(r'parsers')
59
59
60 termsize = scmplatform.termsize
60 termsize = scmplatform.termsize
61
61
62 class status(tuple):
62 class status(tuple):
63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
64 and 'ignored' properties are only relevant to the working copy.
64 and 'ignored' properties are only relevant to the working copy.
65 '''
65 '''
66
66
67 __slots__ = ()
67 __slots__ = ()
68
68
69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
70 clean):
70 clean):
71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
72 ignored, clean))
72 ignored, clean))
73
73
74 @property
74 @property
75 def modified(self):
75 def modified(self):
76 '''files that have been modified'''
76 '''files that have been modified'''
77 return self[0]
77 return self[0]
78
78
79 @property
79 @property
80 def added(self):
80 def added(self):
81 '''files that have been added'''
81 '''files that have been added'''
82 return self[1]
82 return self[1]
83
83
84 @property
84 @property
85 def removed(self):
85 def removed(self):
86 '''files that have been removed'''
86 '''files that have been removed'''
87 return self[2]
87 return self[2]
88
88
89 @property
89 @property
90 def deleted(self):
90 def deleted(self):
91 '''files that are in the dirstate, but have been deleted from the
91 '''files that are in the dirstate, but have been deleted from the
92 working copy (aka "missing")
92 working copy (aka "missing")
93 '''
93 '''
94 return self[3]
94 return self[3]
95
95
96 @property
96 @property
97 def unknown(self):
97 def unknown(self):
98 '''files not in the dirstate that are not ignored'''
98 '''files not in the dirstate that are not ignored'''
99 return self[4]
99 return self[4]
100
100
101 @property
101 @property
102 def ignored(self):
102 def ignored(self):
103 '''files not in the dirstate that are ignored (by _dirignore())'''
103 '''files not in the dirstate that are ignored (by _dirignore())'''
104 return self[5]
104 return self[5]
105
105
106 @property
106 @property
107 def clean(self):
107 def clean(self):
108 '''files that have not been modified'''
108 '''files that have not been modified'''
109 return self[6]
109 return self[6]
110
110
111 def __repr__(self, *args, **kwargs):
111 def __repr__(self, *args, **kwargs):
112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
113 r'unknown=%s, ignored=%s, clean=%s>') %
113 r'unknown=%s, ignored=%s, clean=%s>') %
114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
115
115
116 def itersubrepos(ctx1, ctx2):
116 def itersubrepos(ctx1, ctx2):
117 """find subrepos in ctx1 or ctx2"""
117 """find subrepos in ctx1 or ctx2"""
118 # Create a (subpath, ctx) mapping where we prefer subpaths from
118 # Create a (subpath, ctx) mapping where we prefer subpaths from
119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
120 # has been modified (in ctx2) but not yet committed (in ctx1).
120 # has been modified (in ctx2) but not yet committed (in ctx1).
121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
123
123
124 missing = set()
124 missing = set()
125
125
126 for subpath in ctx2.substate:
126 for subpath in ctx2.substate:
127 if subpath not in ctx1.substate:
127 if subpath not in ctx1.substate:
128 del subpaths[subpath]
128 del subpaths[subpath]
129 missing.add(subpath)
129 missing.add(subpath)
130
130
131 for subpath, ctx in sorted(subpaths.iteritems()):
131 for subpath, ctx in sorted(subpaths.iteritems()):
132 yield subpath, ctx.sub(subpath)
132 yield subpath, ctx.sub(subpath)
133
133
134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
135 # status and diff will have an accurate result when it does
135 # status and diff will have an accurate result when it does
136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
137 # against itself.
137 # against itself.
138 for subpath in missing:
138 for subpath in missing:
139 yield subpath, ctx2.nullsub(subpath, ctx1)
139 yield subpath, ctx2.nullsub(subpath, ctx1)
140
140
141 def nochangesfound(ui, repo, excluded=None):
141 def nochangesfound(ui, repo, excluded=None):
142 '''Report no changes for push/pull, excluded is None or a list of
142 '''Report no changes for push/pull, excluded is None or a list of
143 nodes excluded from the push/pull.
143 nodes excluded from the push/pull.
144 '''
144 '''
145 secretlist = []
145 secretlist = []
146 if excluded:
146 if excluded:
147 for n in excluded:
147 for n in excluded:
148 ctx = repo[n]
148 ctx = repo[n]
149 if ctx.phase() >= phases.secret and not ctx.extinct():
149 if ctx.phase() >= phases.secret and not ctx.extinct():
150 secretlist.append(n)
150 secretlist.append(n)
151
151
152 if secretlist:
152 if secretlist:
153 ui.status(_("no changes found (ignored %d secret changesets)\n")
153 ui.status(_("no changes found (ignored %d secret changesets)\n")
154 % len(secretlist))
154 % len(secretlist))
155 else:
155 else:
156 ui.status(_("no changes found\n"))
156 ui.status(_("no changes found\n"))
157
157
158 def callcatch(ui, func):
158 def callcatch(ui, func):
159 """call func() with global exception handling
159 """call func() with global exception handling
160
160
161 return func() if no exception happens. otherwise do some error handling
161 return func() if no exception happens. otherwise do some error handling
162 and return an exit code accordingly. does not handle all exceptions.
162 and return an exit code accordingly. does not handle all exceptions.
163 """
163 """
164 try:
164 try:
165 try:
165 try:
166 return func()
166 return func()
167 except: # re-raises
167 except: # re-raises
168 ui.traceback()
168 ui.traceback()
169 raise
169 raise
170 # Global exception handling, alphabetically
170 # Global exception handling, alphabetically
171 # Mercurial-specific first, followed by built-in and library exceptions
171 # Mercurial-specific first, followed by built-in and library exceptions
172 except error.LockHeld as inst:
172 except error.LockHeld as inst:
173 if inst.errno == errno.ETIMEDOUT:
173 if inst.errno == errno.ETIMEDOUT:
174 reason = _('timed out waiting for lock held by %r') % (
174 reason = _('timed out waiting for lock held by %r') % (
175 pycompat.bytestr(inst.locker))
175 pycompat.bytestr(inst.locker))
176 else:
176 else:
177 reason = _('lock held by %r') % inst.locker
177 reason = _('lock held by %r') % inst.locker
178 ui.error(_("abort: %s: %s\n") % (
178 ui.error(_("abort: %s: %s\n") % (
179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
180 if not inst.locker:
180 if not inst.locker:
181 ui.error(_("(lock might be very busy)\n"))
181 ui.error(_("(lock might be very busy)\n"))
182 except error.LockUnavailable as inst:
182 except error.LockUnavailable as inst:
183 ui.error(_("abort: could not lock %s: %s\n") %
183 ui.error(_("abort: could not lock %s: %s\n") %
184 (inst.desc or stringutil.forcebytestr(inst.filename),
184 (inst.desc or stringutil.forcebytestr(inst.filename),
185 encoding.strtolocal(inst.strerror)))
185 encoding.strtolocal(inst.strerror)))
186 except error.OutOfBandError as inst:
186 except error.OutOfBandError as inst:
187 if inst.args:
187 if inst.args:
188 msg = _("abort: remote error:\n")
188 msg = _("abort: remote error:\n")
189 else:
189 else:
190 msg = _("abort: remote error\n")
190 msg = _("abort: remote error\n")
191 ui.error(msg)
191 ui.error(msg)
192 if inst.args:
192 if inst.args:
193 ui.error(''.join(inst.args))
193 ui.error(''.join(inst.args))
194 if inst.hint:
194 if inst.hint:
195 ui.error('(%s)\n' % inst.hint)
195 ui.error('(%s)\n' % inst.hint)
196 except error.RepoError as inst:
196 except error.RepoError as inst:
197 ui.error(_("abort: %s!\n") % inst)
197 ui.error(_("abort: %s!\n") % inst)
198 if inst.hint:
198 if inst.hint:
199 ui.error(_("(%s)\n") % inst.hint)
199 ui.error(_("(%s)\n") % inst.hint)
200 except error.ResponseError as inst:
200 except error.ResponseError as inst:
201 ui.error(_("abort: %s") % inst.args[0])
201 ui.error(_("abort: %s") % inst.args[0])
202 msg = inst.args[1]
202 msg = inst.args[1]
203 if isinstance(msg, type(u'')):
203 if isinstance(msg, type(u'')):
204 msg = pycompat.sysbytes(msg)
204 msg = pycompat.sysbytes(msg)
205 if not isinstance(msg, bytes):
205 if not isinstance(msg, bytes):
206 ui.error(" %r\n" % (msg,))
206 ui.error(" %r\n" % (msg,))
207 elif not msg:
207 elif not msg:
208 ui.error(_(" empty string\n"))
208 ui.error(_(" empty string\n"))
209 else:
209 else:
210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
211 except error.CensoredNodeError as inst:
211 except error.CensoredNodeError as inst:
212 ui.error(_("abort: file censored %s!\n") % inst)
212 ui.error(_("abort: file censored %s!\n") % inst)
213 except error.StorageError as inst:
213 except error.StorageError as inst:
214 ui.error(_("abort: %s!\n") % inst)
214 ui.error(_("abort: %s!\n") % inst)
215 if inst.hint:
215 if inst.hint:
216 ui.error(_("(%s)\n") % inst.hint)
216 ui.error(_("(%s)\n") % inst.hint)
217 except error.InterventionRequired as inst:
217 except error.InterventionRequired as inst:
218 ui.error("%s\n" % inst)
218 ui.error("%s\n" % inst)
219 if inst.hint:
219 if inst.hint:
220 ui.error(_("(%s)\n") % inst.hint)
220 ui.error(_("(%s)\n") % inst.hint)
221 return 1
221 return 1
222 except error.WdirUnsupported:
222 except error.WdirUnsupported:
223 ui.error(_("abort: working directory revision cannot be specified\n"))
223 ui.error(_("abort: working directory revision cannot be specified\n"))
224 except error.Abort as inst:
224 except error.Abort as inst:
225 ui.error(_("abort: %s\n") % inst)
225 ui.error(_("abort: %s\n") % inst)
226 if inst.hint:
226 if inst.hint:
227 ui.error(_("(%s)\n") % inst.hint)
227 ui.error(_("(%s)\n") % inst.hint)
228 except ImportError as inst:
228 except ImportError as inst:
229 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
229 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
230 m = stringutil.forcebytestr(inst).split()[-1]
230 m = stringutil.forcebytestr(inst).split()[-1]
231 if m in "mpatch bdiff".split():
231 if m in "mpatch bdiff".split():
232 ui.error(_("(did you forget to compile extensions?)\n"))
232 ui.error(_("(did you forget to compile extensions?)\n"))
233 elif m in "zlib".split():
233 elif m in "zlib".split():
234 ui.error(_("(is your Python install correct?)\n"))
234 ui.error(_("(is your Python install correct?)\n"))
235 except IOError as inst:
235 except IOError as inst:
236 if util.safehasattr(inst, "code"):
236 if util.safehasattr(inst, "code"):
237 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
237 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
238 elif util.safehasattr(inst, "reason"):
238 elif util.safehasattr(inst, "reason"):
239 try: # usually it is in the form (errno, strerror)
239 try: # usually it is in the form (errno, strerror)
240 reason = inst.reason.args[1]
240 reason = inst.reason.args[1]
241 except (AttributeError, IndexError):
241 except (AttributeError, IndexError):
242 # it might be anything, for example a string
242 # it might be anything, for example a string
243 reason = inst.reason
243 reason = inst.reason
244 if isinstance(reason, pycompat.unicode):
244 if isinstance(reason, pycompat.unicode):
245 # SSLError of Python 2.7.9 contains a unicode
245 # SSLError of Python 2.7.9 contains a unicode
246 reason = encoding.unitolocal(reason)
246 reason = encoding.unitolocal(reason)
247 ui.error(_("abort: error: %s\n") % reason)
247 ui.error(_("abort: error: %s\n") % reason)
248 elif (util.safehasattr(inst, "args")
248 elif (util.safehasattr(inst, "args")
249 and inst.args and inst.args[0] == errno.EPIPE):
249 and inst.args and inst.args[0] == errno.EPIPE):
250 pass
250 pass
251 elif getattr(inst, "strerror", None):
251 elif getattr(inst, "strerror", None):
252 if getattr(inst, "filename", None):
252 if getattr(inst, "filename", None):
253 ui.error(_("abort: %s: %s\n") % (
253 ui.error(_("abort: %s: %s\n") % (
254 encoding.strtolocal(inst.strerror),
254 encoding.strtolocal(inst.strerror),
255 stringutil.forcebytestr(inst.filename)))
255 stringutil.forcebytestr(inst.filename)))
256 else:
256 else:
257 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
257 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
258 else:
258 else:
259 raise
259 raise
260 except OSError as inst:
260 except OSError as inst:
261 if getattr(inst, "filename", None) is not None:
261 if getattr(inst, "filename", None) is not None:
262 ui.error(_("abort: %s: '%s'\n") % (
262 ui.error(_("abort: %s: '%s'\n") % (
263 encoding.strtolocal(inst.strerror),
263 encoding.strtolocal(inst.strerror),
264 stringutil.forcebytestr(inst.filename)))
264 stringutil.forcebytestr(inst.filename)))
265 else:
265 else:
266 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
266 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
267 except MemoryError:
267 except MemoryError:
268 ui.error(_("abort: out of memory\n"))
268 ui.error(_("abort: out of memory\n"))
269 except SystemExit as inst:
269 except SystemExit as inst:
270 # Commands shouldn't sys.exit directly, but give a return code.
270 # Commands shouldn't sys.exit directly, but give a return code.
271 # Just in case catch this and and pass exit code to caller.
271 # Just in case catch this and and pass exit code to caller.
272 return inst.code
272 return inst.code
273 except socket.error as inst:
273 except socket.error as inst:
274 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
274 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
275
275
276 return -1
276 return -1
277
277
278 def checknewlabel(repo, lbl, kind):
278 def checknewlabel(repo, lbl, kind):
279 # Do not use the "kind" parameter in ui output.
279 # Do not use the "kind" parameter in ui output.
280 # It makes strings difficult to translate.
280 # It makes strings difficult to translate.
281 if lbl in ['tip', '.', 'null']:
281 if lbl in ['tip', '.', 'null']:
282 raise error.Abort(_("the name '%s' is reserved") % lbl)
282 raise error.Abort(_("the name '%s' is reserved") % lbl)
283 for c in (':', '\0', '\n', '\r'):
283 for c in (':', '\0', '\n', '\r'):
284 if c in lbl:
284 if c in lbl:
285 raise error.Abort(
285 raise error.Abort(
286 _("%r cannot be used in a name") % pycompat.bytestr(c))
286 _("%r cannot be used in a name") % pycompat.bytestr(c))
287 try:
287 try:
288 int(lbl)
288 int(lbl)
289 raise error.Abort(_("cannot use an integer as a name"))
289 raise error.Abort(_("cannot use an integer as a name"))
290 except ValueError:
290 except ValueError:
291 pass
291 pass
292 if lbl.strip() != lbl:
292 if lbl.strip() != lbl:
293 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
293 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
294
294
295 def checkfilename(f):
295 def checkfilename(f):
296 '''Check that the filename f is an acceptable filename for a tracked file'''
296 '''Check that the filename f is an acceptable filename for a tracked file'''
297 if '\r' in f or '\n' in f:
297 if '\r' in f or '\n' in f:
298 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
298 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
299 % pycompat.bytestr(f))
299 % pycompat.bytestr(f))
300
300
301 def checkportable(ui, f):
301 def checkportable(ui, f):
302 '''Check if filename f is portable and warn or abort depending on config'''
302 '''Check if filename f is portable and warn or abort depending on config'''
303 checkfilename(f)
303 checkfilename(f)
304 abort, warn = checkportabilityalert(ui)
304 abort, warn = checkportabilityalert(ui)
305 if abort or warn:
305 if abort or warn:
306 msg = util.checkwinfilename(f)
306 msg = util.checkwinfilename(f)
307 if msg:
307 if msg:
308 msg = "%s: %s" % (msg, procutil.shellquote(f))
308 msg = "%s: %s" % (msg, procutil.shellquote(f))
309 if abort:
309 if abort:
310 raise error.Abort(msg)
310 raise error.Abort(msg)
311 ui.warn(_("warning: %s\n") % msg)
311 ui.warn(_("warning: %s\n") % msg)
312
312
313 def checkportabilityalert(ui):
313 def checkportabilityalert(ui):
314 '''check if the user's config requests nothing, a warning, or abort for
314 '''check if the user's config requests nothing, a warning, or abort for
315 non-portable filenames'''
315 non-portable filenames'''
316 val = ui.config('ui', 'portablefilenames')
316 val = ui.config('ui', 'portablefilenames')
317 lval = val.lower()
317 lval = val.lower()
318 bval = stringutil.parsebool(val)
318 bval = stringutil.parsebool(val)
319 abort = pycompat.iswindows or lval == 'abort'
319 abort = pycompat.iswindows or lval == 'abort'
320 warn = bval or lval == 'warn'
320 warn = bval or lval == 'warn'
321 if bval is None and not (warn or abort or lval == 'ignore'):
321 if bval is None and not (warn or abort or lval == 'ignore'):
322 raise error.ConfigError(
322 raise error.ConfigError(
323 _("ui.portablefilenames value is invalid ('%s')") % val)
323 _("ui.portablefilenames value is invalid ('%s')") % val)
324 return abort, warn
324 return abort, warn
325
325
326 class casecollisionauditor(object):
326 class casecollisionauditor(object):
327 def __init__(self, ui, abort, dirstate):
327 def __init__(self, ui, abort, dirstate):
328 self._ui = ui
328 self._ui = ui
329 self._abort = abort
329 self._abort = abort
330 allfiles = '\0'.join(dirstate._map)
330 allfiles = '\0'.join(dirstate._map)
331 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
331 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
332 self._dirstate = dirstate
332 self._dirstate = dirstate
333 # The purpose of _newfiles is so that we don't complain about
333 # The purpose of _newfiles is so that we don't complain about
334 # case collisions if someone were to call this object with the
334 # case collisions if someone were to call this object with the
335 # same filename twice.
335 # same filename twice.
336 self._newfiles = set()
336 self._newfiles = set()
337
337
338 def __call__(self, f):
338 def __call__(self, f):
339 if f in self._newfiles:
339 if f in self._newfiles:
340 return
340 return
341 fl = encoding.lower(f)
341 fl = encoding.lower(f)
342 if fl in self._loweredfiles and f not in self._dirstate:
342 if fl in self._loweredfiles and f not in self._dirstate:
343 msg = _('possible case-folding collision for %s') % f
343 msg = _('possible case-folding collision for %s') % f
344 if self._abort:
344 if self._abort:
345 raise error.Abort(msg)
345 raise error.Abort(msg)
346 self._ui.warn(_("warning: %s\n") % msg)
346 self._ui.warn(_("warning: %s\n") % msg)
347 self._loweredfiles.add(fl)
347 self._loweredfiles.add(fl)
348 self._newfiles.add(f)
348 self._newfiles.add(f)
349
349
350 def filteredhash(repo, maxrev):
350 def filteredhash(repo, maxrev):
351 """build hash of filtered revisions in the current repoview.
351 """build hash of filtered revisions in the current repoview.
352
352
353 Multiple caches perform up-to-date validation by checking that the
353 Multiple caches perform up-to-date validation by checking that the
354 tiprev and tipnode stored in the cache file match the current repository.
354 tiprev and tipnode stored in the cache file match the current repository.
355 However, this is not sufficient for validating repoviews because the set
355 However, this is not sufficient for validating repoviews because the set
356 of revisions in the view may change without the repository tiprev and
356 of revisions in the view may change without the repository tiprev and
357 tipnode changing.
357 tipnode changing.
358
358
359 This function hashes all the revs filtered from the view and returns
359 This function hashes all the revs filtered from the view and returns
360 that SHA-1 digest.
360 that SHA-1 digest.
361 """
361 """
362 cl = repo.changelog
362 cl = repo.changelog
363 if not cl.filteredrevs:
363 if not cl.filteredrevs:
364 return None
364 return None
365 key = None
365 key = None
366 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
366 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
367 if revs:
367 if revs:
368 s = hashlib.sha1()
368 s = hashlib.sha1()
369 for rev in revs:
369 for rev in revs:
370 s.update('%d;' % rev)
370 s.update('%d;' % rev)
371 key = s.digest()
371 key = s.digest()
372 return key
372 return key
373
373
374 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
374 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
375 '''yield every hg repository under path, always recursively.
375 '''yield every hg repository under path, always recursively.
376 The recurse flag will only control recursion into repo working dirs'''
376 The recurse flag will only control recursion into repo working dirs'''
377 def errhandler(err):
377 def errhandler(err):
378 if err.filename == path:
378 if err.filename == path:
379 raise err
379 raise err
380 samestat = getattr(os.path, 'samestat', None)
380 samestat = getattr(os.path, 'samestat', None)
381 if followsym and samestat is not None:
381 if followsym and samestat is not None:
382 def adddir(dirlst, dirname):
382 def adddir(dirlst, dirname):
383 dirstat = os.stat(dirname)
383 dirstat = os.stat(dirname)
384 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
384 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
385 if not match:
385 if not match:
386 dirlst.append(dirstat)
386 dirlst.append(dirstat)
387 return not match
387 return not match
388 else:
388 else:
389 followsym = False
389 followsym = False
390
390
391 if (seen_dirs is None) and followsym:
391 if (seen_dirs is None) and followsym:
392 seen_dirs = []
392 seen_dirs = []
393 adddir(seen_dirs, path)
393 adddir(seen_dirs, path)
394 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
394 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
395 dirs.sort()
395 dirs.sort()
396 if '.hg' in dirs:
396 if '.hg' in dirs:
397 yield root # found a repository
397 yield root # found a repository
398 qroot = os.path.join(root, '.hg', 'patches')
398 qroot = os.path.join(root, '.hg', 'patches')
399 if os.path.isdir(os.path.join(qroot, '.hg')):
399 if os.path.isdir(os.path.join(qroot, '.hg')):
400 yield qroot # we have a patch queue repo here
400 yield qroot # we have a patch queue repo here
401 if recurse:
401 if recurse:
402 # avoid recursing inside the .hg directory
402 # avoid recursing inside the .hg directory
403 dirs.remove('.hg')
403 dirs.remove('.hg')
404 else:
404 else:
405 dirs[:] = [] # don't descend further
405 dirs[:] = [] # don't descend further
406 elif followsym:
406 elif followsym:
407 newdirs = []
407 newdirs = []
408 for d in dirs:
408 for d in dirs:
409 fname = os.path.join(root, d)
409 fname = os.path.join(root, d)
410 if adddir(seen_dirs, fname):
410 if adddir(seen_dirs, fname):
411 if os.path.islink(fname):
411 if os.path.islink(fname):
412 for hgname in walkrepos(fname, True, seen_dirs):
412 for hgname in walkrepos(fname, True, seen_dirs):
413 yield hgname
413 yield hgname
414 else:
414 else:
415 newdirs.append(d)
415 newdirs.append(d)
416 dirs[:] = newdirs
416 dirs[:] = newdirs
417
417
418 def binnode(ctx):
418 def binnode(ctx):
419 """Return binary node id for a given basectx"""
419 """Return binary node id for a given basectx"""
420 node = ctx.node()
420 node = ctx.node()
421 if node is None:
421 if node is None:
422 return wdirid
422 return wdirid
423 return node
423 return node
424
424
425 def intrev(ctx):
425 def intrev(ctx):
426 """Return integer for a given basectx that can be used in comparison or
426 """Return integer for a given basectx that can be used in comparison or
427 arithmetic operation"""
427 arithmetic operation"""
428 rev = ctx.rev()
428 rev = ctx.rev()
429 if rev is None:
429 if rev is None:
430 return wdirrev
430 return wdirrev
431 return rev
431 return rev
432
432
433 def formatchangeid(ctx):
433 def formatchangeid(ctx):
434 """Format changectx as '{rev}:{node|formatnode}', which is the default
434 """Format changectx as '{rev}:{node|formatnode}', which is the default
435 template provided by logcmdutil.changesettemplater"""
435 template provided by logcmdutil.changesettemplater"""
436 repo = ctx.repo()
436 repo = ctx.repo()
437 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
437 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
438
438
439 def formatrevnode(ui, rev, node):
439 def formatrevnode(ui, rev, node):
440 """Format given revision and node depending on the current verbosity"""
440 """Format given revision and node depending on the current verbosity"""
441 if ui.debugflag:
441 if ui.debugflag:
442 hexfunc = hex
442 hexfunc = hex
443 else:
443 else:
444 hexfunc = short
444 hexfunc = short
445 return '%d:%s' % (rev, hexfunc(node))
445 return '%d:%s' % (rev, hexfunc(node))
446
446
447 def resolvehexnodeidprefix(repo, prefix):
447 def resolvehexnodeidprefix(repo, prefix):
448 if (prefix.startswith('x') and
448 if (prefix.startswith('x') and
449 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
449 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
450 prefix = prefix[1:]
450 prefix = prefix[1:]
451 try:
451 try:
452 # Uses unfiltered repo because it's faster when prefix is ambiguous/
452 # Uses unfiltered repo because it's faster when prefix is ambiguous/
453 # This matches the shortesthexnodeidprefix() function below.
453 # This matches the shortesthexnodeidprefix() function below.
454 node = repo.unfiltered().changelog._partialmatch(prefix)
454 node = repo.unfiltered().changelog._partialmatch(prefix)
455 except error.AmbiguousPrefixLookupError:
455 except error.AmbiguousPrefixLookupError:
456 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
456 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
457 if revset:
457 if revset:
458 # Clear config to avoid infinite recursion
458 # Clear config to avoid infinite recursion
459 configoverrides = {('experimental',
459 configoverrides = {('experimental',
460 'revisions.disambiguatewithin'): None}
460 'revisions.disambiguatewithin'): None}
461 with repo.ui.configoverride(configoverrides):
461 with repo.ui.configoverride(configoverrides):
462 revs = repo.anyrevs([revset], user=True)
462 revs = repo.anyrevs([revset], user=True)
463 matches = []
463 matches = []
464 for rev in revs:
464 for rev in revs:
465 node = repo.changelog.node(rev)
465 node = repo.changelog.node(rev)
466 if hex(node).startswith(prefix):
466 if hex(node).startswith(prefix):
467 matches.append(node)
467 matches.append(node)
468 if len(matches) == 1:
468 if len(matches) == 1:
469 return matches[0]
469 return matches[0]
470 raise
470 raise
471 if node is None:
471 if node is None:
472 return
472 return
473 repo.changelog.rev(node) # make sure node isn't filtered
473 repo.changelog.rev(node) # make sure node isn't filtered
474 return node
474 return node
475
475
476 def mayberevnum(repo, prefix):
476 def mayberevnum(repo, prefix):
477 """Checks if the given prefix may be mistaken for a revision number"""
477 """Checks if the given prefix may be mistaken for a revision number"""
478 try:
478 try:
479 i = int(prefix)
479 i = int(prefix)
480 # if we are a pure int, then starting with zero will not be
480 # if we are a pure int, then starting with zero will not be
481 # confused as a rev; or, obviously, if the int is larger
481 # confused as a rev; or, obviously, if the int is larger
482 # than the value of the tip rev. We still need to disambiguate if
482 # than the value of the tip rev. We still need to disambiguate if
483 # prefix == '0', since that *is* a valid revnum.
483 # prefix == '0', since that *is* a valid revnum.
484 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
484 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
485 return False
485 return False
486 return True
486 return True
487 except ValueError:
487 except ValueError:
488 return False
488 return False
489
489
490 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
490 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
491 """Find the shortest unambiguous prefix that matches hexnode.
491 """Find the shortest unambiguous prefix that matches hexnode.
492
492
493 If "cache" is not None, it must be a dictionary that can be used for
493 If "cache" is not None, it must be a dictionary that can be used for
494 caching between calls to this method.
494 caching between calls to this method.
495 """
495 """
496 # _partialmatch() of filtered changelog could take O(len(repo)) time,
496 # _partialmatch() of filtered changelog could take O(len(repo)) time,
497 # which would be unacceptably slow. so we look for hash collision in
497 # which would be unacceptably slow. so we look for hash collision in
498 # unfiltered space, which means some hashes may be slightly longer.
498 # unfiltered space, which means some hashes may be slightly longer.
499
499
500 minlength=max(minlength, 1)
500 minlength=max(minlength, 1)
501
501
502 def disambiguate(prefix):
502 def disambiguate(prefix):
503 """Disambiguate against revnums."""
503 """Disambiguate against revnums."""
504 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
504 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
505 if mayberevnum(repo, prefix):
505 if mayberevnum(repo, prefix):
506 return 'x' + prefix
506 return 'x' + prefix
507 else:
507 else:
508 return prefix
508 return prefix
509
509
510 hexnode = hex(node)
510 hexnode = hex(node)
511 for length in range(len(prefix), len(hexnode) + 1):
511 for length in range(len(prefix), len(hexnode) + 1):
512 prefix = hexnode[:length]
512 prefix = hexnode[:length]
513 if not mayberevnum(repo, prefix):
513 if not mayberevnum(repo, prefix):
514 return prefix
514 return prefix
515
515
516 cl = repo.unfiltered().changelog
516 cl = repo.unfiltered().changelog
517 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
517 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
518 if revset:
518 if revset:
519 revs = None
519 revs = None
520 if cache is not None:
520 if cache is not None:
521 revs = cache.get('disambiguationrevset')
521 revs = cache.get('disambiguationrevset')
522 if revs is None:
522 if revs is None:
523 revs = repo.anyrevs([revset], user=True)
523 revs = repo.anyrevs([revset], user=True)
524 if cache is not None:
524 if cache is not None:
525 cache['disambiguationrevset'] = revs
525 cache['disambiguationrevset'] = revs
526 if cl.rev(node) in revs:
526 if cl.rev(node) in revs:
527 hexnode = hex(node)
527 hexnode = hex(node)
528 nodetree = None
528 nodetree = None
529 if cache is not None:
529 if cache is not None:
530 nodetree = cache.get('disambiguationnodetree')
530 nodetree = cache.get('disambiguationnodetree')
531 if not nodetree:
531 if not nodetree:
532 try:
532 try:
533 nodetree = parsers.nodetree(cl.index, len(revs))
533 nodetree = parsers.nodetree(cl.index, len(revs))
534 except AttributeError:
534 except AttributeError:
535 # no native nodetree
535 # no native nodetree
536 pass
536 pass
537 else:
537 else:
538 for r in revs:
538 for r in revs:
539 nodetree.insert(r)
539 nodetree.insert(r)
540 if cache is not None:
540 if cache is not None:
541 cache['disambiguationnodetree'] = nodetree
541 cache['disambiguationnodetree'] = nodetree
542 if nodetree is not None:
542 if nodetree is not None:
543 length = max(nodetree.shortest(node), minlength)
543 length = max(nodetree.shortest(node), minlength)
544 prefix = hexnode[:length]
544 prefix = hexnode[:length]
545 return disambiguate(prefix)
545 return disambiguate(prefix)
546 for length in range(minlength, len(hexnode) + 1):
546 for length in range(minlength, len(hexnode) + 1):
547 matches = []
547 matches = []
548 prefix = hexnode[:length]
548 prefix = hexnode[:length]
549 for rev in revs:
549 for rev in revs:
550 otherhexnode = repo[rev].hex()
550 otherhexnode = repo[rev].hex()
551 if prefix == otherhexnode[:length]:
551 if prefix == otherhexnode[:length]:
552 matches.append(otherhexnode)
552 matches.append(otherhexnode)
553 if len(matches) == 1:
553 if len(matches) == 1:
554 return disambiguate(prefix)
554 return disambiguate(prefix)
555
555
556 try:
556 try:
557 return disambiguate(cl.shortest(node, minlength))
557 return disambiguate(cl.shortest(node, minlength))
558 except error.LookupError:
558 except error.LookupError:
559 raise error.RepoLookupError()
559 raise error.RepoLookupError()
560
560
561 def isrevsymbol(repo, symbol):
561 def isrevsymbol(repo, symbol):
562 """Checks if a symbol exists in the repo.
562 """Checks if a symbol exists in the repo.
563
563
564 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
564 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
565 symbol is an ambiguous nodeid prefix.
565 symbol is an ambiguous nodeid prefix.
566 """
566 """
567 try:
567 try:
568 revsymbol(repo, symbol)
568 revsymbol(repo, symbol)
569 return True
569 return True
570 except error.RepoLookupError:
570 except error.RepoLookupError:
571 return False
571 return False
572
572
573 def revsymbol(repo, symbol):
573 def revsymbol(repo, symbol):
574 """Returns a context given a single revision symbol (as string).
574 """Returns a context given a single revision symbol (as string).
575
575
576 This is similar to revsingle(), but accepts only a single revision symbol,
576 This is similar to revsingle(), but accepts only a single revision symbol,
577 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
577 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
578 not "max(public())".
578 not "max(public())".
579 """
579 """
580 if not isinstance(symbol, bytes):
580 if not isinstance(symbol, bytes):
581 msg = ("symbol (%s of type %s) was not a string, did you mean "
581 msg = ("symbol (%s of type %s) was not a string, did you mean "
582 "repo[symbol]?" % (symbol, type(symbol)))
582 "repo[symbol]?" % (symbol, type(symbol)))
583 raise error.ProgrammingError(msg)
583 raise error.ProgrammingError(msg)
584 try:
584 try:
585 if symbol in ('.', 'tip', 'null'):
585 if symbol in ('.', 'tip', 'null'):
586 return repo[symbol]
586 return repo[symbol]
587
587
588 try:
588 try:
589 r = int(symbol)
589 r = int(symbol)
590 if '%d' % r != symbol:
590 if '%d' % r != symbol:
591 raise ValueError
591 raise ValueError
592 l = len(repo.changelog)
592 l = len(repo.changelog)
593 if r < 0:
593 if r < 0:
594 r += l
594 r += l
595 if r < 0 or r >= l and r != wdirrev:
595 if r < 0 or r >= l and r != wdirrev:
596 raise ValueError
596 raise ValueError
597 return repo[r]
597 return repo[r]
598 except error.FilteredIndexError:
598 except error.FilteredIndexError:
599 raise
599 raise
600 except (ValueError, OverflowError, IndexError):
600 except (ValueError, OverflowError, IndexError):
601 pass
601 pass
602
602
603 if len(symbol) == 40:
603 if len(symbol) == 40:
604 try:
604 try:
605 node = bin(symbol)
605 node = bin(symbol)
606 rev = repo.changelog.rev(node)
606 rev = repo.changelog.rev(node)
607 return repo[rev]
607 return repo[rev]
608 except error.FilteredLookupError:
608 except error.FilteredLookupError:
609 raise
609 raise
610 except (TypeError, LookupError):
610 except (TypeError, LookupError):
611 pass
611 pass
612
612
613 # look up bookmarks through the name interface
613 # look up bookmarks through the name interface
614 try:
614 try:
615 node = repo.names.singlenode(repo, symbol)
615 node = repo.names.singlenode(repo, symbol)
616 rev = repo.changelog.rev(node)
616 rev = repo.changelog.rev(node)
617 return repo[rev]
617 return repo[rev]
618 except KeyError:
618 except KeyError:
619 pass
619 pass
620
620
621 node = resolvehexnodeidprefix(repo, symbol)
621 node = resolvehexnodeidprefix(repo, symbol)
622 if node is not None:
622 if node is not None:
623 rev = repo.changelog.rev(node)
623 rev = repo.changelog.rev(node)
624 return repo[rev]
624 return repo[rev]
625
625
626 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
626 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
627
627
628 except error.WdirUnsupported:
628 except error.WdirUnsupported:
629 return repo[None]
629 return repo[None]
630 except (error.FilteredIndexError, error.FilteredLookupError,
630 except (error.FilteredIndexError, error.FilteredLookupError,
631 error.FilteredRepoLookupError):
631 error.FilteredRepoLookupError):
632 raise _filterederror(repo, symbol)
632 raise _filterederror(repo, symbol)
633
633
634 def _filterederror(repo, changeid):
634 def _filterederror(repo, changeid):
635 """build an exception to be raised about a filtered changeid
635 """build an exception to be raised about a filtered changeid
636
636
637 This is extracted in a function to help extensions (eg: evolve) to
637 This is extracted in a function to help extensions (eg: evolve) to
638 experiment with various message variants."""
638 experiment with various message variants."""
639 if repo.filtername.startswith('visible'):
639 if repo.filtername.startswith('visible'):
640
640
641 # Check if the changeset is obsolete
641 # Check if the changeset is obsolete
642 unfilteredrepo = repo.unfiltered()
642 unfilteredrepo = repo.unfiltered()
643 ctx = revsymbol(unfilteredrepo, changeid)
643 ctx = revsymbol(unfilteredrepo, changeid)
644
644
645 # If the changeset is obsolete, enrich the message with the reason
645 # If the changeset is obsolete, enrich the message with the reason
646 # that made this changeset not visible
646 # that made this changeset not visible
647 if ctx.obsolete():
647 if ctx.obsolete():
648 msg = obsutil._getfilteredreason(repo, changeid, ctx)
648 msg = obsutil._getfilteredreason(repo, changeid, ctx)
649 else:
649 else:
650 msg = _("hidden revision '%s'") % changeid
650 msg = _("hidden revision '%s'") % changeid
651
651
652 hint = _('use --hidden to access hidden revisions')
652 hint = _('use --hidden to access hidden revisions')
653
653
654 return error.FilteredRepoLookupError(msg, hint=hint)
654 return error.FilteredRepoLookupError(msg, hint=hint)
655 msg = _("filtered revision '%s' (not in '%s' subset)")
655 msg = _("filtered revision '%s' (not in '%s' subset)")
656 msg %= (changeid, repo.filtername)
656 msg %= (changeid, repo.filtername)
657 return error.FilteredRepoLookupError(msg)
657 return error.FilteredRepoLookupError(msg)
658
658
659 def revsingle(repo, revspec, default='.', localalias=None):
659 def revsingle(repo, revspec, default='.', localalias=None):
660 if not revspec and revspec != 0:
660 if not revspec and revspec != 0:
661 return repo[default]
661 return repo[default]
662
662
663 l = revrange(repo, [revspec], localalias=localalias)
663 l = revrange(repo, [revspec], localalias=localalias)
664 if not l:
664 if not l:
665 raise error.Abort(_('empty revision set'))
665 raise error.Abort(_('empty revision set'))
666 return repo[l.last()]
666 return repo[l.last()]
667
667
668 def _pairspec(revspec):
668 def _pairspec(revspec):
669 tree = revsetlang.parse(revspec)
669 tree = revsetlang.parse(revspec)
670 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
670 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
671
671
672 def revpair(repo, revs):
672 def revpair(repo, revs):
673 if not revs:
673 if not revs:
674 return repo['.'], repo[None]
674 return repo['.'], repo[None]
675
675
676 l = revrange(repo, revs)
676 l = revrange(repo, revs)
677
677
678 if not l:
678 if not l:
679 first = second = None
679 first = second = None
680 elif l.isascending():
680 elif l.isascending():
681 first = l.min()
681 first = l.min()
682 second = l.max()
682 second = l.max()
683 elif l.isdescending():
683 elif l.isdescending():
684 first = l.max()
684 first = l.max()
685 second = l.min()
685 second = l.min()
686 else:
686 else:
687 first = l.first()
687 first = l.first()
688 second = l.last()
688 second = l.last()
689
689
690 if first is None:
690 if first is None:
691 raise error.Abort(_('empty revision range'))
691 raise error.Abort(_('empty revision range'))
692 if (first == second and len(revs) >= 2
692 if (first == second and len(revs) >= 2
693 and not all(revrange(repo, [r]) for r in revs)):
693 and not all(revrange(repo, [r]) for r in revs)):
694 raise error.Abort(_('empty revision on one side of range'))
694 raise error.Abort(_('empty revision on one side of range'))
695
695
696 # if top-level is range expression, the result must always be a pair
696 # if top-level is range expression, the result must always be a pair
697 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
697 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
698 return repo[first], repo[None]
698 return repo[first], repo[None]
699
699
700 return repo[first], repo[second]
700 return repo[first], repo[second]
701
701
702 def revrange(repo, specs, localalias=None):
702 def revrange(repo, specs, localalias=None):
703 """Execute 1 to many revsets and return the union.
703 """Execute 1 to many revsets and return the union.
704
704
705 This is the preferred mechanism for executing revsets using user-specified
705 This is the preferred mechanism for executing revsets using user-specified
706 config options, such as revset aliases.
706 config options, such as revset aliases.
707
707
708 The revsets specified by ``specs`` will be executed via a chained ``OR``
708 The revsets specified by ``specs`` will be executed via a chained ``OR``
709 expression. If ``specs`` is empty, an empty result is returned.
709 expression. If ``specs`` is empty, an empty result is returned.
710
710
711 ``specs`` can contain integers, in which case they are assumed to be
711 ``specs`` can contain integers, in which case they are assumed to be
712 revision numbers.
712 revision numbers.
713
713
714 It is assumed the revsets are already formatted. If you have arguments
714 It is assumed the revsets are already formatted. If you have arguments
715 that need to be expanded in the revset, call ``revsetlang.formatspec()``
715 that need to be expanded in the revset, call ``revsetlang.formatspec()``
716 and pass the result as an element of ``specs``.
716 and pass the result as an element of ``specs``.
717
717
718 Specifying a single revset is allowed.
718 Specifying a single revset is allowed.
719
719
720 Returns a ``revset.abstractsmartset`` which is a list-like interface over
720 Returns a ``revset.abstractsmartset`` which is a list-like interface over
721 integer revisions.
721 integer revisions.
722 """
722 """
723 allspecs = []
723 allspecs = []
724 for spec in specs:
724 for spec in specs:
725 if isinstance(spec, int):
725 if isinstance(spec, int):
726 spec = revsetlang.formatspec('rev(%d)', spec)
726 spec = revsetlang.formatspec('rev(%d)', spec)
727 allspecs.append(spec)
727 allspecs.append(spec)
728 return repo.anyrevs(allspecs, user=True, localalias=localalias)
728 return repo.anyrevs(allspecs, user=True, localalias=localalias)
729
729
730 def meaningfulparents(repo, ctx):
730 def meaningfulparents(repo, ctx):
731 """Return list of meaningful (or all if debug) parentrevs for rev.
731 """Return list of meaningful (or all if debug) parentrevs for rev.
732
732
733 For merges (two non-nullrev revisions) both parents are meaningful.
733 For merges (two non-nullrev revisions) both parents are meaningful.
734 Otherwise the first parent revision is considered meaningful if it
734 Otherwise the first parent revision is considered meaningful if it
735 is not the preceding revision.
735 is not the preceding revision.
736 """
736 """
737 parents = ctx.parents()
737 parents = ctx.parents()
738 if len(parents) > 1:
738 if len(parents) > 1:
739 return parents
739 return parents
740 if repo.ui.debugflag:
740 if repo.ui.debugflag:
741 return [parents[0], repo[nullrev]]
741 return [parents[0], repo[nullrev]]
742 if parents[0].rev() >= intrev(ctx) - 1:
742 if parents[0].rev() >= intrev(ctx) - 1:
743 return []
743 return []
744 return parents
744 return parents
745
745
746 def expandpats(pats):
746 def expandpats(pats):
747 '''Expand bare globs when running on windows.
747 '''Expand bare globs when running on windows.
748 On posix we assume it already has already been done by sh.'''
748 On posix we assume it already has already been done by sh.'''
749 if not util.expandglobs:
749 if not util.expandglobs:
750 return list(pats)
750 return list(pats)
751 ret = []
751 ret = []
752 for kindpat in pats:
752 for kindpat in pats:
753 kind, pat = matchmod._patsplit(kindpat, None)
753 kind, pat = matchmod._patsplit(kindpat, None)
754 if kind is None:
754 if kind is None:
755 try:
755 try:
756 globbed = glob.glob(pat)
756 globbed = glob.glob(pat)
757 except re.error:
757 except re.error:
758 globbed = [pat]
758 globbed = [pat]
759 if globbed:
759 if globbed:
760 ret.extend(globbed)
760 ret.extend(globbed)
761 continue
761 continue
762 ret.append(kindpat)
762 ret.append(kindpat)
763 return ret
763 return ret
764
764
765 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
765 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
766 badfn=None):
766 badfn=None):
767 '''Return a matcher and the patterns that were used.
767 '''Return a matcher and the patterns that were used.
768 The matcher will warn about bad matches, unless an alternate badfn callback
768 The matcher will warn about bad matches, unless an alternate badfn callback
769 is provided.'''
769 is provided.'''
770 if pats == ("",):
770 if pats == ("",):
771 pats = []
771 pats = []
772 if opts is None:
772 if opts is None:
773 opts = {}
773 opts = {}
774 if not globbed and default == 'relpath':
774 if not globbed and default == 'relpath':
775 pats = expandpats(pats or [])
775 pats = expandpats(pats or [])
776
776
777 def bad(f, msg):
777 def bad(f, msg):
778 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
778 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
779
779
780 if badfn is None:
780 if badfn is None:
781 badfn = bad
781 badfn = bad
782
782
783 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
783 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
784 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
784 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
785
785
786 if m.always():
786 if m.always():
787 pats = []
787 pats = []
788 return m, pats
788 return m, pats
789
789
790 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
790 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
791 badfn=None):
791 badfn=None):
792 '''Return a matcher that will warn about bad matches.'''
792 '''Return a matcher that will warn about bad matches.'''
793 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
793 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
794
794
795 def matchall(repo):
795 def matchall(repo):
796 '''Return a matcher that will efficiently match everything.'''
796 '''Return a matcher that will efficiently match everything.'''
797 return matchmod.always(repo.root, repo.getcwd())
797 return matchmod.always(repo.root, repo.getcwd())
798
798
799 def matchfiles(repo, files, badfn=None):
799 def matchfiles(repo, files, badfn=None):
800 '''Return a matcher that will efficiently match exactly these files.'''
800 '''Return a matcher that will efficiently match exactly these files.'''
801 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
801 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
802
802
803 def parsefollowlinespattern(repo, rev, pat, msg):
803 def parsefollowlinespattern(repo, rev, pat, msg):
804 """Return a file name from `pat` pattern suitable for usage in followlines
804 """Return a file name from `pat` pattern suitable for usage in followlines
805 logic.
805 logic.
806 """
806 """
807 if not matchmod.patkind(pat):
807 if not matchmod.patkind(pat):
808 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
808 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
809 else:
809 else:
810 ctx = repo[rev]
810 ctx = repo[rev]
811 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
811 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
812 files = [f for f in ctx if m(f)]
812 files = [f for f in ctx if m(f)]
813 if len(files) != 1:
813 if len(files) != 1:
814 raise error.ParseError(msg)
814 raise error.ParseError(msg)
815 return files[0]
815 return files[0]
816
816
817 def getorigvfs(ui, repo):
817 def getorigvfs(ui, repo):
818 """return a vfs suitable to save 'orig' file
818 """return a vfs suitable to save 'orig' file
819
819
820 return None if no special directory is configured"""
820 return None if no special directory is configured"""
821 origbackuppath = ui.config('ui', 'origbackuppath')
821 origbackuppath = ui.config('ui', 'origbackuppath')
822 if not origbackuppath:
822 if not origbackuppath:
823 return None
823 return None
824 return vfs.vfs(repo.wvfs.join(origbackuppath))
824 return vfs.vfs(repo.wvfs.join(origbackuppath))
825
825
826 def origpath(ui, repo, filepath):
826 def origpath(ui, repo, filepath):
827 '''customize where .orig files are created
827 '''customize where .orig files are created
828
828
829 Fetch user defined path from config file: [ui] origbackuppath = <path>
829 Fetch user defined path from config file: [ui] origbackuppath = <path>
830 Fall back to default (filepath with .orig suffix) if not specified
830 Fall back to default (filepath with .orig suffix) if not specified
831 '''
831 '''
832 origvfs = getorigvfs(ui, repo)
832 origvfs = getorigvfs(ui, repo)
833 if origvfs is None:
833 if origvfs is None:
834 return filepath + ".orig"
834 return filepath + ".orig"
835
835
836 # Convert filepath from an absolute path into a path inside the repo.
836 # Convert filepath from an absolute path into a path inside the repo.
837 filepathfromroot = util.normpath(os.path.relpath(filepath,
837 filepathfromroot = util.normpath(os.path.relpath(filepath,
838 start=repo.root))
838 start=repo.root))
839
839
840 origbackupdir = origvfs.dirname(filepathfromroot)
840 origbackupdir = origvfs.dirname(filepathfromroot)
841 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
841 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
842 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
842 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
843
843
844 # Remove any files that conflict with the backup file's path
844 # Remove any files that conflict with the backup file's path
845 for f in reversed(list(util.finddirs(filepathfromroot))):
845 for f in reversed(list(util.finddirs(filepathfromroot))):
846 if origvfs.isfileorlink(f):
846 if origvfs.isfileorlink(f):
847 ui.note(_('removing conflicting file: %s\n')
847 ui.note(_('removing conflicting file: %s\n')
848 % origvfs.join(f))
848 % origvfs.join(f))
849 origvfs.unlink(f)
849 origvfs.unlink(f)
850 break
850 break
851
851
852 origvfs.makedirs(origbackupdir)
852 origvfs.makedirs(origbackupdir)
853
853
854 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
854 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
855 ui.note(_('removing conflicting directory: %s\n')
855 ui.note(_('removing conflicting directory: %s\n')
856 % origvfs.join(filepathfromroot))
856 % origvfs.join(filepathfromroot))
857 origvfs.rmtree(filepathfromroot, forcibly=True)
857 origvfs.rmtree(filepathfromroot, forcibly=True)
858
858
859 return origvfs.join(filepathfromroot)
859 return origvfs.join(filepathfromroot)
860
860
861 class _containsnode(object):
861 class _containsnode(object):
862 """proxy __contains__(node) to container.__contains__ which accepts revs"""
862 """proxy __contains__(node) to container.__contains__ which accepts revs"""
863
863
864 def __init__(self, repo, revcontainer):
864 def __init__(self, repo, revcontainer):
865 self._torev = repo.changelog.rev
865 self._torev = repo.changelog.rev
866 self._revcontains = revcontainer.__contains__
866 self._revcontains = revcontainer.__contains__
867
867
868 def __contains__(self, node):
868 def __contains__(self, node):
869 return self._revcontains(self._torev(node))
869 return self._revcontains(self._torev(node))
870
870
871 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
871 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
872 fixphase=False, targetphase=None, backup=True):
872 fixphase=False, targetphase=None, backup=True):
873 """do common cleanups when old nodes are replaced by new nodes
873 """do common cleanups when old nodes are replaced by new nodes
874
874
875 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
875 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
876 (we might also want to move working directory parent in the future)
876 (we might also want to move working directory parent in the future)
877
877
878 By default, bookmark moves are calculated automatically from 'replacements',
878 By default, bookmark moves are calculated automatically from 'replacements',
879 but 'moves' can be used to override that. Also, 'moves' may include
879 but 'moves' can be used to override that. Also, 'moves' may include
880 additional bookmark moves that should not have associated obsmarkers.
880 additional bookmark moves that should not have associated obsmarkers.
881
881
882 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
882 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
883 have replacements. operation is a string, like "rebase".
883 have replacements. operation is a string, like "rebase".
884
884
885 metadata is dictionary containing metadata to be stored in obsmarker if
885 metadata is dictionary containing metadata to be stored in obsmarker if
886 obsolescence is enabled.
886 obsolescence is enabled.
887 """
887 """
888 assert fixphase or targetphase is None
888 assert fixphase or targetphase is None
889 if not replacements and not moves:
889 if not replacements and not moves:
890 return
890 return
891
891
892 # translate mapping's other forms
892 # translate mapping's other forms
893 if not util.safehasattr(replacements, 'items'):
893 if not util.safehasattr(replacements, 'items'):
894 replacements = {(n,): () for n in replacements}
894 replacements = {(n,): () for n in replacements}
895 else:
895 else:
896 # upgrading non tuple "source" to tuple ones for BC
896 # upgrading non tuple "source" to tuple ones for BC
897 repls = {}
897 repls = {}
898 for key, value in replacements.items():
898 for key, value in replacements.items():
899 if not isinstance(key, tuple):
899 if not isinstance(key, tuple):
900 key = (key,)
900 key = (key,)
901 repls[key] = value
901 repls[key] = value
902 replacements = repls
902 replacements = repls
903
903
904 # Unfiltered repo is needed since nodes in replacements might be hidden.
904 # Unfiltered repo is needed since nodes in replacements might be hidden.
905 unfi = repo.unfiltered()
905 unfi = repo.unfiltered()
906
906
907 # Calculate bookmark movements
907 # Calculate bookmark movements
908 if moves is None:
908 if moves is None:
909 moves = {}
909 moves = {}
910 for oldnodes, newnodes in replacements.items():
910 for oldnodes, newnodes in replacements.items():
911 for oldnode in oldnodes:
911 for oldnode in oldnodes:
912 if oldnode in moves:
912 if oldnode in moves:
913 continue
913 continue
914 if len(newnodes) > 1:
914 if len(newnodes) > 1:
915 # usually a split, take the one with biggest rev number
915 # usually a split, take the one with biggest rev number
916 newnode = next(unfi.set('max(%ln)', newnodes)).node()
916 newnode = next(unfi.set('max(%ln)', newnodes)).node()
917 elif len(newnodes) == 0:
917 elif len(newnodes) == 0:
918 # move bookmark backwards
918 # move bookmark backwards
919 allreplaced = []
919 allreplaced = []
920 for rep in replacements:
920 for rep in replacements:
921 allreplaced.extend(rep)
921 allreplaced.extend(rep)
922 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
922 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
923 allreplaced))
923 allreplaced))
924 if roots:
924 if roots:
925 newnode = roots[0].node()
925 newnode = roots[0].node()
926 else:
926 else:
927 newnode = nullid
927 newnode = nullid
928 else:
928 else:
929 newnode = newnodes[0]
929 newnode = newnodes[0]
930 moves[oldnode] = newnode
930 moves[oldnode] = newnode
931
931
932 allnewnodes = [n for ns in replacements.values() for n in ns]
932 allnewnodes = [n for ns in replacements.values() for n in ns]
933 toretract = {}
933 toretract = {}
934 toadvance = {}
934 toadvance = {}
935 if fixphase:
935 if fixphase:
936 precursors = {}
936 precursors = {}
937 for oldnodes, newnodes in replacements.items():
937 for oldnodes, newnodes in replacements.items():
938 for oldnode in oldnodes:
938 for oldnode in oldnodes:
939 for newnode in newnodes:
939 for newnode in newnodes:
940 precursors.setdefault(newnode, []).append(oldnode)
940 precursors.setdefault(newnode, []).append(oldnode)
941
941
942 allnewnodes.sort(key=lambda n: unfi[n].rev())
942 allnewnodes.sort(key=lambda n: unfi[n].rev())
943 newphases = {}
943 newphases = {}
944 def phase(ctx):
944 def phase(ctx):
945 return newphases.get(ctx.node(), ctx.phase())
945 return newphases.get(ctx.node(), ctx.phase())
946 for newnode in allnewnodes:
946 for newnode in allnewnodes:
947 ctx = unfi[newnode]
947 ctx = unfi[newnode]
948 parentphase = max(phase(p) for p in ctx.parents())
948 parentphase = max(phase(p) for p in ctx.parents())
949 if targetphase is None:
949 if targetphase is None:
950 oldphase = max(unfi[oldnode].phase()
950 oldphase = max(unfi[oldnode].phase()
951 for oldnode in precursors[newnode])
951 for oldnode in precursors[newnode])
952 newphase = max(oldphase, parentphase)
952 newphase = max(oldphase, parentphase)
953 else:
953 else:
954 newphase = max(targetphase, parentphase)
954 newphase = max(targetphase, parentphase)
955 newphases[newnode] = newphase
955 newphases[newnode] = newphase
956 if newphase > ctx.phase():
956 if newphase > ctx.phase():
957 toretract.setdefault(newphase, []).append(newnode)
957 toretract.setdefault(newphase, []).append(newnode)
958 elif newphase < ctx.phase():
958 elif newphase < ctx.phase():
959 toadvance.setdefault(newphase, []).append(newnode)
959 toadvance.setdefault(newphase, []).append(newnode)
960
960
961 with repo.transaction('cleanup') as tr:
961 with repo.transaction('cleanup') as tr:
962 # Move bookmarks
962 # Move bookmarks
963 bmarks = repo._bookmarks
963 bmarks = repo._bookmarks
964 bmarkchanges = []
964 bmarkchanges = []
965 for oldnode, newnode in moves.items():
965 for oldnode, newnode in moves.items():
966 oldbmarks = repo.nodebookmarks(oldnode)
966 oldbmarks = repo.nodebookmarks(oldnode)
967 if not oldbmarks:
967 if not oldbmarks:
968 continue
968 continue
969 from . import bookmarks # avoid import cycle
969 from . import bookmarks # avoid import cycle
970 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
970 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
971 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
971 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
972 hex(oldnode), hex(newnode)))
972 hex(oldnode), hex(newnode)))
973 # Delete divergent bookmarks being parents of related newnodes
973 # Delete divergent bookmarks being parents of related newnodes
974 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
974 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
975 allnewnodes, newnode, oldnode)
975 allnewnodes, newnode, oldnode)
976 deletenodes = _containsnode(repo, deleterevs)
976 deletenodes = _containsnode(repo, deleterevs)
977 for name in oldbmarks:
977 for name in oldbmarks:
978 bmarkchanges.append((name, newnode))
978 bmarkchanges.append((name, newnode))
979 for b in bookmarks.divergent2delete(repo, deletenodes, name):
979 for b in bookmarks.divergent2delete(repo, deletenodes, name):
980 bmarkchanges.append((b, None))
980 bmarkchanges.append((b, None))
981
981
982 if bmarkchanges:
982 if bmarkchanges:
983 bmarks.applychanges(repo, tr, bmarkchanges)
983 bmarks.applychanges(repo, tr, bmarkchanges)
984
984
985 for phase, nodes in toretract.items():
985 for phase, nodes in toretract.items():
986 phases.retractboundary(repo, tr, phase, nodes)
986 phases.retractboundary(repo, tr, phase, nodes)
987 for phase, nodes in toadvance.items():
987 for phase, nodes in toadvance.items():
988 phases.advanceboundary(repo, tr, phase, nodes)
988 phases.advanceboundary(repo, tr, phase, nodes)
989
989
990 # Obsolete or strip nodes
990 # Obsolete or strip nodes
991 if obsolete.isenabled(repo, obsolete.createmarkersopt):
991 if obsolete.isenabled(repo, obsolete.createmarkersopt):
992 # If a node is already obsoleted, and we want to obsolete it
992 # If a node is already obsoleted, and we want to obsolete it
993 # without a successor, skip that obssolete request since it's
993 # without a successor, skip that obssolete request since it's
994 # unnecessary. That's the "if s or not isobs(n)" check below.
994 # unnecessary. That's the "if s or not isobs(n)" check below.
995 # Also sort the node in topology order, that might be useful for
995 # Also sort the node in topology order, that might be useful for
996 # some obsstore logic.
996 # some obsstore logic.
997 # NOTE: the sorting might belong to createmarkers.
997 # NOTE: the sorting might belong to createmarkers.
998 torev = unfi.changelog.rev
998 torev = unfi.changelog.rev
999 sortfunc = lambda ns: torev(ns[0][0])
999 sortfunc = lambda ns: torev(ns[0][0])
1000 rels = []
1000 rels = []
1001 for ns, s in sorted(replacements.items(), key=sortfunc):
1001 for ns, s in sorted(replacements.items(), key=sortfunc):
1002 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
1002 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
1003 rels.append(rel)
1003 rels.append(rel)
1004 if rels:
1004 if rels:
1005 obsolete.createmarkers(repo, rels, operation=operation,
1005 obsolete.createmarkers(repo, rels, operation=operation,
1006 metadata=metadata)
1006 metadata=metadata)
1007 else:
1007 else:
1008 from . import repair # avoid import cycle
1008 from . import repair # avoid import cycle
1009 tostrip = list(n for ns in replacements for n in ns)
1009 tostrip = list(n for ns in replacements for n in ns)
1010 if tostrip:
1010 if tostrip:
1011 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1011 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1012 backup=backup)
1012 backup=backup)
1013
1013
1014 def addremove(repo, matcher, prefix, opts=None):
1014 def addremove(repo, matcher, prefix, opts=None):
1015 if opts is None:
1015 if opts is None:
1016 opts = {}
1016 opts = {}
1017 m = matcher
1017 m = matcher
1018 dry_run = opts.get('dry_run')
1018 dry_run = opts.get('dry_run')
1019 try:
1019 try:
1020 similarity = float(opts.get('similarity') or 0)
1020 similarity = float(opts.get('similarity') or 0)
1021 except ValueError:
1021 except ValueError:
1022 raise error.Abort(_('similarity must be a number'))
1022 raise error.Abort(_('similarity must be a number'))
1023 if similarity < 0 or similarity > 100:
1023 if similarity < 0 or similarity > 100:
1024 raise error.Abort(_('similarity must be between 0 and 100'))
1024 raise error.Abort(_('similarity must be between 0 and 100'))
1025 similarity /= 100.0
1025 similarity /= 100.0
1026
1026
1027 ret = 0
1027 ret = 0
1028 join = lambda f: os.path.join(prefix, f)
1028 join = lambda f: os.path.join(prefix, f)
1029
1029
1030 wctx = repo[None]
1030 wctx = repo[None]
1031 for subpath in sorted(wctx.substate):
1031 for subpath in sorted(wctx.substate):
1032 submatch = matchmod.subdirmatcher(subpath, m)
1032 submatch = matchmod.subdirmatcher(subpath, m)
1033 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1033 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1034 sub = wctx.sub(subpath)
1034 sub = wctx.sub(subpath)
1035 try:
1035 try:
1036 if sub.addremove(submatch, prefix, opts):
1036 if sub.addremove(submatch, prefix, opts):
1037 ret = 1
1037 ret = 1
1038 except error.LookupError:
1038 except error.LookupError:
1039 repo.ui.status(_("skipping missing subrepository: %s\n")
1039 repo.ui.status(_("skipping missing subrepository: %s\n")
1040 % join(subpath))
1040 % join(subpath))
1041
1041
1042 rejected = []
1042 rejected = []
1043 def badfn(f, msg):
1043 def badfn(f, msg):
1044 if f in m.files():
1044 if f in m.files():
1045 m.bad(f, msg)
1045 m.bad(f, msg)
1046 rejected.append(f)
1046 rejected.append(f)
1047
1047
1048 badmatch = matchmod.badmatch(m, badfn)
1048 badmatch = matchmod.badmatch(m, badfn)
1049 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1049 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1050 badmatch)
1050 badmatch)
1051
1051
1052 unknownset = set(unknown + forgotten)
1052 unknownset = set(unknown + forgotten)
1053 toprint = unknownset.copy()
1053 toprint = unknownset.copy()
1054 toprint.update(deleted)
1054 toprint.update(deleted)
1055 for abs in sorted(toprint):
1055 for abs in sorted(toprint):
1056 if repo.ui.verbose or not m.exact(abs):
1056 if repo.ui.verbose or not m.exact(abs):
1057 if abs in unknownset:
1057 if abs in unknownset:
1058 status = _('adding %s\n') % m.uipath(abs)
1058 status = _('adding %s\n') % m.uipath(abs)
1059 label = 'ui.addremove.added'
1059 label = 'ui.addremove.added'
1060 else:
1060 else:
1061 status = _('removing %s\n') % m.uipath(abs)
1061 status = _('removing %s\n') % m.uipath(abs)
1062 label = 'ui.addremove.removed'
1062 label = 'ui.addremove.removed'
1063 repo.ui.status(status, label=label)
1063 repo.ui.status(status, label=label)
1064
1064
1065 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1065 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1066 similarity)
1066 similarity)
1067
1067
1068 if not dry_run:
1068 if not dry_run:
1069 _markchanges(repo, unknown + forgotten, deleted, renames)
1069 _markchanges(repo, unknown + forgotten, deleted, renames)
1070
1070
1071 for f in rejected:
1071 for f in rejected:
1072 if f in m.files():
1072 if f in m.files():
1073 return 1
1073 return 1
1074 return ret
1074 return ret
1075
1075
1076 def marktouched(repo, files, similarity=0.0):
1076 def marktouched(repo, files, similarity=0.0):
1077 '''Assert that files have somehow been operated upon. files are relative to
1077 '''Assert that files have somehow been operated upon. files are relative to
1078 the repo root.'''
1078 the repo root.'''
1079 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1079 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1080 rejected = []
1080 rejected = []
1081
1081
1082 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1082 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1083
1083
1084 if repo.ui.verbose:
1084 if repo.ui.verbose:
1085 unknownset = set(unknown + forgotten)
1085 unknownset = set(unknown + forgotten)
1086 toprint = unknownset.copy()
1086 toprint = unknownset.copy()
1087 toprint.update(deleted)
1087 toprint.update(deleted)
1088 for abs in sorted(toprint):
1088 for abs in sorted(toprint):
1089 if abs in unknownset:
1089 if abs in unknownset:
1090 status = _('adding %s\n') % abs
1090 status = _('adding %s\n') % abs
1091 else:
1091 else:
1092 status = _('removing %s\n') % abs
1092 status = _('removing %s\n') % abs
1093 repo.ui.status(status)
1093 repo.ui.status(status)
1094
1094
1095 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1095 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1096 similarity)
1096 similarity)
1097
1097
1098 _markchanges(repo, unknown + forgotten, deleted, renames)
1098 _markchanges(repo, unknown + forgotten, deleted, renames)
1099
1099
1100 for f in rejected:
1100 for f in rejected:
1101 if f in m.files():
1101 if f in m.files():
1102 return 1
1102 return 1
1103 return 0
1103 return 0
1104
1104
1105 def _interestingfiles(repo, matcher):
1105 def _interestingfiles(repo, matcher):
1106 '''Walk dirstate with matcher, looking for files that addremove would care
1106 '''Walk dirstate with matcher, looking for files that addremove would care
1107 about.
1107 about.
1108
1108
1109 This is different from dirstate.status because it doesn't care about
1109 This is different from dirstate.status because it doesn't care about
1110 whether files are modified or clean.'''
1110 whether files are modified or clean.'''
1111 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1111 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1112 audit_path = pathutil.pathauditor(repo.root, cached=True)
1112 audit_path = pathutil.pathauditor(repo.root, cached=True)
1113
1113
1114 ctx = repo[None]
1114 ctx = repo[None]
1115 dirstate = repo.dirstate
1115 dirstate = repo.dirstate
1116 matcher = repo.narrowmatch(matcher, includeexact=True)
1116 matcher = repo.narrowmatch(matcher, includeexact=True)
1117 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1117 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1118 unknown=True, ignored=False, full=False)
1118 unknown=True, ignored=False, full=False)
1119 for abs, st in walkresults.iteritems():
1119 for abs, st in walkresults.iteritems():
1120 dstate = dirstate[abs]
1120 dstate = dirstate[abs]
1121 if dstate == '?' and audit_path.check(abs):
1121 if dstate == '?' and audit_path.check(abs):
1122 unknown.append(abs)
1122 unknown.append(abs)
1123 elif dstate != 'r' and not st:
1123 elif dstate != 'r' and not st:
1124 deleted.append(abs)
1124 deleted.append(abs)
1125 elif dstate == 'r' and st:
1125 elif dstate == 'r' and st:
1126 forgotten.append(abs)
1126 forgotten.append(abs)
1127 # for finding renames
1127 # for finding renames
1128 elif dstate == 'r' and not st:
1128 elif dstate == 'r' and not st:
1129 removed.append(abs)
1129 removed.append(abs)
1130 elif dstate == 'a':
1130 elif dstate == 'a':
1131 added.append(abs)
1131 added.append(abs)
1132
1132
1133 return added, unknown, deleted, removed, forgotten
1133 return added, unknown, deleted, removed, forgotten
1134
1134
1135 def _findrenames(repo, matcher, added, removed, similarity):
1135 def _findrenames(repo, matcher, added, removed, similarity):
1136 '''Find renames from removed files to added ones.'''
1136 '''Find renames from removed files to added ones.'''
1137 renames = {}
1137 renames = {}
1138 if similarity > 0:
1138 if similarity > 0:
1139 for old, new, score in similar.findrenames(repo, added, removed,
1139 for old, new, score in similar.findrenames(repo, added, removed,
1140 similarity):
1140 similarity):
1141 if (repo.ui.verbose or not matcher.exact(old)
1141 if (repo.ui.verbose or not matcher.exact(old)
1142 or not matcher.exact(new)):
1142 or not matcher.exact(new)):
1143 repo.ui.status(_('recording removal of %s as rename to %s '
1143 repo.ui.status(_('recording removal of %s as rename to %s '
1144 '(%d%% similar)\n') %
1144 '(%d%% similar)\n') %
1145 (matcher.rel(old), matcher.rel(new),
1145 (matcher.rel(old), matcher.rel(new),
1146 score * 100))
1146 score * 100))
1147 renames[new] = old
1147 renames[new] = old
1148 return renames
1148 return renames
1149
1149
1150 def _markchanges(repo, unknown, deleted, renames):
1150 def _markchanges(repo, unknown, deleted, renames):
1151 '''Marks the files in unknown as added, the files in deleted as removed,
1151 '''Marks the files in unknown as added, the files in deleted as removed,
1152 and the files in renames as copied.'''
1152 and the files in renames as copied.'''
1153 wctx = repo[None]
1153 wctx = repo[None]
1154 with repo.wlock():
1154 with repo.wlock():
1155 wctx.forget(deleted)
1155 wctx.forget(deleted)
1156 wctx.add(unknown)
1156 wctx.add(unknown)
1157 for new, old in renames.iteritems():
1157 for new, old in renames.iteritems():
1158 wctx.copy(old, new)
1158 wctx.copy(old, new)
1159
1159
1160 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1160 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1161 """Update the dirstate to reflect the intent of copying src to dst. For
1161 """Update the dirstate to reflect the intent of copying src to dst. For
1162 different reasons it might not end with dst being marked as copied from src.
1162 different reasons it might not end with dst being marked as copied from src.
1163 """
1163 """
1164 origsrc = repo.dirstate.copied(src) or src
1164 origsrc = repo.dirstate.copied(src) or src
1165 if dst == origsrc: # copying back a copy?
1165 if dst == origsrc: # copying back a copy?
1166 if repo.dirstate[dst] not in 'mn' and not dryrun:
1166 if repo.dirstate[dst] not in 'mn' and not dryrun:
1167 repo.dirstate.normallookup(dst)
1167 repo.dirstate.normallookup(dst)
1168 else:
1168 else:
1169 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1169 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1170 if not ui.quiet:
1170 if not ui.quiet:
1171 ui.warn(_("%s has not been committed yet, so no copy "
1171 ui.warn(_("%s has not been committed yet, so no copy "
1172 "data will be stored for %s.\n")
1172 "data will be stored for %s.\n")
1173 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1173 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1174 if repo.dirstate[dst] in '?r' and not dryrun:
1174 if repo.dirstate[dst] in '?r' and not dryrun:
1175 wctx.add([dst])
1175 wctx.add([dst])
1176 elif not dryrun:
1176 elif not dryrun:
1177 wctx.copy(origsrc, dst)
1177 wctx.copy(origsrc, dst)
1178
1178
1179 def writerequires(opener, requirements):
1179 def writerequires(opener, requirements):
1180 with opener('requires', 'w', atomictemp=True) as fp:
1180 with opener('requires', 'w', atomictemp=True) as fp:
1181 for r in sorted(requirements):
1181 for r in sorted(requirements):
1182 fp.write("%s\n" % r)
1182 fp.write("%s\n" % r)
1183
1183
1184 class filecachesubentry(object):
1184 class filecachesubentry(object):
1185 def __init__(self, path, stat):
1185 def __init__(self, path, stat):
1186 self.path = path
1186 self.path = path
1187 self.cachestat = None
1187 self.cachestat = None
1188 self._cacheable = None
1188 self._cacheable = None
1189
1189
1190 if stat:
1190 if stat:
1191 self.cachestat = filecachesubentry.stat(self.path)
1191 self.cachestat = filecachesubentry.stat(self.path)
1192
1192
1193 if self.cachestat:
1193 if self.cachestat:
1194 self._cacheable = self.cachestat.cacheable()
1194 self._cacheable = self.cachestat.cacheable()
1195 else:
1195 else:
1196 # None means we don't know yet
1196 # None means we don't know yet
1197 self._cacheable = None
1197 self._cacheable = None
1198
1198
1199 def refresh(self):
1199 def refresh(self):
1200 if self.cacheable():
1200 if self.cacheable():
1201 self.cachestat = filecachesubentry.stat(self.path)
1201 self.cachestat = filecachesubentry.stat(self.path)
1202
1202
1203 def cacheable(self):
1203 def cacheable(self):
1204 if self._cacheable is not None:
1204 if self._cacheable is not None:
1205 return self._cacheable
1205 return self._cacheable
1206
1206
1207 # we don't know yet, assume it is for now
1207 # we don't know yet, assume it is for now
1208 return True
1208 return True
1209
1209
1210 def changed(self):
1210 def changed(self):
1211 # no point in going further if we can't cache it
1211 # no point in going further if we can't cache it
1212 if not self.cacheable():
1212 if not self.cacheable():
1213 return True
1213 return True
1214
1214
1215 newstat = filecachesubentry.stat(self.path)
1215 newstat = filecachesubentry.stat(self.path)
1216
1216
1217 # we may not know if it's cacheable yet, check again now
1217 # we may not know if it's cacheable yet, check again now
1218 if newstat and self._cacheable is None:
1218 if newstat and self._cacheable is None:
1219 self._cacheable = newstat.cacheable()
1219 self._cacheable = newstat.cacheable()
1220
1220
1221 # check again
1221 # check again
1222 if not self._cacheable:
1222 if not self._cacheable:
1223 return True
1223 return True
1224
1224
1225 if self.cachestat != newstat:
1225 if self.cachestat != newstat:
1226 self.cachestat = newstat
1226 self.cachestat = newstat
1227 return True
1227 return True
1228 else:
1228 else:
1229 return False
1229 return False
1230
1230
1231 @staticmethod
1231 @staticmethod
1232 def stat(path):
1232 def stat(path):
1233 try:
1233 try:
1234 return util.cachestat(path)
1234 return util.cachestat(path)
1235 except OSError as e:
1235 except OSError as e:
1236 if e.errno != errno.ENOENT:
1236 if e.errno != errno.ENOENT:
1237 raise
1237 raise
1238
1238
1239 class filecacheentry(object):
1239 class filecacheentry(object):
1240 def __init__(self, paths, stat=True):
1240 def __init__(self, paths, stat=True):
1241 self._entries = []
1241 self._entries = []
1242 for path in paths:
1242 for path in paths:
1243 self._entries.append(filecachesubentry(path, stat))
1243 self._entries.append(filecachesubentry(path, stat))
1244
1244
1245 def changed(self):
1245 def changed(self):
1246 '''true if any entry has changed'''
1246 '''true if any entry has changed'''
1247 for entry in self._entries:
1247 for entry in self._entries:
1248 if entry.changed():
1248 if entry.changed():
1249 return True
1249 return True
1250 return False
1250 return False
1251
1251
1252 def refresh(self):
1252 def refresh(self):
1253 for entry in self._entries:
1253 for entry in self._entries:
1254 entry.refresh()
1254 entry.refresh()
1255
1255
1256 class filecache(object):
1256 class filecache(object):
1257 """A property like decorator that tracks files under .hg/ for updates.
1257 """A property like decorator that tracks files under .hg/ for updates.
1258
1258
1259 On first access, the files defined as arguments are stat()ed and the
1259 On first access, the files defined as arguments are stat()ed and the
1260 results cached. The decorated function is called. The results are stashed
1260 results cached. The decorated function is called. The results are stashed
1261 away in a ``_filecache`` dict on the object whose method is decorated.
1261 away in a ``_filecache`` dict on the object whose method is decorated.
1262
1262
1263 On subsequent access, the cached result is used as it is set to the
1263 On subsequent access, the cached result is used as it is set to the
1264 instance dictionary.
1264 instance dictionary.
1265
1265
1266 On external property set/delete operations, the caller must update the
1266 On external property set/delete operations, the caller must update the
1267 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1267 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1268 instead of directly setting <attr>.
1268 instead of directly setting <attr>.
1269
1269
1270 When using the property API, the cached data is always used if available.
1270 When using the property API, the cached data is always used if available.
1271 No stat() is performed to check if the file has changed.
1271 No stat() is performed to check if the file has changed.
1272
1272
1273 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1273 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1274 can populate an entry before the property's getter is called. In this case,
1274 can populate an entry before the property's getter is called. In this case,
1275 entries in ``_filecache`` will be used during property operations,
1275 entries in ``_filecache`` will be used during property operations,
1276 if available. If the underlying file changes, it is up to external callers
1276 if available. If the underlying file changes, it is up to external callers
1277 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1277 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1278 method result as well as possibly calling ``del obj._filecache[attr]`` to
1278 method result as well as possibly calling ``del obj._filecache[attr]`` to
1279 remove the ``filecacheentry``.
1279 remove the ``filecacheentry``.
1280 """
1280 """
1281
1281
1282 def __init__(self, *paths):
1282 def __init__(self, *paths):
1283 self.paths = paths
1283 self.paths = paths
1284
1284
1285 def join(self, obj, fname):
1285 def join(self, obj, fname):
1286 """Used to compute the runtime path of a cached file.
1286 """Used to compute the runtime path of a cached file.
1287
1287
1288 Users should subclass filecache and provide their own version of this
1288 Users should subclass filecache and provide their own version of this
1289 function to call the appropriate join function on 'obj' (an instance
1289 function to call the appropriate join function on 'obj' (an instance
1290 of the class that its member function was decorated).
1290 of the class that its member function was decorated).
1291 """
1291 """
1292 raise NotImplementedError
1292 raise NotImplementedError
1293
1293
1294 def __call__(self, func):
1294 def __call__(self, func):
1295 self.func = func
1295 self.func = func
1296 self.sname = func.__name__
1296 self.sname = func.__name__
1297 self.name = pycompat.sysbytes(self.sname)
1297 self.name = pycompat.sysbytes(self.sname)
1298 return self
1298 return self
1299
1299
1300 def __get__(self, obj, type=None):
1300 def __get__(self, obj, type=None):
1301 # if accessed on the class, return the descriptor itself.
1301 # if accessed on the class, return the descriptor itself.
1302 if obj is None:
1302 if obj is None:
1303 return self
1303 return self
1304
1304
1305 assert self.sname not in obj.__dict__
1305 assert self.sname not in obj.__dict__
1306
1306
1307 entry = obj._filecache.get(self.name)
1307 entry = obj._filecache.get(self.name)
1308
1308
1309 if entry:
1309 if entry:
1310 if entry.changed():
1310 if entry.changed():
1311 entry.obj = self.func(obj)
1311 entry.obj = self.func(obj)
1312 else:
1312 else:
1313 paths = [self.join(obj, path) for path in self.paths]
1313 paths = [self.join(obj, path) for path in self.paths]
1314
1314
1315 # We stat -before- creating the object so our cache doesn't lie if
1315 # We stat -before- creating the object so our cache doesn't lie if
1316 # a writer modified between the time we read and stat
1316 # a writer modified between the time we read and stat
1317 entry = filecacheentry(paths, True)
1317 entry = filecacheentry(paths, True)
1318 entry.obj = self.func(obj)
1318 entry.obj = self.func(obj)
1319
1319
1320 obj._filecache[self.name] = entry
1320 obj._filecache[self.name] = entry
1321
1321
1322 obj.__dict__[self.sname] = entry.obj
1322 obj.__dict__[self.sname] = entry.obj
1323 return entry.obj
1323 return entry.obj
1324
1324
1325 # don't implement __set__(), which would make __dict__ lookup as slow as
1325 # don't implement __set__(), which would make __dict__ lookup as slow as
1326 # function call.
1326 # function call.
1327
1327
1328 def set(self, obj, value):
1328 def set(self, obj, value):
1329 if self.name not in obj._filecache:
1329 if self.name not in obj._filecache:
1330 # we add an entry for the missing value because X in __dict__
1330 # we add an entry for the missing value because X in __dict__
1331 # implies X in _filecache
1331 # implies X in _filecache
1332 paths = [self.join(obj, path) for path in self.paths]
1332 paths = [self.join(obj, path) for path in self.paths]
1333 ce = filecacheentry(paths, False)
1333 ce = filecacheentry(paths, False)
1334 obj._filecache[self.name] = ce
1334 obj._filecache[self.name] = ce
1335 else:
1335 else:
1336 ce = obj._filecache[self.name]
1336 ce = obj._filecache[self.name]
1337
1337
1338 ce.obj = value # update cached copy
1338 ce.obj = value # update cached copy
1339 obj.__dict__[self.sname] = value # update copy returned by obj.x
1339 obj.__dict__[self.sname] = value # update copy returned by obj.x
1340
1340
1341 def extdatasource(repo, source):
1341 def extdatasource(repo, source):
1342 """Gather a map of rev -> value dict from the specified source
1342 """Gather a map of rev -> value dict from the specified source
1343
1343
1344 A source spec is treated as a URL, with a special case shell: type
1344 A source spec is treated as a URL, with a special case shell: type
1345 for parsing the output from a shell command.
1345 for parsing the output from a shell command.
1346
1346
1347 The data is parsed as a series of newline-separated records where
1347 The data is parsed as a series of newline-separated records where
1348 each record is a revision specifier optionally followed by a space
1348 each record is a revision specifier optionally followed by a space
1349 and a freeform string value. If the revision is known locally, it
1349 and a freeform string value. If the revision is known locally, it
1350 is converted to a rev, otherwise the record is skipped.
1350 is converted to a rev, otherwise the record is skipped.
1351
1351
1352 Note that both key and value are treated as UTF-8 and converted to
1352 Note that both key and value are treated as UTF-8 and converted to
1353 the local encoding. This allows uniformity between local and
1353 the local encoding. This allows uniformity between local and
1354 remote data sources.
1354 remote data sources.
1355 """
1355 """
1356
1356
1357 spec = repo.ui.config("extdata", source)
1357 spec = repo.ui.config("extdata", source)
1358 if not spec:
1358 if not spec:
1359 raise error.Abort(_("unknown extdata source '%s'") % source)
1359 raise error.Abort(_("unknown extdata source '%s'") % source)
1360
1360
1361 data = {}
1361 data = {}
1362 src = proc = None
1362 src = proc = None
1363 try:
1363 try:
1364 if spec.startswith("shell:"):
1364 if spec.startswith("shell:"):
1365 # external commands should be run relative to the repo root
1365 # external commands should be run relative to the repo root
1366 cmd = spec[6:]
1366 cmd = spec[6:]
1367 proc = subprocess.Popen(procutil.tonativestr(cmd),
1367 proc = subprocess.Popen(procutil.tonativestr(cmd),
1368 shell=True, bufsize=-1,
1368 shell=True, bufsize=-1,
1369 close_fds=procutil.closefds,
1369 close_fds=procutil.closefds,
1370 stdout=subprocess.PIPE,
1370 stdout=subprocess.PIPE,
1371 cwd=procutil.tonativestr(repo.root))
1371 cwd=procutil.tonativestr(repo.root))
1372 src = proc.stdout
1372 src = proc.stdout
1373 else:
1373 else:
1374 # treat as a URL or file
1374 # treat as a URL or file
1375 src = url.open(repo.ui, spec)
1375 src = url.open(repo.ui, spec)
1376 for l in src:
1376 for l in src:
1377 if " " in l:
1377 if " " in l:
1378 k, v = l.strip().split(" ", 1)
1378 k, v = l.strip().split(" ", 1)
1379 else:
1379 else:
1380 k, v = l.strip(), ""
1380 k, v = l.strip(), ""
1381
1381
1382 k = encoding.tolocal(k)
1382 k = encoding.tolocal(k)
1383 try:
1383 try:
1384 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1384 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1385 except (error.LookupError, error.RepoLookupError):
1385 except (error.LookupError, error.RepoLookupError):
1386 pass # we ignore data for nodes that don't exist locally
1386 pass # we ignore data for nodes that don't exist locally
1387 finally:
1387 finally:
1388 if proc:
1388 if proc:
1389 proc.communicate()
1389 proc.communicate()
1390 if src:
1390 if src:
1391 src.close()
1391 src.close()
1392 if proc and proc.returncode != 0:
1392 if proc and proc.returncode != 0:
1393 raise error.Abort(_("extdata command '%s' failed: %s")
1393 raise error.Abort(_("extdata command '%s' failed: %s")
1394 % (cmd, procutil.explainexit(proc.returncode)))
1394 % (cmd, procutil.explainexit(proc.returncode)))
1395
1395
1396 return data
1396 return data
1397
1397
1398 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1398 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1399 if lock is None:
1399 if lock is None:
1400 raise error.LockInheritanceContractViolation(
1400 raise error.LockInheritanceContractViolation(
1401 'lock can only be inherited while held')
1401 'lock can only be inherited while held')
1402 if environ is None:
1402 if environ is None:
1403 environ = {}
1403 environ = {}
1404 with lock.inherit() as locker:
1404 with lock.inherit() as locker:
1405 environ[envvar] = locker
1405 environ[envvar] = locker
1406 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1406 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1407
1407
1408 def wlocksub(repo, cmd, *args, **kwargs):
1408 def wlocksub(repo, cmd, *args, **kwargs):
1409 """run cmd as a subprocess that allows inheriting repo's wlock
1409 """run cmd as a subprocess that allows inheriting repo's wlock
1410
1410
1411 This can only be called while the wlock is held. This takes all the
1411 This can only be called while the wlock is held. This takes all the
1412 arguments that ui.system does, and returns the exit code of the
1412 arguments that ui.system does, and returns the exit code of the
1413 subprocess."""
1413 subprocess."""
1414 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1414 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1415 **kwargs)
1415 **kwargs)
1416
1416
1417 class progress(object):
1417 class progress(object):
1418 def __init__(self, ui, topic, unit="", total=None):
1418 def __init__(self, ui, topic, unit="", total=None):
1419 self.ui = ui
1419 self.ui = ui
1420 self.pos = 0
1420 self.pos = 0
1421 self.topic = topic
1421 self.topic = topic
1422 self.unit = unit
1422 self.unit = unit
1423 self.total = total
1423 self.total = total
1424
1424
1425 def __enter__(self):
1425 def __enter__(self):
1426 return self
1426 return self
1427
1427
1428 def __exit__(self, exc_type, exc_value, exc_tb):
1428 def __exit__(self, exc_type, exc_value, exc_tb):
1429 self.complete()
1429 self.complete()
1430
1430
1431 def update(self, pos, item="", total=None):
1431 def update(self, pos, item="", total=None):
1432 assert pos is not None
1432 assert pos is not None
1433 if total:
1433 if total:
1434 self.total = total
1434 self.total = total
1435 self.pos = pos
1435 self.pos = pos
1436 self._print(item)
1436 self._print(item)
1437
1437
1438 def increment(self, step=1, item="", total=None):
1438 def increment(self, step=1, item="", total=None):
1439 self.update(self.pos + step, item, total)
1439 self.update(self.pos + step, item, total)
1440
1440
1441 def complete(self):
1441 def complete(self):
1442 self.ui.progress(self.topic, None)
1442 self.pos = None
1443 self.unit = ""
1444 self.total = None
1445 self._print("")
1443
1446
1444 def _print(self, item):
1447 def _print(self, item):
1445 self.ui.progress(self.topic, self.pos, item, self.unit,
1448 if getattr(self.ui._fmsgerr, 'structured', False):
1446 self.total)
1449 # channel for machine-readable output with metadata, just send
1450 # raw information
1451 # TODO: consider porting some useful information (e.g. estimated
1452 # time) from progbar. we might want to support update delay to
1453 # reduce the cost of transferring progress messages.
1454 self.ui._fmsgerr.write(None, type=b'progress', topic=self.topic,
1455 pos=self.pos, item=item, unit=self.unit,
1456 total=self.total)
1457 elif self.ui._progbar is not None:
1458 self.ui._progbar.progress(self.topic, self.pos, item=item,
1459 unit=self.unit, total=self.total)
1460
1461 # Looking up progress.debug in tight loops is expensive. The value
1462 # is cached on the progbar object and we can avoid the lookup in
1463 # the common case where a progbar is active.
1464 if self.pos is None or not self.ui._progbar.debug:
1465 return
1466
1467 # Keep this logic in sync with above.
1468 if self.pos is None or not self.ui.configbool('progress', 'debug'):
1469 return
1470
1471 if self.unit:
1472 unit = ' ' + self.unit
1473 if item:
1474 item = ' ' + item
1475
1476 if self.total:
1477 pct = 100.0 * self.pos / self.total
1478 self.ui.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1479 % (self.topic, item, self.pos, self.total, unit, pct))
1480 else:
1481 self.ui.debug('%s:%s %d%s\n' % (self.topic, item, self.pos, unit))
1447
1482
1448 def gdinitconfig(ui):
1483 def gdinitconfig(ui):
1449 """helper function to know if a repo should be created as general delta
1484 """helper function to know if a repo should be created as general delta
1450 """
1485 """
1451 # experimental config: format.generaldelta
1486 # experimental config: format.generaldelta
1452 return (ui.configbool('format', 'generaldelta')
1487 return (ui.configbool('format', 'generaldelta')
1453 or ui.configbool('format', 'usegeneraldelta'))
1488 or ui.configbool('format', 'usegeneraldelta'))
1454
1489
1455 def gddeltaconfig(ui):
1490 def gddeltaconfig(ui):
1456 """helper function to know if incoming delta should be optimised
1491 """helper function to know if incoming delta should be optimised
1457 """
1492 """
1458 # experimental config: format.generaldelta
1493 # experimental config: format.generaldelta
1459 return ui.configbool('format', 'generaldelta')
1494 return ui.configbool('format', 'generaldelta')
1460
1495
1461 class simplekeyvaluefile(object):
1496 class simplekeyvaluefile(object):
1462 """A simple file with key=value lines
1497 """A simple file with key=value lines
1463
1498
1464 Keys must be alphanumerics and start with a letter, values must not
1499 Keys must be alphanumerics and start with a letter, values must not
1465 contain '\n' characters"""
1500 contain '\n' characters"""
1466 firstlinekey = '__firstline'
1501 firstlinekey = '__firstline'
1467
1502
1468 def __init__(self, vfs, path, keys=None):
1503 def __init__(self, vfs, path, keys=None):
1469 self.vfs = vfs
1504 self.vfs = vfs
1470 self.path = path
1505 self.path = path
1471
1506
1472 def read(self, firstlinenonkeyval=False):
1507 def read(self, firstlinenonkeyval=False):
1473 """Read the contents of a simple key-value file
1508 """Read the contents of a simple key-value file
1474
1509
1475 'firstlinenonkeyval' indicates whether the first line of file should
1510 'firstlinenonkeyval' indicates whether the first line of file should
1476 be treated as a key-value pair or reuturned fully under the
1511 be treated as a key-value pair or reuturned fully under the
1477 __firstline key."""
1512 __firstline key."""
1478 lines = self.vfs.readlines(self.path)
1513 lines = self.vfs.readlines(self.path)
1479 d = {}
1514 d = {}
1480 if firstlinenonkeyval:
1515 if firstlinenonkeyval:
1481 if not lines:
1516 if not lines:
1482 e = _("empty simplekeyvalue file")
1517 e = _("empty simplekeyvalue file")
1483 raise error.CorruptedState(e)
1518 raise error.CorruptedState(e)
1484 # we don't want to include '\n' in the __firstline
1519 # we don't want to include '\n' in the __firstline
1485 d[self.firstlinekey] = lines[0][:-1]
1520 d[self.firstlinekey] = lines[0][:-1]
1486 del lines[0]
1521 del lines[0]
1487
1522
1488 try:
1523 try:
1489 # the 'if line.strip()' part prevents us from failing on empty
1524 # the 'if line.strip()' part prevents us from failing on empty
1490 # lines which only contain '\n' therefore are not skipped
1525 # lines which only contain '\n' therefore are not skipped
1491 # by 'if line'
1526 # by 'if line'
1492 updatedict = dict(line[:-1].split('=', 1) for line in lines
1527 updatedict = dict(line[:-1].split('=', 1) for line in lines
1493 if line.strip())
1528 if line.strip())
1494 if self.firstlinekey in updatedict:
1529 if self.firstlinekey in updatedict:
1495 e = _("%r can't be used as a key")
1530 e = _("%r can't be used as a key")
1496 raise error.CorruptedState(e % self.firstlinekey)
1531 raise error.CorruptedState(e % self.firstlinekey)
1497 d.update(updatedict)
1532 d.update(updatedict)
1498 except ValueError as e:
1533 except ValueError as e:
1499 raise error.CorruptedState(str(e))
1534 raise error.CorruptedState(str(e))
1500 return d
1535 return d
1501
1536
1502 def write(self, data, firstline=None):
1537 def write(self, data, firstline=None):
1503 """Write key=>value mapping to a file
1538 """Write key=>value mapping to a file
1504 data is a dict. Keys must be alphanumerical and start with a letter.
1539 data is a dict. Keys must be alphanumerical and start with a letter.
1505 Values must not contain newline characters.
1540 Values must not contain newline characters.
1506
1541
1507 If 'firstline' is not None, it is written to file before
1542 If 'firstline' is not None, it is written to file before
1508 everything else, as it is, not in a key=value form"""
1543 everything else, as it is, not in a key=value form"""
1509 lines = []
1544 lines = []
1510 if firstline is not None:
1545 if firstline is not None:
1511 lines.append('%s\n' % firstline)
1546 lines.append('%s\n' % firstline)
1512
1547
1513 for k, v in data.items():
1548 for k, v in data.items():
1514 if k == self.firstlinekey:
1549 if k == self.firstlinekey:
1515 e = "key name '%s' is reserved" % self.firstlinekey
1550 e = "key name '%s' is reserved" % self.firstlinekey
1516 raise error.ProgrammingError(e)
1551 raise error.ProgrammingError(e)
1517 if not k[0:1].isalpha():
1552 if not k[0:1].isalpha():
1518 e = "keys must start with a letter in a key-value file"
1553 e = "keys must start with a letter in a key-value file"
1519 raise error.ProgrammingError(e)
1554 raise error.ProgrammingError(e)
1520 if not k.isalnum():
1555 if not k.isalnum():
1521 e = "invalid key name in a simple key-value file"
1556 e = "invalid key name in a simple key-value file"
1522 raise error.ProgrammingError(e)
1557 raise error.ProgrammingError(e)
1523 if '\n' in v:
1558 if '\n' in v:
1524 e = "invalid value in a simple key-value file"
1559 e = "invalid value in a simple key-value file"
1525 raise error.ProgrammingError(e)
1560 raise error.ProgrammingError(e)
1526 lines.append("%s=%s\n" % (k, v))
1561 lines.append("%s=%s\n" % (k, v))
1527 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1562 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1528 fp.write(''.join(lines))
1563 fp.write(''.join(lines))
1529
1564
1530 _reportobsoletedsource = [
1565 _reportobsoletedsource = [
1531 'debugobsolete',
1566 'debugobsolete',
1532 'pull',
1567 'pull',
1533 'push',
1568 'push',
1534 'serve',
1569 'serve',
1535 'unbundle',
1570 'unbundle',
1536 ]
1571 ]
1537
1572
1538 _reportnewcssource = [
1573 _reportnewcssource = [
1539 'pull',
1574 'pull',
1540 'unbundle',
1575 'unbundle',
1541 ]
1576 ]
1542
1577
1543 def prefetchfiles(repo, revs, match):
1578 def prefetchfiles(repo, revs, match):
1544 """Invokes the registered file prefetch functions, allowing extensions to
1579 """Invokes the registered file prefetch functions, allowing extensions to
1545 ensure the corresponding files are available locally, before the command
1580 ensure the corresponding files are available locally, before the command
1546 uses them."""
1581 uses them."""
1547 if match:
1582 if match:
1548 # The command itself will complain about files that don't exist, so
1583 # The command itself will complain about files that don't exist, so
1549 # don't duplicate the message.
1584 # don't duplicate the message.
1550 match = matchmod.badmatch(match, lambda fn, msg: None)
1585 match = matchmod.badmatch(match, lambda fn, msg: None)
1551 else:
1586 else:
1552 match = matchall(repo)
1587 match = matchall(repo)
1553
1588
1554 fileprefetchhooks(repo, revs, match)
1589 fileprefetchhooks(repo, revs, match)
1555
1590
1556 # a list of (repo, revs, match) prefetch functions
1591 # a list of (repo, revs, match) prefetch functions
1557 fileprefetchhooks = util.hooks()
1592 fileprefetchhooks = util.hooks()
1558
1593
1559 # A marker that tells the evolve extension to suppress its own reporting
1594 # A marker that tells the evolve extension to suppress its own reporting
1560 _reportstroubledchangesets = True
1595 _reportstroubledchangesets = True
1561
1596
1562 def registersummarycallback(repo, otr, txnname=''):
1597 def registersummarycallback(repo, otr, txnname=''):
1563 """register a callback to issue a summary after the transaction is closed
1598 """register a callback to issue a summary after the transaction is closed
1564 """
1599 """
1565 def txmatch(sources):
1600 def txmatch(sources):
1566 return any(txnname.startswith(source) for source in sources)
1601 return any(txnname.startswith(source) for source in sources)
1567
1602
1568 categories = []
1603 categories = []
1569
1604
1570 def reportsummary(func):
1605 def reportsummary(func):
1571 """decorator for report callbacks."""
1606 """decorator for report callbacks."""
1572 # The repoview life cycle is shorter than the one of the actual
1607 # The repoview life cycle is shorter than the one of the actual
1573 # underlying repository. So the filtered object can die before the
1608 # underlying repository. So the filtered object can die before the
1574 # weakref is used leading to troubles. We keep a reference to the
1609 # weakref is used leading to troubles. We keep a reference to the
1575 # unfiltered object and restore the filtering when retrieving the
1610 # unfiltered object and restore the filtering when retrieving the
1576 # repository through the weakref.
1611 # repository through the weakref.
1577 filtername = repo.filtername
1612 filtername = repo.filtername
1578 reporef = weakref.ref(repo.unfiltered())
1613 reporef = weakref.ref(repo.unfiltered())
1579 def wrapped(tr):
1614 def wrapped(tr):
1580 repo = reporef()
1615 repo = reporef()
1581 if filtername:
1616 if filtername:
1582 repo = repo.filtered(filtername)
1617 repo = repo.filtered(filtername)
1583 func(repo, tr)
1618 func(repo, tr)
1584 newcat = '%02i-txnreport' % len(categories)
1619 newcat = '%02i-txnreport' % len(categories)
1585 otr.addpostclose(newcat, wrapped)
1620 otr.addpostclose(newcat, wrapped)
1586 categories.append(newcat)
1621 categories.append(newcat)
1587 return wrapped
1622 return wrapped
1588
1623
1589 if txmatch(_reportobsoletedsource):
1624 if txmatch(_reportobsoletedsource):
1590 @reportsummary
1625 @reportsummary
1591 def reportobsoleted(repo, tr):
1626 def reportobsoleted(repo, tr):
1592 obsoleted = obsutil.getobsoleted(repo, tr)
1627 obsoleted = obsutil.getobsoleted(repo, tr)
1593 if obsoleted:
1628 if obsoleted:
1594 repo.ui.status(_('obsoleted %i changesets\n')
1629 repo.ui.status(_('obsoleted %i changesets\n')
1595 % len(obsoleted))
1630 % len(obsoleted))
1596
1631
1597 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1632 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1598 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1633 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1599 instabilitytypes = [
1634 instabilitytypes = [
1600 ('orphan', 'orphan'),
1635 ('orphan', 'orphan'),
1601 ('phase-divergent', 'phasedivergent'),
1636 ('phase-divergent', 'phasedivergent'),
1602 ('content-divergent', 'contentdivergent'),
1637 ('content-divergent', 'contentdivergent'),
1603 ]
1638 ]
1604
1639
1605 def getinstabilitycounts(repo):
1640 def getinstabilitycounts(repo):
1606 filtered = repo.changelog.filteredrevs
1641 filtered = repo.changelog.filteredrevs
1607 counts = {}
1642 counts = {}
1608 for instability, revset in instabilitytypes:
1643 for instability, revset in instabilitytypes:
1609 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1644 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1610 filtered)
1645 filtered)
1611 return counts
1646 return counts
1612
1647
1613 oldinstabilitycounts = getinstabilitycounts(repo)
1648 oldinstabilitycounts = getinstabilitycounts(repo)
1614 @reportsummary
1649 @reportsummary
1615 def reportnewinstabilities(repo, tr):
1650 def reportnewinstabilities(repo, tr):
1616 newinstabilitycounts = getinstabilitycounts(repo)
1651 newinstabilitycounts = getinstabilitycounts(repo)
1617 for instability, revset in instabilitytypes:
1652 for instability, revset in instabilitytypes:
1618 delta = (newinstabilitycounts[instability] -
1653 delta = (newinstabilitycounts[instability] -
1619 oldinstabilitycounts[instability])
1654 oldinstabilitycounts[instability])
1620 msg = getinstabilitymessage(delta, instability)
1655 msg = getinstabilitymessage(delta, instability)
1621 if msg:
1656 if msg:
1622 repo.ui.warn(msg)
1657 repo.ui.warn(msg)
1623
1658
1624 if txmatch(_reportnewcssource):
1659 if txmatch(_reportnewcssource):
1625 @reportsummary
1660 @reportsummary
1626 def reportnewcs(repo, tr):
1661 def reportnewcs(repo, tr):
1627 """Report the range of new revisions pulled/unbundled."""
1662 """Report the range of new revisions pulled/unbundled."""
1628 origrepolen = tr.changes.get('origrepolen', len(repo))
1663 origrepolen = tr.changes.get('origrepolen', len(repo))
1629 unfi = repo.unfiltered()
1664 unfi = repo.unfiltered()
1630 if origrepolen >= len(unfi):
1665 if origrepolen >= len(unfi):
1631 return
1666 return
1632
1667
1633 # Compute the bounds of new visible revisions' range.
1668 # Compute the bounds of new visible revisions' range.
1634 revs = smartset.spanset(repo, start=origrepolen)
1669 revs = smartset.spanset(repo, start=origrepolen)
1635 if revs:
1670 if revs:
1636 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1671 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1637
1672
1638 if minrev == maxrev:
1673 if minrev == maxrev:
1639 revrange = minrev
1674 revrange = minrev
1640 else:
1675 else:
1641 revrange = '%s:%s' % (minrev, maxrev)
1676 revrange = '%s:%s' % (minrev, maxrev)
1642 draft = len(repo.revs('%ld and draft()', revs))
1677 draft = len(repo.revs('%ld and draft()', revs))
1643 secret = len(repo.revs('%ld and secret()', revs))
1678 secret = len(repo.revs('%ld and secret()', revs))
1644 if not (draft or secret):
1679 if not (draft or secret):
1645 msg = _('new changesets %s\n') % revrange
1680 msg = _('new changesets %s\n') % revrange
1646 elif draft and secret:
1681 elif draft and secret:
1647 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1682 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1648 msg %= (revrange, draft, secret)
1683 msg %= (revrange, draft, secret)
1649 elif draft:
1684 elif draft:
1650 msg = _('new changesets %s (%d drafts)\n')
1685 msg = _('new changesets %s (%d drafts)\n')
1651 msg %= (revrange, draft)
1686 msg %= (revrange, draft)
1652 elif secret:
1687 elif secret:
1653 msg = _('new changesets %s (%d secrets)\n')
1688 msg = _('new changesets %s (%d secrets)\n')
1654 msg %= (revrange, secret)
1689 msg %= (revrange, secret)
1655 else:
1690 else:
1656 errormsg = 'entered unreachable condition'
1691 errormsg = 'entered unreachable condition'
1657 raise error.ProgrammingError(errormsg)
1692 raise error.ProgrammingError(errormsg)
1658 repo.ui.status(msg)
1693 repo.ui.status(msg)
1659
1694
1660 # search new changesets directly pulled as obsolete
1695 # search new changesets directly pulled as obsolete
1661 duplicates = tr.changes.get('revduplicates', ())
1696 duplicates = tr.changes.get('revduplicates', ())
1662 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1697 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1663 origrepolen, duplicates)
1698 origrepolen, duplicates)
1664 cl = repo.changelog
1699 cl = repo.changelog
1665 extinctadded = [r for r in obsadded if r not in cl]
1700 extinctadded = [r for r in obsadded if r not in cl]
1666 if extinctadded:
1701 if extinctadded:
1667 # They are not just obsolete, but obsolete and invisible
1702 # They are not just obsolete, but obsolete and invisible
1668 # we call them "extinct" internally but the terms have not been
1703 # we call them "extinct" internally but the terms have not been
1669 # exposed to users.
1704 # exposed to users.
1670 msg = '(%d other changesets obsolete on arrival)\n'
1705 msg = '(%d other changesets obsolete on arrival)\n'
1671 repo.ui.status(msg % len(extinctadded))
1706 repo.ui.status(msg % len(extinctadded))
1672
1707
1673 @reportsummary
1708 @reportsummary
1674 def reportphasechanges(repo, tr):
1709 def reportphasechanges(repo, tr):
1675 """Report statistics of phase changes for changesets pre-existing
1710 """Report statistics of phase changes for changesets pre-existing
1676 pull/unbundle.
1711 pull/unbundle.
1677 """
1712 """
1678 origrepolen = tr.changes.get('origrepolen', len(repo))
1713 origrepolen = tr.changes.get('origrepolen', len(repo))
1679 phasetracking = tr.changes.get('phases', {})
1714 phasetracking = tr.changes.get('phases', {})
1680 if not phasetracking:
1715 if not phasetracking:
1681 return
1716 return
1682 published = [
1717 published = [
1683 rev for rev, (old, new) in phasetracking.iteritems()
1718 rev for rev, (old, new) in phasetracking.iteritems()
1684 if new == phases.public and rev < origrepolen
1719 if new == phases.public and rev < origrepolen
1685 ]
1720 ]
1686 if not published:
1721 if not published:
1687 return
1722 return
1688 repo.ui.status(_('%d local changesets published\n')
1723 repo.ui.status(_('%d local changesets published\n')
1689 % len(published))
1724 % len(published))
1690
1725
1691 def getinstabilitymessage(delta, instability):
1726 def getinstabilitymessage(delta, instability):
1692 """function to return the message to show warning about new instabilities
1727 """function to return the message to show warning about new instabilities
1693
1728
1694 exists as a separate function so that extension can wrap to show more
1729 exists as a separate function so that extension can wrap to show more
1695 information like how to fix instabilities"""
1730 information like how to fix instabilities"""
1696 if delta > 0:
1731 if delta > 0:
1697 return _('%i new %s changesets\n') % (delta, instability)
1732 return _('%i new %s changesets\n') % (delta, instability)
1698
1733
1699 def nodesummaries(repo, nodes, maxnumnodes=4):
1734 def nodesummaries(repo, nodes, maxnumnodes=4):
1700 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1735 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1701 return ' '.join(short(h) for h in nodes)
1736 return ' '.join(short(h) for h in nodes)
1702 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1737 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1703 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1738 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1704
1739
1705 def enforcesinglehead(repo, tr, desc):
1740 def enforcesinglehead(repo, tr, desc):
1706 """check that no named branch has multiple heads"""
1741 """check that no named branch has multiple heads"""
1707 if desc in ('strip', 'repair'):
1742 if desc in ('strip', 'repair'):
1708 # skip the logic during strip
1743 # skip the logic during strip
1709 return
1744 return
1710 visible = repo.filtered('visible')
1745 visible = repo.filtered('visible')
1711 # possible improvement: we could restrict the check to affected branch
1746 # possible improvement: we could restrict the check to affected branch
1712 for name, heads in visible.branchmap().iteritems():
1747 for name, heads in visible.branchmap().iteritems():
1713 if len(heads) > 1:
1748 if len(heads) > 1:
1714 msg = _('rejecting multiple heads on branch "%s"')
1749 msg = _('rejecting multiple heads on branch "%s"')
1715 msg %= name
1750 msg %= name
1716 hint = _('%d heads: %s')
1751 hint = _('%d heads: %s')
1717 hint %= (len(heads), nodesummaries(repo, heads))
1752 hint %= (len(heads), nodesummaries(repo, heads))
1718 raise error.Abort(msg, hint=hint)
1753 raise error.Abort(msg, hint=hint)
1719
1754
1720 def wrapconvertsink(sink):
1755 def wrapconvertsink(sink):
1721 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1756 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1722 before it is used, whether or not the convert extension was formally loaded.
1757 before it is used, whether or not the convert extension was formally loaded.
1723 """
1758 """
1724 return sink
1759 return sink
1725
1760
1726 def unhidehashlikerevs(repo, specs, hiddentype):
1761 def unhidehashlikerevs(repo, specs, hiddentype):
1727 """parse the user specs and unhide changesets whose hash or revision number
1762 """parse the user specs and unhide changesets whose hash or revision number
1728 is passed.
1763 is passed.
1729
1764
1730 hiddentype can be: 1) 'warn': warn while unhiding changesets
1765 hiddentype can be: 1) 'warn': warn while unhiding changesets
1731 2) 'nowarn': don't warn while unhiding changesets
1766 2) 'nowarn': don't warn while unhiding changesets
1732
1767
1733 returns a repo object with the required changesets unhidden
1768 returns a repo object with the required changesets unhidden
1734 """
1769 """
1735 if not repo.filtername or not repo.ui.configbool('experimental',
1770 if not repo.filtername or not repo.ui.configbool('experimental',
1736 'directaccess'):
1771 'directaccess'):
1737 return repo
1772 return repo
1738
1773
1739 if repo.filtername not in ('visible', 'visible-hidden'):
1774 if repo.filtername not in ('visible', 'visible-hidden'):
1740 return repo
1775 return repo
1741
1776
1742 symbols = set()
1777 symbols = set()
1743 for spec in specs:
1778 for spec in specs:
1744 try:
1779 try:
1745 tree = revsetlang.parse(spec)
1780 tree = revsetlang.parse(spec)
1746 except error.ParseError: # will be reported by scmutil.revrange()
1781 except error.ParseError: # will be reported by scmutil.revrange()
1747 continue
1782 continue
1748
1783
1749 symbols.update(revsetlang.gethashlikesymbols(tree))
1784 symbols.update(revsetlang.gethashlikesymbols(tree))
1750
1785
1751 if not symbols:
1786 if not symbols:
1752 return repo
1787 return repo
1753
1788
1754 revs = _getrevsfromsymbols(repo, symbols)
1789 revs = _getrevsfromsymbols(repo, symbols)
1755
1790
1756 if not revs:
1791 if not revs:
1757 return repo
1792 return repo
1758
1793
1759 if hiddentype == 'warn':
1794 if hiddentype == 'warn':
1760 unfi = repo.unfiltered()
1795 unfi = repo.unfiltered()
1761 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1796 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1762 repo.ui.warn(_("warning: accessing hidden changesets for write "
1797 repo.ui.warn(_("warning: accessing hidden changesets for write "
1763 "operation: %s\n") % revstr)
1798 "operation: %s\n") % revstr)
1764
1799
1765 # we have to use new filtername to separate branch/tags cache until we can
1800 # we have to use new filtername to separate branch/tags cache until we can
1766 # disbale these cache when revisions are dynamically pinned.
1801 # disbale these cache when revisions are dynamically pinned.
1767 return repo.filtered('visible-hidden', revs)
1802 return repo.filtered('visible-hidden', revs)
1768
1803
1769 def _getrevsfromsymbols(repo, symbols):
1804 def _getrevsfromsymbols(repo, symbols):
1770 """parse the list of symbols and returns a set of revision numbers of hidden
1805 """parse the list of symbols and returns a set of revision numbers of hidden
1771 changesets present in symbols"""
1806 changesets present in symbols"""
1772 revs = set()
1807 revs = set()
1773 unfi = repo.unfiltered()
1808 unfi = repo.unfiltered()
1774 unficl = unfi.changelog
1809 unficl = unfi.changelog
1775 cl = repo.changelog
1810 cl = repo.changelog
1776 tiprev = len(unficl)
1811 tiprev = len(unficl)
1777 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1812 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1778 for s in symbols:
1813 for s in symbols:
1779 try:
1814 try:
1780 n = int(s)
1815 n = int(s)
1781 if n <= tiprev:
1816 if n <= tiprev:
1782 if not allowrevnums:
1817 if not allowrevnums:
1783 continue
1818 continue
1784 else:
1819 else:
1785 if n not in cl:
1820 if n not in cl:
1786 revs.add(n)
1821 revs.add(n)
1787 continue
1822 continue
1788 except ValueError:
1823 except ValueError:
1789 pass
1824 pass
1790
1825
1791 try:
1826 try:
1792 s = resolvehexnodeidprefix(unfi, s)
1827 s = resolvehexnodeidprefix(unfi, s)
1793 except (error.LookupError, error.WdirUnsupported):
1828 except (error.LookupError, error.WdirUnsupported):
1794 s = None
1829 s = None
1795
1830
1796 if s is not None:
1831 if s is not None:
1797 rev = unficl.rev(s)
1832 rev = unficl.rev(s)
1798 if rev not in cl:
1833 if rev not in cl:
1799 revs.add(rev)
1834 revs.add(rev)
1800
1835
1801 return revs
1836 return revs
1802
1837
1803 def bookmarkrevs(repo, mark):
1838 def bookmarkrevs(repo, mark):
1804 """
1839 """
1805 Select revisions reachable by a given bookmark
1840 Select revisions reachable by a given bookmark
1806 """
1841 """
1807 return repo.revs("ancestors(bookmark(%s)) - "
1842 return repo.revs("ancestors(bookmark(%s)) - "
1808 "ancestors(head() and not bookmark(%s)) - "
1843 "ancestors(head() and not bookmark(%s)) - "
1809 "ancestors(bookmark() and not bookmark(%s))",
1844 "ancestors(bookmark() and not bookmark(%s))",
1810 mark, mark, mark)
1845 mark, mark, mark)
@@ -1,2056 +1,2028 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import 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 traceback
21 import traceback
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import hex
24 from .node import hex
25
25
26 from . import (
26 from . import (
27 color,
27 color,
28 config,
28 config,
29 configitems,
29 configitems,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 loggingutil,
33 loggingutil,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40 from .utils import (
40 from .utils import (
41 dateutil,
41 dateutil,
42 procutil,
42 procutil,
43 stringutil,
43 stringutil,
44 )
44 )
45
45
46 urlreq = util.urlreq
46 urlreq = util.urlreq
47
47
48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
49 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
50 if not c.isalnum())
50 if not c.isalnum())
51
51
52 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
53 tweakrc = b"""
53 tweakrc = b"""
54 [ui]
54 [ui]
55 # The rollback command is dangerous. As a rule, don't use it.
55 # The rollback command is dangerous. As a rule, don't use it.
56 rollback = False
56 rollback = False
57 # Make `hg status` report copy information
57 # Make `hg status` report copy information
58 statuscopies = yes
58 statuscopies = yes
59 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 # Prefer curses UIs when available. Revert to plain-text with `text`.
60 interface = curses
60 interface = curses
61
61
62 [commands]
62 [commands]
63 # Grep working directory by default.
63 # Grep working directory by default.
64 grep.all-files = True
64 grep.all-files = True
65 # Make `hg status` emit cwd-relative paths by default.
65 # Make `hg status` emit cwd-relative paths by default.
66 status.relative = yes
66 status.relative = yes
67 # Refuse to perform an `hg update` that would cause a file content merge
67 # Refuse to perform an `hg update` that would cause a file content merge
68 update.check = noconflict
68 update.check = noconflict
69 # Show conflicts information in `hg status`
69 # Show conflicts information in `hg status`
70 status.verbose = True
70 status.verbose = True
71
71
72 [diff]
72 [diff]
73 git = 1
73 git = 1
74 showfunc = 1
74 showfunc = 1
75 word-diff = 1
75 word-diff = 1
76 """
76 """
77
77
78 samplehgrcs = {
78 samplehgrcs = {
79 'user':
79 'user':
80 b"""# example user config (see 'hg help config' for more info)
80 b"""# example user config (see 'hg help config' for more info)
81 [ui]
81 [ui]
82 # name and email, e.g.
82 # name and email, e.g.
83 # username = Jane Doe <jdoe@example.com>
83 # username = Jane Doe <jdoe@example.com>
84 username =
84 username =
85
85
86 # We recommend enabling tweakdefaults to get slight improvements to
86 # We recommend enabling tweakdefaults to get slight improvements to
87 # the UI over time. Make sure to set HGPLAIN in the environment when
87 # the UI over time. Make sure to set HGPLAIN in the environment when
88 # writing scripts!
88 # writing scripts!
89 # tweakdefaults = True
89 # tweakdefaults = True
90
90
91 # uncomment to disable color in command output
91 # uncomment to disable color in command output
92 # (see 'hg help color' for details)
92 # (see 'hg help color' for details)
93 # color = never
93 # color = never
94
94
95 # uncomment to disable command output pagination
95 # uncomment to disable command output pagination
96 # (see 'hg help pager' for details)
96 # (see 'hg help pager' for details)
97 # paginate = never
97 # paginate = never
98
98
99 [extensions]
99 [extensions]
100 # uncomment these lines to enable some popular extensions
100 # uncomment these lines to enable some popular extensions
101 # (see 'hg help extensions' for more info)
101 # (see 'hg help extensions' for more info)
102 #
102 #
103 # churn =
103 # churn =
104 """,
104 """,
105
105
106 'cloned':
106 'cloned':
107 b"""# example repository config (see 'hg help config' for more info)
107 b"""# example repository config (see 'hg help config' for more info)
108 [paths]
108 [paths]
109 default = %s
109 default = %s
110
110
111 # path aliases to other clones of this repo in URLs or filesystem paths
111 # path aliases to other clones of this repo in URLs or filesystem paths
112 # (see 'hg help config.paths' for more info)
112 # (see 'hg help config.paths' for more info)
113 #
113 #
114 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
114 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
116 # my-clone = /home/jdoe/jdoes-clone
116 # my-clone = /home/jdoe/jdoes-clone
117
117
118 [ui]
118 [ui]
119 # name and email (local to this repository, optional), e.g.
119 # name and email (local to this repository, optional), e.g.
120 # username = Jane Doe <jdoe@example.com>
120 # username = Jane Doe <jdoe@example.com>
121 """,
121 """,
122
122
123 'local':
123 'local':
124 b"""# example repository config (see 'hg help config' for more info)
124 b"""# example repository config (see 'hg help config' for more info)
125 [paths]
125 [paths]
126 # path aliases to other clones of this repo in URLs or filesystem paths
126 # path aliases to other clones of this repo in URLs or filesystem paths
127 # (see 'hg help config.paths' for more info)
127 # (see 'hg help config.paths' for more info)
128 #
128 #
129 # default = http://example.com/hg/example-repo
129 # default = http://example.com/hg/example-repo
130 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
130 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
132 # my-clone = /home/jdoe/jdoes-clone
132 # my-clone = /home/jdoe/jdoes-clone
133
133
134 [ui]
134 [ui]
135 # name and email (local to this repository, optional), e.g.
135 # name and email (local to this repository, optional), e.g.
136 # username = Jane Doe <jdoe@example.com>
136 # username = Jane Doe <jdoe@example.com>
137 """,
137 """,
138
138
139 'global':
139 'global':
140 b"""# example system-wide hg config (see 'hg help config' for more info)
140 b"""# example system-wide hg config (see 'hg help config' for more info)
141
141
142 [ui]
142 [ui]
143 # uncomment to disable color in command output
143 # uncomment to disable color in command output
144 # (see 'hg help color' for details)
144 # (see 'hg help color' for details)
145 # color = never
145 # color = never
146
146
147 # uncomment to disable command output pagination
147 # uncomment to disable command output pagination
148 # (see 'hg help pager' for details)
148 # (see 'hg help pager' for details)
149 # paginate = never
149 # paginate = never
150
150
151 [extensions]
151 [extensions]
152 # uncomment these lines to enable some popular extensions
152 # uncomment these lines to enable some popular extensions
153 # (see 'hg help extensions' for more info)
153 # (see 'hg help extensions' for more info)
154 #
154 #
155 # blackbox =
155 # blackbox =
156 # churn =
156 # churn =
157 """,
157 """,
158 }
158 }
159
159
160 def _maybestrurl(maybebytes):
160 def _maybestrurl(maybebytes):
161 return pycompat.rapply(pycompat.strurl, maybebytes)
161 return pycompat.rapply(pycompat.strurl, maybebytes)
162
162
163 def _maybebytesurl(maybestr):
163 def _maybebytesurl(maybestr):
164 return pycompat.rapply(pycompat.bytesurl, maybestr)
164 return pycompat.rapply(pycompat.bytesurl, maybestr)
165
165
166 class httppasswordmgrdbproxy(object):
166 class httppasswordmgrdbproxy(object):
167 """Delays loading urllib2 until it's needed."""
167 """Delays loading urllib2 until it's needed."""
168 def __init__(self):
168 def __init__(self):
169 self._mgr = None
169 self._mgr = None
170
170
171 def _get_mgr(self):
171 def _get_mgr(self):
172 if self._mgr is None:
172 if self._mgr is None:
173 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
173 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
174 return self._mgr
174 return self._mgr
175
175
176 def add_password(self, realm, uris, user, passwd):
176 def add_password(self, realm, uris, user, passwd):
177 return self._get_mgr().add_password(
177 return self._get_mgr().add_password(
178 _maybestrurl(realm), _maybestrurl(uris),
178 _maybestrurl(realm), _maybestrurl(uris),
179 _maybestrurl(user), _maybestrurl(passwd))
179 _maybestrurl(user), _maybestrurl(passwd))
180
180
181 def find_user_password(self, realm, uri):
181 def find_user_password(self, realm, uri):
182 mgr = self._get_mgr()
182 mgr = self._get_mgr()
183 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
183 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
184 _maybestrurl(uri)))
184 _maybestrurl(uri)))
185
185
186 def _catchterm(*args):
186 def _catchterm(*args):
187 raise error.SignalInterrupt
187 raise error.SignalInterrupt
188
188
189 # unique object used to detect no default value has been provided when
189 # unique object used to detect no default value has been provided when
190 # retrieving configuration value.
190 # retrieving configuration value.
191 _unset = object()
191 _unset = object()
192
192
193 # _reqexithandlers: callbacks run at the end of a request
193 # _reqexithandlers: callbacks run at the end of a request
194 _reqexithandlers = []
194 _reqexithandlers = []
195
195
196 class ui(object):
196 class ui(object):
197 def __init__(self, src=None):
197 def __init__(self, src=None):
198 """Create a fresh new ui object if no src given
198 """Create a fresh new ui object if no src given
199
199
200 Use uimod.ui.load() to create a ui which knows global and user configs.
200 Use uimod.ui.load() to create a ui which knows global and user configs.
201 In most cases, you should use ui.copy() to create a copy of an existing
201 In most cases, you should use ui.copy() to create a copy of an existing
202 ui object.
202 ui object.
203 """
203 """
204 # _buffers: used for temporary capture of output
204 # _buffers: used for temporary capture of output
205 self._buffers = []
205 self._buffers = []
206 # 3-tuple describing how each buffer in the stack behaves.
206 # 3-tuple describing how each buffer in the stack behaves.
207 # Values are (capture stderr, capture subprocesses, apply labels).
207 # Values are (capture stderr, capture subprocesses, apply labels).
208 self._bufferstates = []
208 self._bufferstates = []
209 # When a buffer is active, defines whether we are expanding labels.
209 # When a buffer is active, defines whether we are expanding labels.
210 # This exists to prevent an extra list lookup.
210 # This exists to prevent an extra list lookup.
211 self._bufferapplylabels = None
211 self._bufferapplylabels = None
212 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
212 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
213 self._reportuntrusted = True
213 self._reportuntrusted = True
214 self._knownconfig = configitems.coreitems
214 self._knownconfig = configitems.coreitems
215 self._ocfg = config.config() # overlay
215 self._ocfg = config.config() # overlay
216 self._tcfg = config.config() # trusted
216 self._tcfg = config.config() # trusted
217 self._ucfg = config.config() # untrusted
217 self._ucfg = config.config() # untrusted
218 self._trustusers = set()
218 self._trustusers = set()
219 self._trustgroups = set()
219 self._trustgroups = set()
220 self.callhooks = True
220 self.callhooks = True
221 # Insecure server connections requested.
221 # Insecure server connections requested.
222 self.insecureconnections = False
222 self.insecureconnections = False
223 # Blocked time
223 # Blocked time
224 self.logblockedtimes = False
224 self.logblockedtimes = False
225 # color mode: see mercurial/color.py for possible value
225 # color mode: see mercurial/color.py for possible value
226 self._colormode = None
226 self._colormode = None
227 self._terminfoparams = {}
227 self._terminfoparams = {}
228 self._styles = {}
228 self._styles = {}
229 self._uninterruptible = False
229 self._uninterruptible = False
230
230
231 if src:
231 if src:
232 self._fout = src._fout
232 self._fout = src._fout
233 self._ferr = src._ferr
233 self._ferr = src._ferr
234 self._fin = src._fin
234 self._fin = src._fin
235 self._fmsg = src._fmsg
235 self._fmsg = src._fmsg
236 self._fmsgout = src._fmsgout
236 self._fmsgout = src._fmsgout
237 self._fmsgerr = src._fmsgerr
237 self._fmsgerr = src._fmsgerr
238 self._finoutredirected = src._finoutredirected
238 self._finoutredirected = src._finoutredirected
239 self._loggers = src._loggers.copy()
239 self._loggers = src._loggers.copy()
240 self.pageractive = src.pageractive
240 self.pageractive = src.pageractive
241 self._disablepager = src._disablepager
241 self._disablepager = src._disablepager
242 self._tweaked = src._tweaked
242 self._tweaked = src._tweaked
243
243
244 self._tcfg = src._tcfg.copy()
244 self._tcfg = src._tcfg.copy()
245 self._ucfg = src._ucfg.copy()
245 self._ucfg = src._ucfg.copy()
246 self._ocfg = src._ocfg.copy()
246 self._ocfg = src._ocfg.copy()
247 self._trustusers = src._trustusers.copy()
247 self._trustusers = src._trustusers.copy()
248 self._trustgroups = src._trustgroups.copy()
248 self._trustgroups = src._trustgroups.copy()
249 self.environ = src.environ
249 self.environ = src.environ
250 self.callhooks = src.callhooks
250 self.callhooks = src.callhooks
251 self.insecureconnections = src.insecureconnections
251 self.insecureconnections = src.insecureconnections
252 self._colormode = src._colormode
252 self._colormode = src._colormode
253 self._terminfoparams = src._terminfoparams.copy()
253 self._terminfoparams = src._terminfoparams.copy()
254 self._styles = src._styles.copy()
254 self._styles = src._styles.copy()
255
255
256 self.fixconfig()
256 self.fixconfig()
257
257
258 self.httppasswordmgrdb = src.httppasswordmgrdb
258 self.httppasswordmgrdb = src.httppasswordmgrdb
259 self._blockedtimes = src._blockedtimes
259 self._blockedtimes = src._blockedtimes
260 else:
260 else:
261 self._fout = procutil.stdout
261 self._fout = procutil.stdout
262 self._ferr = procutil.stderr
262 self._ferr = procutil.stderr
263 self._fin = procutil.stdin
263 self._fin = procutil.stdin
264 self._fmsg = None
264 self._fmsg = None
265 self._fmsgout = self.fout # configurable
265 self._fmsgout = self.fout # configurable
266 self._fmsgerr = self.ferr # configurable
266 self._fmsgerr = self.ferr # configurable
267 self._finoutredirected = False
267 self._finoutredirected = False
268 self._loggers = {}
268 self._loggers = {}
269 self.pageractive = False
269 self.pageractive = False
270 self._disablepager = False
270 self._disablepager = False
271 self._tweaked = False
271 self._tweaked = False
272
272
273 # shared read-only environment
273 # shared read-only environment
274 self.environ = encoding.environ
274 self.environ = encoding.environ
275
275
276 self.httppasswordmgrdb = httppasswordmgrdbproxy()
276 self.httppasswordmgrdb = httppasswordmgrdbproxy()
277 self._blockedtimes = collections.defaultdict(int)
277 self._blockedtimes = collections.defaultdict(int)
278
278
279 allowed = self.configlist('experimental', 'exportableenviron')
279 allowed = self.configlist('experimental', 'exportableenviron')
280 if '*' in allowed:
280 if '*' in allowed:
281 self._exportableenviron = self.environ
281 self._exportableenviron = self.environ
282 else:
282 else:
283 self._exportableenviron = {}
283 self._exportableenviron = {}
284 for k in allowed:
284 for k in allowed:
285 if k in self.environ:
285 if k in self.environ:
286 self._exportableenviron[k] = self.environ[k]
286 self._exportableenviron[k] = self.environ[k]
287
287
288 @classmethod
288 @classmethod
289 def load(cls):
289 def load(cls):
290 """Create a ui and load global and user configs"""
290 """Create a ui and load global and user configs"""
291 u = cls()
291 u = cls()
292 # we always trust global config files and environment variables
292 # we always trust global config files and environment variables
293 for t, f in rcutil.rccomponents():
293 for t, f in rcutil.rccomponents():
294 if t == 'path':
294 if t == 'path':
295 u.readconfig(f, trust=True)
295 u.readconfig(f, trust=True)
296 elif t == 'items':
296 elif t == 'items':
297 sections = set()
297 sections = set()
298 for section, name, value, source in f:
298 for section, name, value, source in f:
299 # do not set u._ocfg
299 # do not set u._ocfg
300 # XXX clean this up once immutable config object is a thing
300 # XXX clean this up once immutable config object is a thing
301 u._tcfg.set(section, name, value, source)
301 u._tcfg.set(section, name, value, source)
302 u._ucfg.set(section, name, value, source)
302 u._ucfg.set(section, name, value, source)
303 sections.add(section)
303 sections.add(section)
304 for section in sections:
304 for section in sections:
305 u.fixconfig(section=section)
305 u.fixconfig(section=section)
306 else:
306 else:
307 raise error.ProgrammingError('unknown rctype: %s' % t)
307 raise error.ProgrammingError('unknown rctype: %s' % t)
308 u._maybetweakdefaults()
308 u._maybetweakdefaults()
309 return u
309 return u
310
310
311 def _maybetweakdefaults(self):
311 def _maybetweakdefaults(self):
312 if not self.configbool('ui', 'tweakdefaults'):
312 if not self.configbool('ui', 'tweakdefaults'):
313 return
313 return
314 if self._tweaked or self.plain('tweakdefaults'):
314 if self._tweaked or self.plain('tweakdefaults'):
315 return
315 return
316
316
317 # Note: it is SUPER IMPORTANT that you set self._tweaked to
317 # Note: it is SUPER IMPORTANT that you set self._tweaked to
318 # True *before* any calls to setconfig(), otherwise you'll get
318 # True *before* any calls to setconfig(), otherwise you'll get
319 # infinite recursion between setconfig and this method.
319 # infinite recursion between setconfig and this method.
320 #
320 #
321 # TODO: We should extract an inner method in setconfig() to
321 # TODO: We should extract an inner method in setconfig() to
322 # avoid this weirdness.
322 # avoid this weirdness.
323 self._tweaked = True
323 self._tweaked = True
324 tmpcfg = config.config()
324 tmpcfg = config.config()
325 tmpcfg.parse('<tweakdefaults>', tweakrc)
325 tmpcfg.parse('<tweakdefaults>', tweakrc)
326 for section in tmpcfg:
326 for section in tmpcfg:
327 for name, value in tmpcfg.items(section):
327 for name, value in tmpcfg.items(section):
328 if not self.hasconfig(section, name):
328 if not self.hasconfig(section, name):
329 self.setconfig(section, name, value, "<tweakdefaults>")
329 self.setconfig(section, name, value, "<tweakdefaults>")
330
330
331 def copy(self):
331 def copy(self):
332 return self.__class__(self)
332 return self.__class__(self)
333
333
334 def resetstate(self):
334 def resetstate(self):
335 """Clear internal state that shouldn't persist across commands"""
335 """Clear internal state that shouldn't persist across commands"""
336 if self._progbar:
336 if self._progbar:
337 self._progbar.resetstate() # reset last-print time of progress bar
337 self._progbar.resetstate() # reset last-print time of progress bar
338 self.httppasswordmgrdb = httppasswordmgrdbproxy()
338 self.httppasswordmgrdb = httppasswordmgrdbproxy()
339
339
340 @contextlib.contextmanager
340 @contextlib.contextmanager
341 def timeblockedsection(self, key):
341 def timeblockedsection(self, key):
342 # this is open-coded below - search for timeblockedsection to find them
342 # this is open-coded below - search for timeblockedsection to find them
343 starttime = util.timer()
343 starttime = util.timer()
344 try:
344 try:
345 yield
345 yield
346 finally:
346 finally:
347 self._blockedtimes[key + '_blocked'] += \
347 self._blockedtimes[key + '_blocked'] += \
348 (util.timer() - starttime) * 1000
348 (util.timer() - starttime) * 1000
349
349
350 @contextlib.contextmanager
350 @contextlib.contextmanager
351 def uninterruptible(self):
351 def uninterruptible(self):
352 """Mark an operation as unsafe.
352 """Mark an operation as unsafe.
353
353
354 Most operations on a repository are safe to interrupt, but a
354 Most operations on a repository are safe to interrupt, but a
355 few are risky (for example repair.strip). This context manager
355 few are risky (for example repair.strip). This context manager
356 lets you advise Mercurial that something risky is happening so
356 lets you advise Mercurial that something risky is happening so
357 that control-C etc can be blocked if desired.
357 that control-C etc can be blocked if desired.
358 """
358 """
359 enabled = self.configbool('experimental', 'nointerrupt')
359 enabled = self.configbool('experimental', 'nointerrupt')
360 if (enabled and
360 if (enabled and
361 self.configbool('experimental', 'nointerrupt-interactiveonly')):
361 self.configbool('experimental', 'nointerrupt-interactiveonly')):
362 enabled = self.interactive()
362 enabled = self.interactive()
363 if self._uninterruptible or not enabled:
363 if self._uninterruptible or not enabled:
364 # if nointerrupt support is turned off, the process isn't
364 # if nointerrupt support is turned off, the process isn't
365 # interactive, or we're already in an uninterruptible
365 # interactive, or we're already in an uninterruptible
366 # block, do nothing.
366 # block, do nothing.
367 yield
367 yield
368 return
368 return
369 def warn():
369 def warn():
370 self.warn(_("shutting down cleanly\n"))
370 self.warn(_("shutting down cleanly\n"))
371 self.warn(
371 self.warn(
372 _("press ^C again to terminate immediately (dangerous)\n"))
372 _("press ^C again to terminate immediately (dangerous)\n"))
373 return True
373 return True
374 with procutil.uninterruptible(warn):
374 with procutil.uninterruptible(warn):
375 try:
375 try:
376 self._uninterruptible = True
376 self._uninterruptible = True
377 yield
377 yield
378 finally:
378 finally:
379 self._uninterruptible = False
379 self._uninterruptible = False
380
380
381 def formatter(self, topic, opts):
381 def formatter(self, topic, opts):
382 return formatter.formatter(self, self, topic, opts)
382 return formatter.formatter(self, self, topic, opts)
383
383
384 def _trusted(self, fp, f):
384 def _trusted(self, fp, f):
385 st = util.fstat(fp)
385 st = util.fstat(fp)
386 if util.isowner(st):
386 if util.isowner(st):
387 return True
387 return True
388
388
389 tusers, tgroups = self._trustusers, self._trustgroups
389 tusers, tgroups = self._trustusers, self._trustgroups
390 if '*' in tusers or '*' in tgroups:
390 if '*' in tusers or '*' in tgroups:
391 return True
391 return True
392
392
393 user = util.username(st.st_uid)
393 user = util.username(st.st_uid)
394 group = util.groupname(st.st_gid)
394 group = util.groupname(st.st_gid)
395 if user in tusers or group in tgroups or user == util.username():
395 if user in tusers or group in tgroups or user == util.username():
396 return True
396 return True
397
397
398 if self._reportuntrusted:
398 if self._reportuntrusted:
399 self.warn(_('not trusting file %s from untrusted '
399 self.warn(_('not trusting file %s from untrusted '
400 'user %s, group %s\n') % (f, user, group))
400 'user %s, group %s\n') % (f, user, group))
401 return False
401 return False
402
402
403 def readconfig(self, filename, root=None, trust=False,
403 def readconfig(self, filename, root=None, trust=False,
404 sections=None, remap=None):
404 sections=None, remap=None):
405 try:
405 try:
406 fp = open(filename, r'rb')
406 fp = open(filename, r'rb')
407 except IOError:
407 except IOError:
408 if not sections: # ignore unless we were looking for something
408 if not sections: # ignore unless we were looking for something
409 return
409 return
410 raise
410 raise
411
411
412 cfg = config.config()
412 cfg = config.config()
413 trusted = sections or trust or self._trusted(fp, filename)
413 trusted = sections or trust or self._trusted(fp, filename)
414
414
415 try:
415 try:
416 cfg.read(filename, fp, sections=sections, remap=remap)
416 cfg.read(filename, fp, sections=sections, remap=remap)
417 fp.close()
417 fp.close()
418 except error.ConfigError as inst:
418 except error.ConfigError as inst:
419 if trusted:
419 if trusted:
420 raise
420 raise
421 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
421 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
422
422
423 if self.plain():
423 if self.plain():
424 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
424 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
425 'logtemplate', 'message-output', 'statuscopies', 'style',
425 'logtemplate', 'message-output', 'statuscopies', 'style',
426 'traceback', 'verbose'):
426 'traceback', 'verbose'):
427 if k in cfg['ui']:
427 if k in cfg['ui']:
428 del cfg['ui'][k]
428 del cfg['ui'][k]
429 for k, v in cfg.items('defaults'):
429 for k, v in cfg.items('defaults'):
430 del cfg['defaults'][k]
430 del cfg['defaults'][k]
431 for k, v in cfg.items('commands'):
431 for k, v in cfg.items('commands'):
432 del cfg['commands'][k]
432 del cfg['commands'][k]
433 # Don't remove aliases from the configuration if in the exceptionlist
433 # Don't remove aliases from the configuration if in the exceptionlist
434 if self.plain('alias'):
434 if self.plain('alias'):
435 for k, v in cfg.items('alias'):
435 for k, v in cfg.items('alias'):
436 del cfg['alias'][k]
436 del cfg['alias'][k]
437 if self.plain('revsetalias'):
437 if self.plain('revsetalias'):
438 for k, v in cfg.items('revsetalias'):
438 for k, v in cfg.items('revsetalias'):
439 del cfg['revsetalias'][k]
439 del cfg['revsetalias'][k]
440 if self.plain('templatealias'):
440 if self.plain('templatealias'):
441 for k, v in cfg.items('templatealias'):
441 for k, v in cfg.items('templatealias'):
442 del cfg['templatealias'][k]
442 del cfg['templatealias'][k]
443
443
444 if trusted:
444 if trusted:
445 self._tcfg.update(cfg)
445 self._tcfg.update(cfg)
446 self._tcfg.update(self._ocfg)
446 self._tcfg.update(self._ocfg)
447 self._ucfg.update(cfg)
447 self._ucfg.update(cfg)
448 self._ucfg.update(self._ocfg)
448 self._ucfg.update(self._ocfg)
449
449
450 if root is None:
450 if root is None:
451 root = os.path.expanduser('~')
451 root = os.path.expanduser('~')
452 self.fixconfig(root=root)
452 self.fixconfig(root=root)
453
453
454 def fixconfig(self, root=None, section=None):
454 def fixconfig(self, root=None, section=None):
455 if section in (None, 'paths'):
455 if section in (None, 'paths'):
456 # expand vars and ~
456 # expand vars and ~
457 # translate paths relative to root (or home) into absolute paths
457 # translate paths relative to root (or home) into absolute paths
458 root = root or encoding.getcwd()
458 root = root or encoding.getcwd()
459 for c in self._tcfg, self._ucfg, self._ocfg:
459 for c in self._tcfg, self._ucfg, self._ocfg:
460 for n, p in c.items('paths'):
460 for n, p in c.items('paths'):
461 # Ignore sub-options.
461 # Ignore sub-options.
462 if ':' in n:
462 if ':' in n:
463 continue
463 continue
464 if not p:
464 if not p:
465 continue
465 continue
466 if '%%' in p:
466 if '%%' in p:
467 s = self.configsource('paths', n) or 'none'
467 s = self.configsource('paths', n) or 'none'
468 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
468 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
469 % (n, p, s))
469 % (n, p, s))
470 p = p.replace('%%', '%')
470 p = p.replace('%%', '%')
471 p = util.expandpath(p)
471 p = util.expandpath(p)
472 if not util.hasscheme(p) and not os.path.isabs(p):
472 if not util.hasscheme(p) and not os.path.isabs(p):
473 p = os.path.normpath(os.path.join(root, p))
473 p = os.path.normpath(os.path.join(root, p))
474 c.set("paths", n, p)
474 c.set("paths", n, p)
475
475
476 if section in (None, 'ui'):
476 if section in (None, 'ui'):
477 # update ui options
477 # update ui options
478 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
478 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
479 self.debugflag = self.configbool('ui', 'debug')
479 self.debugflag = self.configbool('ui', 'debug')
480 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
480 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
481 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
481 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
482 if self.verbose and self.quiet:
482 if self.verbose and self.quiet:
483 self.quiet = self.verbose = False
483 self.quiet = self.verbose = False
484 self._reportuntrusted = self.debugflag or self.configbool("ui",
484 self._reportuntrusted = self.debugflag or self.configbool("ui",
485 "report_untrusted")
485 "report_untrusted")
486 self.tracebackflag = self.configbool('ui', 'traceback')
486 self.tracebackflag = self.configbool('ui', 'traceback')
487 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
487 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
488
488
489 if section in (None, 'trusted'):
489 if section in (None, 'trusted'):
490 # update trust information
490 # update trust information
491 self._trustusers.update(self.configlist('trusted', 'users'))
491 self._trustusers.update(self.configlist('trusted', 'users'))
492 self._trustgroups.update(self.configlist('trusted', 'groups'))
492 self._trustgroups.update(self.configlist('trusted', 'groups'))
493
493
494 if section in (None, b'devel', b'ui') and self.debugflag:
494 if section in (None, b'devel', b'ui') and self.debugflag:
495 tracked = set()
495 tracked = set()
496 if self.configbool(b'devel', b'debug.extensions'):
496 if self.configbool(b'devel', b'debug.extensions'):
497 tracked.add(b'extension')
497 tracked.add(b'extension')
498 if tracked:
498 if tracked:
499 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
499 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
500 self.setlogger(b'debug', logger)
500 self.setlogger(b'debug', logger)
501
501
502 def backupconfig(self, section, item):
502 def backupconfig(self, section, item):
503 return (self._ocfg.backup(section, item),
503 return (self._ocfg.backup(section, item),
504 self._tcfg.backup(section, item),
504 self._tcfg.backup(section, item),
505 self._ucfg.backup(section, item),)
505 self._ucfg.backup(section, item),)
506 def restoreconfig(self, data):
506 def restoreconfig(self, data):
507 self._ocfg.restore(data[0])
507 self._ocfg.restore(data[0])
508 self._tcfg.restore(data[1])
508 self._tcfg.restore(data[1])
509 self._ucfg.restore(data[2])
509 self._ucfg.restore(data[2])
510
510
511 def setconfig(self, section, name, value, source=''):
511 def setconfig(self, section, name, value, source=''):
512 for cfg in (self._ocfg, self._tcfg, self._ucfg):
512 for cfg in (self._ocfg, self._tcfg, self._ucfg):
513 cfg.set(section, name, value, source)
513 cfg.set(section, name, value, source)
514 self.fixconfig(section=section)
514 self.fixconfig(section=section)
515 self._maybetweakdefaults()
515 self._maybetweakdefaults()
516
516
517 def _data(self, untrusted):
517 def _data(self, untrusted):
518 return untrusted and self._ucfg or self._tcfg
518 return untrusted and self._ucfg or self._tcfg
519
519
520 def configsource(self, section, name, untrusted=False):
520 def configsource(self, section, name, untrusted=False):
521 return self._data(untrusted).source(section, name)
521 return self._data(untrusted).source(section, name)
522
522
523 def config(self, section, name, default=_unset, untrusted=False):
523 def config(self, section, name, default=_unset, untrusted=False):
524 """return the plain string version of a config"""
524 """return the plain string version of a config"""
525 value = self._config(section, name, default=default,
525 value = self._config(section, name, default=default,
526 untrusted=untrusted)
526 untrusted=untrusted)
527 if value is _unset:
527 if value is _unset:
528 return None
528 return None
529 return value
529 return value
530
530
531 def _config(self, section, name, default=_unset, untrusted=False):
531 def _config(self, section, name, default=_unset, untrusted=False):
532 value = itemdefault = default
532 value = itemdefault = default
533 item = self._knownconfig.get(section, {}).get(name)
533 item = self._knownconfig.get(section, {}).get(name)
534 alternates = [(section, name)]
534 alternates = [(section, name)]
535
535
536 if item is not None:
536 if item is not None:
537 alternates.extend(item.alias)
537 alternates.extend(item.alias)
538 if callable(item.default):
538 if callable(item.default):
539 itemdefault = item.default()
539 itemdefault = item.default()
540 else:
540 else:
541 itemdefault = item.default
541 itemdefault = item.default
542 else:
542 else:
543 msg = ("accessing unregistered config item: '%s.%s'")
543 msg = ("accessing unregistered config item: '%s.%s'")
544 msg %= (section, name)
544 msg %= (section, name)
545 self.develwarn(msg, 2, 'warn-config-unknown')
545 self.develwarn(msg, 2, 'warn-config-unknown')
546
546
547 if default is _unset:
547 if default is _unset:
548 if item is None:
548 if item is None:
549 value = default
549 value = default
550 elif item.default is configitems.dynamicdefault:
550 elif item.default is configitems.dynamicdefault:
551 value = None
551 value = None
552 msg = "config item requires an explicit default value: '%s.%s'"
552 msg = "config item requires an explicit default value: '%s.%s'"
553 msg %= (section, name)
553 msg %= (section, name)
554 self.develwarn(msg, 2, 'warn-config-default')
554 self.develwarn(msg, 2, 'warn-config-default')
555 else:
555 else:
556 value = itemdefault
556 value = itemdefault
557 elif (item is not None
557 elif (item is not None
558 and item.default is not configitems.dynamicdefault
558 and item.default is not configitems.dynamicdefault
559 and default != itemdefault):
559 and default != itemdefault):
560 msg = ("specifying a mismatched default value for a registered "
560 msg = ("specifying a mismatched default value for a registered "
561 "config item: '%s.%s' '%s'")
561 "config item: '%s.%s' '%s'")
562 msg %= (section, name, pycompat.bytestr(default))
562 msg %= (section, name, pycompat.bytestr(default))
563 self.develwarn(msg, 2, 'warn-config-default')
563 self.develwarn(msg, 2, 'warn-config-default')
564
564
565 for s, n in alternates:
565 for s, n in alternates:
566 candidate = self._data(untrusted).get(s, n, None)
566 candidate = self._data(untrusted).get(s, n, None)
567 if candidate is not None:
567 if candidate is not None:
568 value = candidate
568 value = candidate
569 section = s
569 section = s
570 name = n
570 name = n
571 break
571 break
572
572
573 if self.debugflag and not untrusted and self._reportuntrusted:
573 if self.debugflag and not untrusted and self._reportuntrusted:
574 for s, n in alternates:
574 for s, n in alternates:
575 uvalue = self._ucfg.get(s, n)
575 uvalue = self._ucfg.get(s, n)
576 if uvalue is not None and uvalue != value:
576 if uvalue is not None and uvalue != value:
577 self.debug("ignoring untrusted configuration option "
577 self.debug("ignoring untrusted configuration option "
578 "%s.%s = %s\n" % (s, n, uvalue))
578 "%s.%s = %s\n" % (s, n, uvalue))
579 return value
579 return value
580
580
581 def configsuboptions(self, section, name, default=_unset, untrusted=False):
581 def configsuboptions(self, section, name, default=_unset, untrusted=False):
582 """Get a config option and all sub-options.
582 """Get a config option and all sub-options.
583
583
584 Some config options have sub-options that are declared with the
584 Some config options have sub-options that are declared with the
585 format "key:opt = value". This method is used to return the main
585 format "key:opt = value". This method is used to return the main
586 option and all its declared sub-options.
586 option and all its declared sub-options.
587
587
588 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
588 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
589 is a dict of defined sub-options where keys and values are strings.
589 is a dict of defined sub-options where keys and values are strings.
590 """
590 """
591 main = self.config(section, name, default, untrusted=untrusted)
591 main = self.config(section, name, default, untrusted=untrusted)
592 data = self._data(untrusted)
592 data = self._data(untrusted)
593 sub = {}
593 sub = {}
594 prefix = '%s:' % name
594 prefix = '%s:' % name
595 for k, v in data.items(section):
595 for k, v in data.items(section):
596 if k.startswith(prefix):
596 if k.startswith(prefix):
597 sub[k[len(prefix):]] = v
597 sub[k[len(prefix):]] = v
598
598
599 if self.debugflag and not untrusted and self._reportuntrusted:
599 if self.debugflag and not untrusted and self._reportuntrusted:
600 for k, v in sub.items():
600 for k, v in sub.items():
601 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
601 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
602 if uvalue is not None and uvalue != v:
602 if uvalue is not None and uvalue != v:
603 self.debug('ignoring untrusted configuration option '
603 self.debug('ignoring untrusted configuration option '
604 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
604 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
605
605
606 return main, sub
606 return main, sub
607
607
608 def configpath(self, section, name, default=_unset, untrusted=False):
608 def configpath(self, section, name, default=_unset, untrusted=False):
609 'get a path config item, expanded relative to repo root or config file'
609 'get a path config item, expanded relative to repo root or config file'
610 v = self.config(section, name, default, untrusted)
610 v = self.config(section, name, default, untrusted)
611 if v is None:
611 if v is None:
612 return None
612 return None
613 if not os.path.isabs(v) or "://" not in v:
613 if not os.path.isabs(v) or "://" not in v:
614 src = self.configsource(section, name, untrusted)
614 src = self.configsource(section, name, untrusted)
615 if ':' in src:
615 if ':' in src:
616 base = os.path.dirname(src.rsplit(':')[0])
616 base = os.path.dirname(src.rsplit(':')[0])
617 v = os.path.join(base, os.path.expanduser(v))
617 v = os.path.join(base, os.path.expanduser(v))
618 return v
618 return v
619
619
620 def configbool(self, section, name, default=_unset, untrusted=False):
620 def configbool(self, section, name, default=_unset, untrusted=False):
621 """parse a configuration element as a boolean
621 """parse a configuration element as a boolean
622
622
623 >>> u = ui(); s = b'foo'
623 >>> u = ui(); s = b'foo'
624 >>> u.setconfig(s, b'true', b'yes')
624 >>> u.setconfig(s, b'true', b'yes')
625 >>> u.configbool(s, b'true')
625 >>> u.configbool(s, b'true')
626 True
626 True
627 >>> u.setconfig(s, b'false', b'no')
627 >>> u.setconfig(s, b'false', b'no')
628 >>> u.configbool(s, b'false')
628 >>> u.configbool(s, b'false')
629 False
629 False
630 >>> u.configbool(s, b'unknown')
630 >>> u.configbool(s, b'unknown')
631 False
631 False
632 >>> u.configbool(s, b'unknown', True)
632 >>> u.configbool(s, b'unknown', True)
633 True
633 True
634 >>> u.setconfig(s, b'invalid', b'somevalue')
634 >>> u.setconfig(s, b'invalid', b'somevalue')
635 >>> u.configbool(s, b'invalid')
635 >>> u.configbool(s, b'invalid')
636 Traceback (most recent call last):
636 Traceback (most recent call last):
637 ...
637 ...
638 ConfigError: foo.invalid is not a boolean ('somevalue')
638 ConfigError: foo.invalid is not a boolean ('somevalue')
639 """
639 """
640
640
641 v = self._config(section, name, default, untrusted=untrusted)
641 v = self._config(section, name, default, untrusted=untrusted)
642 if v is None:
642 if v is None:
643 return v
643 return v
644 if v is _unset:
644 if v is _unset:
645 if default is _unset:
645 if default is _unset:
646 return False
646 return False
647 return default
647 return default
648 if isinstance(v, bool):
648 if isinstance(v, bool):
649 return v
649 return v
650 b = stringutil.parsebool(v)
650 b = stringutil.parsebool(v)
651 if b is None:
651 if b is None:
652 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
652 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
653 % (section, name, v))
653 % (section, name, v))
654 return b
654 return b
655
655
656 def configwith(self, convert, section, name, default=_unset,
656 def configwith(self, convert, section, name, default=_unset,
657 desc=None, untrusted=False):
657 desc=None, untrusted=False):
658 """parse a configuration element with a conversion function
658 """parse a configuration element with a conversion function
659
659
660 >>> u = ui(); s = b'foo'
660 >>> u = ui(); s = b'foo'
661 >>> u.setconfig(s, b'float1', b'42')
661 >>> u.setconfig(s, b'float1', b'42')
662 >>> u.configwith(float, s, b'float1')
662 >>> u.configwith(float, s, b'float1')
663 42.0
663 42.0
664 >>> u.setconfig(s, b'float2', b'-4.25')
664 >>> u.setconfig(s, b'float2', b'-4.25')
665 >>> u.configwith(float, s, b'float2')
665 >>> u.configwith(float, s, b'float2')
666 -4.25
666 -4.25
667 >>> u.configwith(float, s, b'unknown', 7)
667 >>> u.configwith(float, s, b'unknown', 7)
668 7.0
668 7.0
669 >>> u.setconfig(s, b'invalid', b'somevalue')
669 >>> u.setconfig(s, b'invalid', b'somevalue')
670 >>> u.configwith(float, s, b'invalid')
670 >>> u.configwith(float, s, b'invalid')
671 Traceback (most recent call last):
671 Traceback (most recent call last):
672 ...
672 ...
673 ConfigError: foo.invalid is not a valid float ('somevalue')
673 ConfigError: foo.invalid is not a valid float ('somevalue')
674 >>> u.configwith(float, s, b'invalid', desc=b'womble')
674 >>> u.configwith(float, s, b'invalid', desc=b'womble')
675 Traceback (most recent call last):
675 Traceback (most recent call last):
676 ...
676 ...
677 ConfigError: foo.invalid is not a valid womble ('somevalue')
677 ConfigError: foo.invalid is not a valid womble ('somevalue')
678 """
678 """
679
679
680 v = self.config(section, name, default, untrusted)
680 v = self.config(section, name, default, untrusted)
681 if v is None:
681 if v is None:
682 return v # do not attempt to convert None
682 return v # do not attempt to convert None
683 try:
683 try:
684 return convert(v)
684 return convert(v)
685 except (ValueError, error.ParseError):
685 except (ValueError, error.ParseError):
686 if desc is None:
686 if desc is None:
687 desc = pycompat.sysbytes(convert.__name__)
687 desc = pycompat.sysbytes(convert.__name__)
688 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
688 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
689 % (section, name, desc, v))
689 % (section, name, desc, v))
690
690
691 def configint(self, section, name, default=_unset, untrusted=False):
691 def configint(self, section, name, default=_unset, untrusted=False):
692 """parse a configuration element as an integer
692 """parse a configuration element as an integer
693
693
694 >>> u = ui(); s = b'foo'
694 >>> u = ui(); s = b'foo'
695 >>> u.setconfig(s, b'int1', b'42')
695 >>> u.setconfig(s, b'int1', b'42')
696 >>> u.configint(s, b'int1')
696 >>> u.configint(s, b'int1')
697 42
697 42
698 >>> u.setconfig(s, b'int2', b'-42')
698 >>> u.setconfig(s, b'int2', b'-42')
699 >>> u.configint(s, b'int2')
699 >>> u.configint(s, b'int2')
700 -42
700 -42
701 >>> u.configint(s, b'unknown', 7)
701 >>> u.configint(s, b'unknown', 7)
702 7
702 7
703 >>> u.setconfig(s, b'invalid', b'somevalue')
703 >>> u.setconfig(s, b'invalid', b'somevalue')
704 >>> u.configint(s, b'invalid')
704 >>> u.configint(s, b'invalid')
705 Traceback (most recent call last):
705 Traceback (most recent call last):
706 ...
706 ...
707 ConfigError: foo.invalid is not a valid integer ('somevalue')
707 ConfigError: foo.invalid is not a valid integer ('somevalue')
708 """
708 """
709
709
710 return self.configwith(int, section, name, default, 'integer',
710 return self.configwith(int, section, name, default, 'integer',
711 untrusted)
711 untrusted)
712
712
713 def configbytes(self, section, name, default=_unset, untrusted=False):
713 def configbytes(self, section, name, default=_unset, untrusted=False):
714 """parse a configuration element as a quantity in bytes
714 """parse a configuration element as a quantity in bytes
715
715
716 Units can be specified as b (bytes), k or kb (kilobytes), m or
716 Units can be specified as b (bytes), k or kb (kilobytes), m or
717 mb (megabytes), g or gb (gigabytes).
717 mb (megabytes), g or gb (gigabytes).
718
718
719 >>> u = ui(); s = b'foo'
719 >>> u = ui(); s = b'foo'
720 >>> u.setconfig(s, b'val1', b'42')
720 >>> u.setconfig(s, b'val1', b'42')
721 >>> u.configbytes(s, b'val1')
721 >>> u.configbytes(s, b'val1')
722 42
722 42
723 >>> u.setconfig(s, b'val2', b'42.5 kb')
723 >>> u.setconfig(s, b'val2', b'42.5 kb')
724 >>> u.configbytes(s, b'val2')
724 >>> u.configbytes(s, b'val2')
725 43520
725 43520
726 >>> u.configbytes(s, b'unknown', b'7 MB')
726 >>> u.configbytes(s, b'unknown', b'7 MB')
727 7340032
727 7340032
728 >>> u.setconfig(s, b'invalid', b'somevalue')
728 >>> u.setconfig(s, b'invalid', b'somevalue')
729 >>> u.configbytes(s, b'invalid')
729 >>> u.configbytes(s, b'invalid')
730 Traceback (most recent call last):
730 Traceback (most recent call last):
731 ...
731 ...
732 ConfigError: foo.invalid is not a byte quantity ('somevalue')
732 ConfigError: foo.invalid is not a byte quantity ('somevalue')
733 """
733 """
734
734
735 value = self._config(section, name, default, untrusted)
735 value = self._config(section, name, default, untrusted)
736 if value is _unset:
736 if value is _unset:
737 if default is _unset:
737 if default is _unset:
738 default = 0
738 default = 0
739 value = default
739 value = default
740 if not isinstance(value, bytes):
740 if not isinstance(value, bytes):
741 return value
741 return value
742 try:
742 try:
743 return util.sizetoint(value)
743 return util.sizetoint(value)
744 except error.ParseError:
744 except error.ParseError:
745 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
745 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
746 % (section, name, value))
746 % (section, name, value))
747
747
748 def configlist(self, section, name, default=_unset, untrusted=False):
748 def configlist(self, section, name, default=_unset, untrusted=False):
749 """parse a configuration element as a list of comma/space separated
749 """parse a configuration element as a list of comma/space separated
750 strings
750 strings
751
751
752 >>> u = ui(); s = b'foo'
752 >>> u = ui(); s = b'foo'
753 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
753 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
754 >>> u.configlist(s, b'list1')
754 >>> u.configlist(s, b'list1')
755 ['this', 'is', 'a small', 'test']
755 ['this', 'is', 'a small', 'test']
756 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
756 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
757 >>> u.configlist(s, b'list2')
757 >>> u.configlist(s, b'list2')
758 ['this', 'is', 'a small', 'test']
758 ['this', 'is', 'a small', 'test']
759 """
759 """
760 # default is not always a list
760 # default is not always a list
761 v = self.configwith(config.parselist, section, name, default,
761 v = self.configwith(config.parselist, section, name, default,
762 'list', untrusted)
762 'list', untrusted)
763 if isinstance(v, bytes):
763 if isinstance(v, bytes):
764 return config.parselist(v)
764 return config.parselist(v)
765 elif v is None:
765 elif v is None:
766 return []
766 return []
767 return v
767 return v
768
768
769 def configdate(self, section, name, default=_unset, untrusted=False):
769 def configdate(self, section, name, default=_unset, untrusted=False):
770 """parse a configuration element as a tuple of ints
770 """parse a configuration element as a tuple of ints
771
771
772 >>> u = ui(); s = b'foo'
772 >>> u = ui(); s = b'foo'
773 >>> u.setconfig(s, b'date', b'0 0')
773 >>> u.setconfig(s, b'date', b'0 0')
774 >>> u.configdate(s, b'date')
774 >>> u.configdate(s, b'date')
775 (0, 0)
775 (0, 0)
776 """
776 """
777 if self.config(section, name, default, untrusted):
777 if self.config(section, name, default, untrusted):
778 return self.configwith(dateutil.parsedate, section, name, default,
778 return self.configwith(dateutil.parsedate, section, name, default,
779 'date', untrusted)
779 'date', untrusted)
780 if default is _unset:
780 if default is _unset:
781 return None
781 return None
782 return default
782 return default
783
783
784 def hasconfig(self, section, name, untrusted=False):
784 def hasconfig(self, section, name, untrusted=False):
785 return self._data(untrusted).hasitem(section, name)
785 return self._data(untrusted).hasitem(section, name)
786
786
787 def has_section(self, section, untrusted=False):
787 def has_section(self, section, untrusted=False):
788 '''tell whether section exists in config.'''
788 '''tell whether section exists in config.'''
789 return section in self._data(untrusted)
789 return section in self._data(untrusted)
790
790
791 def configitems(self, section, untrusted=False, ignoresub=False):
791 def configitems(self, section, untrusted=False, ignoresub=False):
792 items = self._data(untrusted).items(section)
792 items = self._data(untrusted).items(section)
793 if ignoresub:
793 if ignoresub:
794 items = [i for i in items if ':' not in i[0]]
794 items = [i for i in items if ':' not in i[0]]
795 if self.debugflag and not untrusted and self._reportuntrusted:
795 if self.debugflag and not untrusted and self._reportuntrusted:
796 for k, v in self._ucfg.items(section):
796 for k, v in self._ucfg.items(section):
797 if self._tcfg.get(section, k) != v:
797 if self._tcfg.get(section, k) != v:
798 self.debug("ignoring untrusted configuration option "
798 self.debug("ignoring untrusted configuration option "
799 "%s.%s = %s\n" % (section, k, v))
799 "%s.%s = %s\n" % (section, k, v))
800 return items
800 return items
801
801
802 def walkconfig(self, untrusted=False):
802 def walkconfig(self, untrusted=False):
803 cfg = self._data(untrusted)
803 cfg = self._data(untrusted)
804 for section in cfg.sections():
804 for section in cfg.sections():
805 for name, value in self.configitems(section, untrusted):
805 for name, value in self.configitems(section, untrusted):
806 yield section, name, value
806 yield section, name, value
807
807
808 def plain(self, feature=None):
808 def plain(self, feature=None):
809 '''is plain mode active?
809 '''is plain mode active?
810
810
811 Plain mode means that all configuration variables which affect
811 Plain mode means that all configuration variables which affect
812 the behavior and output of Mercurial should be
812 the behavior and output of Mercurial should be
813 ignored. Additionally, the output should be stable,
813 ignored. Additionally, the output should be stable,
814 reproducible and suitable for use in scripts or applications.
814 reproducible and suitable for use in scripts or applications.
815
815
816 The only way to trigger plain mode is by setting either the
816 The only way to trigger plain mode is by setting either the
817 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
817 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
818
818
819 The return value can either be
819 The return value can either be
820 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
820 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
821 - False if feature is disabled by default and not included in HGPLAIN
821 - False if feature is disabled by default and not included in HGPLAIN
822 - True otherwise
822 - True otherwise
823 '''
823 '''
824 if ('HGPLAIN' not in encoding.environ and
824 if ('HGPLAIN' not in encoding.environ and
825 'HGPLAINEXCEPT' not in encoding.environ):
825 'HGPLAINEXCEPT' not in encoding.environ):
826 return False
826 return False
827 exceptions = encoding.environ.get('HGPLAINEXCEPT',
827 exceptions = encoding.environ.get('HGPLAINEXCEPT',
828 '').strip().split(',')
828 '').strip().split(',')
829 # TODO: add support for HGPLAIN=+feature,-feature syntax
829 # TODO: add support for HGPLAIN=+feature,-feature syntax
830 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
830 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
831 exceptions.append('strictflags')
831 exceptions.append('strictflags')
832 if feature and exceptions:
832 if feature and exceptions:
833 return feature not in exceptions
833 return feature not in exceptions
834 return True
834 return True
835
835
836 def username(self, acceptempty=False):
836 def username(self, acceptempty=False):
837 """Return default username to be used in commits.
837 """Return default username to be used in commits.
838
838
839 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
839 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
840 and stop searching if one of these is set.
840 and stop searching if one of these is set.
841 If not found and acceptempty is True, returns None.
841 If not found and acceptempty is True, returns None.
842 If not found and ui.askusername is True, ask the user, else use
842 If not found and ui.askusername is True, ask the user, else use
843 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
843 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
844 If no username could be found, raise an Abort error.
844 If no username could be found, raise an Abort error.
845 """
845 """
846 user = encoding.environ.get("HGUSER")
846 user = encoding.environ.get("HGUSER")
847 if user is None:
847 if user is None:
848 user = self.config("ui", "username")
848 user = self.config("ui", "username")
849 if user is not None:
849 if user is not None:
850 user = os.path.expandvars(user)
850 user = os.path.expandvars(user)
851 if user is None:
851 if user is None:
852 user = encoding.environ.get("EMAIL")
852 user = encoding.environ.get("EMAIL")
853 if user is None and acceptempty:
853 if user is None and acceptempty:
854 return user
854 return user
855 if user is None and self.configbool("ui", "askusername"):
855 if user is None and self.configbool("ui", "askusername"):
856 user = self.prompt(_("enter a commit username:"), default=None)
856 user = self.prompt(_("enter a commit username:"), default=None)
857 if user is None and not self.interactive():
857 if user is None and not self.interactive():
858 try:
858 try:
859 user = '%s@%s' % (procutil.getuser(),
859 user = '%s@%s' % (procutil.getuser(),
860 encoding.strtolocal(socket.getfqdn()))
860 encoding.strtolocal(socket.getfqdn()))
861 self.warn(_("no username found, using '%s' instead\n") % user)
861 self.warn(_("no username found, using '%s' instead\n") % user)
862 except KeyError:
862 except KeyError:
863 pass
863 pass
864 if not user:
864 if not user:
865 raise error.Abort(_('no username supplied'),
865 raise error.Abort(_('no username supplied'),
866 hint=_("use 'hg config --edit' "
866 hint=_("use 'hg config --edit' "
867 'to set your username'))
867 'to set your username'))
868 if "\n" in user:
868 if "\n" in user:
869 raise error.Abort(_("username %r contains a newline\n")
869 raise error.Abort(_("username %r contains a newline\n")
870 % pycompat.bytestr(user))
870 % pycompat.bytestr(user))
871 return user
871 return user
872
872
873 def shortuser(self, user):
873 def shortuser(self, user):
874 """Return a short representation of a user name or email address."""
874 """Return a short representation of a user name or email address."""
875 if not self.verbose:
875 if not self.verbose:
876 user = stringutil.shortuser(user)
876 user = stringutil.shortuser(user)
877 return user
877 return user
878
878
879 def expandpath(self, loc, default=None):
879 def expandpath(self, loc, default=None):
880 """Return repository location relative to cwd or from [paths]"""
880 """Return repository location relative to cwd or from [paths]"""
881 try:
881 try:
882 p = self.paths.getpath(loc)
882 p = self.paths.getpath(loc)
883 if p:
883 if p:
884 return p.rawloc
884 return p.rawloc
885 except error.RepoError:
885 except error.RepoError:
886 pass
886 pass
887
887
888 if default:
888 if default:
889 try:
889 try:
890 p = self.paths.getpath(default)
890 p = self.paths.getpath(default)
891 if p:
891 if p:
892 return p.rawloc
892 return p.rawloc
893 except error.RepoError:
893 except error.RepoError:
894 pass
894 pass
895
895
896 return loc
896 return loc
897
897
898 @util.propertycache
898 @util.propertycache
899 def paths(self):
899 def paths(self):
900 return paths(self)
900 return paths(self)
901
901
902 @property
902 @property
903 def fout(self):
903 def fout(self):
904 return self._fout
904 return self._fout
905
905
906 @fout.setter
906 @fout.setter
907 def fout(self, f):
907 def fout(self, f):
908 self._fout = f
908 self._fout = f
909 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
909 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
910
910
911 @property
911 @property
912 def ferr(self):
912 def ferr(self):
913 return self._ferr
913 return self._ferr
914
914
915 @ferr.setter
915 @ferr.setter
916 def ferr(self, f):
916 def ferr(self, f):
917 self._ferr = f
917 self._ferr = f
918 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
918 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
919
919
920 @property
920 @property
921 def fin(self):
921 def fin(self):
922 return self._fin
922 return self._fin
923
923
924 @fin.setter
924 @fin.setter
925 def fin(self, f):
925 def fin(self, f):
926 self._fin = f
926 self._fin = f
927
927
928 @property
928 @property
929 def fmsg(self):
929 def fmsg(self):
930 """Stream dedicated for status/error messages; may be None if
930 """Stream dedicated for status/error messages; may be None if
931 fout/ferr are used"""
931 fout/ferr are used"""
932 return self._fmsg
932 return self._fmsg
933
933
934 @fmsg.setter
934 @fmsg.setter
935 def fmsg(self, f):
935 def fmsg(self, f):
936 self._fmsg = f
936 self._fmsg = f
937 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
937 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
938
938
939 def pushbuffer(self, error=False, subproc=False, labeled=False):
939 def pushbuffer(self, error=False, subproc=False, labeled=False):
940 """install a buffer to capture standard output of the ui object
940 """install a buffer to capture standard output of the ui object
941
941
942 If error is True, the error output will be captured too.
942 If error is True, the error output will be captured too.
943
943
944 If subproc is True, output from subprocesses (typically hooks) will be
944 If subproc is True, output from subprocesses (typically hooks) will be
945 captured too.
945 captured too.
946
946
947 If labeled is True, any labels associated with buffered
947 If labeled is True, any labels associated with buffered
948 output will be handled. By default, this has no effect
948 output will be handled. By default, this has no effect
949 on the output returned, but extensions and GUI tools may
949 on the output returned, but extensions and GUI tools may
950 handle this argument and returned styled output. If output
950 handle this argument and returned styled output. If output
951 is being buffered so it can be captured and parsed or
951 is being buffered so it can be captured and parsed or
952 processed, labeled should not be set to True.
952 processed, labeled should not be set to True.
953 """
953 """
954 self._buffers.append([])
954 self._buffers.append([])
955 self._bufferstates.append((error, subproc, labeled))
955 self._bufferstates.append((error, subproc, labeled))
956 self._bufferapplylabels = labeled
956 self._bufferapplylabels = labeled
957
957
958 def popbuffer(self):
958 def popbuffer(self):
959 '''pop the last buffer and return the buffered output'''
959 '''pop the last buffer and return the buffered output'''
960 self._bufferstates.pop()
960 self._bufferstates.pop()
961 if self._bufferstates:
961 if self._bufferstates:
962 self._bufferapplylabels = self._bufferstates[-1][2]
962 self._bufferapplylabels = self._bufferstates[-1][2]
963 else:
963 else:
964 self._bufferapplylabels = None
964 self._bufferapplylabels = None
965
965
966 return "".join(self._buffers.pop())
966 return "".join(self._buffers.pop())
967
967
968 def _isbuffered(self, dest):
968 def _isbuffered(self, dest):
969 if dest is self._fout:
969 if dest is self._fout:
970 return bool(self._buffers)
970 return bool(self._buffers)
971 if dest is self._ferr:
971 if dest is self._ferr:
972 return bool(self._bufferstates and self._bufferstates[-1][0])
972 return bool(self._bufferstates and self._bufferstates[-1][0])
973 return False
973 return False
974
974
975 def canwritewithoutlabels(self):
975 def canwritewithoutlabels(self):
976 '''check if write skips the label'''
976 '''check if write skips the label'''
977 if self._buffers and not self._bufferapplylabels:
977 if self._buffers and not self._bufferapplylabels:
978 return True
978 return True
979 return self._colormode is None
979 return self._colormode is None
980
980
981 def canbatchlabeledwrites(self):
981 def canbatchlabeledwrites(self):
982 '''check if write calls with labels are batchable'''
982 '''check if write calls with labels are batchable'''
983 # Windows color printing is special, see ``write``.
983 # Windows color printing is special, see ``write``.
984 return self._colormode != 'win32'
984 return self._colormode != 'win32'
985
985
986 def write(self, *args, **opts):
986 def write(self, *args, **opts):
987 '''write args to output
987 '''write args to output
988
988
989 By default, this method simply writes to the buffer or stdout.
989 By default, this method simply writes to the buffer or stdout.
990 Color mode can be set on the UI class to have the output decorated
990 Color mode can be set on the UI class to have the output decorated
991 with color modifier before being written to stdout.
991 with color modifier before being written to stdout.
992
992
993 The color used is controlled by an optional keyword argument, "label".
993 The color used is controlled by an optional keyword argument, "label".
994 This should be a string containing label names separated by space.
994 This should be a string containing label names separated by space.
995 Label names take the form of "topic.type". For example, ui.debug()
995 Label names take the form of "topic.type". For example, ui.debug()
996 issues a label of "ui.debug".
996 issues a label of "ui.debug".
997
997
998 When labeling output for a specific command, a label of
998 When labeling output for a specific command, a label of
999 "cmdname.type" is recommended. For example, status issues
999 "cmdname.type" is recommended. For example, status issues
1000 a label of "status.modified" for modified files.
1000 a label of "status.modified" for modified files.
1001 '''
1001 '''
1002 self._write(self._fout, *args, **opts)
1002 self._write(self._fout, *args, **opts)
1003
1003
1004 def write_err(self, *args, **opts):
1004 def write_err(self, *args, **opts):
1005 self._write(self._ferr, *args, **opts)
1005 self._write(self._ferr, *args, **opts)
1006
1006
1007 def _write(self, dest, *args, **opts):
1007 def _write(self, dest, *args, **opts):
1008 if self._isbuffered(dest):
1008 if self._isbuffered(dest):
1009 if self._bufferapplylabels:
1009 if self._bufferapplylabels:
1010 label = opts.get(r'label', '')
1010 label = opts.get(r'label', '')
1011 self._buffers[-1].extend(self.label(a, label) for a in args)
1011 self._buffers[-1].extend(self.label(a, label) for a in args)
1012 else:
1012 else:
1013 self._buffers[-1].extend(args)
1013 self._buffers[-1].extend(args)
1014 else:
1014 else:
1015 self._writenobuf(dest, *args, **opts)
1015 self._writenobuf(dest, *args, **opts)
1016
1016
1017 def _writenobuf(self, dest, *args, **opts):
1017 def _writenobuf(self, dest, *args, **opts):
1018 self._progclear()
1018 self._progclear()
1019 msg = b''.join(args)
1019 msg = b''.join(args)
1020
1020
1021 # opencode timeblockedsection because this is a critical path
1021 # opencode timeblockedsection because this is a critical path
1022 starttime = util.timer()
1022 starttime = util.timer()
1023 try:
1023 try:
1024 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1024 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1025 self._fout.flush()
1025 self._fout.flush()
1026 if getattr(dest, 'structured', False):
1026 if getattr(dest, 'structured', False):
1027 # channel for machine-readable output with metadata, where
1027 # channel for machine-readable output with metadata, where
1028 # no extra colorization is necessary.
1028 # no extra colorization is necessary.
1029 dest.write(msg, **opts)
1029 dest.write(msg, **opts)
1030 elif self._colormode == 'win32':
1030 elif self._colormode == 'win32':
1031 # windows color printing is its own can of crab, defer to
1031 # windows color printing is its own can of crab, defer to
1032 # the color module and that is it.
1032 # the color module and that is it.
1033 color.win32print(self, dest.write, msg, **opts)
1033 color.win32print(self, dest.write, msg, **opts)
1034 else:
1034 else:
1035 if self._colormode is not None:
1035 if self._colormode is not None:
1036 label = opts.get(r'label', '')
1036 label = opts.get(r'label', '')
1037 msg = self.label(msg, label)
1037 msg = self.label(msg, label)
1038 dest.write(msg)
1038 dest.write(msg)
1039 # stderr may be buffered under win32 when redirected to files,
1039 # stderr may be buffered under win32 when redirected to files,
1040 # including stdout.
1040 # including stdout.
1041 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1041 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1042 dest.flush()
1042 dest.flush()
1043 except IOError as err:
1043 except IOError as err:
1044 if (dest is self._ferr
1044 if (dest is self._ferr
1045 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1045 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1046 # no way to report the error, so ignore it
1046 # no way to report the error, so ignore it
1047 return
1047 return
1048 raise error.StdioError(err)
1048 raise error.StdioError(err)
1049 finally:
1049 finally:
1050 self._blockedtimes['stdio_blocked'] += \
1050 self._blockedtimes['stdio_blocked'] += \
1051 (util.timer() - starttime) * 1000
1051 (util.timer() - starttime) * 1000
1052
1052
1053 def _writemsg(self, dest, *args, **opts):
1053 def _writemsg(self, dest, *args, **opts):
1054 _writemsgwith(self._write, dest, *args, **opts)
1054 _writemsgwith(self._write, dest, *args, **opts)
1055
1055
1056 def _writemsgnobuf(self, dest, *args, **opts):
1056 def _writemsgnobuf(self, dest, *args, **opts):
1057 _writemsgwith(self._writenobuf, dest, *args, **opts)
1057 _writemsgwith(self._writenobuf, dest, *args, **opts)
1058
1058
1059 def flush(self):
1059 def flush(self):
1060 # opencode timeblockedsection because this is a critical path
1060 # opencode timeblockedsection because this is a critical path
1061 starttime = util.timer()
1061 starttime = util.timer()
1062 try:
1062 try:
1063 try:
1063 try:
1064 self._fout.flush()
1064 self._fout.flush()
1065 except IOError as err:
1065 except IOError as err:
1066 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1066 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1067 raise error.StdioError(err)
1067 raise error.StdioError(err)
1068 finally:
1068 finally:
1069 try:
1069 try:
1070 self._ferr.flush()
1070 self._ferr.flush()
1071 except IOError as err:
1071 except IOError as err:
1072 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1072 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1073 raise error.StdioError(err)
1073 raise error.StdioError(err)
1074 finally:
1074 finally:
1075 self._blockedtimes['stdio_blocked'] += \
1075 self._blockedtimes['stdio_blocked'] += \
1076 (util.timer() - starttime) * 1000
1076 (util.timer() - starttime) * 1000
1077
1077
1078 def _isatty(self, fh):
1078 def _isatty(self, fh):
1079 if self.configbool('ui', 'nontty'):
1079 if self.configbool('ui', 'nontty'):
1080 return False
1080 return False
1081 return procutil.isatty(fh)
1081 return procutil.isatty(fh)
1082
1082
1083 def disablepager(self):
1083 def disablepager(self):
1084 self._disablepager = True
1084 self._disablepager = True
1085
1085
1086 def pager(self, command):
1086 def pager(self, command):
1087 """Start a pager for subsequent command output.
1087 """Start a pager for subsequent command output.
1088
1088
1089 Commands which produce a long stream of output should call
1089 Commands which produce a long stream of output should call
1090 this function to activate the user's preferred pagination
1090 this function to activate the user's preferred pagination
1091 mechanism (which may be no pager). Calling this function
1091 mechanism (which may be no pager). Calling this function
1092 precludes any future use of interactive functionality, such as
1092 precludes any future use of interactive functionality, such as
1093 prompting the user or activating curses.
1093 prompting the user or activating curses.
1094
1094
1095 Args:
1095 Args:
1096 command: The full, non-aliased name of the command. That is, "log"
1096 command: The full, non-aliased name of the command. That is, "log"
1097 not "history, "summary" not "summ", etc.
1097 not "history, "summary" not "summ", etc.
1098 """
1098 """
1099 if (self._disablepager
1099 if (self._disablepager
1100 or self.pageractive):
1100 or self.pageractive):
1101 # how pager should do is already determined
1101 # how pager should do is already determined
1102 return
1102 return
1103
1103
1104 if not command.startswith('internal-always-') and (
1104 if not command.startswith('internal-always-') and (
1105 # explicit --pager=on (= 'internal-always-' prefix) should
1105 # explicit --pager=on (= 'internal-always-' prefix) should
1106 # take precedence over disabling factors below
1106 # take precedence over disabling factors below
1107 command in self.configlist('pager', 'ignore')
1107 command in self.configlist('pager', 'ignore')
1108 or not self.configbool('ui', 'paginate')
1108 or not self.configbool('ui', 'paginate')
1109 or not self.configbool('pager', 'attend-' + command, True)
1109 or not self.configbool('pager', 'attend-' + command, True)
1110 or encoding.environ.get('TERM') == 'dumb'
1110 or encoding.environ.get('TERM') == 'dumb'
1111 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1111 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1112 # formatted() will need some adjustment.
1112 # formatted() will need some adjustment.
1113 or not self.formatted()
1113 or not self.formatted()
1114 or self.plain()
1114 or self.plain()
1115 or self._buffers
1115 or self._buffers
1116 # TODO: expose debugger-enabled on the UI object
1116 # TODO: expose debugger-enabled on the UI object
1117 or '--debugger' in pycompat.sysargv):
1117 or '--debugger' in pycompat.sysargv):
1118 # We only want to paginate if the ui appears to be
1118 # We only want to paginate if the ui appears to be
1119 # interactive, the user didn't say HGPLAIN or
1119 # interactive, the user didn't say HGPLAIN or
1120 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1120 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1121 return
1121 return
1122
1122
1123 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1123 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1124 if not pagercmd:
1124 if not pagercmd:
1125 return
1125 return
1126
1126
1127 pagerenv = {}
1127 pagerenv = {}
1128 for name, value in rcutil.defaultpagerenv().items():
1128 for name, value in rcutil.defaultpagerenv().items():
1129 if name not in encoding.environ:
1129 if name not in encoding.environ:
1130 pagerenv[name] = value
1130 pagerenv[name] = value
1131
1131
1132 self.debug('starting pager for command %s\n' %
1132 self.debug('starting pager for command %s\n' %
1133 stringutil.pprint(command))
1133 stringutil.pprint(command))
1134 self.flush()
1134 self.flush()
1135
1135
1136 wasformatted = self.formatted()
1136 wasformatted = self.formatted()
1137 if util.safehasattr(signal, "SIGPIPE"):
1137 if util.safehasattr(signal, "SIGPIPE"):
1138 signal.signal(signal.SIGPIPE, _catchterm)
1138 signal.signal(signal.SIGPIPE, _catchterm)
1139 if self._runpager(pagercmd, pagerenv):
1139 if self._runpager(pagercmd, pagerenv):
1140 self.pageractive = True
1140 self.pageractive = True
1141 # Preserve the formatted-ness of the UI. This is important
1141 # Preserve the formatted-ness of the UI. This is important
1142 # because we mess with stdout, which might confuse
1142 # because we mess with stdout, which might confuse
1143 # auto-detection of things being formatted.
1143 # auto-detection of things being formatted.
1144 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1144 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1145 self.setconfig('ui', 'interactive', False, 'pager')
1145 self.setconfig('ui', 'interactive', False, 'pager')
1146
1146
1147 # If pagermode differs from color.mode, reconfigure color now that
1147 # If pagermode differs from color.mode, reconfigure color now that
1148 # pageractive is set.
1148 # pageractive is set.
1149 cm = self._colormode
1149 cm = self._colormode
1150 if cm != self.config('color', 'pagermode', cm):
1150 if cm != self.config('color', 'pagermode', cm):
1151 color.setup(self)
1151 color.setup(self)
1152 else:
1152 else:
1153 # If the pager can't be spawned in dispatch when --pager=on is
1153 # If the pager can't be spawned in dispatch when --pager=on is
1154 # given, don't try again when the command runs, to avoid a duplicate
1154 # given, don't try again when the command runs, to avoid a duplicate
1155 # warning about a missing pager command.
1155 # warning about a missing pager command.
1156 self.disablepager()
1156 self.disablepager()
1157
1157
1158 def _runpager(self, command, env=None):
1158 def _runpager(self, command, env=None):
1159 """Actually start the pager and set up file descriptors.
1159 """Actually start the pager and set up file descriptors.
1160
1160
1161 This is separate in part so that extensions (like chg) can
1161 This is separate in part so that extensions (like chg) can
1162 override how a pager is invoked.
1162 override how a pager is invoked.
1163 """
1163 """
1164 if command == 'cat':
1164 if command == 'cat':
1165 # Save ourselves some work.
1165 # Save ourselves some work.
1166 return False
1166 return False
1167 # If the command doesn't contain any of these characters, we
1167 # If the command doesn't contain any of these characters, we
1168 # assume it's a binary and exec it directly. This means for
1168 # assume it's a binary and exec it directly. This means for
1169 # simple pager command configurations, we can degrade
1169 # simple pager command configurations, we can degrade
1170 # gracefully and tell the user about their broken pager.
1170 # gracefully and tell the user about their broken pager.
1171 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1171 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1172
1172
1173 if pycompat.iswindows and not shell:
1173 if pycompat.iswindows and not shell:
1174 # Window's built-in `more` cannot be invoked with shell=False, but
1174 # Window's built-in `more` cannot be invoked with shell=False, but
1175 # its `more.com` can. Hide this implementation detail from the
1175 # its `more.com` can. Hide this implementation detail from the
1176 # user so we can also get sane bad PAGER behavior. MSYS has
1176 # user so we can also get sane bad PAGER behavior. MSYS has
1177 # `more.exe`, so do a cmd.exe style resolution of the executable to
1177 # `more.exe`, so do a cmd.exe style resolution of the executable to
1178 # determine which one to use.
1178 # determine which one to use.
1179 fullcmd = procutil.findexe(command)
1179 fullcmd = procutil.findexe(command)
1180 if not fullcmd:
1180 if not fullcmd:
1181 self.warn(_("missing pager command '%s', skipping pager\n")
1181 self.warn(_("missing pager command '%s', skipping pager\n")
1182 % command)
1182 % command)
1183 return False
1183 return False
1184
1184
1185 command = fullcmd
1185 command = fullcmd
1186
1186
1187 try:
1187 try:
1188 pager = subprocess.Popen(
1188 pager = subprocess.Popen(
1189 procutil.tonativestr(command), shell=shell, bufsize=-1,
1189 procutil.tonativestr(command), shell=shell, bufsize=-1,
1190 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1190 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1191 stdout=procutil.stdout, stderr=procutil.stderr,
1191 stdout=procutil.stdout, stderr=procutil.stderr,
1192 env=procutil.tonativeenv(procutil.shellenviron(env)))
1192 env=procutil.tonativeenv(procutil.shellenviron(env)))
1193 except OSError as e:
1193 except OSError as e:
1194 if e.errno == errno.ENOENT and not shell:
1194 if e.errno == errno.ENOENT and not shell:
1195 self.warn(_("missing pager command '%s', skipping pager\n")
1195 self.warn(_("missing pager command '%s', skipping pager\n")
1196 % command)
1196 % command)
1197 return False
1197 return False
1198 raise
1198 raise
1199
1199
1200 # back up original file descriptors
1200 # back up original file descriptors
1201 stdoutfd = os.dup(procutil.stdout.fileno())
1201 stdoutfd = os.dup(procutil.stdout.fileno())
1202 stderrfd = os.dup(procutil.stderr.fileno())
1202 stderrfd = os.dup(procutil.stderr.fileno())
1203
1203
1204 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1204 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1205 if self._isatty(procutil.stderr):
1205 if self._isatty(procutil.stderr):
1206 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1206 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1207
1207
1208 @self.atexit
1208 @self.atexit
1209 def killpager():
1209 def killpager():
1210 if util.safehasattr(signal, "SIGINT"):
1210 if util.safehasattr(signal, "SIGINT"):
1211 signal.signal(signal.SIGINT, signal.SIG_IGN)
1211 signal.signal(signal.SIGINT, signal.SIG_IGN)
1212 # restore original fds, closing pager.stdin copies in the process
1212 # restore original fds, closing pager.stdin copies in the process
1213 os.dup2(stdoutfd, procutil.stdout.fileno())
1213 os.dup2(stdoutfd, procutil.stdout.fileno())
1214 os.dup2(stderrfd, procutil.stderr.fileno())
1214 os.dup2(stderrfd, procutil.stderr.fileno())
1215 pager.stdin.close()
1215 pager.stdin.close()
1216 pager.wait()
1216 pager.wait()
1217
1217
1218 return True
1218 return True
1219
1219
1220 @property
1220 @property
1221 def _exithandlers(self):
1221 def _exithandlers(self):
1222 return _reqexithandlers
1222 return _reqexithandlers
1223
1223
1224 def atexit(self, func, *args, **kwargs):
1224 def atexit(self, func, *args, **kwargs):
1225 '''register a function to run after dispatching a request
1225 '''register a function to run after dispatching a request
1226
1226
1227 Handlers do not stay registered across request boundaries.'''
1227 Handlers do not stay registered across request boundaries.'''
1228 self._exithandlers.append((func, args, kwargs))
1228 self._exithandlers.append((func, args, kwargs))
1229 return func
1229 return func
1230
1230
1231 def interface(self, feature):
1231 def interface(self, feature):
1232 """what interface to use for interactive console features?
1232 """what interface to use for interactive console features?
1233
1233
1234 The interface is controlled by the value of `ui.interface` but also by
1234 The interface is controlled by the value of `ui.interface` but also by
1235 the value of feature-specific configuration. For example:
1235 the value of feature-specific configuration. For example:
1236
1236
1237 ui.interface.histedit = text
1237 ui.interface.histedit = text
1238 ui.interface.chunkselector = curses
1238 ui.interface.chunkselector = curses
1239
1239
1240 Here the features are "histedit" and "chunkselector".
1240 Here the features are "histedit" and "chunkselector".
1241
1241
1242 The configuration above means that the default interfaces for commands
1242 The configuration above means that the default interfaces for commands
1243 is curses, the interface for histedit is text and the interface for
1243 is curses, the interface for histedit is text and the interface for
1244 selecting chunk is crecord (the best curses interface available).
1244 selecting chunk is crecord (the best curses interface available).
1245
1245
1246 Consider the following example:
1246 Consider the following example:
1247 ui.interface = curses
1247 ui.interface = curses
1248 ui.interface.histedit = text
1248 ui.interface.histedit = text
1249
1249
1250 Then histedit will use the text interface and chunkselector will use
1250 Then histedit will use the text interface and chunkselector will use
1251 the default curses interface (crecord at the moment).
1251 the default curses interface (crecord at the moment).
1252 """
1252 """
1253 alldefaults = frozenset(["text", "curses"])
1253 alldefaults = frozenset(["text", "curses"])
1254
1254
1255 featureinterfaces = {
1255 featureinterfaces = {
1256 "chunkselector": [
1256 "chunkselector": [
1257 "text",
1257 "text",
1258 "curses",
1258 "curses",
1259 ],
1259 ],
1260 "histedit": [
1260 "histedit": [
1261 "text",
1261 "text",
1262 "curses",
1262 "curses",
1263 ],
1263 ],
1264 }
1264 }
1265
1265
1266 # Feature-specific interface
1266 # Feature-specific interface
1267 if feature not in featureinterfaces.keys():
1267 if feature not in featureinterfaces.keys():
1268 # Programming error, not user error
1268 # Programming error, not user error
1269 raise ValueError("Unknown feature requested %s" % feature)
1269 raise ValueError("Unknown feature requested %s" % feature)
1270
1270
1271 availableinterfaces = frozenset(featureinterfaces[feature])
1271 availableinterfaces = frozenset(featureinterfaces[feature])
1272 if alldefaults > availableinterfaces:
1272 if alldefaults > availableinterfaces:
1273 # Programming error, not user error. We need a use case to
1273 # Programming error, not user error. We need a use case to
1274 # define the right thing to do here.
1274 # define the right thing to do here.
1275 raise ValueError(
1275 raise ValueError(
1276 "Feature %s does not handle all default interfaces" %
1276 "Feature %s does not handle all default interfaces" %
1277 feature)
1277 feature)
1278
1278
1279 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1279 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1280 return "text"
1280 return "text"
1281
1281
1282 # Default interface for all the features
1282 # Default interface for all the features
1283 defaultinterface = "text"
1283 defaultinterface = "text"
1284 i = self.config("ui", "interface")
1284 i = self.config("ui", "interface")
1285 if i in alldefaults:
1285 if i in alldefaults:
1286 defaultinterface = i
1286 defaultinterface = i
1287
1287
1288 choseninterface = defaultinterface
1288 choseninterface = defaultinterface
1289 f = self.config("ui", "interface.%s" % feature)
1289 f = self.config("ui", "interface.%s" % feature)
1290 if f in availableinterfaces:
1290 if f in availableinterfaces:
1291 choseninterface = f
1291 choseninterface = f
1292
1292
1293 if i is not None and defaultinterface != i:
1293 if i is not None and defaultinterface != i:
1294 if f is not None:
1294 if f is not None:
1295 self.warn(_("invalid value for ui.interface: %s\n") %
1295 self.warn(_("invalid value for ui.interface: %s\n") %
1296 (i,))
1296 (i,))
1297 else:
1297 else:
1298 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1298 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1299 (i, choseninterface))
1299 (i, choseninterface))
1300 if f is not None and choseninterface != f:
1300 if f is not None and choseninterface != f:
1301 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1301 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1302 (feature, f, choseninterface))
1302 (feature, f, choseninterface))
1303
1303
1304 return choseninterface
1304 return choseninterface
1305
1305
1306 def interactive(self):
1306 def interactive(self):
1307 '''is interactive input allowed?
1307 '''is interactive input allowed?
1308
1308
1309 An interactive session is a session where input can be reasonably read
1309 An interactive session is a session where input can be reasonably read
1310 from `sys.stdin'. If this function returns false, any attempt to read
1310 from `sys.stdin'. If this function returns false, any attempt to read
1311 from stdin should fail with an error, unless a sensible default has been
1311 from stdin should fail with an error, unless a sensible default has been
1312 specified.
1312 specified.
1313
1313
1314 Interactiveness is triggered by the value of the `ui.interactive'
1314 Interactiveness is triggered by the value of the `ui.interactive'
1315 configuration variable or - if it is unset - when `sys.stdin' points
1315 configuration variable or - if it is unset - when `sys.stdin' points
1316 to a terminal device.
1316 to a terminal device.
1317
1317
1318 This function refers to input only; for output, see `ui.formatted()'.
1318 This function refers to input only; for output, see `ui.formatted()'.
1319 '''
1319 '''
1320 i = self.configbool("ui", "interactive")
1320 i = self.configbool("ui", "interactive")
1321 if i is None:
1321 if i is None:
1322 # some environments replace stdin without implementing isatty
1322 # some environments replace stdin without implementing isatty
1323 # usually those are non-interactive
1323 # usually those are non-interactive
1324 return self._isatty(self._fin)
1324 return self._isatty(self._fin)
1325
1325
1326 return i
1326 return i
1327
1327
1328 def termwidth(self):
1328 def termwidth(self):
1329 '''how wide is the terminal in columns?
1329 '''how wide is the terminal in columns?
1330 '''
1330 '''
1331 if 'COLUMNS' in encoding.environ:
1331 if 'COLUMNS' in encoding.environ:
1332 try:
1332 try:
1333 return int(encoding.environ['COLUMNS'])
1333 return int(encoding.environ['COLUMNS'])
1334 except ValueError:
1334 except ValueError:
1335 pass
1335 pass
1336 return scmutil.termsize(self)[0]
1336 return scmutil.termsize(self)[0]
1337
1337
1338 def formatted(self):
1338 def formatted(self):
1339 '''should formatted output be used?
1339 '''should formatted output be used?
1340
1340
1341 It is often desirable to format the output to suite the output medium.
1341 It is often desirable to format the output to suite the output medium.
1342 Examples of this are truncating long lines or colorizing messages.
1342 Examples of this are truncating long lines or colorizing messages.
1343 However, this is not often not desirable when piping output into other
1343 However, this is not often not desirable when piping output into other
1344 utilities, e.g. `grep'.
1344 utilities, e.g. `grep'.
1345
1345
1346 Formatted output is triggered by the value of the `ui.formatted'
1346 Formatted output is triggered by the value of the `ui.formatted'
1347 configuration variable or - if it is unset - when `sys.stdout' points
1347 configuration variable or - if it is unset - when `sys.stdout' points
1348 to a terminal device. Please note that `ui.formatted' should be
1348 to a terminal device. Please note that `ui.formatted' should be
1349 considered an implementation detail; it is not intended for use outside
1349 considered an implementation detail; it is not intended for use outside
1350 Mercurial or its extensions.
1350 Mercurial or its extensions.
1351
1351
1352 This function refers to output only; for input, see `ui.interactive()'.
1352 This function refers to output only; for input, see `ui.interactive()'.
1353 This function always returns false when in plain mode, see `ui.plain()'.
1353 This function always returns false when in plain mode, see `ui.plain()'.
1354 '''
1354 '''
1355 if self.plain():
1355 if self.plain():
1356 return False
1356 return False
1357
1357
1358 i = self.configbool("ui", "formatted")
1358 i = self.configbool("ui", "formatted")
1359 if i is None:
1359 if i is None:
1360 # some environments replace stdout without implementing isatty
1360 # some environments replace stdout without implementing isatty
1361 # usually those are non-interactive
1361 # usually those are non-interactive
1362 return self._isatty(self._fout)
1362 return self._isatty(self._fout)
1363
1363
1364 return i
1364 return i
1365
1365
1366 def _readline(self):
1366 def _readline(self):
1367 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1367 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1368 # because they have to be text streams with *no buffering*. Instead,
1368 # because they have to be text streams with *no buffering*. Instead,
1369 # we use rawinput() only if call_readline() will be invoked by
1369 # we use rawinput() only if call_readline() will be invoked by
1370 # PyOS_Readline(), so no I/O will be made at Python layer.
1370 # PyOS_Readline(), so no I/O will be made at Python layer.
1371 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1371 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1372 and procutil.isstdin(self._fin)
1372 and procutil.isstdin(self._fin)
1373 and procutil.isstdout(self._fout))
1373 and procutil.isstdout(self._fout))
1374 if usereadline:
1374 if usereadline:
1375 try:
1375 try:
1376 # magically add command line editing support, where
1376 # magically add command line editing support, where
1377 # available
1377 # available
1378 import readline
1378 import readline
1379 # force demandimport to really load the module
1379 # force demandimport to really load the module
1380 readline.read_history_file
1380 readline.read_history_file
1381 # windows sometimes raises something other than ImportError
1381 # windows sometimes raises something other than ImportError
1382 except Exception:
1382 except Exception:
1383 usereadline = False
1383 usereadline = False
1384
1384
1385 # prompt ' ' must exist; otherwise readline may delete entire line
1385 # prompt ' ' must exist; otherwise readline may delete entire line
1386 # - http://bugs.python.org/issue12833
1386 # - http://bugs.python.org/issue12833
1387 with self.timeblockedsection('stdio'):
1387 with self.timeblockedsection('stdio'):
1388 if usereadline:
1388 if usereadline:
1389 line = encoding.strtolocal(pycompat.rawinput(r' '))
1389 line = encoding.strtolocal(pycompat.rawinput(r' '))
1390 # When stdin is in binary mode on Windows, it can cause
1390 # When stdin is in binary mode on Windows, it can cause
1391 # raw_input() to emit an extra trailing carriage return
1391 # raw_input() to emit an extra trailing carriage return
1392 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1392 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1393 line = line[:-1]
1393 line = line[:-1]
1394 else:
1394 else:
1395 self._fout.write(b' ')
1395 self._fout.write(b' ')
1396 self._fout.flush()
1396 self._fout.flush()
1397 line = self._fin.readline()
1397 line = self._fin.readline()
1398 if not line:
1398 if not line:
1399 raise EOFError
1399 raise EOFError
1400 line = line.rstrip(pycompat.oslinesep)
1400 line = line.rstrip(pycompat.oslinesep)
1401
1401
1402 return line
1402 return line
1403
1403
1404 def prompt(self, msg, default="y"):
1404 def prompt(self, msg, default="y"):
1405 """Prompt user with msg, read response.
1405 """Prompt user with msg, read response.
1406 If ui is not interactive, the default is returned.
1406 If ui is not interactive, the default is returned.
1407 """
1407 """
1408 return self._prompt(msg, default=default)
1408 return self._prompt(msg, default=default)
1409
1409
1410 def _prompt(self, msg, **opts):
1410 def _prompt(self, msg, **opts):
1411 default = opts[r'default']
1411 default = opts[r'default']
1412 if not self.interactive():
1412 if not self.interactive():
1413 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1413 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1414 self._writemsg(self._fmsgout, default or '', "\n",
1414 self._writemsg(self._fmsgout, default or '', "\n",
1415 type='promptecho')
1415 type='promptecho')
1416 return default
1416 return default
1417 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1417 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1418 self.flush()
1418 self.flush()
1419 try:
1419 try:
1420 r = self._readline()
1420 r = self._readline()
1421 if not r:
1421 if not r:
1422 r = default
1422 r = default
1423 if self.configbool('ui', 'promptecho'):
1423 if self.configbool('ui', 'promptecho'):
1424 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1424 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1425 return r
1425 return r
1426 except EOFError:
1426 except EOFError:
1427 raise error.ResponseExpected()
1427 raise error.ResponseExpected()
1428
1428
1429 @staticmethod
1429 @staticmethod
1430 def extractchoices(prompt):
1430 def extractchoices(prompt):
1431 """Extract prompt message and list of choices from specified prompt.
1431 """Extract prompt message and list of choices from specified prompt.
1432
1432
1433 This returns tuple "(message, choices)", and "choices" is the
1433 This returns tuple "(message, choices)", and "choices" is the
1434 list of tuple "(response character, text without &)".
1434 list of tuple "(response character, text without &)".
1435
1435
1436 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1436 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1437 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1437 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1438 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1438 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1439 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1439 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1440 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1440 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1441 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1441 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1442 """
1442 """
1443
1443
1444 # Sadly, the prompt string may have been built with a filename
1444 # Sadly, the prompt string may have been built with a filename
1445 # containing "$$" so let's try to find the first valid-looking
1445 # containing "$$" so let's try to find the first valid-looking
1446 # prompt to start parsing. Sadly, we also can't rely on
1446 # prompt to start parsing. Sadly, we also can't rely on
1447 # choices containing spaces, ASCII, or basically anything
1447 # choices containing spaces, ASCII, or basically anything
1448 # except an ampersand followed by a character.
1448 # except an ampersand followed by a character.
1449 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1449 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1450 msg = m.group(1)
1450 msg = m.group(1)
1451 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1451 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1452 def choicetuple(s):
1452 def choicetuple(s):
1453 ampidx = s.index('&')
1453 ampidx = s.index('&')
1454 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1454 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1455 return (msg, [choicetuple(s) for s in choices])
1455 return (msg, [choicetuple(s) for s in choices])
1456
1456
1457 def promptchoice(self, prompt, default=0):
1457 def promptchoice(self, prompt, default=0):
1458 """Prompt user with a message, read response, and ensure it matches
1458 """Prompt user with a message, read response, and ensure it matches
1459 one of the provided choices. The prompt is formatted as follows:
1459 one of the provided choices. The prompt is formatted as follows:
1460
1460
1461 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1461 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1462
1462
1463 The index of the choice is returned. Responses are case
1463 The index of the choice is returned. Responses are case
1464 insensitive. If ui is not interactive, the default is
1464 insensitive. If ui is not interactive, the default is
1465 returned.
1465 returned.
1466 """
1466 """
1467
1467
1468 msg, choices = self.extractchoices(prompt)
1468 msg, choices = self.extractchoices(prompt)
1469 resps = [r for r, t in choices]
1469 resps = [r for r, t in choices]
1470 while True:
1470 while True:
1471 r = self._prompt(msg, default=resps[default], choices=choices)
1471 r = self._prompt(msg, default=resps[default], choices=choices)
1472 if r.lower() in resps:
1472 if r.lower() in resps:
1473 return resps.index(r.lower())
1473 return resps.index(r.lower())
1474 # TODO: shouldn't it be a warning?
1474 # TODO: shouldn't it be a warning?
1475 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1475 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1476
1476
1477 def getpass(self, prompt=None, default=None):
1477 def getpass(self, prompt=None, default=None):
1478 if not self.interactive():
1478 if not self.interactive():
1479 return default
1479 return default
1480 try:
1480 try:
1481 self._writemsg(self._fmsgerr, prompt or _('password: '),
1481 self._writemsg(self._fmsgerr, prompt or _('password: '),
1482 type='prompt', password=True)
1482 type='prompt', password=True)
1483 # disable getpass() only if explicitly specified. it's still valid
1483 # disable getpass() only if explicitly specified. it's still valid
1484 # to interact with tty even if fin is not a tty.
1484 # to interact with tty even if fin is not a tty.
1485 with self.timeblockedsection('stdio'):
1485 with self.timeblockedsection('stdio'):
1486 if self.configbool('ui', 'nontty'):
1486 if self.configbool('ui', 'nontty'):
1487 l = self._fin.readline()
1487 l = self._fin.readline()
1488 if not l:
1488 if not l:
1489 raise EOFError
1489 raise EOFError
1490 return l.rstrip('\n')
1490 return l.rstrip('\n')
1491 else:
1491 else:
1492 return getpass.getpass('')
1492 return getpass.getpass('')
1493 except EOFError:
1493 except EOFError:
1494 raise error.ResponseExpected()
1494 raise error.ResponseExpected()
1495
1495
1496 def status(self, *msg, **opts):
1496 def status(self, *msg, **opts):
1497 '''write status message to output (if ui.quiet is False)
1497 '''write status message to output (if ui.quiet is False)
1498
1498
1499 This adds an output label of "ui.status".
1499 This adds an output label of "ui.status".
1500 '''
1500 '''
1501 if not self.quiet:
1501 if not self.quiet:
1502 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1502 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1503
1503
1504 def warn(self, *msg, **opts):
1504 def warn(self, *msg, **opts):
1505 '''write warning message to output (stderr)
1505 '''write warning message to output (stderr)
1506
1506
1507 This adds an output label of "ui.warning".
1507 This adds an output label of "ui.warning".
1508 '''
1508 '''
1509 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1509 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1510
1510
1511 def error(self, *msg, **opts):
1511 def error(self, *msg, **opts):
1512 '''write error message to output (stderr)
1512 '''write error message to output (stderr)
1513
1513
1514 This adds an output label of "ui.error".
1514 This adds an output label of "ui.error".
1515 '''
1515 '''
1516 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1516 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1517
1517
1518 def note(self, *msg, **opts):
1518 def note(self, *msg, **opts):
1519 '''write note to output (if ui.verbose is True)
1519 '''write note to output (if ui.verbose is True)
1520
1520
1521 This adds an output label of "ui.note".
1521 This adds an output label of "ui.note".
1522 '''
1522 '''
1523 if self.verbose:
1523 if self.verbose:
1524 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1524 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1525
1525
1526 def debug(self, *msg, **opts):
1526 def debug(self, *msg, **opts):
1527 '''write debug message to output (if ui.debugflag is True)
1527 '''write debug message to output (if ui.debugflag is True)
1528
1528
1529 This adds an output label of "ui.debug".
1529 This adds an output label of "ui.debug".
1530 '''
1530 '''
1531 if self.debugflag:
1531 if self.debugflag:
1532 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1532 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1533 self.log(b'debug', b'%s', b''.join(msg))
1533 self.log(b'debug', b'%s', b''.join(msg))
1534
1534
1535 def edit(self, text, user, extra=None, editform=None, pending=None,
1535 def edit(self, text, user, extra=None, editform=None, pending=None,
1536 repopath=None, action=None):
1536 repopath=None, action=None):
1537 if action is None:
1537 if action is None:
1538 self.develwarn('action is None but will soon be a required '
1538 self.develwarn('action is None but will soon be a required '
1539 'parameter to ui.edit()')
1539 'parameter to ui.edit()')
1540 extra_defaults = {
1540 extra_defaults = {
1541 'prefix': 'editor',
1541 'prefix': 'editor',
1542 'suffix': '.txt',
1542 'suffix': '.txt',
1543 }
1543 }
1544 if extra is not None:
1544 if extra is not None:
1545 if extra.get('suffix') is not None:
1545 if extra.get('suffix') is not None:
1546 self.develwarn('extra.suffix is not None but will soon be '
1546 self.develwarn('extra.suffix is not None but will soon be '
1547 'ignored by ui.edit()')
1547 'ignored by ui.edit()')
1548 extra_defaults.update(extra)
1548 extra_defaults.update(extra)
1549 extra = extra_defaults
1549 extra = extra_defaults
1550
1550
1551 if action == 'diff':
1551 if action == 'diff':
1552 suffix = '.diff'
1552 suffix = '.diff'
1553 elif action:
1553 elif action:
1554 suffix = '.%s.hg.txt' % action
1554 suffix = '.%s.hg.txt' % action
1555 else:
1555 else:
1556 suffix = extra['suffix']
1556 suffix = extra['suffix']
1557
1557
1558 rdir = None
1558 rdir = None
1559 if self.configbool('experimental', 'editortmpinhg'):
1559 if self.configbool('experimental', 'editortmpinhg'):
1560 rdir = repopath
1560 rdir = repopath
1561 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1561 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1562 suffix=suffix,
1562 suffix=suffix,
1563 dir=rdir)
1563 dir=rdir)
1564 try:
1564 try:
1565 f = os.fdopen(fd, r'wb')
1565 f = os.fdopen(fd, r'wb')
1566 f.write(util.tonativeeol(text))
1566 f.write(util.tonativeeol(text))
1567 f.close()
1567 f.close()
1568
1568
1569 environ = {'HGUSER': user}
1569 environ = {'HGUSER': user}
1570 if 'transplant_source' in extra:
1570 if 'transplant_source' in extra:
1571 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1571 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1572 for label in ('intermediate-source', 'source', 'rebase_source'):
1572 for label in ('intermediate-source', 'source', 'rebase_source'):
1573 if label in extra:
1573 if label in extra:
1574 environ.update({'HGREVISION': extra[label]})
1574 environ.update({'HGREVISION': extra[label]})
1575 break
1575 break
1576 if editform:
1576 if editform:
1577 environ.update({'HGEDITFORM': editform})
1577 environ.update({'HGEDITFORM': editform})
1578 if pending:
1578 if pending:
1579 environ.update({'HG_PENDING': pending})
1579 environ.update({'HG_PENDING': pending})
1580
1580
1581 editor = self.geteditor()
1581 editor = self.geteditor()
1582
1582
1583 self.system("%s \"%s\"" % (editor, name),
1583 self.system("%s \"%s\"" % (editor, name),
1584 environ=environ,
1584 environ=environ,
1585 onerr=error.Abort, errprefix=_("edit failed"),
1585 onerr=error.Abort, errprefix=_("edit failed"),
1586 blockedtag='editor')
1586 blockedtag='editor')
1587
1587
1588 f = open(name, r'rb')
1588 f = open(name, r'rb')
1589 t = util.fromnativeeol(f.read())
1589 t = util.fromnativeeol(f.read())
1590 f.close()
1590 f.close()
1591 finally:
1591 finally:
1592 os.unlink(name)
1592 os.unlink(name)
1593
1593
1594 return t
1594 return t
1595
1595
1596 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1596 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1597 blockedtag=None):
1597 blockedtag=None):
1598 '''execute shell command with appropriate output stream. command
1598 '''execute shell command with appropriate output stream. command
1599 output will be redirected if fout is not stdout.
1599 output will be redirected if fout is not stdout.
1600
1600
1601 if command fails and onerr is None, return status, else raise onerr
1601 if command fails and onerr is None, return status, else raise onerr
1602 object as exception.
1602 object as exception.
1603 '''
1603 '''
1604 if blockedtag is None:
1604 if blockedtag is None:
1605 # Long cmds tend to be because of an absolute path on cmd. Keep
1605 # Long cmds tend to be because of an absolute path on cmd. Keep
1606 # the tail end instead
1606 # the tail end instead
1607 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1607 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1608 blockedtag = 'unknown_system_' + cmdsuffix
1608 blockedtag = 'unknown_system_' + cmdsuffix
1609 out = self._fout
1609 out = self._fout
1610 if any(s[1] for s in self._bufferstates):
1610 if any(s[1] for s in self._bufferstates):
1611 out = self
1611 out = self
1612 with self.timeblockedsection(blockedtag):
1612 with self.timeblockedsection(blockedtag):
1613 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1613 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1614 if rc and onerr:
1614 if rc and onerr:
1615 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1615 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1616 procutil.explainexit(rc))
1616 procutil.explainexit(rc))
1617 if errprefix:
1617 if errprefix:
1618 errmsg = '%s: %s' % (errprefix, errmsg)
1618 errmsg = '%s: %s' % (errprefix, errmsg)
1619 raise onerr(errmsg)
1619 raise onerr(errmsg)
1620 return rc
1620 return rc
1621
1621
1622 def _runsystem(self, cmd, environ, cwd, out):
1622 def _runsystem(self, cmd, environ, cwd, out):
1623 """actually execute the given shell command (can be overridden by
1623 """actually execute the given shell command (can be overridden by
1624 extensions like chg)"""
1624 extensions like chg)"""
1625 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1625 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1626
1626
1627 def traceback(self, exc=None, force=False):
1627 def traceback(self, exc=None, force=False):
1628 '''print exception traceback if traceback printing enabled or forced.
1628 '''print exception traceback if traceback printing enabled or forced.
1629 only to call in exception handler. returns true if traceback
1629 only to call in exception handler. returns true if traceback
1630 printed.'''
1630 printed.'''
1631 if self.tracebackflag or force:
1631 if self.tracebackflag or force:
1632 if exc is None:
1632 if exc is None:
1633 exc = sys.exc_info()
1633 exc = sys.exc_info()
1634 cause = getattr(exc[1], 'cause', None)
1634 cause = getattr(exc[1], 'cause', None)
1635
1635
1636 if cause is not None:
1636 if cause is not None:
1637 causetb = traceback.format_tb(cause[2])
1637 causetb = traceback.format_tb(cause[2])
1638 exctb = traceback.format_tb(exc[2])
1638 exctb = traceback.format_tb(exc[2])
1639 exconly = traceback.format_exception_only(cause[0], cause[1])
1639 exconly = traceback.format_exception_only(cause[0], cause[1])
1640
1640
1641 # exclude frame where 'exc' was chained and rethrown from exctb
1641 # exclude frame where 'exc' was chained and rethrown from exctb
1642 self.write_err('Traceback (most recent call last):\n',
1642 self.write_err('Traceback (most recent call last):\n',
1643 ''.join(exctb[:-1]),
1643 ''.join(exctb[:-1]),
1644 ''.join(causetb),
1644 ''.join(causetb),
1645 ''.join(exconly))
1645 ''.join(exconly))
1646 else:
1646 else:
1647 output = traceback.format_exception(exc[0], exc[1], exc[2])
1647 output = traceback.format_exception(exc[0], exc[1], exc[2])
1648 self.write_err(encoding.strtolocal(r''.join(output)))
1648 self.write_err(encoding.strtolocal(r''.join(output)))
1649 return self.tracebackflag or force
1649 return self.tracebackflag or force
1650
1650
1651 def geteditor(self):
1651 def geteditor(self):
1652 '''return editor to use'''
1652 '''return editor to use'''
1653 if pycompat.sysplatform == 'plan9':
1653 if pycompat.sysplatform == 'plan9':
1654 # vi is the MIPS instruction simulator on Plan 9. We
1654 # vi is the MIPS instruction simulator on Plan 9. We
1655 # instead default to E to plumb commit messages to
1655 # instead default to E to plumb commit messages to
1656 # avoid confusion.
1656 # avoid confusion.
1657 editor = 'E'
1657 editor = 'E'
1658 else:
1658 else:
1659 editor = 'vi'
1659 editor = 'vi'
1660 return (encoding.environ.get("HGEDITOR") or
1660 return (encoding.environ.get("HGEDITOR") or
1661 self.config("ui", "editor", editor))
1661 self.config("ui", "editor", editor))
1662
1662
1663 @util.propertycache
1663 @util.propertycache
1664 def _progbar(self):
1664 def _progbar(self):
1665 """setup the progbar singleton to the ui object"""
1665 """setup the progbar singleton to the ui object"""
1666 if (self.quiet or self.debugflag
1666 if (self.quiet or self.debugflag
1667 or self.configbool('progress', 'disable')
1667 or self.configbool('progress', 'disable')
1668 or not progress.shouldprint(self)):
1668 or not progress.shouldprint(self)):
1669 return None
1669 return None
1670 return getprogbar(self)
1670 return getprogbar(self)
1671
1671
1672 def _progclear(self):
1672 def _progclear(self):
1673 """clear progress bar output if any. use it before any output"""
1673 """clear progress bar output if any. use it before any output"""
1674 if not haveprogbar(): # nothing loaded yet
1674 if not haveprogbar(): # nothing loaded yet
1675 return
1675 return
1676 if self._progbar is not None and self._progbar.printed:
1676 if self._progbar is not None and self._progbar.printed:
1677 self._progbar.clear()
1677 self._progbar.clear()
1678
1678
1679 def progress(self, topic, pos, item="", unit="", total=None):
1679 def progress(self, topic, pos, item="", unit="", total=None):
1680 '''show a progress message
1680 '''show a progress message
1681
1681
1682 By default a textual progress bar will be displayed if an operation
1682 By default a textual progress bar will be displayed if an operation
1683 takes too long. 'topic' is the current operation, 'item' is a
1683 takes too long. 'topic' is the current operation, 'item' is a
1684 non-numeric marker of the current position (i.e. the currently
1684 non-numeric marker of the current position (i.e. the currently
1685 in-process file), 'pos' is the current numeric position (i.e.
1685 in-process file), 'pos' is the current numeric position (i.e.
1686 revision, bytes, etc.), unit is a corresponding unit label,
1686 revision, bytes, etc.), unit is a corresponding unit label,
1687 and total is the highest expected pos.
1687 and total is the highest expected pos.
1688
1688
1689 Multiple nested topics may be active at a time.
1689 Multiple nested topics may be active at a time.
1690
1690
1691 All topics should be marked closed by setting pos to None at
1691 All topics should be marked closed by setting pos to None at
1692 termination.
1692 termination.
1693 '''
1693 '''
1694 if getattr(self._fmsgerr, 'structured', False):
1694 progress = self.makeprogress(topic, unit, total)
1695 # channel for machine-readable output with metadata, just send
1695 if pos is not None:
1696 # raw information
1696 progress.update(pos, item=item)
1697 # TODO: consider porting some useful information (e.g. estimated
1698 # time) from progbar. we might want to support update delay to
1699 # reduce the cost of transferring progress messages.
1700 self._fmsgerr.write(None, type=b'progress', topic=topic, pos=pos,
1701 item=item, unit=unit, total=total)
1702 elif self._progbar is not None:
1703 self._progbar.progress(topic, pos, item=item, unit=unit,
1704 total=total)
1705
1706 # Looking up progress.debug in tight loops is expensive. The value
1707 # is cached on the progbar object and we can avoid the lookup in
1708 # the common case where a progbar is active.
1709 if pos is None or not self._progbar.debug:
1710 return
1711
1712 # Keep this logic in sync with above.
1713 if pos is None or not self.configbool('progress', 'debug'):
1714 return
1715
1716 if unit:
1717 unit = ' ' + unit
1718 if item:
1719 item = ' ' + item
1720
1721 if total:
1722 pct = 100.0 * pos / total
1723 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1724 % (topic, item, pos, total, unit, pct))
1725 else:
1697 else:
1726 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1698 progress.complete()
1727
1699
1728 def makeprogress(self, topic, unit="", total=None):
1700 def makeprogress(self, topic, unit="", total=None):
1729 '''exists only so low-level modules won't need to import scmutil'''
1701 '''exists only so low-level modules won't need to import scmutil'''
1730 return scmutil.progress(self, topic, unit, total)
1702 return scmutil.progress(self, topic, unit, total)
1731
1703
1732 def getlogger(self, name):
1704 def getlogger(self, name):
1733 """Returns a logger of the given name; or None if not registered"""
1705 """Returns a logger of the given name; or None if not registered"""
1734 return self._loggers.get(name)
1706 return self._loggers.get(name)
1735
1707
1736 def setlogger(self, name, logger):
1708 def setlogger(self, name, logger):
1737 """Install logger which can be identified later by the given name
1709 """Install logger which can be identified later by the given name
1738
1710
1739 More than one loggers can be registered. Use extension or module
1711 More than one loggers can be registered. Use extension or module
1740 name to uniquely identify the logger instance.
1712 name to uniquely identify the logger instance.
1741 """
1713 """
1742 self._loggers[name] = logger
1714 self._loggers[name] = logger
1743
1715
1744 def log(self, event, msgfmt, *msgargs, **opts):
1716 def log(self, event, msgfmt, *msgargs, **opts):
1745 '''hook for logging facility extensions
1717 '''hook for logging facility extensions
1746
1718
1747 event should be a readily-identifiable subsystem, which will
1719 event should be a readily-identifiable subsystem, which will
1748 allow filtering.
1720 allow filtering.
1749
1721
1750 msgfmt should be a newline-terminated format string to log, and
1722 msgfmt should be a newline-terminated format string to log, and
1751 *msgargs are %-formatted into it.
1723 *msgargs are %-formatted into it.
1752
1724
1753 **opts currently has no defined meanings.
1725 **opts currently has no defined meanings.
1754 '''
1726 '''
1755 if not self._loggers:
1727 if not self._loggers:
1756 return
1728 return
1757 activeloggers = [l for l in self._loggers.itervalues()
1729 activeloggers = [l for l in self._loggers.itervalues()
1758 if l.tracked(event)]
1730 if l.tracked(event)]
1759 if not activeloggers:
1731 if not activeloggers:
1760 return
1732 return
1761 msg = msgfmt % msgargs
1733 msg = msgfmt % msgargs
1762 opts = pycompat.byteskwargs(opts)
1734 opts = pycompat.byteskwargs(opts)
1763 # guard against recursion from e.g. ui.debug()
1735 # guard against recursion from e.g. ui.debug()
1764 registeredloggers = self._loggers
1736 registeredloggers = self._loggers
1765 self._loggers = {}
1737 self._loggers = {}
1766 try:
1738 try:
1767 for logger in activeloggers:
1739 for logger in activeloggers:
1768 logger.log(self, event, msg, opts)
1740 logger.log(self, event, msg, opts)
1769 finally:
1741 finally:
1770 self._loggers = registeredloggers
1742 self._loggers = registeredloggers
1771
1743
1772 def label(self, msg, label):
1744 def label(self, msg, label):
1773 '''style msg based on supplied label
1745 '''style msg based on supplied label
1774
1746
1775 If some color mode is enabled, this will add the necessary control
1747 If some color mode is enabled, this will add the necessary control
1776 characters to apply such color. In addition, 'debug' color mode adds
1748 characters to apply such color. In addition, 'debug' color mode adds
1777 markup showing which label affects a piece of text.
1749 markup showing which label affects a piece of text.
1778
1750
1779 ui.write(s, 'label') is equivalent to
1751 ui.write(s, 'label') is equivalent to
1780 ui.write(ui.label(s, 'label')).
1752 ui.write(ui.label(s, 'label')).
1781 '''
1753 '''
1782 if self._colormode is not None:
1754 if self._colormode is not None:
1783 return color.colorlabel(self, msg, label)
1755 return color.colorlabel(self, msg, label)
1784 return msg
1756 return msg
1785
1757
1786 def develwarn(self, msg, stacklevel=1, config=None):
1758 def develwarn(self, msg, stacklevel=1, config=None):
1787 """issue a developer warning message
1759 """issue a developer warning message
1788
1760
1789 Use 'stacklevel' to report the offender some layers further up in the
1761 Use 'stacklevel' to report the offender some layers further up in the
1790 stack.
1762 stack.
1791 """
1763 """
1792 if not self.configbool('devel', 'all-warnings'):
1764 if not self.configbool('devel', 'all-warnings'):
1793 if config is None or not self.configbool('devel', config):
1765 if config is None or not self.configbool('devel', config):
1794 return
1766 return
1795 msg = 'devel-warn: ' + msg
1767 msg = 'devel-warn: ' + msg
1796 stacklevel += 1 # get in develwarn
1768 stacklevel += 1 # get in develwarn
1797 if self.tracebackflag:
1769 if self.tracebackflag:
1798 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1770 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1799 self.log('develwarn', '%s at:\n%s' %
1771 self.log('develwarn', '%s at:\n%s' %
1800 (msg, ''.join(util.getstackframes(stacklevel))))
1772 (msg, ''.join(util.getstackframes(stacklevel))))
1801 else:
1773 else:
1802 curframe = inspect.currentframe()
1774 curframe = inspect.currentframe()
1803 calframe = inspect.getouterframes(curframe, 2)
1775 calframe = inspect.getouterframes(curframe, 2)
1804 fname, lineno, fmsg = calframe[stacklevel][1:4]
1776 fname, lineno, fmsg = calframe[stacklevel][1:4]
1805 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1777 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1806 self.write_err('%s at: %s:%d (%s)\n'
1778 self.write_err('%s at: %s:%d (%s)\n'
1807 % (msg, fname, lineno, fmsg))
1779 % (msg, fname, lineno, fmsg))
1808 self.log('develwarn', '%s at: %s:%d (%s)\n',
1780 self.log('develwarn', '%s at: %s:%d (%s)\n',
1809 msg, fname, lineno, fmsg)
1781 msg, fname, lineno, fmsg)
1810 curframe = calframe = None # avoid cycles
1782 curframe = calframe = None # avoid cycles
1811
1783
1812 def deprecwarn(self, msg, version, stacklevel=2):
1784 def deprecwarn(self, msg, version, stacklevel=2):
1813 """issue a deprecation warning
1785 """issue a deprecation warning
1814
1786
1815 - msg: message explaining what is deprecated and how to upgrade,
1787 - msg: message explaining what is deprecated and how to upgrade,
1816 - version: last version where the API will be supported,
1788 - version: last version where the API will be supported,
1817 """
1789 """
1818 if not (self.configbool('devel', 'all-warnings')
1790 if not (self.configbool('devel', 'all-warnings')
1819 or self.configbool('devel', 'deprec-warn')):
1791 or self.configbool('devel', 'deprec-warn')):
1820 return
1792 return
1821 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1793 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1822 " update your code.)") % version
1794 " update your code.)") % version
1823 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1795 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1824
1796
1825 def exportableenviron(self):
1797 def exportableenviron(self):
1826 """The environment variables that are safe to export, e.g. through
1798 """The environment variables that are safe to export, e.g. through
1827 hgweb.
1799 hgweb.
1828 """
1800 """
1829 return self._exportableenviron
1801 return self._exportableenviron
1830
1802
1831 @contextlib.contextmanager
1803 @contextlib.contextmanager
1832 def configoverride(self, overrides, source=""):
1804 def configoverride(self, overrides, source=""):
1833 """Context manager for temporary config overrides
1805 """Context manager for temporary config overrides
1834 `overrides` must be a dict of the following structure:
1806 `overrides` must be a dict of the following structure:
1835 {(section, name) : value}"""
1807 {(section, name) : value}"""
1836 backups = {}
1808 backups = {}
1837 try:
1809 try:
1838 for (section, name), value in overrides.items():
1810 for (section, name), value in overrides.items():
1839 backups[(section, name)] = self.backupconfig(section, name)
1811 backups[(section, name)] = self.backupconfig(section, name)
1840 self.setconfig(section, name, value, source)
1812 self.setconfig(section, name, value, source)
1841 yield
1813 yield
1842 finally:
1814 finally:
1843 for __, backup in backups.items():
1815 for __, backup in backups.items():
1844 self.restoreconfig(backup)
1816 self.restoreconfig(backup)
1845 # just restoring ui.quiet config to the previous value is not enough
1817 # just restoring ui.quiet config to the previous value is not enough
1846 # as it does not update ui.quiet class member
1818 # as it does not update ui.quiet class member
1847 if ('ui', 'quiet') in overrides:
1819 if ('ui', 'quiet') in overrides:
1848 self.fixconfig(section='ui')
1820 self.fixconfig(section='ui')
1849
1821
1850 class paths(dict):
1822 class paths(dict):
1851 """Represents a collection of paths and their configs.
1823 """Represents a collection of paths and their configs.
1852
1824
1853 Data is initially derived from ui instances and the config files they have
1825 Data is initially derived from ui instances and the config files they have
1854 loaded.
1826 loaded.
1855 """
1827 """
1856 def __init__(self, ui):
1828 def __init__(self, ui):
1857 dict.__init__(self)
1829 dict.__init__(self)
1858
1830
1859 for name, loc in ui.configitems('paths', ignoresub=True):
1831 for name, loc in ui.configitems('paths', ignoresub=True):
1860 # No location is the same as not existing.
1832 # No location is the same as not existing.
1861 if not loc:
1833 if not loc:
1862 continue
1834 continue
1863 loc, sub = ui.configsuboptions('paths', name)
1835 loc, sub = ui.configsuboptions('paths', name)
1864 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1836 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1865
1837
1866 def getpath(self, name, default=None):
1838 def getpath(self, name, default=None):
1867 """Return a ``path`` from a string, falling back to default.
1839 """Return a ``path`` from a string, falling back to default.
1868
1840
1869 ``name`` can be a named path or locations. Locations are filesystem
1841 ``name`` can be a named path or locations. Locations are filesystem
1870 paths or URIs.
1842 paths or URIs.
1871
1843
1872 Returns None if ``name`` is not a registered path, a URI, or a local
1844 Returns None if ``name`` is not a registered path, a URI, or a local
1873 path to a repo.
1845 path to a repo.
1874 """
1846 """
1875 # Only fall back to default if no path was requested.
1847 # Only fall back to default if no path was requested.
1876 if name is None:
1848 if name is None:
1877 if not default:
1849 if not default:
1878 default = ()
1850 default = ()
1879 elif not isinstance(default, (tuple, list)):
1851 elif not isinstance(default, (tuple, list)):
1880 default = (default,)
1852 default = (default,)
1881 for k in default:
1853 for k in default:
1882 try:
1854 try:
1883 return self[k]
1855 return self[k]
1884 except KeyError:
1856 except KeyError:
1885 continue
1857 continue
1886 return None
1858 return None
1887
1859
1888 # Most likely empty string.
1860 # Most likely empty string.
1889 # This may need to raise in the future.
1861 # This may need to raise in the future.
1890 if not name:
1862 if not name:
1891 return None
1863 return None
1892
1864
1893 try:
1865 try:
1894 return self[name]
1866 return self[name]
1895 except KeyError:
1867 except KeyError:
1896 # Try to resolve as a local path or URI.
1868 # Try to resolve as a local path or URI.
1897 try:
1869 try:
1898 # We don't pass sub-options in, so no need to pass ui instance.
1870 # We don't pass sub-options in, so no need to pass ui instance.
1899 return path(None, None, rawloc=name)
1871 return path(None, None, rawloc=name)
1900 except ValueError:
1872 except ValueError:
1901 raise error.RepoError(_('repository %s does not exist') %
1873 raise error.RepoError(_('repository %s does not exist') %
1902 name)
1874 name)
1903
1875
1904 _pathsuboptions = {}
1876 _pathsuboptions = {}
1905
1877
1906 def pathsuboption(option, attr):
1878 def pathsuboption(option, attr):
1907 """Decorator used to declare a path sub-option.
1879 """Decorator used to declare a path sub-option.
1908
1880
1909 Arguments are the sub-option name and the attribute it should set on
1881 Arguments are the sub-option name and the attribute it should set on
1910 ``path`` instances.
1882 ``path`` instances.
1911
1883
1912 The decorated function will receive as arguments a ``ui`` instance,
1884 The decorated function will receive as arguments a ``ui`` instance,
1913 ``path`` instance, and the string value of this option from the config.
1885 ``path`` instance, and the string value of this option from the config.
1914 The function should return the value that will be set on the ``path``
1886 The function should return the value that will be set on the ``path``
1915 instance.
1887 instance.
1916
1888
1917 This decorator can be used to perform additional verification of
1889 This decorator can be used to perform additional verification of
1918 sub-options and to change the type of sub-options.
1890 sub-options and to change the type of sub-options.
1919 """
1891 """
1920 def register(func):
1892 def register(func):
1921 _pathsuboptions[option] = (attr, func)
1893 _pathsuboptions[option] = (attr, func)
1922 return func
1894 return func
1923 return register
1895 return register
1924
1896
1925 @pathsuboption('pushurl', 'pushloc')
1897 @pathsuboption('pushurl', 'pushloc')
1926 def pushurlpathoption(ui, path, value):
1898 def pushurlpathoption(ui, path, value):
1927 u = util.url(value)
1899 u = util.url(value)
1928 # Actually require a URL.
1900 # Actually require a URL.
1929 if not u.scheme:
1901 if not u.scheme:
1930 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1902 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1931 return None
1903 return None
1932
1904
1933 # Don't support the #foo syntax in the push URL to declare branch to
1905 # Don't support the #foo syntax in the push URL to declare branch to
1934 # push.
1906 # push.
1935 if u.fragment:
1907 if u.fragment:
1936 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1908 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1937 'ignoring)\n') % path.name)
1909 'ignoring)\n') % path.name)
1938 u.fragment = None
1910 u.fragment = None
1939
1911
1940 return bytes(u)
1912 return bytes(u)
1941
1913
1942 @pathsuboption('pushrev', 'pushrev')
1914 @pathsuboption('pushrev', 'pushrev')
1943 def pushrevpathoption(ui, path, value):
1915 def pushrevpathoption(ui, path, value):
1944 return value
1916 return value
1945
1917
1946 class path(object):
1918 class path(object):
1947 """Represents an individual path and its configuration."""
1919 """Represents an individual path and its configuration."""
1948
1920
1949 def __init__(self, ui, name, rawloc=None, suboptions=None):
1921 def __init__(self, ui, name, rawloc=None, suboptions=None):
1950 """Construct a path from its config options.
1922 """Construct a path from its config options.
1951
1923
1952 ``ui`` is the ``ui`` instance the path is coming from.
1924 ``ui`` is the ``ui`` instance the path is coming from.
1953 ``name`` is the symbolic name of the path.
1925 ``name`` is the symbolic name of the path.
1954 ``rawloc`` is the raw location, as defined in the config.
1926 ``rawloc`` is the raw location, as defined in the config.
1955 ``pushloc`` is the raw locations pushes should be made to.
1927 ``pushloc`` is the raw locations pushes should be made to.
1956
1928
1957 If ``name`` is not defined, we require that the location be a) a local
1929 If ``name`` is not defined, we require that the location be a) a local
1958 filesystem path with a .hg directory or b) a URL. If not,
1930 filesystem path with a .hg directory or b) a URL. If not,
1959 ``ValueError`` is raised.
1931 ``ValueError`` is raised.
1960 """
1932 """
1961 if not rawloc:
1933 if not rawloc:
1962 raise ValueError('rawloc must be defined')
1934 raise ValueError('rawloc must be defined')
1963
1935
1964 # Locations may define branches via syntax <base>#<branch>.
1936 # Locations may define branches via syntax <base>#<branch>.
1965 u = util.url(rawloc)
1937 u = util.url(rawloc)
1966 branch = None
1938 branch = None
1967 if u.fragment:
1939 if u.fragment:
1968 branch = u.fragment
1940 branch = u.fragment
1969 u.fragment = None
1941 u.fragment = None
1970
1942
1971 self.url = u
1943 self.url = u
1972 self.branch = branch
1944 self.branch = branch
1973
1945
1974 self.name = name
1946 self.name = name
1975 self.rawloc = rawloc
1947 self.rawloc = rawloc
1976 self.loc = '%s' % u
1948 self.loc = '%s' % u
1977
1949
1978 # When given a raw location but not a symbolic name, validate the
1950 # When given a raw location but not a symbolic name, validate the
1979 # location is valid.
1951 # location is valid.
1980 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1952 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1981 raise ValueError('location is not a URL or path to a local '
1953 raise ValueError('location is not a URL or path to a local '
1982 'repo: %s' % rawloc)
1954 'repo: %s' % rawloc)
1983
1955
1984 suboptions = suboptions or {}
1956 suboptions = suboptions or {}
1985
1957
1986 # Now process the sub-options. If a sub-option is registered, its
1958 # Now process the sub-options. If a sub-option is registered, its
1987 # attribute will always be present. The value will be None if there
1959 # attribute will always be present. The value will be None if there
1988 # was no valid sub-option.
1960 # was no valid sub-option.
1989 for suboption, (attr, func) in _pathsuboptions.iteritems():
1961 for suboption, (attr, func) in _pathsuboptions.iteritems():
1990 if suboption not in suboptions:
1962 if suboption not in suboptions:
1991 setattr(self, attr, None)
1963 setattr(self, attr, None)
1992 continue
1964 continue
1993
1965
1994 value = func(ui, self, suboptions[suboption])
1966 value = func(ui, self, suboptions[suboption])
1995 setattr(self, attr, value)
1967 setattr(self, attr, value)
1996
1968
1997 def _isvalidlocalpath(self, path):
1969 def _isvalidlocalpath(self, path):
1998 """Returns True if the given path is a potentially valid repository.
1970 """Returns True if the given path is a potentially valid repository.
1999 This is its own function so that extensions can change the definition of
1971 This is its own function so that extensions can change the definition of
2000 'valid' in this case (like when pulling from a git repo into a hg
1972 'valid' in this case (like when pulling from a git repo into a hg
2001 one)."""
1973 one)."""
2002 return os.path.isdir(os.path.join(path, '.hg'))
1974 return os.path.isdir(os.path.join(path, '.hg'))
2003
1975
2004 @property
1976 @property
2005 def suboptions(self):
1977 def suboptions(self):
2006 """Return sub-options and their values for this path.
1978 """Return sub-options and their values for this path.
2007
1979
2008 This is intended to be used for presentation purposes.
1980 This is intended to be used for presentation purposes.
2009 """
1981 """
2010 d = {}
1982 d = {}
2011 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1983 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2012 value = getattr(self, attr)
1984 value = getattr(self, attr)
2013 if value is not None:
1985 if value is not None:
2014 d[subopt] = value
1986 d[subopt] = value
2015 return d
1987 return d
2016
1988
2017 # we instantiate one globally shared progress bar to avoid
1989 # we instantiate one globally shared progress bar to avoid
2018 # competing progress bars when multiple UI objects get created
1990 # competing progress bars when multiple UI objects get created
2019 _progresssingleton = None
1991 _progresssingleton = None
2020
1992
2021 def getprogbar(ui):
1993 def getprogbar(ui):
2022 global _progresssingleton
1994 global _progresssingleton
2023 if _progresssingleton is None:
1995 if _progresssingleton is None:
2024 # passing 'ui' object to the singleton is fishy,
1996 # passing 'ui' object to the singleton is fishy,
2025 # this is how the extension used to work but feel free to rework it.
1997 # this is how the extension used to work but feel free to rework it.
2026 _progresssingleton = progress.progbar(ui)
1998 _progresssingleton = progress.progbar(ui)
2027 return _progresssingleton
1999 return _progresssingleton
2028
2000
2029 def haveprogbar():
2001 def haveprogbar():
2030 return _progresssingleton is not None
2002 return _progresssingleton is not None
2031
2003
2032 def _selectmsgdests(ui):
2004 def _selectmsgdests(ui):
2033 name = ui.config(b'ui', b'message-output')
2005 name = ui.config(b'ui', b'message-output')
2034 if name == b'channel':
2006 if name == b'channel':
2035 if ui.fmsg:
2007 if ui.fmsg:
2036 return ui.fmsg, ui.fmsg
2008 return ui.fmsg, ui.fmsg
2037 else:
2009 else:
2038 # fall back to ferr if channel isn't ready so that status/error
2010 # fall back to ferr if channel isn't ready so that status/error
2039 # messages can be printed
2011 # messages can be printed
2040 return ui.ferr, ui.ferr
2012 return ui.ferr, ui.ferr
2041 if name == b'stdio':
2013 if name == b'stdio':
2042 return ui.fout, ui.ferr
2014 return ui.fout, ui.ferr
2043 if name == b'stderr':
2015 if name == b'stderr':
2044 return ui.ferr, ui.ferr
2016 return ui.ferr, ui.ferr
2045 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2017 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2046
2018
2047 def _writemsgwith(write, dest, *args, **opts):
2019 def _writemsgwith(write, dest, *args, **opts):
2048 """Write ui message with the given ui._write*() function
2020 """Write ui message with the given ui._write*() function
2049
2021
2050 The specified message type is translated to 'ui.<type>' label if the dest
2022 The specified message type is translated to 'ui.<type>' label if the dest
2051 isn't a structured channel, so that the message will be colorized.
2023 isn't a structured channel, so that the message will be colorized.
2052 """
2024 """
2053 # TODO: maybe change 'type' to a mandatory option
2025 # TODO: maybe change 'type' to a mandatory option
2054 if r'type' in opts and not getattr(dest, 'structured', False):
2026 if r'type' in opts and not getattr(dest, 'structured', False):
2055 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2027 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2056 write(dest, *args, **opts)
2028 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now