##// END OF EJS Templates
util: avoid echoing the password to the console on Windows py3 (issue6446)...
Matt Harbison -
r47949:5b351317 stable
parent child Browse files
Show More
@@ -1,783 +1,787 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
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 fcntl
11 import fcntl
12 import getpass
12 import getpass
13 import grp
13 import grp
14 import os
14 import os
15 import pwd
15 import pwd
16 import re
16 import re
17 import select
17 import select
18 import stat
18 import stat
19 import sys
19 import sys
20 import tempfile
20 import tempfile
21 import unicodedata
21 import unicodedata
22
22
23 from .i18n import _
23 from .i18n import _
24 from .pycompat import (
24 from .pycompat import (
25 getattr,
25 getattr,
26 open,
26 open,
27 )
27 )
28 from . import (
28 from . import (
29 encoding,
29 encoding,
30 error,
30 error,
31 policy,
31 policy,
32 pycompat,
32 pycompat,
33 )
33 )
34
34
35 osutil = policy.importmod('osutil')
35 osutil = policy.importmod('osutil')
36
36
37 normpath = os.path.normpath
37 normpath = os.path.normpath
38 samestat = os.path.samestat
38 samestat = os.path.samestat
39 try:
39 try:
40 oslink = os.link
40 oslink = os.link
41 except AttributeError:
41 except AttributeError:
42 # Some platforms build Python without os.link on systems that are
42 # Some platforms build Python without os.link on systems that are
43 # vaguely unix-like but don't have hardlink support. For those
43 # vaguely unix-like but don't have hardlink support. For those
44 # poor souls, just say we tried and that it failed so we fall back
44 # poor souls, just say we tried and that it failed so we fall back
45 # to copies.
45 # to copies.
46 def oslink(src, dst):
46 def oslink(src, dst):
47 raise OSError(
47 raise OSError(
48 errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst)
48 errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst)
49 )
49 )
50
50
51
51
52 readlink = os.readlink
52 readlink = os.readlink
53 unlink = os.unlink
53 unlink = os.unlink
54 rename = os.rename
54 rename = os.rename
55 removedirs = os.removedirs
55 removedirs = os.removedirs
56 expandglobs = False
56 expandglobs = False
57
57
58 umask = os.umask(0)
58 umask = os.umask(0)
59 os.umask(umask)
59 os.umask(umask)
60
60
61 if not pycompat.ispy3:
61 if not pycompat.ispy3:
62
62
63 def posixfile(name, mode='r', buffering=-1):
63 def posixfile(name, mode='r', buffering=-1):
64 fp = open(name, mode=mode, buffering=buffering)
64 fp = open(name, mode=mode, buffering=buffering)
65 # The position when opening in append mode is implementation defined, so
65 # The position when opening in append mode is implementation defined, so
66 # make it consistent by always seeking to the end.
66 # make it consistent by always seeking to the end.
67 if 'a' in mode:
67 if 'a' in mode:
68 fp.seek(0, os.SEEK_END)
68 fp.seek(0, os.SEEK_END)
69 return fp
69 return fp
70
70
71
71
72 else:
72 else:
73 # The underlying file object seeks as required in Python 3:
73 # The underlying file object seeks as required in Python 3:
74 # https://github.com/python/cpython/blob/v3.7.3/Modules/_io/fileio.c#L474
74 # https://github.com/python/cpython/blob/v3.7.3/Modules/_io/fileio.c#L474
75 posixfile = open
75 posixfile = open
76
76
77
77
78 def split(p):
78 def split(p):
79 """Same as posixpath.split, but faster
79 """Same as posixpath.split, but faster
80
80
81 >>> import posixpath
81 >>> import posixpath
82 >>> for f in [b'/absolute/path/to/file',
82 >>> for f in [b'/absolute/path/to/file',
83 ... b'relative/path/to/file',
83 ... b'relative/path/to/file',
84 ... b'file_alone',
84 ... b'file_alone',
85 ... b'path/to/directory/',
85 ... b'path/to/directory/',
86 ... b'/multiple/path//separators',
86 ... b'/multiple/path//separators',
87 ... b'/file_at_root',
87 ... b'/file_at_root',
88 ... b'///multiple_leading_separators_at_root',
88 ... b'///multiple_leading_separators_at_root',
89 ... b'']:
89 ... b'']:
90 ... assert split(f) == posixpath.split(f), f
90 ... assert split(f) == posixpath.split(f), f
91 """
91 """
92 ht = p.rsplit(b'/', 1)
92 ht = p.rsplit(b'/', 1)
93 if len(ht) == 1:
93 if len(ht) == 1:
94 return b'', p
94 return b'', p
95 nh = ht[0].rstrip(b'/')
95 nh = ht[0].rstrip(b'/')
96 if nh:
96 if nh:
97 return nh, ht[1]
97 return nh, ht[1]
98 return ht[0] + b'/', ht[1]
98 return ht[0] + b'/', ht[1]
99
99
100
100
101 def openhardlinks():
101 def openhardlinks():
102 '''return true if it is safe to hold open file handles to hardlinks'''
102 '''return true if it is safe to hold open file handles to hardlinks'''
103 return True
103 return True
104
104
105
105
106 def nlinks(name):
106 def nlinks(name):
107 '''return number of hardlinks for the given file'''
107 '''return number of hardlinks for the given file'''
108 return os.lstat(name).st_nlink
108 return os.lstat(name).st_nlink
109
109
110
110
111 def parsepatchoutput(output_line):
111 def parsepatchoutput(output_line):
112 """parses the output produced by patch and returns the filename"""
112 """parses the output produced by patch and returns the filename"""
113 pf = output_line[14:]
113 pf = output_line[14:]
114 if pycompat.sysplatform == b'OpenVMS':
114 if pycompat.sysplatform == b'OpenVMS':
115 if pf[0] == b'`':
115 if pf[0] == b'`':
116 pf = pf[1:-1] # Remove the quotes
116 pf = pf[1:-1] # Remove the quotes
117 else:
117 else:
118 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf:
118 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf:
119 pf = pf[1:-1] # Remove the quotes
119 pf = pf[1:-1] # Remove the quotes
120 return pf
120 return pf
121
121
122
122
123 def sshargs(sshcmd, host, user, port):
123 def sshargs(sshcmd, host, user, port):
124 '''Build argument list for ssh'''
124 '''Build argument list for ssh'''
125 args = user and (b"%s@%s" % (user, host)) or host
125 args = user and (b"%s@%s" % (user, host)) or host
126 if b'-' in args[:1]:
126 if b'-' in args[:1]:
127 raise error.Abort(
127 raise error.Abort(
128 _(b'illegal ssh hostname or username starting with -: %s') % args
128 _(b'illegal ssh hostname or username starting with -: %s') % args
129 )
129 )
130 args = shellquote(args)
130 args = shellquote(args)
131 if port:
131 if port:
132 args = b'-p %s %s' % (shellquote(port), args)
132 args = b'-p %s %s' % (shellquote(port), args)
133 return args
133 return args
134
134
135
135
136 def isexec(f):
136 def isexec(f):
137 """check whether a file is executable"""
137 """check whether a file is executable"""
138 return os.lstat(f).st_mode & 0o100 != 0
138 return os.lstat(f).st_mode & 0o100 != 0
139
139
140
140
141 def setflags(f, l, x):
141 def setflags(f, l, x):
142 st = os.lstat(f)
142 st = os.lstat(f)
143 s = st.st_mode
143 s = st.st_mode
144 if l:
144 if l:
145 if not stat.S_ISLNK(s):
145 if not stat.S_ISLNK(s):
146 # switch file to link
146 # switch file to link
147 with open(f, b'rb') as fp:
147 with open(f, b'rb') as fp:
148 data = fp.read()
148 data = fp.read()
149 unlink(f)
149 unlink(f)
150 try:
150 try:
151 os.symlink(data, f)
151 os.symlink(data, f)
152 except OSError:
152 except OSError:
153 # failed to make a link, rewrite file
153 # failed to make a link, rewrite file
154 with open(f, b"wb") as fp:
154 with open(f, b"wb") as fp:
155 fp.write(data)
155 fp.write(data)
156
156
157 # no chmod needed at this point
157 # no chmod needed at this point
158 return
158 return
159 if stat.S_ISLNK(s):
159 if stat.S_ISLNK(s):
160 # switch link to file
160 # switch link to file
161 data = os.readlink(f)
161 data = os.readlink(f)
162 unlink(f)
162 unlink(f)
163 with open(f, b"wb") as fp:
163 with open(f, b"wb") as fp:
164 fp.write(data)
164 fp.write(data)
165 s = 0o666 & ~umask # avoid restatting for chmod
165 s = 0o666 & ~umask # avoid restatting for chmod
166
166
167 sx = s & 0o100
167 sx = s & 0o100
168 if st.st_nlink > 1 and bool(x) != bool(sx):
168 if st.st_nlink > 1 and bool(x) != bool(sx):
169 # the file is a hardlink, break it
169 # the file is a hardlink, break it
170 with open(f, b"rb") as fp:
170 with open(f, b"rb") as fp:
171 data = fp.read()
171 data = fp.read()
172 unlink(f)
172 unlink(f)
173 with open(f, b"wb") as fp:
173 with open(f, b"wb") as fp:
174 fp.write(data)
174 fp.write(data)
175
175
176 if x and not sx:
176 if x and not sx:
177 # Turn on +x for every +r bit when making a file executable
177 # Turn on +x for every +r bit when making a file executable
178 # and obey umask.
178 # and obey umask.
179 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
179 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
180 elif not x and sx:
180 elif not x and sx:
181 # Turn off all +x bits
181 # Turn off all +x bits
182 os.chmod(f, s & 0o666)
182 os.chmod(f, s & 0o666)
183
183
184
184
185 def copymode(src, dst, mode=None, enforcewritable=False):
185 def copymode(src, dst, mode=None, enforcewritable=False):
186 """Copy the file mode from the file at path src to dst.
186 """Copy the file mode from the file at path src to dst.
187 If src doesn't exist, we're using mode instead. If mode is None, we're
187 If src doesn't exist, we're using mode instead. If mode is None, we're
188 using umask."""
188 using umask."""
189 try:
189 try:
190 st_mode = os.lstat(src).st_mode & 0o777
190 st_mode = os.lstat(src).st_mode & 0o777
191 except OSError as inst:
191 except OSError as inst:
192 if inst.errno != errno.ENOENT:
192 if inst.errno != errno.ENOENT:
193 raise
193 raise
194 st_mode = mode
194 st_mode = mode
195 if st_mode is None:
195 if st_mode is None:
196 st_mode = ~umask
196 st_mode = ~umask
197 st_mode &= 0o666
197 st_mode &= 0o666
198
198
199 new_mode = st_mode
199 new_mode = st_mode
200
200
201 if enforcewritable:
201 if enforcewritable:
202 new_mode |= stat.S_IWUSR
202 new_mode |= stat.S_IWUSR
203
203
204 os.chmod(dst, new_mode)
204 os.chmod(dst, new_mode)
205
205
206
206
207 def checkexec(path):
207 def checkexec(path):
208 """
208 """
209 Check whether the given path is on a filesystem with UNIX-like exec flags
209 Check whether the given path is on a filesystem with UNIX-like exec flags
210
210
211 Requires a directory (like /foo/.hg)
211 Requires a directory (like /foo/.hg)
212 """
212 """
213
213
214 # VFAT on some Linux versions can flip mode but it doesn't persist
214 # VFAT on some Linux versions can flip mode but it doesn't persist
215 # a FS remount. Frequently we can detect it if files are created
215 # a FS remount. Frequently we can detect it if files are created
216 # with exec bit on.
216 # with exec bit on.
217
217
218 try:
218 try:
219 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
219 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
220 basedir = os.path.join(path, b'.hg')
220 basedir = os.path.join(path, b'.hg')
221 cachedir = os.path.join(basedir, b'wcache')
221 cachedir = os.path.join(basedir, b'wcache')
222 storedir = os.path.join(basedir, b'store')
222 storedir = os.path.join(basedir, b'store')
223 if not os.path.exists(cachedir):
223 if not os.path.exists(cachedir):
224 try:
224 try:
225 # we want to create the 'cache' directory, not the '.hg' one.
225 # we want to create the 'cache' directory, not the '.hg' one.
226 # Automatically creating '.hg' directory could silently spawn
226 # Automatically creating '.hg' directory could silently spawn
227 # invalid Mercurial repositories. That seems like a bad idea.
227 # invalid Mercurial repositories. That seems like a bad idea.
228 os.mkdir(cachedir)
228 os.mkdir(cachedir)
229 if os.path.exists(storedir):
229 if os.path.exists(storedir):
230 copymode(storedir, cachedir)
230 copymode(storedir, cachedir)
231 else:
231 else:
232 copymode(basedir, cachedir)
232 copymode(basedir, cachedir)
233 except (IOError, OSError):
233 except (IOError, OSError):
234 # we other fallback logic triggers
234 # we other fallback logic triggers
235 pass
235 pass
236 if os.path.isdir(cachedir):
236 if os.path.isdir(cachedir):
237 checkisexec = os.path.join(cachedir, b'checkisexec')
237 checkisexec = os.path.join(cachedir, b'checkisexec')
238 checknoexec = os.path.join(cachedir, b'checknoexec')
238 checknoexec = os.path.join(cachedir, b'checknoexec')
239
239
240 try:
240 try:
241 m = os.stat(checkisexec).st_mode
241 m = os.stat(checkisexec).st_mode
242 except OSError as e:
242 except OSError as e:
243 if e.errno != errno.ENOENT:
243 if e.errno != errno.ENOENT:
244 raise
244 raise
245 # checkisexec does not exist - fall through ...
245 # checkisexec does not exist - fall through ...
246 else:
246 else:
247 # checkisexec exists, check if it actually is exec
247 # checkisexec exists, check if it actually is exec
248 if m & EXECFLAGS != 0:
248 if m & EXECFLAGS != 0:
249 # ensure checkisexec exists, check it isn't exec
249 # ensure checkisexec exists, check it isn't exec
250 try:
250 try:
251 m = os.stat(checknoexec).st_mode
251 m = os.stat(checknoexec).st_mode
252 except OSError as e:
252 except OSError as e:
253 if e.errno != errno.ENOENT:
253 if e.errno != errno.ENOENT:
254 raise
254 raise
255 open(checknoexec, b'w').close() # might fail
255 open(checknoexec, b'w').close() # might fail
256 m = os.stat(checknoexec).st_mode
256 m = os.stat(checknoexec).st_mode
257 if m & EXECFLAGS == 0:
257 if m & EXECFLAGS == 0:
258 # check-exec is exec and check-no-exec is not exec
258 # check-exec is exec and check-no-exec is not exec
259 return True
259 return True
260 # checknoexec exists but is exec - delete it
260 # checknoexec exists but is exec - delete it
261 unlink(checknoexec)
261 unlink(checknoexec)
262 # checkisexec exists but is not exec - delete it
262 # checkisexec exists but is not exec - delete it
263 unlink(checkisexec)
263 unlink(checkisexec)
264
264
265 # check using one file, leave it as checkisexec
265 # check using one file, leave it as checkisexec
266 checkdir = cachedir
266 checkdir = cachedir
267 else:
267 else:
268 # check directly in path and don't leave checkisexec behind
268 # check directly in path and don't leave checkisexec behind
269 checkdir = path
269 checkdir = path
270 checkisexec = None
270 checkisexec = None
271 fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-')
271 fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-')
272 try:
272 try:
273 os.close(fh)
273 os.close(fh)
274 m = os.stat(fn).st_mode
274 m = os.stat(fn).st_mode
275 if m & EXECFLAGS == 0:
275 if m & EXECFLAGS == 0:
276 os.chmod(fn, m & 0o777 | EXECFLAGS)
276 os.chmod(fn, m & 0o777 | EXECFLAGS)
277 if os.stat(fn).st_mode & EXECFLAGS != 0:
277 if os.stat(fn).st_mode & EXECFLAGS != 0:
278 if checkisexec is not None:
278 if checkisexec is not None:
279 os.rename(fn, checkisexec)
279 os.rename(fn, checkisexec)
280 fn = None
280 fn = None
281 return True
281 return True
282 finally:
282 finally:
283 if fn is not None:
283 if fn is not None:
284 unlink(fn)
284 unlink(fn)
285 except (IOError, OSError):
285 except (IOError, OSError):
286 # we don't care, the user probably won't be able to commit anyway
286 # we don't care, the user probably won't be able to commit anyway
287 return False
287 return False
288
288
289
289
290 def checklink(path):
290 def checklink(path):
291 """check whether the given path is on a symlink-capable filesystem"""
291 """check whether the given path is on a symlink-capable filesystem"""
292 # mktemp is not racy because symlink creation will fail if the
292 # mktemp is not racy because symlink creation will fail if the
293 # file already exists
293 # file already exists
294 while True:
294 while True:
295 cachedir = os.path.join(path, b'.hg', b'wcache')
295 cachedir = os.path.join(path, b'.hg', b'wcache')
296 checklink = os.path.join(cachedir, b'checklink')
296 checklink = os.path.join(cachedir, b'checklink')
297 # try fast path, read only
297 # try fast path, read only
298 if os.path.islink(checklink):
298 if os.path.islink(checklink):
299 return True
299 return True
300 if os.path.isdir(cachedir):
300 if os.path.isdir(cachedir):
301 checkdir = cachedir
301 checkdir = cachedir
302 else:
302 else:
303 checkdir = path
303 checkdir = path
304 cachedir = None
304 cachedir = None
305 name = tempfile.mktemp(
305 name = tempfile.mktemp(
306 dir=pycompat.fsdecode(checkdir), prefix=r'checklink-'
306 dir=pycompat.fsdecode(checkdir), prefix=r'checklink-'
307 )
307 )
308 name = pycompat.fsencode(name)
308 name = pycompat.fsencode(name)
309 try:
309 try:
310 fd = None
310 fd = None
311 if cachedir is None:
311 if cachedir is None:
312 fd = pycompat.namedtempfile(
312 fd = pycompat.namedtempfile(
313 dir=checkdir, prefix=b'hg-checklink-'
313 dir=checkdir, prefix=b'hg-checklink-'
314 )
314 )
315 target = os.path.basename(fd.name)
315 target = os.path.basename(fd.name)
316 else:
316 else:
317 # create a fixed file to link to; doesn't matter if it
317 # create a fixed file to link to; doesn't matter if it
318 # already exists.
318 # already exists.
319 target = b'checklink-target'
319 target = b'checklink-target'
320 try:
320 try:
321 fullpath = os.path.join(cachedir, target)
321 fullpath = os.path.join(cachedir, target)
322 open(fullpath, b'w').close()
322 open(fullpath, b'w').close()
323 except IOError as inst:
323 except IOError as inst:
324 # pytype: disable=unsupported-operands
324 # pytype: disable=unsupported-operands
325 if inst[0] == errno.EACCES:
325 if inst[0] == errno.EACCES:
326 # pytype: enable=unsupported-operands
326 # pytype: enable=unsupported-operands
327
327
328 # If we can't write to cachedir, just pretend
328 # If we can't write to cachedir, just pretend
329 # that the fs is readonly and by association
329 # that the fs is readonly and by association
330 # that the fs won't support symlinks. This
330 # that the fs won't support symlinks. This
331 # seems like the least dangerous way to avoid
331 # seems like the least dangerous way to avoid
332 # data loss.
332 # data loss.
333 return False
333 return False
334 raise
334 raise
335 try:
335 try:
336 os.symlink(target, name)
336 os.symlink(target, name)
337 if cachedir is None:
337 if cachedir is None:
338 unlink(name)
338 unlink(name)
339 else:
339 else:
340 try:
340 try:
341 os.rename(name, checklink)
341 os.rename(name, checklink)
342 except OSError:
342 except OSError:
343 unlink(name)
343 unlink(name)
344 return True
344 return True
345 except OSError as inst:
345 except OSError as inst:
346 # link creation might race, try again
346 # link creation might race, try again
347 if inst.errno == errno.EEXIST:
347 if inst.errno == errno.EEXIST:
348 continue
348 continue
349 raise
349 raise
350 finally:
350 finally:
351 if fd is not None:
351 if fd is not None:
352 fd.close()
352 fd.close()
353 except AttributeError:
353 except AttributeError:
354 return False
354 return False
355 except OSError as inst:
355 except OSError as inst:
356 # sshfs might report failure while successfully creating the link
356 # sshfs might report failure while successfully creating the link
357 if inst.errno == errno.EIO and os.path.exists(name):
357 if inst.errno == errno.EIO and os.path.exists(name):
358 unlink(name)
358 unlink(name)
359 return False
359 return False
360
360
361
361
362 def checkosfilename(path):
362 def checkosfilename(path):
363 """Check that the base-relative path is a valid filename on this platform.
363 """Check that the base-relative path is a valid filename on this platform.
364 Returns None if the path is ok, or a UI string describing the problem."""
364 Returns None if the path is ok, or a UI string describing the problem."""
365 return None # on posix platforms, every path is ok
365 return None # on posix platforms, every path is ok
366
366
367
367
368 def getfsmountpoint(dirpath):
368 def getfsmountpoint(dirpath):
369 """Get the filesystem mount point from a directory (best-effort)
369 """Get the filesystem mount point from a directory (best-effort)
370
370
371 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
371 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
372 """
372 """
373 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
373 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
374
374
375
375
376 def getfstype(dirpath):
376 def getfstype(dirpath):
377 """Get the filesystem type name from a directory (best-effort)
377 """Get the filesystem type name from a directory (best-effort)
378
378
379 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
379 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
380 """
380 """
381 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
381 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
382
382
383
383
384 def get_password():
385 return encoding.strtolocal(getpass.getpass(''))
386
387
384 def setbinary(fd):
388 def setbinary(fd):
385 pass
389 pass
386
390
387
391
388 def pconvert(path):
392 def pconvert(path):
389 return path
393 return path
390
394
391
395
392 def localpath(path):
396 def localpath(path):
393 return path
397 return path
394
398
395
399
396 def samefile(fpath1, fpath2):
400 def samefile(fpath1, fpath2):
397 """Returns whether path1 and path2 refer to the same file. This is only
401 """Returns whether path1 and path2 refer to the same file. This is only
398 guaranteed to work for files, not directories."""
402 guaranteed to work for files, not directories."""
399 return os.path.samefile(fpath1, fpath2)
403 return os.path.samefile(fpath1, fpath2)
400
404
401
405
402 def samedevice(fpath1, fpath2):
406 def samedevice(fpath1, fpath2):
403 """Returns whether fpath1 and fpath2 are on the same device. This is only
407 """Returns whether fpath1 and fpath2 are on the same device. This is only
404 guaranteed to work for files, not directories."""
408 guaranteed to work for files, not directories."""
405 st1 = os.lstat(fpath1)
409 st1 = os.lstat(fpath1)
406 st2 = os.lstat(fpath2)
410 st2 = os.lstat(fpath2)
407 return st1.st_dev == st2.st_dev
411 return st1.st_dev == st2.st_dev
408
412
409
413
410 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
414 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
411 def normcase(path):
415 def normcase(path):
412 return path.lower()
416 return path.lower()
413
417
414
418
415 # what normcase does to ASCII strings
419 # what normcase does to ASCII strings
416 normcasespec = encoding.normcasespecs.lower
420 normcasespec = encoding.normcasespecs.lower
417 # fallback normcase function for non-ASCII strings
421 # fallback normcase function for non-ASCII strings
418 normcasefallback = normcase
422 normcasefallback = normcase
419
423
420 if pycompat.isdarwin:
424 if pycompat.isdarwin:
421
425
422 def normcase(path):
426 def normcase(path):
423 """
427 """
424 Normalize a filename for OS X-compatible comparison:
428 Normalize a filename for OS X-compatible comparison:
425 - escape-encode invalid characters
429 - escape-encode invalid characters
426 - decompose to NFD
430 - decompose to NFD
427 - lowercase
431 - lowercase
428 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
432 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
429
433
430 >>> normcase(b'UPPER')
434 >>> normcase(b'UPPER')
431 'upper'
435 'upper'
432 >>> normcase(b'Caf\\xc3\\xa9')
436 >>> normcase(b'Caf\\xc3\\xa9')
433 'cafe\\xcc\\x81'
437 'cafe\\xcc\\x81'
434 >>> normcase(b'\\xc3\\x89')
438 >>> normcase(b'\\xc3\\x89')
435 'e\\xcc\\x81'
439 'e\\xcc\\x81'
436 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
440 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
437 '%b8%ca%c3\\xca\\xbe%c8.jpg'
441 '%b8%ca%c3\\xca\\xbe%c8.jpg'
438 """
442 """
439
443
440 try:
444 try:
441 return encoding.asciilower(path) # exception for non-ASCII
445 return encoding.asciilower(path) # exception for non-ASCII
442 except UnicodeDecodeError:
446 except UnicodeDecodeError:
443 return normcasefallback(path)
447 return normcasefallback(path)
444
448
445 normcasespec = encoding.normcasespecs.lower
449 normcasespec = encoding.normcasespecs.lower
446
450
447 def normcasefallback(path):
451 def normcasefallback(path):
448 try:
452 try:
449 u = path.decode('utf-8')
453 u = path.decode('utf-8')
450 except UnicodeDecodeError:
454 except UnicodeDecodeError:
451 # OS X percent-encodes any bytes that aren't valid utf-8
455 # OS X percent-encodes any bytes that aren't valid utf-8
452 s = b''
456 s = b''
453 pos = 0
457 pos = 0
454 l = len(path)
458 l = len(path)
455 while pos < l:
459 while pos < l:
456 try:
460 try:
457 c = encoding.getutf8char(path, pos)
461 c = encoding.getutf8char(path, pos)
458 pos += len(c)
462 pos += len(c)
459 except ValueError:
463 except ValueError:
460 c = b'%%%02X' % ord(path[pos : pos + 1])
464 c = b'%%%02X' % ord(path[pos : pos + 1])
461 pos += 1
465 pos += 1
462 s += c
466 s += c
463
467
464 u = s.decode('utf-8')
468 u = s.decode('utf-8')
465
469
466 # Decompose then lowercase (HFS+ technote specifies lower)
470 # Decompose then lowercase (HFS+ technote specifies lower)
467 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
471 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
468 # drop HFS+ ignored characters
472 # drop HFS+ ignored characters
469 return encoding.hfsignoreclean(enc)
473 return encoding.hfsignoreclean(enc)
470
474
471
475
472 if pycompat.sysplatform == b'cygwin':
476 if pycompat.sysplatform == b'cygwin':
473 # workaround for cygwin, in which mount point part of path is
477 # workaround for cygwin, in which mount point part of path is
474 # treated as case sensitive, even though underlying NTFS is case
478 # treated as case sensitive, even though underlying NTFS is case
475 # insensitive.
479 # insensitive.
476
480
477 # default mount points
481 # default mount points
478 cygwinmountpoints = sorted(
482 cygwinmountpoints = sorted(
479 [
483 [
480 b"/usr/bin",
484 b"/usr/bin",
481 b"/usr/lib",
485 b"/usr/lib",
482 b"/cygdrive",
486 b"/cygdrive",
483 ],
487 ],
484 reverse=True,
488 reverse=True,
485 )
489 )
486
490
487 # use upper-ing as normcase as same as NTFS workaround
491 # use upper-ing as normcase as same as NTFS workaround
488 def normcase(path):
492 def normcase(path):
489 pathlen = len(path)
493 pathlen = len(path)
490 if (pathlen == 0) or (path[0] != pycompat.ossep):
494 if (pathlen == 0) or (path[0] != pycompat.ossep):
491 # treat as relative
495 # treat as relative
492 return encoding.upper(path)
496 return encoding.upper(path)
493
497
494 # to preserve case of mountpoint part
498 # to preserve case of mountpoint part
495 for mp in cygwinmountpoints:
499 for mp in cygwinmountpoints:
496 if not path.startswith(mp):
500 if not path.startswith(mp):
497 continue
501 continue
498
502
499 mplen = len(mp)
503 mplen = len(mp)
500 if mplen == pathlen: # mount point itself
504 if mplen == pathlen: # mount point itself
501 return mp
505 return mp
502 if path[mplen] == pycompat.ossep:
506 if path[mplen] == pycompat.ossep:
503 return mp + encoding.upper(path[mplen:])
507 return mp + encoding.upper(path[mplen:])
504
508
505 return encoding.upper(path)
509 return encoding.upper(path)
506
510
507 normcasespec = encoding.normcasespecs.other
511 normcasespec = encoding.normcasespecs.other
508 normcasefallback = normcase
512 normcasefallback = normcase
509
513
510 # Cygwin translates native ACLs to POSIX permissions,
514 # Cygwin translates native ACLs to POSIX permissions,
511 # but these translations are not supported by native
515 # but these translations are not supported by native
512 # tools, so the exec bit tends to be set erroneously.
516 # tools, so the exec bit tends to be set erroneously.
513 # Therefore, disable executable bit access on Cygwin.
517 # Therefore, disable executable bit access on Cygwin.
514 def checkexec(path):
518 def checkexec(path):
515 return False
519 return False
516
520
517 # Similarly, Cygwin's symlink emulation is likely to create
521 # Similarly, Cygwin's symlink emulation is likely to create
518 # problems when Mercurial is used from both Cygwin and native
522 # problems when Mercurial is used from both Cygwin and native
519 # Windows, with other native tools, or on shared volumes
523 # Windows, with other native tools, or on shared volumes
520 def checklink(path):
524 def checklink(path):
521 return False
525 return False
522
526
523
527
524 _needsshellquote = None
528 _needsshellquote = None
525
529
526
530
527 def shellquote(s):
531 def shellquote(s):
528 if pycompat.sysplatform == b'OpenVMS':
532 if pycompat.sysplatform == b'OpenVMS':
529 return b'"%s"' % s
533 return b'"%s"' % s
530 global _needsshellquote
534 global _needsshellquote
531 if _needsshellquote is None:
535 if _needsshellquote is None:
532 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
536 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
533 if s and not _needsshellquote(s):
537 if s and not _needsshellquote(s):
534 # "s" shouldn't have to be quoted
538 # "s" shouldn't have to be quoted
535 return s
539 return s
536 else:
540 else:
537 return b"'%s'" % s.replace(b"'", b"'\\''")
541 return b"'%s'" % s.replace(b"'", b"'\\''")
538
542
539
543
540 def shellsplit(s):
544 def shellsplit(s):
541 """Parse a command string in POSIX shell way (best-effort)"""
545 """Parse a command string in POSIX shell way (best-effort)"""
542 return pycompat.shlexsplit(s, posix=True)
546 return pycompat.shlexsplit(s, posix=True)
543
547
544
548
545 def testpid(pid):
549 def testpid(pid):
546 '''return False if pid dead, True if running or not sure'''
550 '''return False if pid dead, True if running or not sure'''
547 if pycompat.sysplatform == b'OpenVMS':
551 if pycompat.sysplatform == b'OpenVMS':
548 return True
552 return True
549 try:
553 try:
550 os.kill(pid, 0)
554 os.kill(pid, 0)
551 return True
555 return True
552 except OSError as inst:
556 except OSError as inst:
553 return inst.errno != errno.ESRCH
557 return inst.errno != errno.ESRCH
554
558
555
559
556 def isowner(st):
560 def isowner(st):
557 """Return True if the stat object st is from the current user."""
561 """Return True if the stat object st is from the current user."""
558 return st.st_uid == os.getuid()
562 return st.st_uid == os.getuid()
559
563
560
564
561 def findexe(command):
565 def findexe(command):
562 """Find executable for command searching like which does.
566 """Find executable for command searching like which does.
563 If command is a basename then PATH is searched for command.
567 If command is a basename then PATH is searched for command.
564 PATH isn't searched if command is an absolute or relative path.
568 PATH isn't searched if command is an absolute or relative path.
565 If command isn't found None is returned."""
569 If command isn't found None is returned."""
566 if pycompat.sysplatform == b'OpenVMS':
570 if pycompat.sysplatform == b'OpenVMS':
567 return command
571 return command
568
572
569 def findexisting(executable):
573 def findexisting(executable):
570 b'Will return executable if existing file'
574 b'Will return executable if existing file'
571 if os.path.isfile(executable) and os.access(executable, os.X_OK):
575 if os.path.isfile(executable) and os.access(executable, os.X_OK):
572 return executable
576 return executable
573 return None
577 return None
574
578
575 if pycompat.ossep in command:
579 if pycompat.ossep in command:
576 return findexisting(command)
580 return findexisting(command)
577
581
578 if pycompat.sysplatform == b'plan9':
582 if pycompat.sysplatform == b'plan9':
579 return findexisting(os.path.join(b'/bin', command))
583 return findexisting(os.path.join(b'/bin', command))
580
584
581 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
585 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
582 executable = findexisting(os.path.join(path, command))
586 executable = findexisting(os.path.join(path, command))
583 if executable is not None:
587 if executable is not None:
584 return executable
588 return executable
585 return None
589 return None
586
590
587
591
588 def setsignalhandler():
592 def setsignalhandler():
589 pass
593 pass
590
594
591
595
592 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
596 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
593
597
594
598
595 def statfiles(files):
599 def statfiles(files):
596 """Stat each file in files. Yield each stat, or None if a file does not
600 """Stat each file in files. Yield each stat, or None if a file does not
597 exist or has a type we don't care about."""
601 exist or has a type we don't care about."""
598 lstat = os.lstat
602 lstat = os.lstat
599 getkind = stat.S_IFMT
603 getkind = stat.S_IFMT
600 for nf in files:
604 for nf in files:
601 try:
605 try:
602 st = lstat(nf)
606 st = lstat(nf)
603 if getkind(st.st_mode) not in _wantedkinds:
607 if getkind(st.st_mode) not in _wantedkinds:
604 st = None
608 st = None
605 except OSError as err:
609 except OSError as err:
606 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
610 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
607 raise
611 raise
608 st = None
612 st = None
609 yield st
613 yield st
610
614
611
615
612 def getuser():
616 def getuser():
613 '''return name of current user'''
617 '''return name of current user'''
614 return pycompat.fsencode(getpass.getuser())
618 return pycompat.fsencode(getpass.getuser())
615
619
616
620
617 def username(uid=None):
621 def username(uid=None):
618 """Return the name of the user with the given uid.
622 """Return the name of the user with the given uid.
619
623
620 If uid is None, return the name of the current user."""
624 If uid is None, return the name of the current user."""
621
625
622 if uid is None:
626 if uid is None:
623 uid = os.getuid()
627 uid = os.getuid()
624 try:
628 try:
625 return pycompat.fsencode(pwd.getpwuid(uid)[0])
629 return pycompat.fsencode(pwd.getpwuid(uid)[0])
626 except KeyError:
630 except KeyError:
627 return b'%d' % uid
631 return b'%d' % uid
628
632
629
633
630 def groupname(gid=None):
634 def groupname(gid=None):
631 """Return the name of the group with the given gid.
635 """Return the name of the group with the given gid.
632
636
633 If gid is None, return the name of the current group."""
637 If gid is None, return the name of the current group."""
634
638
635 if gid is None:
639 if gid is None:
636 gid = os.getgid()
640 gid = os.getgid()
637 try:
641 try:
638 return pycompat.fsencode(grp.getgrgid(gid)[0])
642 return pycompat.fsencode(grp.getgrgid(gid)[0])
639 except KeyError:
643 except KeyError:
640 return pycompat.bytestr(gid)
644 return pycompat.bytestr(gid)
641
645
642
646
643 def groupmembers(name):
647 def groupmembers(name):
644 """Return the list of members of the group with the given
648 """Return the list of members of the group with the given
645 name, KeyError if the group does not exist.
649 name, KeyError if the group does not exist.
646 """
650 """
647 name = pycompat.fsdecode(name)
651 name = pycompat.fsdecode(name)
648 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem))
652 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem))
649
653
650
654
651 def spawndetached(args):
655 def spawndetached(args):
652 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), args[0], args)
656 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), args[0], args)
653
657
654
658
655 def gethgcmd():
659 def gethgcmd():
656 return sys.argv[:1]
660 return sys.argv[:1]
657
661
658
662
659 def makedir(path, notindexed):
663 def makedir(path, notindexed):
660 os.mkdir(path)
664 os.mkdir(path)
661
665
662
666
663 def lookupreg(key, name=None, scope=None):
667 def lookupreg(key, name=None, scope=None):
664 return None
668 return None
665
669
666
670
667 def hidewindow():
671 def hidewindow():
668 """Hide current shell window.
672 """Hide current shell window.
669
673
670 Used to hide the window opened when starting asynchronous
674 Used to hide the window opened when starting asynchronous
671 child process under Windows, unneeded on other systems.
675 child process under Windows, unneeded on other systems.
672 """
676 """
673 pass
677 pass
674
678
675
679
676 class cachestat(object):
680 class cachestat(object):
677 def __init__(self, path):
681 def __init__(self, path):
678 self.stat = os.stat(path)
682 self.stat = os.stat(path)
679
683
680 def cacheable(self):
684 def cacheable(self):
681 return bool(self.stat.st_ino)
685 return bool(self.stat.st_ino)
682
686
683 __hash__ = object.__hash__
687 __hash__ = object.__hash__
684
688
685 def __eq__(self, other):
689 def __eq__(self, other):
686 try:
690 try:
687 # Only dev, ino, size, mtime and atime are likely to change. Out
691 # Only dev, ino, size, mtime and atime are likely to change. Out
688 # of these, we shouldn't compare atime but should compare the
692 # of these, we shouldn't compare atime but should compare the
689 # rest. However, one of the other fields changing indicates
693 # rest. However, one of the other fields changing indicates
690 # something fishy going on, so return False if anything but atime
694 # something fishy going on, so return False if anything but atime
691 # changes.
695 # changes.
692 return (
696 return (
693 self.stat.st_mode == other.stat.st_mode
697 self.stat.st_mode == other.stat.st_mode
694 and self.stat.st_ino == other.stat.st_ino
698 and self.stat.st_ino == other.stat.st_ino
695 and self.stat.st_dev == other.stat.st_dev
699 and self.stat.st_dev == other.stat.st_dev
696 and self.stat.st_nlink == other.stat.st_nlink
700 and self.stat.st_nlink == other.stat.st_nlink
697 and self.stat.st_uid == other.stat.st_uid
701 and self.stat.st_uid == other.stat.st_uid
698 and self.stat.st_gid == other.stat.st_gid
702 and self.stat.st_gid == other.stat.st_gid
699 and self.stat.st_size == other.stat.st_size
703 and self.stat.st_size == other.stat.st_size
700 and self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME]
704 and self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME]
701 and self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME]
705 and self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME]
702 )
706 )
703 except AttributeError:
707 except AttributeError:
704 return False
708 return False
705
709
706 def __ne__(self, other):
710 def __ne__(self, other):
707 return not self == other
711 return not self == other
708
712
709
713
710 def statislink(st):
714 def statislink(st):
711 '''check whether a stat result is a symlink'''
715 '''check whether a stat result is a symlink'''
712 return st and stat.S_ISLNK(st.st_mode)
716 return st and stat.S_ISLNK(st.st_mode)
713
717
714
718
715 def statisexec(st):
719 def statisexec(st):
716 '''check whether a stat result is an executable file'''
720 '''check whether a stat result is an executable file'''
717 return st and (st.st_mode & 0o100 != 0)
721 return st and (st.st_mode & 0o100 != 0)
718
722
719
723
720 def poll(fds):
724 def poll(fds):
721 """block until something happens on any file descriptor
725 """block until something happens on any file descriptor
722
726
723 This is a generic helper that will check for any activity
727 This is a generic helper that will check for any activity
724 (read, write. exception) and return the list of touched files.
728 (read, write. exception) and return the list of touched files.
725
729
726 In unsupported cases, it will raise a NotImplementedError"""
730 In unsupported cases, it will raise a NotImplementedError"""
727 try:
731 try:
728 while True:
732 while True:
729 try:
733 try:
730 res = select.select(fds, fds, fds)
734 res = select.select(fds, fds, fds)
731 break
735 break
732 except select.error as inst:
736 except select.error as inst:
733 if inst.args[0] == errno.EINTR:
737 if inst.args[0] == errno.EINTR:
734 continue
738 continue
735 raise
739 raise
736 except ValueError: # out of range file descriptor
740 except ValueError: # out of range file descriptor
737 raise NotImplementedError()
741 raise NotImplementedError()
738 return sorted(list(set(sum(res, []))))
742 return sorted(list(set(sum(res, []))))
739
743
740
744
741 def readpipe(pipe):
745 def readpipe(pipe):
742 """Read all available data from a pipe."""
746 """Read all available data from a pipe."""
743 # We can't fstat() a pipe because Linux will always report 0.
747 # We can't fstat() a pipe because Linux will always report 0.
744 # So, we set the pipe to non-blocking mode and read everything
748 # So, we set the pipe to non-blocking mode and read everything
745 # that's available.
749 # that's available.
746 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
750 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
747 flags |= os.O_NONBLOCK
751 flags |= os.O_NONBLOCK
748 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
752 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
749
753
750 try:
754 try:
751 chunks = []
755 chunks = []
752 while True:
756 while True:
753 try:
757 try:
754 s = pipe.read()
758 s = pipe.read()
755 if not s:
759 if not s:
756 break
760 break
757 chunks.append(s)
761 chunks.append(s)
758 except IOError:
762 except IOError:
759 break
763 break
760
764
761 return b''.join(chunks)
765 return b''.join(chunks)
762 finally:
766 finally:
763 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
767 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
764
768
765
769
766 def bindunixsocket(sock, path):
770 def bindunixsocket(sock, path):
767 """Bind the UNIX domain socket to the specified path"""
771 """Bind the UNIX domain socket to the specified path"""
768 # use relative path instead of full path at bind() if possible, since
772 # use relative path instead of full path at bind() if possible, since
769 # AF_UNIX path has very small length limit (107 chars) on common
773 # AF_UNIX path has very small length limit (107 chars) on common
770 # platforms (see sys/un.h)
774 # platforms (see sys/un.h)
771 dirname, basename = os.path.split(path)
775 dirname, basename = os.path.split(path)
772 bakwdfd = None
776 bakwdfd = None
773
777
774 try:
778 try:
775 if dirname:
779 if dirname:
776 bakwdfd = os.open(b'.', os.O_DIRECTORY)
780 bakwdfd = os.open(b'.', os.O_DIRECTORY)
777 os.chdir(dirname)
781 os.chdir(dirname)
778 sock.bind(basename)
782 sock.bind(basename)
779 if bakwdfd:
783 if bakwdfd:
780 os.fchdir(bakwdfd)
784 os.fchdir(bakwdfd)
781 finally:
785 finally:
782 if bakwdfd:
786 if bakwdfd:
783 os.close(bakwdfd)
787 os.close(bakwdfd)
@@ -1,2228 +1,2227 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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 datetime
12 import datetime
13 import errno
13 import errno
14 import getpass
15 import inspect
14 import inspect
16 import os
15 import os
17 import re
16 import re
18 import signal
17 import signal
19 import socket
18 import socket
20 import subprocess
19 import subprocess
21 import sys
20 import sys
22 import traceback
21 import traceback
23
22
24 from .i18n import _
23 from .i18n import _
25 from .node import hex
24 from .node import hex
26 from .pycompat import (
25 from .pycompat import (
27 getattr,
26 getattr,
28 open,
27 open,
29 )
28 )
30
29
31 from . import (
30 from . import (
32 color,
31 color,
33 config,
32 config,
34 configitems,
33 configitems,
35 encoding,
34 encoding,
36 error,
35 error,
37 formatter,
36 formatter,
38 loggingutil,
37 loggingutil,
39 progress,
38 progress,
40 pycompat,
39 pycompat,
41 rcutil,
40 rcutil,
42 scmutil,
41 scmutil,
43 util,
42 util,
44 )
43 )
45 from .utils import (
44 from .utils import (
46 dateutil,
45 dateutil,
47 procutil,
46 procutil,
48 resourceutil,
47 resourceutil,
49 stringutil,
48 stringutil,
50 urlutil,
49 urlutil,
51 )
50 )
52
51
53 urlreq = util.urlreq
52 urlreq = util.urlreq
54
53
55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
54 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
56 _keepalnum = b''.join(
55 _keepalnum = b''.join(
57 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
56 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
58 )
57 )
59
58
60 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
59 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
61 tweakrc = b"""
60 tweakrc = b"""
62 [ui]
61 [ui]
63 # The rollback command is dangerous. As a rule, don't use it.
62 # The rollback command is dangerous. As a rule, don't use it.
64 rollback = False
63 rollback = False
65 # Make `hg status` report copy information
64 # Make `hg status` report copy information
66 statuscopies = yes
65 statuscopies = yes
67 # Prefer curses UIs when available. Revert to plain-text with `text`.
66 # Prefer curses UIs when available. Revert to plain-text with `text`.
68 interface = curses
67 interface = curses
69 # Make compatible commands emit cwd-relative paths by default.
68 # Make compatible commands emit cwd-relative paths by default.
70 relative-paths = yes
69 relative-paths = yes
71
70
72 [commands]
71 [commands]
73 # Grep working directory by default.
72 # Grep working directory by default.
74 grep.all-files = True
73 grep.all-files = True
75 # Refuse to perform an `hg update` that would cause a file content merge
74 # Refuse to perform an `hg update` that would cause a file content merge
76 update.check = noconflict
75 update.check = noconflict
77 # Show conflicts information in `hg status`
76 # Show conflicts information in `hg status`
78 status.verbose = True
77 status.verbose = True
79 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
78 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
80 resolve.explicit-re-merge = True
79 resolve.explicit-re-merge = True
81
80
82 [diff]
81 [diff]
83 git = 1
82 git = 1
84 showfunc = 1
83 showfunc = 1
85 word-diff = 1
84 word-diff = 1
86 """
85 """
87
86
88 samplehgrcs = {
87 samplehgrcs = {
89 b'user': b"""# example user config (see 'hg help config' for more info)
88 b'user': b"""# example user config (see 'hg help config' for more info)
90 [ui]
89 [ui]
91 # name and email, e.g.
90 # name and email, e.g.
92 # username = Jane Doe <jdoe@example.com>
91 # username = Jane Doe <jdoe@example.com>
93 username =
92 username =
94
93
95 # We recommend enabling tweakdefaults to get slight improvements to
94 # We recommend enabling tweakdefaults to get slight improvements to
96 # the UI over time. Make sure to set HGPLAIN in the environment when
95 # the UI over time. Make sure to set HGPLAIN in the environment when
97 # writing scripts!
96 # writing scripts!
98 # tweakdefaults = True
97 # tweakdefaults = True
99
98
100 # uncomment to disable color in command output
99 # uncomment to disable color in command output
101 # (see 'hg help color' for details)
100 # (see 'hg help color' for details)
102 # color = never
101 # color = never
103
102
104 # uncomment to disable command output pagination
103 # uncomment to disable command output pagination
105 # (see 'hg help pager' for details)
104 # (see 'hg help pager' for details)
106 # paginate = never
105 # paginate = never
107
106
108 [extensions]
107 [extensions]
109 # uncomment the lines below to enable some popular extensions
108 # uncomment the lines below to enable some popular extensions
110 # (see 'hg help extensions' for more info)
109 # (see 'hg help extensions' for more info)
111 #
110 #
112 # histedit =
111 # histedit =
113 # rebase =
112 # rebase =
114 # uncommit =
113 # uncommit =
115 """,
114 """,
116 b'cloned': b"""# example repository config (see 'hg help config' for more info)
115 b'cloned': b"""# example repository config (see 'hg help config' for more info)
117 [paths]
116 [paths]
118 default = %s
117 default = %s
119
118
120 # path aliases to other clones of this repo in URLs or filesystem paths
119 # path aliases to other clones of this repo in URLs or filesystem paths
121 # (see 'hg help config.paths' for more info)
120 # (see 'hg help config.paths' for more info)
122 #
121 #
123 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
122 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
124 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
123 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
125 # my-clone = /home/jdoe/jdoes-clone
124 # my-clone = /home/jdoe/jdoes-clone
126
125
127 [ui]
126 [ui]
128 # name and email (local to this repository, optional), e.g.
127 # name and email (local to this repository, optional), e.g.
129 # username = Jane Doe <jdoe@example.com>
128 # username = Jane Doe <jdoe@example.com>
130 """,
129 """,
131 b'local': b"""# example repository config (see 'hg help config' for more info)
130 b'local': b"""# example repository config (see 'hg help config' for more info)
132 [paths]
131 [paths]
133 # path aliases to other clones of this repo in URLs or filesystem paths
132 # path aliases to other clones of this repo in URLs or filesystem paths
134 # (see 'hg help config.paths' for more info)
133 # (see 'hg help config.paths' for more info)
135 #
134 #
136 # default = http://example.com/hg/example-repo
135 # default = http://example.com/hg/example-repo
137 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
136 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
138 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
137 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
139 # my-clone = /home/jdoe/jdoes-clone
138 # my-clone = /home/jdoe/jdoes-clone
140
139
141 [ui]
140 [ui]
142 # name and email (local to this repository, optional), e.g.
141 # name and email (local to this repository, optional), e.g.
143 # username = Jane Doe <jdoe@example.com>
142 # username = Jane Doe <jdoe@example.com>
144 """,
143 """,
145 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
144 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
146
145
147 [ui]
146 [ui]
148 # uncomment to disable color in command output
147 # uncomment to disable color in command output
149 # (see 'hg help color' for details)
148 # (see 'hg help color' for details)
150 # color = never
149 # color = never
151
150
152 # uncomment to disable command output pagination
151 # uncomment to disable command output pagination
153 # (see 'hg help pager' for details)
152 # (see 'hg help pager' for details)
154 # paginate = never
153 # paginate = never
155
154
156 [extensions]
155 [extensions]
157 # uncomment the lines below to enable some popular extensions
156 # uncomment the lines below to enable some popular extensions
158 # (see 'hg help extensions' for more info)
157 # (see 'hg help extensions' for more info)
159 #
158 #
160 # blackbox =
159 # blackbox =
161 # churn =
160 # churn =
162 """,
161 """,
163 }
162 }
164
163
165
164
166 def _maybestrurl(maybebytes):
165 def _maybestrurl(maybebytes):
167 return pycompat.rapply(pycompat.strurl, maybebytes)
166 return pycompat.rapply(pycompat.strurl, maybebytes)
168
167
169
168
170 def _maybebytesurl(maybestr):
169 def _maybebytesurl(maybestr):
171 return pycompat.rapply(pycompat.bytesurl, maybestr)
170 return pycompat.rapply(pycompat.bytesurl, maybestr)
172
171
173
172
174 class httppasswordmgrdbproxy(object):
173 class httppasswordmgrdbproxy(object):
175 """Delays loading urllib2 until it's needed."""
174 """Delays loading urllib2 until it's needed."""
176
175
177 def __init__(self):
176 def __init__(self):
178 self._mgr = None
177 self._mgr = None
179
178
180 def _get_mgr(self):
179 def _get_mgr(self):
181 if self._mgr is None:
180 if self._mgr is None:
182 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
181 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
183 return self._mgr
182 return self._mgr
184
183
185 def add_password(self, realm, uris, user, passwd):
184 def add_password(self, realm, uris, user, passwd):
186 return self._get_mgr().add_password(
185 return self._get_mgr().add_password(
187 _maybestrurl(realm),
186 _maybestrurl(realm),
188 _maybestrurl(uris),
187 _maybestrurl(uris),
189 _maybestrurl(user),
188 _maybestrurl(user),
190 _maybestrurl(passwd),
189 _maybestrurl(passwd),
191 )
190 )
192
191
193 def find_user_password(self, realm, uri):
192 def find_user_password(self, realm, uri):
194 mgr = self._get_mgr()
193 mgr = self._get_mgr()
195 return _maybebytesurl(
194 return _maybebytesurl(
196 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
195 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
197 )
196 )
198
197
199
198
200 def _catchterm(*args):
199 def _catchterm(*args):
201 raise error.SignalInterrupt
200 raise error.SignalInterrupt
202
201
203
202
204 # unique object used to detect no default value has been provided when
203 # unique object used to detect no default value has been provided when
205 # retrieving configuration value.
204 # retrieving configuration value.
206 _unset = object()
205 _unset = object()
207
206
208 # _reqexithandlers: callbacks run at the end of a request
207 # _reqexithandlers: callbacks run at the end of a request
209 _reqexithandlers = []
208 _reqexithandlers = []
210
209
211
210
212 class ui(object):
211 class ui(object):
213 def __init__(self, src=None):
212 def __init__(self, src=None):
214 """Create a fresh new ui object if no src given
213 """Create a fresh new ui object if no src given
215
214
216 Use uimod.ui.load() to create a ui which knows global and user configs.
215 Use uimod.ui.load() to create a ui which knows global and user configs.
217 In most cases, you should use ui.copy() to create a copy of an existing
216 In most cases, you should use ui.copy() to create a copy of an existing
218 ui object.
217 ui object.
219 """
218 """
220 # _buffers: used for temporary capture of output
219 # _buffers: used for temporary capture of output
221 self._buffers = []
220 self._buffers = []
222 # 3-tuple describing how each buffer in the stack behaves.
221 # 3-tuple describing how each buffer in the stack behaves.
223 # Values are (capture stderr, capture subprocesses, apply labels).
222 # Values are (capture stderr, capture subprocesses, apply labels).
224 self._bufferstates = []
223 self._bufferstates = []
225 # When a buffer is active, defines whether we are expanding labels.
224 # When a buffer is active, defines whether we are expanding labels.
226 # This exists to prevent an extra list lookup.
225 # This exists to prevent an extra list lookup.
227 self._bufferapplylabels = None
226 self._bufferapplylabels = None
228 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
227 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
229 self._reportuntrusted = True
228 self._reportuntrusted = True
230 self._knownconfig = configitems.coreitems
229 self._knownconfig = configitems.coreitems
231 self._ocfg = config.config() # overlay
230 self._ocfg = config.config() # overlay
232 self._tcfg = config.config() # trusted
231 self._tcfg = config.config() # trusted
233 self._ucfg = config.config() # untrusted
232 self._ucfg = config.config() # untrusted
234 self._trustusers = set()
233 self._trustusers = set()
235 self._trustgroups = set()
234 self._trustgroups = set()
236 self.callhooks = True
235 self.callhooks = True
237 # Insecure server connections requested.
236 # Insecure server connections requested.
238 self.insecureconnections = False
237 self.insecureconnections = False
239 # Blocked time
238 # Blocked time
240 self.logblockedtimes = False
239 self.logblockedtimes = False
241 # color mode: see mercurial/color.py for possible value
240 # color mode: see mercurial/color.py for possible value
242 self._colormode = None
241 self._colormode = None
243 self._terminfoparams = {}
242 self._terminfoparams = {}
244 self._styles = {}
243 self._styles = {}
245 self._uninterruptible = False
244 self._uninterruptible = False
246 self.showtimestamp = False
245 self.showtimestamp = False
247
246
248 if src:
247 if src:
249 self._fout = src._fout
248 self._fout = src._fout
250 self._ferr = src._ferr
249 self._ferr = src._ferr
251 self._fin = src._fin
250 self._fin = src._fin
252 self._fmsg = src._fmsg
251 self._fmsg = src._fmsg
253 self._fmsgout = src._fmsgout
252 self._fmsgout = src._fmsgout
254 self._fmsgerr = src._fmsgerr
253 self._fmsgerr = src._fmsgerr
255 self._finoutredirected = src._finoutredirected
254 self._finoutredirected = src._finoutredirected
256 self._loggers = src._loggers.copy()
255 self._loggers = src._loggers.copy()
257 self.pageractive = src.pageractive
256 self.pageractive = src.pageractive
258 self._disablepager = src._disablepager
257 self._disablepager = src._disablepager
259 self._tweaked = src._tweaked
258 self._tweaked = src._tweaked
260
259
261 self._tcfg = src._tcfg.copy()
260 self._tcfg = src._tcfg.copy()
262 self._ucfg = src._ucfg.copy()
261 self._ucfg = src._ucfg.copy()
263 self._ocfg = src._ocfg.copy()
262 self._ocfg = src._ocfg.copy()
264 self._trustusers = src._trustusers.copy()
263 self._trustusers = src._trustusers.copy()
265 self._trustgroups = src._trustgroups.copy()
264 self._trustgroups = src._trustgroups.copy()
266 self.environ = src.environ
265 self.environ = src.environ
267 self.callhooks = src.callhooks
266 self.callhooks = src.callhooks
268 self.insecureconnections = src.insecureconnections
267 self.insecureconnections = src.insecureconnections
269 self._colormode = src._colormode
268 self._colormode = src._colormode
270 self._terminfoparams = src._terminfoparams.copy()
269 self._terminfoparams = src._terminfoparams.copy()
271 self._styles = src._styles.copy()
270 self._styles = src._styles.copy()
272
271
273 self.fixconfig()
272 self.fixconfig()
274
273
275 self.httppasswordmgrdb = src.httppasswordmgrdb
274 self.httppasswordmgrdb = src.httppasswordmgrdb
276 self._blockedtimes = src._blockedtimes
275 self._blockedtimes = src._blockedtimes
277 else:
276 else:
278 self._fout = procutil.stdout
277 self._fout = procutil.stdout
279 self._ferr = procutil.stderr
278 self._ferr = procutil.stderr
280 self._fin = procutil.stdin
279 self._fin = procutil.stdin
281 self._fmsg = None
280 self._fmsg = None
282 self._fmsgout = self.fout # configurable
281 self._fmsgout = self.fout # configurable
283 self._fmsgerr = self.ferr # configurable
282 self._fmsgerr = self.ferr # configurable
284 self._finoutredirected = False
283 self._finoutredirected = False
285 self._loggers = {}
284 self._loggers = {}
286 self.pageractive = False
285 self.pageractive = False
287 self._disablepager = False
286 self._disablepager = False
288 self._tweaked = False
287 self._tweaked = False
289
288
290 # shared read-only environment
289 # shared read-only environment
291 self.environ = encoding.environ
290 self.environ = encoding.environ
292
291
293 self.httppasswordmgrdb = httppasswordmgrdbproxy()
292 self.httppasswordmgrdb = httppasswordmgrdbproxy()
294 self._blockedtimes = collections.defaultdict(int)
293 self._blockedtimes = collections.defaultdict(int)
295
294
296 allowed = self.configlist(b'experimental', b'exportableenviron')
295 allowed = self.configlist(b'experimental', b'exportableenviron')
297 if b'*' in allowed:
296 if b'*' in allowed:
298 self._exportableenviron = self.environ
297 self._exportableenviron = self.environ
299 else:
298 else:
300 self._exportableenviron = {}
299 self._exportableenviron = {}
301 for k in allowed:
300 for k in allowed:
302 if k in self.environ:
301 if k in self.environ:
303 self._exportableenviron[k] = self.environ[k]
302 self._exportableenviron[k] = self.environ[k]
304
303
305 def _new_source(self):
304 def _new_source(self):
306 self._ocfg.new_source()
305 self._ocfg.new_source()
307 self._tcfg.new_source()
306 self._tcfg.new_source()
308 self._ucfg.new_source()
307 self._ucfg.new_source()
309
308
310 @classmethod
309 @classmethod
311 def load(cls):
310 def load(cls):
312 """Create a ui and load global and user configs"""
311 """Create a ui and load global and user configs"""
313 u = cls()
312 u = cls()
314 # we always trust global config files and environment variables
313 # we always trust global config files and environment variables
315 for t, f in rcutil.rccomponents():
314 for t, f in rcutil.rccomponents():
316 if t == b'path':
315 if t == b'path':
317 u.readconfig(f, trust=True)
316 u.readconfig(f, trust=True)
318 elif t == b'resource':
317 elif t == b'resource':
319 u.read_resource_config(f, trust=True)
318 u.read_resource_config(f, trust=True)
320 elif t == b'items':
319 elif t == b'items':
321 u._new_source()
320 u._new_source()
322 sections = set()
321 sections = set()
323 for section, name, value, source in f:
322 for section, name, value, source in f:
324 # do not set u._ocfg
323 # do not set u._ocfg
325 # XXX clean this up once immutable config object is a thing
324 # XXX clean this up once immutable config object is a thing
326 u._tcfg.set(section, name, value, source)
325 u._tcfg.set(section, name, value, source)
327 u._ucfg.set(section, name, value, source)
326 u._ucfg.set(section, name, value, source)
328 sections.add(section)
327 sections.add(section)
329 for section in sections:
328 for section in sections:
330 u.fixconfig(section=section)
329 u.fixconfig(section=section)
331 else:
330 else:
332 raise error.ProgrammingError(b'unknown rctype: %s' % t)
331 raise error.ProgrammingError(b'unknown rctype: %s' % t)
333 u._maybetweakdefaults()
332 u._maybetweakdefaults()
334 u._new_source() # anything after that is a different level
333 u._new_source() # anything after that is a different level
335 return u
334 return u
336
335
337 def _maybetweakdefaults(self):
336 def _maybetweakdefaults(self):
338 if not self.configbool(b'ui', b'tweakdefaults'):
337 if not self.configbool(b'ui', b'tweakdefaults'):
339 return
338 return
340 if self._tweaked or self.plain(b'tweakdefaults'):
339 if self._tweaked or self.plain(b'tweakdefaults'):
341 return
340 return
342
341
343 # Note: it is SUPER IMPORTANT that you set self._tweaked to
342 # Note: it is SUPER IMPORTANT that you set self._tweaked to
344 # True *before* any calls to setconfig(), otherwise you'll get
343 # True *before* any calls to setconfig(), otherwise you'll get
345 # infinite recursion between setconfig and this method.
344 # infinite recursion between setconfig and this method.
346 #
345 #
347 # TODO: We should extract an inner method in setconfig() to
346 # TODO: We should extract an inner method in setconfig() to
348 # avoid this weirdness.
347 # avoid this weirdness.
349 self._tweaked = True
348 self._tweaked = True
350 tmpcfg = config.config()
349 tmpcfg = config.config()
351 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
350 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
352 for section in tmpcfg:
351 for section in tmpcfg:
353 for name, value in tmpcfg.items(section):
352 for name, value in tmpcfg.items(section):
354 if not self.hasconfig(section, name):
353 if not self.hasconfig(section, name):
355 self.setconfig(section, name, value, b"<tweakdefaults>")
354 self.setconfig(section, name, value, b"<tweakdefaults>")
356
355
357 def copy(self):
356 def copy(self):
358 return self.__class__(self)
357 return self.__class__(self)
359
358
360 def resetstate(self):
359 def resetstate(self):
361 """Clear internal state that shouldn't persist across commands"""
360 """Clear internal state that shouldn't persist across commands"""
362 if self._progbar:
361 if self._progbar:
363 self._progbar.resetstate() # reset last-print time of progress bar
362 self._progbar.resetstate() # reset last-print time of progress bar
364 self.httppasswordmgrdb = httppasswordmgrdbproxy()
363 self.httppasswordmgrdb = httppasswordmgrdbproxy()
365
364
366 @contextlib.contextmanager
365 @contextlib.contextmanager
367 def timeblockedsection(self, key):
366 def timeblockedsection(self, key):
368 # this is open-coded below - search for timeblockedsection to find them
367 # this is open-coded below - search for timeblockedsection to find them
369 starttime = util.timer()
368 starttime = util.timer()
370 try:
369 try:
371 yield
370 yield
372 finally:
371 finally:
373 self._blockedtimes[key + b'_blocked'] += (
372 self._blockedtimes[key + b'_blocked'] += (
374 util.timer() - starttime
373 util.timer() - starttime
375 ) * 1000
374 ) * 1000
376
375
377 @contextlib.contextmanager
376 @contextlib.contextmanager
378 def uninterruptible(self):
377 def uninterruptible(self):
379 """Mark an operation as unsafe.
378 """Mark an operation as unsafe.
380
379
381 Most operations on a repository are safe to interrupt, but a
380 Most operations on a repository are safe to interrupt, but a
382 few are risky (for example repair.strip). This context manager
381 few are risky (for example repair.strip). This context manager
383 lets you advise Mercurial that something risky is happening so
382 lets you advise Mercurial that something risky is happening so
384 that control-C etc can be blocked if desired.
383 that control-C etc can be blocked if desired.
385 """
384 """
386 enabled = self.configbool(b'experimental', b'nointerrupt')
385 enabled = self.configbool(b'experimental', b'nointerrupt')
387 if enabled and self.configbool(
386 if enabled and self.configbool(
388 b'experimental', b'nointerrupt-interactiveonly'
387 b'experimental', b'nointerrupt-interactiveonly'
389 ):
388 ):
390 enabled = self.interactive()
389 enabled = self.interactive()
391 if self._uninterruptible or not enabled:
390 if self._uninterruptible or not enabled:
392 # if nointerrupt support is turned off, the process isn't
391 # if nointerrupt support is turned off, the process isn't
393 # interactive, or we're already in an uninterruptible
392 # interactive, or we're already in an uninterruptible
394 # block, do nothing.
393 # block, do nothing.
395 yield
394 yield
396 return
395 return
397
396
398 def warn():
397 def warn():
399 self.warn(_(b"shutting down cleanly\n"))
398 self.warn(_(b"shutting down cleanly\n"))
400 self.warn(
399 self.warn(
401 _(b"press ^C again to terminate immediately (dangerous)\n")
400 _(b"press ^C again to terminate immediately (dangerous)\n")
402 )
401 )
403 return True
402 return True
404
403
405 with procutil.uninterruptible(warn):
404 with procutil.uninterruptible(warn):
406 try:
405 try:
407 self._uninterruptible = True
406 self._uninterruptible = True
408 yield
407 yield
409 finally:
408 finally:
410 self._uninterruptible = False
409 self._uninterruptible = False
411
410
412 def formatter(self, topic, opts):
411 def formatter(self, topic, opts):
413 return formatter.formatter(self, self, topic, opts)
412 return formatter.formatter(self, self, topic, opts)
414
413
415 def _trusted(self, fp, f):
414 def _trusted(self, fp, f):
416 st = util.fstat(fp)
415 st = util.fstat(fp)
417 if util.isowner(st):
416 if util.isowner(st):
418 return True
417 return True
419
418
420 tusers, tgroups = self._trustusers, self._trustgroups
419 tusers, tgroups = self._trustusers, self._trustgroups
421 if b'*' in tusers or b'*' in tgroups:
420 if b'*' in tusers or b'*' in tgroups:
422 return True
421 return True
423
422
424 user = util.username(st.st_uid)
423 user = util.username(st.st_uid)
425 group = util.groupname(st.st_gid)
424 group = util.groupname(st.st_gid)
426 if user in tusers or group in tgroups or user == util.username():
425 if user in tusers or group in tgroups or user == util.username():
427 return True
426 return True
428
427
429 if self._reportuntrusted:
428 if self._reportuntrusted:
430 self.warn(
429 self.warn(
431 _(
430 _(
432 b'not trusting file %s from untrusted '
431 b'not trusting file %s from untrusted '
433 b'user %s, group %s\n'
432 b'user %s, group %s\n'
434 )
433 )
435 % (f, user, group)
434 % (f, user, group)
436 )
435 )
437 return False
436 return False
438
437
439 def read_resource_config(
438 def read_resource_config(
440 self, name, root=None, trust=False, sections=None, remap=None
439 self, name, root=None, trust=False, sections=None, remap=None
441 ):
440 ):
442 try:
441 try:
443 fp = resourceutil.open_resource(name[0], name[1])
442 fp = resourceutil.open_resource(name[0], name[1])
444 except IOError:
443 except IOError:
445 if not sections: # ignore unless we were looking for something
444 if not sections: # ignore unless we were looking for something
446 return
445 return
447 raise
446 raise
448
447
449 self._readconfig(
448 self._readconfig(
450 b'resource:%s.%s' % name, fp, root, trust, sections, remap
449 b'resource:%s.%s' % name, fp, root, trust, sections, remap
451 )
450 )
452
451
453 def readconfig(
452 def readconfig(
454 self, filename, root=None, trust=False, sections=None, remap=None
453 self, filename, root=None, trust=False, sections=None, remap=None
455 ):
454 ):
456 try:
455 try:
457 fp = open(filename, 'rb')
456 fp = open(filename, 'rb')
458 except IOError:
457 except IOError:
459 if not sections: # ignore unless we were looking for something
458 if not sections: # ignore unless we were looking for something
460 return
459 return
461 raise
460 raise
462
461
463 self._readconfig(filename, fp, root, trust, sections, remap)
462 self._readconfig(filename, fp, root, trust, sections, remap)
464
463
465 def _readconfig(
464 def _readconfig(
466 self, filename, fp, root=None, trust=False, sections=None, remap=None
465 self, filename, fp, root=None, trust=False, sections=None, remap=None
467 ):
466 ):
468 with fp:
467 with fp:
469 cfg = config.config()
468 cfg = config.config()
470 trusted = sections or trust or self._trusted(fp, filename)
469 trusted = sections or trust or self._trusted(fp, filename)
471
470
472 try:
471 try:
473 cfg.read(filename, fp, sections=sections, remap=remap)
472 cfg.read(filename, fp, sections=sections, remap=remap)
474 except error.ConfigError as inst:
473 except error.ConfigError as inst:
475 if trusted:
474 if trusted:
476 raise
475 raise
477 self.warn(
476 self.warn(
478 _(b'ignored %s: %s\n') % (inst.location, inst.message)
477 _(b'ignored %s: %s\n') % (inst.location, inst.message)
479 )
478 )
480
479
481 self._applyconfig(cfg, trusted, root)
480 self._applyconfig(cfg, trusted, root)
482
481
483 def applyconfig(self, configitems, source=b"", root=None):
482 def applyconfig(self, configitems, source=b"", root=None):
484 """Add configitems from a non-file source. Unlike with ``setconfig()``,
483 """Add configitems from a non-file source. Unlike with ``setconfig()``,
485 they can be overridden by subsequent config file reads. The items are
484 they can be overridden by subsequent config file reads. The items are
486 in the same format as ``configoverride()``, namely a dict of the
485 in the same format as ``configoverride()``, namely a dict of the
487 following structures: {(section, name) : value}
486 following structures: {(section, name) : value}
488
487
489 Typically this is used by extensions that inject themselves into the
488 Typically this is used by extensions that inject themselves into the
490 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
489 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
491 """
490 """
492 cfg = config.config()
491 cfg = config.config()
493
492
494 for (section, name), value in configitems.items():
493 for (section, name), value in configitems.items():
495 cfg.set(section, name, value, source)
494 cfg.set(section, name, value, source)
496
495
497 self._applyconfig(cfg, True, root)
496 self._applyconfig(cfg, True, root)
498
497
499 def _applyconfig(self, cfg, trusted, root):
498 def _applyconfig(self, cfg, trusted, root):
500 if self.plain():
499 if self.plain():
501 for k in (
500 for k in (
502 b'debug',
501 b'debug',
503 b'fallbackencoding',
502 b'fallbackencoding',
504 b'quiet',
503 b'quiet',
505 b'slash',
504 b'slash',
506 b'logtemplate',
505 b'logtemplate',
507 b'message-output',
506 b'message-output',
508 b'statuscopies',
507 b'statuscopies',
509 b'style',
508 b'style',
510 b'traceback',
509 b'traceback',
511 b'verbose',
510 b'verbose',
512 ):
511 ):
513 if k in cfg[b'ui']:
512 if k in cfg[b'ui']:
514 del cfg[b'ui'][k]
513 del cfg[b'ui'][k]
515 for k, v in cfg.items(b'defaults'):
514 for k, v in cfg.items(b'defaults'):
516 del cfg[b'defaults'][k]
515 del cfg[b'defaults'][k]
517 for k, v in cfg.items(b'commands'):
516 for k, v in cfg.items(b'commands'):
518 del cfg[b'commands'][k]
517 del cfg[b'commands'][k]
519 for k, v in cfg.items(b'command-templates'):
518 for k, v in cfg.items(b'command-templates'):
520 del cfg[b'command-templates'][k]
519 del cfg[b'command-templates'][k]
521 # Don't remove aliases from the configuration if in the exceptionlist
520 # Don't remove aliases from the configuration if in the exceptionlist
522 if self.plain(b'alias'):
521 if self.plain(b'alias'):
523 for k, v in cfg.items(b'alias'):
522 for k, v in cfg.items(b'alias'):
524 del cfg[b'alias'][k]
523 del cfg[b'alias'][k]
525 if self.plain(b'revsetalias'):
524 if self.plain(b'revsetalias'):
526 for k, v in cfg.items(b'revsetalias'):
525 for k, v in cfg.items(b'revsetalias'):
527 del cfg[b'revsetalias'][k]
526 del cfg[b'revsetalias'][k]
528 if self.plain(b'templatealias'):
527 if self.plain(b'templatealias'):
529 for k, v in cfg.items(b'templatealias'):
528 for k, v in cfg.items(b'templatealias'):
530 del cfg[b'templatealias'][k]
529 del cfg[b'templatealias'][k]
531
530
532 if trusted:
531 if trusted:
533 self._tcfg.update(cfg)
532 self._tcfg.update(cfg)
534 self._tcfg.update(self._ocfg)
533 self._tcfg.update(self._ocfg)
535 self._ucfg.update(cfg)
534 self._ucfg.update(cfg)
536 self._ucfg.update(self._ocfg)
535 self._ucfg.update(self._ocfg)
537
536
538 if root is None:
537 if root is None:
539 root = os.path.expanduser(b'~')
538 root = os.path.expanduser(b'~')
540 self.fixconfig(root=root)
539 self.fixconfig(root=root)
541
540
542 def fixconfig(self, root=None, section=None):
541 def fixconfig(self, root=None, section=None):
543 if section in (None, b'paths'):
542 if section in (None, b'paths'):
544 # expand vars and ~
543 # expand vars and ~
545 # translate paths relative to root (or home) into absolute paths
544 # translate paths relative to root (or home) into absolute paths
546 root = root or encoding.getcwd()
545 root = root or encoding.getcwd()
547 for c in self._tcfg, self._ucfg, self._ocfg:
546 for c in self._tcfg, self._ucfg, self._ocfg:
548 for n, p in c.items(b'paths'):
547 for n, p in c.items(b'paths'):
549 # Ignore sub-options.
548 # Ignore sub-options.
550 if b':' in n:
549 if b':' in n:
551 continue
550 continue
552 if not p:
551 if not p:
553 continue
552 continue
554 if b'%%' in p:
553 if b'%%' in p:
555 s = self.configsource(b'paths', n) or b'none'
554 s = self.configsource(b'paths', n) or b'none'
556 self.warn(
555 self.warn(
557 _(b"(deprecated '%%' in path %s=%s from %s)\n")
556 _(b"(deprecated '%%' in path %s=%s from %s)\n")
558 % (n, p, s)
557 % (n, p, s)
559 )
558 )
560 p = p.replace(b'%%', b'%')
559 p = p.replace(b'%%', b'%')
561 p = util.expandpath(p)
560 p = util.expandpath(p)
562 if not urlutil.hasscheme(p) and not os.path.isabs(p):
561 if not urlutil.hasscheme(p) and not os.path.isabs(p):
563 p = os.path.normpath(os.path.join(root, p))
562 p = os.path.normpath(os.path.join(root, p))
564 c.alter(b"paths", n, p)
563 c.alter(b"paths", n, p)
565
564
566 if section in (None, b'ui'):
565 if section in (None, b'ui'):
567 # update ui options
566 # update ui options
568 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
567 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
569 self.debugflag = self.configbool(b'ui', b'debug')
568 self.debugflag = self.configbool(b'ui', b'debug')
570 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
569 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
571 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
570 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
572 if self.verbose and self.quiet:
571 if self.verbose and self.quiet:
573 self.quiet = self.verbose = False
572 self.quiet = self.verbose = False
574 self._reportuntrusted = self.debugflag or self.configbool(
573 self._reportuntrusted = self.debugflag or self.configbool(
575 b"ui", b"report_untrusted"
574 b"ui", b"report_untrusted"
576 )
575 )
577 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
576 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
578 self.tracebackflag = self.configbool(b'ui', b'traceback')
577 self.tracebackflag = self.configbool(b'ui', b'traceback')
579 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
578 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
580
579
581 if section in (None, b'trusted'):
580 if section in (None, b'trusted'):
582 # update trust information
581 # update trust information
583 self._trustusers.update(self.configlist(b'trusted', b'users'))
582 self._trustusers.update(self.configlist(b'trusted', b'users'))
584 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
583 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
585
584
586 if section in (None, b'devel', b'ui') and self.debugflag:
585 if section in (None, b'devel', b'ui') and self.debugflag:
587 tracked = set()
586 tracked = set()
588 if self.configbool(b'devel', b'debug.extensions'):
587 if self.configbool(b'devel', b'debug.extensions'):
589 tracked.add(b'extension')
588 tracked.add(b'extension')
590 if tracked:
589 if tracked:
591 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
590 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
592 self.setlogger(b'debug', logger)
591 self.setlogger(b'debug', logger)
593
592
594 def backupconfig(self, section, item):
593 def backupconfig(self, section, item):
595 return (
594 return (
596 self._ocfg.backup(section, item),
595 self._ocfg.backup(section, item),
597 self._tcfg.backup(section, item),
596 self._tcfg.backup(section, item),
598 self._ucfg.backup(section, item),
597 self._ucfg.backup(section, item),
599 )
598 )
600
599
601 def restoreconfig(self, data):
600 def restoreconfig(self, data):
602 self._ocfg.restore(data[0])
601 self._ocfg.restore(data[0])
603 self._tcfg.restore(data[1])
602 self._tcfg.restore(data[1])
604 self._ucfg.restore(data[2])
603 self._ucfg.restore(data[2])
605
604
606 def setconfig(self, section, name, value, source=b''):
605 def setconfig(self, section, name, value, source=b''):
607 for cfg in (self._ocfg, self._tcfg, self._ucfg):
606 for cfg in (self._ocfg, self._tcfg, self._ucfg):
608 cfg.set(section, name, value, source)
607 cfg.set(section, name, value, source)
609 self.fixconfig(section=section)
608 self.fixconfig(section=section)
610 self._maybetweakdefaults()
609 self._maybetweakdefaults()
611
610
612 def _data(self, untrusted):
611 def _data(self, untrusted):
613 return untrusted and self._ucfg or self._tcfg
612 return untrusted and self._ucfg or self._tcfg
614
613
615 def configsource(self, section, name, untrusted=False):
614 def configsource(self, section, name, untrusted=False):
616 return self._data(untrusted).source(section, name)
615 return self._data(untrusted).source(section, name)
617
616
618 def config(self, section, name, default=_unset, untrusted=False):
617 def config(self, section, name, default=_unset, untrusted=False):
619 """return the plain string version of a config"""
618 """return the plain string version of a config"""
620 value = self._config(
619 value = self._config(
621 section, name, default=default, untrusted=untrusted
620 section, name, default=default, untrusted=untrusted
622 )
621 )
623 if value is _unset:
622 if value is _unset:
624 return None
623 return None
625 return value
624 return value
626
625
627 def _config(self, section, name, default=_unset, untrusted=False):
626 def _config(self, section, name, default=_unset, untrusted=False):
628 value = itemdefault = default
627 value = itemdefault = default
629 item = self._knownconfig.get(section, {}).get(name)
628 item = self._knownconfig.get(section, {}).get(name)
630 alternates = [(section, name)]
629 alternates = [(section, name)]
631
630
632 if item is not None:
631 if item is not None:
633 alternates.extend(item.alias)
632 alternates.extend(item.alias)
634 if callable(item.default):
633 if callable(item.default):
635 itemdefault = item.default()
634 itemdefault = item.default()
636 else:
635 else:
637 itemdefault = item.default
636 itemdefault = item.default
638 else:
637 else:
639 msg = b"accessing unregistered config item: '%s.%s'"
638 msg = b"accessing unregistered config item: '%s.%s'"
640 msg %= (section, name)
639 msg %= (section, name)
641 self.develwarn(msg, 2, b'warn-config-unknown')
640 self.develwarn(msg, 2, b'warn-config-unknown')
642
641
643 if default is _unset:
642 if default is _unset:
644 if item is None:
643 if item is None:
645 value = default
644 value = default
646 elif item.default is configitems.dynamicdefault:
645 elif item.default is configitems.dynamicdefault:
647 value = None
646 value = None
648 msg = b"config item requires an explicit default value: '%s.%s'"
647 msg = b"config item requires an explicit default value: '%s.%s'"
649 msg %= (section, name)
648 msg %= (section, name)
650 self.develwarn(msg, 2, b'warn-config-default')
649 self.develwarn(msg, 2, b'warn-config-default')
651 else:
650 else:
652 value = itemdefault
651 value = itemdefault
653 elif (
652 elif (
654 item is not None
653 item is not None
655 and item.default is not configitems.dynamicdefault
654 and item.default is not configitems.dynamicdefault
656 and default != itemdefault
655 and default != itemdefault
657 ):
656 ):
658 msg = (
657 msg = (
659 b"specifying a mismatched default value for a registered "
658 b"specifying a mismatched default value for a registered "
660 b"config item: '%s.%s' '%s'"
659 b"config item: '%s.%s' '%s'"
661 )
660 )
662 msg %= (section, name, pycompat.bytestr(default))
661 msg %= (section, name, pycompat.bytestr(default))
663 self.develwarn(msg, 2, b'warn-config-default')
662 self.develwarn(msg, 2, b'warn-config-default')
664
663
665 candidates = []
664 candidates = []
666 config = self._data(untrusted)
665 config = self._data(untrusted)
667 for s, n in alternates:
666 for s, n in alternates:
668 candidate = config.get(s, n, None)
667 candidate = config.get(s, n, None)
669 if candidate is not None:
668 if candidate is not None:
670 candidates.append((s, n, candidate))
669 candidates.append((s, n, candidate))
671 if candidates:
670 if candidates:
672
671
673 def level(x):
672 def level(x):
674 return config.level(x[0], x[1])
673 return config.level(x[0], x[1])
675
674
676 value = max(candidates, key=level)[2]
675 value = max(candidates, key=level)[2]
677
676
678 if self.debugflag and not untrusted and self._reportuntrusted:
677 if self.debugflag and not untrusted and self._reportuntrusted:
679 for s, n in alternates:
678 for s, n in alternates:
680 uvalue = self._ucfg.get(s, n)
679 uvalue = self._ucfg.get(s, n)
681 if uvalue is not None and uvalue != value:
680 if uvalue is not None and uvalue != value:
682 self.debug(
681 self.debug(
683 b"ignoring untrusted configuration option "
682 b"ignoring untrusted configuration option "
684 b"%s.%s = %s\n" % (s, n, uvalue)
683 b"%s.%s = %s\n" % (s, n, uvalue)
685 )
684 )
686 return value
685 return value
687
686
688 def config_default(self, section, name):
687 def config_default(self, section, name):
689 """return the default value for a config option
688 """return the default value for a config option
690
689
691 The default is returned "raw", for example if it is a callable, the
690 The default is returned "raw", for example if it is a callable, the
692 callable was not called.
691 callable was not called.
693 """
692 """
694 item = self._knownconfig.get(section, {}).get(name)
693 item = self._knownconfig.get(section, {}).get(name)
695
694
696 if item is None:
695 if item is None:
697 raise KeyError((section, name))
696 raise KeyError((section, name))
698 return item.default
697 return item.default
699
698
700 def configsuboptions(self, section, name, default=_unset, untrusted=False):
699 def configsuboptions(self, section, name, default=_unset, untrusted=False):
701 """Get a config option and all sub-options.
700 """Get a config option and all sub-options.
702
701
703 Some config options have sub-options that are declared with the
702 Some config options have sub-options that are declared with the
704 format "key:opt = value". This method is used to return the main
703 format "key:opt = value". This method is used to return the main
705 option and all its declared sub-options.
704 option and all its declared sub-options.
706
705
707 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
706 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
708 is a dict of defined sub-options where keys and values are strings.
707 is a dict of defined sub-options where keys and values are strings.
709 """
708 """
710 main = self.config(section, name, default, untrusted=untrusted)
709 main = self.config(section, name, default, untrusted=untrusted)
711 data = self._data(untrusted)
710 data = self._data(untrusted)
712 sub = {}
711 sub = {}
713 prefix = b'%s:' % name
712 prefix = b'%s:' % name
714 for k, v in data.items(section):
713 for k, v in data.items(section):
715 if k.startswith(prefix):
714 if k.startswith(prefix):
716 sub[k[len(prefix) :]] = v
715 sub[k[len(prefix) :]] = v
717
716
718 if self.debugflag and not untrusted and self._reportuntrusted:
717 if self.debugflag and not untrusted and self._reportuntrusted:
719 for k, v in sub.items():
718 for k, v in sub.items():
720 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
719 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
721 if uvalue is not None and uvalue != v:
720 if uvalue is not None and uvalue != v:
722 self.debug(
721 self.debug(
723 b'ignoring untrusted configuration option '
722 b'ignoring untrusted configuration option '
724 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
723 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
725 )
724 )
726
725
727 return main, sub
726 return main, sub
728
727
729 def configpath(self, section, name, default=_unset, untrusted=False):
728 def configpath(self, section, name, default=_unset, untrusted=False):
730 """get a path config item, expanded relative to repo root or config
729 """get a path config item, expanded relative to repo root or config
731 file"""
730 file"""
732 v = self.config(section, name, default, untrusted)
731 v = self.config(section, name, default, untrusted)
733 if v is None:
732 if v is None:
734 return None
733 return None
735 if not os.path.isabs(v) or b"://" not in v:
734 if not os.path.isabs(v) or b"://" not in v:
736 src = self.configsource(section, name, untrusted)
735 src = self.configsource(section, name, untrusted)
737 if b':' in src:
736 if b':' in src:
738 base = os.path.dirname(src.rsplit(b':')[0])
737 base = os.path.dirname(src.rsplit(b':')[0])
739 v = os.path.join(base, os.path.expanduser(v))
738 v = os.path.join(base, os.path.expanduser(v))
740 return v
739 return v
741
740
742 def configbool(self, section, name, default=_unset, untrusted=False):
741 def configbool(self, section, name, default=_unset, untrusted=False):
743 """parse a configuration element as a boolean
742 """parse a configuration element as a boolean
744
743
745 >>> u = ui(); s = b'foo'
744 >>> u = ui(); s = b'foo'
746 >>> u.setconfig(s, b'true', b'yes')
745 >>> u.setconfig(s, b'true', b'yes')
747 >>> u.configbool(s, b'true')
746 >>> u.configbool(s, b'true')
748 True
747 True
749 >>> u.setconfig(s, b'false', b'no')
748 >>> u.setconfig(s, b'false', b'no')
750 >>> u.configbool(s, b'false')
749 >>> u.configbool(s, b'false')
751 False
750 False
752 >>> u.configbool(s, b'unknown')
751 >>> u.configbool(s, b'unknown')
753 False
752 False
754 >>> u.configbool(s, b'unknown', True)
753 >>> u.configbool(s, b'unknown', True)
755 True
754 True
756 >>> u.setconfig(s, b'invalid', b'somevalue')
755 >>> u.setconfig(s, b'invalid', b'somevalue')
757 >>> u.configbool(s, b'invalid')
756 >>> u.configbool(s, b'invalid')
758 Traceback (most recent call last):
757 Traceback (most recent call last):
759 ...
758 ...
760 ConfigError: foo.invalid is not a boolean ('somevalue')
759 ConfigError: foo.invalid is not a boolean ('somevalue')
761 """
760 """
762
761
763 v = self._config(section, name, default, untrusted=untrusted)
762 v = self._config(section, name, default, untrusted=untrusted)
764 if v is None:
763 if v is None:
765 return v
764 return v
766 if v is _unset:
765 if v is _unset:
767 if default is _unset:
766 if default is _unset:
768 return False
767 return False
769 return default
768 return default
770 if isinstance(v, bool):
769 if isinstance(v, bool):
771 return v
770 return v
772 b = stringutil.parsebool(v)
771 b = stringutil.parsebool(v)
773 if b is None:
772 if b is None:
774 raise error.ConfigError(
773 raise error.ConfigError(
775 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
774 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
776 )
775 )
777 return b
776 return b
778
777
779 def configwith(
778 def configwith(
780 self, convert, section, name, default=_unset, desc=None, untrusted=False
779 self, convert, section, name, default=_unset, desc=None, untrusted=False
781 ):
780 ):
782 """parse a configuration element with a conversion function
781 """parse a configuration element with a conversion function
783
782
784 >>> u = ui(); s = b'foo'
783 >>> u = ui(); s = b'foo'
785 >>> u.setconfig(s, b'float1', b'42')
784 >>> u.setconfig(s, b'float1', b'42')
786 >>> u.configwith(float, s, b'float1')
785 >>> u.configwith(float, s, b'float1')
787 42.0
786 42.0
788 >>> u.setconfig(s, b'float2', b'-4.25')
787 >>> u.setconfig(s, b'float2', b'-4.25')
789 >>> u.configwith(float, s, b'float2')
788 >>> u.configwith(float, s, b'float2')
790 -4.25
789 -4.25
791 >>> u.configwith(float, s, b'unknown', 7)
790 >>> u.configwith(float, s, b'unknown', 7)
792 7.0
791 7.0
793 >>> u.setconfig(s, b'invalid', b'somevalue')
792 >>> u.setconfig(s, b'invalid', b'somevalue')
794 >>> u.configwith(float, s, b'invalid')
793 >>> u.configwith(float, s, b'invalid')
795 Traceback (most recent call last):
794 Traceback (most recent call last):
796 ...
795 ...
797 ConfigError: foo.invalid is not a valid float ('somevalue')
796 ConfigError: foo.invalid is not a valid float ('somevalue')
798 >>> u.configwith(float, s, b'invalid', desc=b'womble')
797 >>> u.configwith(float, s, b'invalid', desc=b'womble')
799 Traceback (most recent call last):
798 Traceback (most recent call last):
800 ...
799 ...
801 ConfigError: foo.invalid is not a valid womble ('somevalue')
800 ConfigError: foo.invalid is not a valid womble ('somevalue')
802 """
801 """
803
802
804 v = self.config(section, name, default, untrusted)
803 v = self.config(section, name, default, untrusted)
805 if v is None:
804 if v is None:
806 return v # do not attempt to convert None
805 return v # do not attempt to convert None
807 try:
806 try:
808 return convert(v)
807 return convert(v)
809 except (ValueError, error.ParseError):
808 except (ValueError, error.ParseError):
810 if desc is None:
809 if desc is None:
811 desc = pycompat.sysbytes(convert.__name__)
810 desc = pycompat.sysbytes(convert.__name__)
812 raise error.ConfigError(
811 raise error.ConfigError(
813 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
812 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
814 )
813 )
815
814
816 def configint(self, section, name, default=_unset, untrusted=False):
815 def configint(self, section, name, default=_unset, untrusted=False):
817 """parse a configuration element as an integer
816 """parse a configuration element as an integer
818
817
819 >>> u = ui(); s = b'foo'
818 >>> u = ui(); s = b'foo'
820 >>> u.setconfig(s, b'int1', b'42')
819 >>> u.setconfig(s, b'int1', b'42')
821 >>> u.configint(s, b'int1')
820 >>> u.configint(s, b'int1')
822 42
821 42
823 >>> u.setconfig(s, b'int2', b'-42')
822 >>> u.setconfig(s, b'int2', b'-42')
824 >>> u.configint(s, b'int2')
823 >>> u.configint(s, b'int2')
825 -42
824 -42
826 >>> u.configint(s, b'unknown', 7)
825 >>> u.configint(s, b'unknown', 7)
827 7
826 7
828 >>> u.setconfig(s, b'invalid', b'somevalue')
827 >>> u.setconfig(s, b'invalid', b'somevalue')
829 >>> u.configint(s, b'invalid')
828 >>> u.configint(s, b'invalid')
830 Traceback (most recent call last):
829 Traceback (most recent call last):
831 ...
830 ...
832 ConfigError: foo.invalid is not a valid integer ('somevalue')
831 ConfigError: foo.invalid is not a valid integer ('somevalue')
833 """
832 """
834
833
835 return self.configwith(
834 return self.configwith(
836 int, section, name, default, b'integer', untrusted
835 int, section, name, default, b'integer', untrusted
837 )
836 )
838
837
839 def configbytes(self, section, name, default=_unset, untrusted=False):
838 def configbytes(self, section, name, default=_unset, untrusted=False):
840 """parse a configuration element as a quantity in bytes
839 """parse a configuration element as a quantity in bytes
841
840
842 Units can be specified as b (bytes), k or kb (kilobytes), m or
841 Units can be specified as b (bytes), k or kb (kilobytes), m or
843 mb (megabytes), g or gb (gigabytes).
842 mb (megabytes), g or gb (gigabytes).
844
843
845 >>> u = ui(); s = b'foo'
844 >>> u = ui(); s = b'foo'
846 >>> u.setconfig(s, b'val1', b'42')
845 >>> u.setconfig(s, b'val1', b'42')
847 >>> u.configbytes(s, b'val1')
846 >>> u.configbytes(s, b'val1')
848 42
847 42
849 >>> u.setconfig(s, b'val2', b'42.5 kb')
848 >>> u.setconfig(s, b'val2', b'42.5 kb')
850 >>> u.configbytes(s, b'val2')
849 >>> u.configbytes(s, b'val2')
851 43520
850 43520
852 >>> u.configbytes(s, b'unknown', b'7 MB')
851 >>> u.configbytes(s, b'unknown', b'7 MB')
853 7340032
852 7340032
854 >>> u.setconfig(s, b'invalid', b'somevalue')
853 >>> u.setconfig(s, b'invalid', b'somevalue')
855 >>> u.configbytes(s, b'invalid')
854 >>> u.configbytes(s, b'invalid')
856 Traceback (most recent call last):
855 Traceback (most recent call last):
857 ...
856 ...
858 ConfigError: foo.invalid is not a byte quantity ('somevalue')
857 ConfigError: foo.invalid is not a byte quantity ('somevalue')
859 """
858 """
860
859
861 value = self._config(section, name, default, untrusted)
860 value = self._config(section, name, default, untrusted)
862 if value is _unset:
861 if value is _unset:
863 if default is _unset:
862 if default is _unset:
864 default = 0
863 default = 0
865 value = default
864 value = default
866 if not isinstance(value, bytes):
865 if not isinstance(value, bytes):
867 return value
866 return value
868 try:
867 try:
869 return util.sizetoint(value)
868 return util.sizetoint(value)
870 except error.ParseError:
869 except error.ParseError:
871 raise error.ConfigError(
870 raise error.ConfigError(
872 _(b"%s.%s is not a byte quantity ('%s')")
871 _(b"%s.%s is not a byte quantity ('%s')")
873 % (section, name, value)
872 % (section, name, value)
874 )
873 )
875
874
876 def configlist(self, section, name, default=_unset, untrusted=False):
875 def configlist(self, section, name, default=_unset, untrusted=False):
877 """parse a configuration element as a list of comma/space separated
876 """parse a configuration element as a list of comma/space separated
878 strings
877 strings
879
878
880 >>> u = ui(); s = b'foo'
879 >>> u = ui(); s = b'foo'
881 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
880 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
882 >>> u.configlist(s, b'list1')
881 >>> u.configlist(s, b'list1')
883 ['this', 'is', 'a small', 'test']
882 ['this', 'is', 'a small', 'test']
884 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
883 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
885 >>> u.configlist(s, b'list2')
884 >>> u.configlist(s, b'list2')
886 ['this', 'is', 'a small', 'test']
885 ['this', 'is', 'a small', 'test']
887 """
886 """
888 # default is not always a list
887 # default is not always a list
889 v = self.configwith(
888 v = self.configwith(
890 config.parselist, section, name, default, b'list', untrusted
889 config.parselist, section, name, default, b'list', untrusted
891 )
890 )
892 if isinstance(v, bytes):
891 if isinstance(v, bytes):
893 return config.parselist(v)
892 return config.parselist(v)
894 elif v is None:
893 elif v is None:
895 return []
894 return []
896 return v
895 return v
897
896
898 def configdate(self, section, name, default=_unset, untrusted=False):
897 def configdate(self, section, name, default=_unset, untrusted=False):
899 """parse a configuration element as a tuple of ints
898 """parse a configuration element as a tuple of ints
900
899
901 >>> u = ui(); s = b'foo'
900 >>> u = ui(); s = b'foo'
902 >>> u.setconfig(s, b'date', b'0 0')
901 >>> u.setconfig(s, b'date', b'0 0')
903 >>> u.configdate(s, b'date')
902 >>> u.configdate(s, b'date')
904 (0, 0)
903 (0, 0)
905 """
904 """
906 if self.config(section, name, default, untrusted):
905 if self.config(section, name, default, untrusted):
907 return self.configwith(
906 return self.configwith(
908 dateutil.parsedate, section, name, default, b'date', untrusted
907 dateutil.parsedate, section, name, default, b'date', untrusted
909 )
908 )
910 if default is _unset:
909 if default is _unset:
911 return None
910 return None
912 return default
911 return default
913
912
914 def configdefault(self, section, name):
913 def configdefault(self, section, name):
915 """returns the default value of the config item"""
914 """returns the default value of the config item"""
916 item = self._knownconfig.get(section, {}).get(name)
915 item = self._knownconfig.get(section, {}).get(name)
917 itemdefault = None
916 itemdefault = None
918 if item is not None:
917 if item is not None:
919 if callable(item.default):
918 if callable(item.default):
920 itemdefault = item.default()
919 itemdefault = item.default()
921 else:
920 else:
922 itemdefault = item.default
921 itemdefault = item.default
923 return itemdefault
922 return itemdefault
924
923
925 def hasconfig(self, section, name, untrusted=False):
924 def hasconfig(self, section, name, untrusted=False):
926 return self._data(untrusted).hasitem(section, name)
925 return self._data(untrusted).hasitem(section, name)
927
926
928 def has_section(self, section, untrusted=False):
927 def has_section(self, section, untrusted=False):
929 '''tell whether section exists in config.'''
928 '''tell whether section exists in config.'''
930 return section in self._data(untrusted)
929 return section in self._data(untrusted)
931
930
932 def configitems(self, section, untrusted=False, ignoresub=False):
931 def configitems(self, section, untrusted=False, ignoresub=False):
933 items = self._data(untrusted).items(section)
932 items = self._data(untrusted).items(section)
934 if ignoresub:
933 if ignoresub:
935 items = [i for i in items if b':' not in i[0]]
934 items = [i for i in items if b':' not in i[0]]
936 if self.debugflag and not untrusted and self._reportuntrusted:
935 if self.debugflag and not untrusted and self._reportuntrusted:
937 for k, v in self._ucfg.items(section):
936 for k, v in self._ucfg.items(section):
938 if self._tcfg.get(section, k) != v:
937 if self._tcfg.get(section, k) != v:
939 self.debug(
938 self.debug(
940 b"ignoring untrusted configuration option "
939 b"ignoring untrusted configuration option "
941 b"%s.%s = %s\n" % (section, k, v)
940 b"%s.%s = %s\n" % (section, k, v)
942 )
941 )
943 return items
942 return items
944
943
945 def walkconfig(self, untrusted=False):
944 def walkconfig(self, untrusted=False):
946 cfg = self._data(untrusted)
945 cfg = self._data(untrusted)
947 for section in cfg.sections():
946 for section in cfg.sections():
948 for name, value in self.configitems(section, untrusted):
947 for name, value in self.configitems(section, untrusted):
949 yield section, name, value
948 yield section, name, value
950
949
951 def plain(self, feature=None):
950 def plain(self, feature=None):
952 """is plain mode active?
951 """is plain mode active?
953
952
954 Plain mode means that all configuration variables which affect
953 Plain mode means that all configuration variables which affect
955 the behavior and output of Mercurial should be
954 the behavior and output of Mercurial should be
956 ignored. Additionally, the output should be stable,
955 ignored. Additionally, the output should be stable,
957 reproducible and suitable for use in scripts or applications.
956 reproducible and suitable for use in scripts or applications.
958
957
959 The only way to trigger plain mode is by setting either the
958 The only way to trigger plain mode is by setting either the
960 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
959 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
961
960
962 The return value can either be
961 The return value can either be
963 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
962 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
964 - False if feature is disabled by default and not included in HGPLAIN
963 - False if feature is disabled by default and not included in HGPLAIN
965 - True otherwise
964 - True otherwise
966 """
965 """
967 if (
966 if (
968 b'HGPLAIN' not in encoding.environ
967 b'HGPLAIN' not in encoding.environ
969 and b'HGPLAINEXCEPT' not in encoding.environ
968 and b'HGPLAINEXCEPT' not in encoding.environ
970 ):
969 ):
971 return False
970 return False
972 exceptions = (
971 exceptions = (
973 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
972 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
974 )
973 )
975 # TODO: add support for HGPLAIN=+feature,-feature syntax
974 # TODO: add support for HGPLAIN=+feature,-feature syntax
976 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
975 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
977 b','
976 b','
978 ):
977 ):
979 exceptions.append(b'strictflags')
978 exceptions.append(b'strictflags')
980 if feature and exceptions:
979 if feature and exceptions:
981 return feature not in exceptions
980 return feature not in exceptions
982 return True
981 return True
983
982
984 def username(self, acceptempty=False):
983 def username(self, acceptempty=False):
985 """Return default username to be used in commits.
984 """Return default username to be used in commits.
986
985
987 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
986 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
988 and stop searching if one of these is set.
987 and stop searching if one of these is set.
989 If not found and acceptempty is True, returns None.
988 If not found and acceptempty is True, returns None.
990 If not found and ui.askusername is True, ask the user, else use
989 If not found and ui.askusername is True, ask the user, else use
991 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
990 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
992 If no username could be found, raise an Abort error.
991 If no username could be found, raise an Abort error.
993 """
992 """
994 user = encoding.environ.get(b"HGUSER")
993 user = encoding.environ.get(b"HGUSER")
995 if user is None:
994 if user is None:
996 user = self.config(b"ui", b"username")
995 user = self.config(b"ui", b"username")
997 if user is not None:
996 if user is not None:
998 user = os.path.expandvars(user)
997 user = os.path.expandvars(user)
999 if user is None:
998 if user is None:
1000 user = encoding.environ.get(b"EMAIL")
999 user = encoding.environ.get(b"EMAIL")
1001 if user is None and acceptempty:
1000 if user is None and acceptempty:
1002 return user
1001 return user
1003 if user is None and self.configbool(b"ui", b"askusername"):
1002 if user is None and self.configbool(b"ui", b"askusername"):
1004 user = self.prompt(_(b"enter a commit username:"), default=None)
1003 user = self.prompt(_(b"enter a commit username:"), default=None)
1005 if user is None and not self.interactive():
1004 if user is None and not self.interactive():
1006 try:
1005 try:
1007 user = b'%s@%s' % (
1006 user = b'%s@%s' % (
1008 procutil.getuser(),
1007 procutil.getuser(),
1009 encoding.strtolocal(socket.getfqdn()),
1008 encoding.strtolocal(socket.getfqdn()),
1010 )
1009 )
1011 self.warn(_(b"no username found, using '%s' instead\n") % user)
1010 self.warn(_(b"no username found, using '%s' instead\n") % user)
1012 except KeyError:
1011 except KeyError:
1013 pass
1012 pass
1014 if not user:
1013 if not user:
1015 raise error.Abort(
1014 raise error.Abort(
1016 _(b'no username supplied'),
1015 _(b'no username supplied'),
1017 hint=_(b"use 'hg config --edit' " b'to set your username'),
1016 hint=_(b"use 'hg config --edit' " b'to set your username'),
1018 )
1017 )
1019 if b"\n" in user:
1018 if b"\n" in user:
1020 raise error.Abort(
1019 raise error.Abort(
1021 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
1020 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
1022 )
1021 )
1023 return user
1022 return user
1024
1023
1025 def shortuser(self, user):
1024 def shortuser(self, user):
1026 """Return a short representation of a user name or email address."""
1025 """Return a short representation of a user name or email address."""
1027 if not self.verbose:
1026 if not self.verbose:
1028 user = stringutil.shortuser(user)
1027 user = stringutil.shortuser(user)
1029 return user
1028 return user
1030
1029
1031 def expandpath(self, loc, default=None):
1030 def expandpath(self, loc, default=None):
1032 """Return repository location relative to cwd or from [paths]"""
1031 """Return repository location relative to cwd or from [paths]"""
1033 msg = b'ui.expandpath is deprecated, use `get_*` functions from urlutil'
1032 msg = b'ui.expandpath is deprecated, use `get_*` functions from urlutil'
1034 self.deprecwarn(msg, b'6.0')
1033 self.deprecwarn(msg, b'6.0')
1035 try:
1034 try:
1036 p = self.getpath(loc)
1035 p = self.getpath(loc)
1037 if p:
1036 if p:
1038 return p.rawloc
1037 return p.rawloc
1039 except error.RepoError:
1038 except error.RepoError:
1040 pass
1039 pass
1041
1040
1042 if default:
1041 if default:
1043 try:
1042 try:
1044 p = self.getpath(default)
1043 p = self.getpath(default)
1045 if p:
1044 if p:
1046 return p.rawloc
1045 return p.rawloc
1047 except error.RepoError:
1046 except error.RepoError:
1048 pass
1047 pass
1049
1048
1050 return loc
1049 return loc
1051
1050
1052 @util.propertycache
1051 @util.propertycache
1053 def paths(self):
1052 def paths(self):
1054 return urlutil.paths(self)
1053 return urlutil.paths(self)
1055
1054
1056 def getpath(self, *args, **kwargs):
1055 def getpath(self, *args, **kwargs):
1057 """see paths.getpath for details
1056 """see paths.getpath for details
1058
1057
1059 This method exist as `getpath` need a ui for potential warning message.
1058 This method exist as `getpath` need a ui for potential warning message.
1060 """
1059 """
1061 return self.paths.getpath(self, *args, **kwargs)
1060 return self.paths.getpath(self, *args, **kwargs)
1062
1061
1063 @property
1062 @property
1064 def fout(self):
1063 def fout(self):
1065 return self._fout
1064 return self._fout
1066
1065
1067 @fout.setter
1066 @fout.setter
1068 def fout(self, f):
1067 def fout(self, f):
1069 self._fout = f
1068 self._fout = f
1070 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1069 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1071
1070
1072 @property
1071 @property
1073 def ferr(self):
1072 def ferr(self):
1074 return self._ferr
1073 return self._ferr
1075
1074
1076 @ferr.setter
1075 @ferr.setter
1077 def ferr(self, f):
1076 def ferr(self, f):
1078 self._ferr = f
1077 self._ferr = f
1079 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1078 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1080
1079
1081 @property
1080 @property
1082 def fin(self):
1081 def fin(self):
1083 return self._fin
1082 return self._fin
1084
1083
1085 @fin.setter
1084 @fin.setter
1086 def fin(self, f):
1085 def fin(self, f):
1087 self._fin = f
1086 self._fin = f
1088
1087
1089 @property
1088 @property
1090 def fmsg(self):
1089 def fmsg(self):
1091 """Stream dedicated for status/error messages; may be None if
1090 """Stream dedicated for status/error messages; may be None if
1092 fout/ferr are used"""
1091 fout/ferr are used"""
1093 return self._fmsg
1092 return self._fmsg
1094
1093
1095 @fmsg.setter
1094 @fmsg.setter
1096 def fmsg(self, f):
1095 def fmsg(self, f):
1097 self._fmsg = f
1096 self._fmsg = f
1098 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1097 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1099
1098
1100 def pushbuffer(self, error=False, subproc=False, labeled=False):
1099 def pushbuffer(self, error=False, subproc=False, labeled=False):
1101 """install a buffer to capture standard output of the ui object
1100 """install a buffer to capture standard output of the ui object
1102
1101
1103 If error is True, the error output will be captured too.
1102 If error is True, the error output will be captured too.
1104
1103
1105 If subproc is True, output from subprocesses (typically hooks) will be
1104 If subproc is True, output from subprocesses (typically hooks) will be
1106 captured too.
1105 captured too.
1107
1106
1108 If labeled is True, any labels associated with buffered
1107 If labeled is True, any labels associated with buffered
1109 output will be handled. By default, this has no effect
1108 output will be handled. By default, this has no effect
1110 on the output returned, but extensions and GUI tools may
1109 on the output returned, but extensions and GUI tools may
1111 handle this argument and returned styled output. If output
1110 handle this argument and returned styled output. If output
1112 is being buffered so it can be captured and parsed or
1111 is being buffered so it can be captured and parsed or
1113 processed, labeled should not be set to True.
1112 processed, labeled should not be set to True.
1114 """
1113 """
1115 self._buffers.append([])
1114 self._buffers.append([])
1116 self._bufferstates.append((error, subproc, labeled))
1115 self._bufferstates.append((error, subproc, labeled))
1117 self._bufferapplylabels = labeled
1116 self._bufferapplylabels = labeled
1118
1117
1119 def popbuffer(self):
1118 def popbuffer(self):
1120 '''pop the last buffer and return the buffered output'''
1119 '''pop the last buffer and return the buffered output'''
1121 self._bufferstates.pop()
1120 self._bufferstates.pop()
1122 if self._bufferstates:
1121 if self._bufferstates:
1123 self._bufferapplylabels = self._bufferstates[-1][2]
1122 self._bufferapplylabels = self._bufferstates[-1][2]
1124 else:
1123 else:
1125 self._bufferapplylabels = None
1124 self._bufferapplylabels = None
1126
1125
1127 return b"".join(self._buffers.pop())
1126 return b"".join(self._buffers.pop())
1128
1127
1129 def _isbuffered(self, dest):
1128 def _isbuffered(self, dest):
1130 if dest is self._fout:
1129 if dest is self._fout:
1131 return bool(self._buffers)
1130 return bool(self._buffers)
1132 if dest is self._ferr:
1131 if dest is self._ferr:
1133 return bool(self._bufferstates and self._bufferstates[-1][0])
1132 return bool(self._bufferstates and self._bufferstates[-1][0])
1134 return False
1133 return False
1135
1134
1136 def canwritewithoutlabels(self):
1135 def canwritewithoutlabels(self):
1137 '''check if write skips the label'''
1136 '''check if write skips the label'''
1138 if self._buffers and not self._bufferapplylabels:
1137 if self._buffers and not self._bufferapplylabels:
1139 return True
1138 return True
1140 return self._colormode is None
1139 return self._colormode is None
1141
1140
1142 def canbatchlabeledwrites(self):
1141 def canbatchlabeledwrites(self):
1143 '''check if write calls with labels are batchable'''
1142 '''check if write calls with labels are batchable'''
1144 # Windows color printing is special, see ``write``.
1143 # Windows color printing is special, see ``write``.
1145 return self._colormode != b'win32'
1144 return self._colormode != b'win32'
1146
1145
1147 def write(self, *args, **opts):
1146 def write(self, *args, **opts):
1148 """write args to output
1147 """write args to output
1149
1148
1150 By default, this method simply writes to the buffer or stdout.
1149 By default, this method simply writes to the buffer or stdout.
1151 Color mode can be set on the UI class to have the output decorated
1150 Color mode can be set on the UI class to have the output decorated
1152 with color modifier before being written to stdout.
1151 with color modifier before being written to stdout.
1153
1152
1154 The color used is controlled by an optional keyword argument, "label".
1153 The color used is controlled by an optional keyword argument, "label".
1155 This should be a string containing label names separated by space.
1154 This should be a string containing label names separated by space.
1156 Label names take the form of "topic.type". For example, ui.debug()
1155 Label names take the form of "topic.type". For example, ui.debug()
1157 issues a label of "ui.debug".
1156 issues a label of "ui.debug".
1158
1157
1159 Progress reports via stderr are normally cleared before writing as
1158 Progress reports via stderr are normally cleared before writing as
1160 stdout and stderr go to the same terminal. This can be skipped with
1159 stdout and stderr go to the same terminal. This can be skipped with
1161 the optional keyword argument "keepprogressbar". The progress bar
1160 the optional keyword argument "keepprogressbar". The progress bar
1162 will continue to occupy a partial line on stderr in that case.
1161 will continue to occupy a partial line on stderr in that case.
1163 This functionality is intended when Mercurial acts as data source
1162 This functionality is intended when Mercurial acts as data source
1164 in a pipe.
1163 in a pipe.
1165
1164
1166 When labeling output for a specific command, a label of
1165 When labeling output for a specific command, a label of
1167 "cmdname.type" is recommended. For example, status issues
1166 "cmdname.type" is recommended. For example, status issues
1168 a label of "status.modified" for modified files.
1167 a label of "status.modified" for modified files.
1169 """
1168 """
1170 dest = self._fout
1169 dest = self._fout
1171
1170
1172 # inlined _write() for speed
1171 # inlined _write() for speed
1173 if self._buffers:
1172 if self._buffers:
1174 label = opts.get('label', b'')
1173 label = opts.get('label', b'')
1175 if label and self._bufferapplylabels:
1174 if label and self._bufferapplylabels:
1176 self._buffers[-1].extend(self.label(a, label) for a in args)
1175 self._buffers[-1].extend(self.label(a, label) for a in args)
1177 else:
1176 else:
1178 self._buffers[-1].extend(args)
1177 self._buffers[-1].extend(args)
1179 return
1178 return
1180
1179
1181 # inlined _writenobuf() for speed
1180 # inlined _writenobuf() for speed
1182 if not opts.get('keepprogressbar', False):
1181 if not opts.get('keepprogressbar', False):
1183 self._progclear()
1182 self._progclear()
1184 msg = b''.join(args)
1183 msg = b''.join(args)
1185
1184
1186 # opencode timeblockedsection because this is a critical path
1185 # opencode timeblockedsection because this is a critical path
1187 starttime = util.timer()
1186 starttime = util.timer()
1188 try:
1187 try:
1189 if self._colormode == b'win32':
1188 if self._colormode == b'win32':
1190 # windows color printing is its own can of crab, defer to
1189 # windows color printing is its own can of crab, defer to
1191 # the color module and that is it.
1190 # the color module and that is it.
1192 color.win32print(self, dest.write, msg, **opts)
1191 color.win32print(self, dest.write, msg, **opts)
1193 else:
1192 else:
1194 if self._colormode is not None:
1193 if self._colormode is not None:
1195 label = opts.get('label', b'')
1194 label = opts.get('label', b'')
1196 msg = self.label(msg, label)
1195 msg = self.label(msg, label)
1197 dest.write(msg)
1196 dest.write(msg)
1198 except IOError as err:
1197 except IOError as err:
1199 raise error.StdioError(err)
1198 raise error.StdioError(err)
1200 finally:
1199 finally:
1201 self._blockedtimes[b'stdio_blocked'] += (
1200 self._blockedtimes[b'stdio_blocked'] += (
1202 util.timer() - starttime
1201 util.timer() - starttime
1203 ) * 1000
1202 ) * 1000
1204
1203
1205 def write_err(self, *args, **opts):
1204 def write_err(self, *args, **opts):
1206 self._write(self._ferr, *args, **opts)
1205 self._write(self._ferr, *args, **opts)
1207
1206
1208 def _write(self, dest, *args, **opts):
1207 def _write(self, dest, *args, **opts):
1209 # update write() as well if you touch this code
1208 # update write() as well if you touch this code
1210 if self._isbuffered(dest):
1209 if self._isbuffered(dest):
1211 label = opts.get('label', b'')
1210 label = opts.get('label', b'')
1212 if label and self._bufferapplylabels:
1211 if label and self._bufferapplylabels:
1213 self._buffers[-1].extend(self.label(a, label) for a in args)
1212 self._buffers[-1].extend(self.label(a, label) for a in args)
1214 else:
1213 else:
1215 self._buffers[-1].extend(args)
1214 self._buffers[-1].extend(args)
1216 else:
1215 else:
1217 self._writenobuf(dest, *args, **opts)
1216 self._writenobuf(dest, *args, **opts)
1218
1217
1219 def _writenobuf(self, dest, *args, **opts):
1218 def _writenobuf(self, dest, *args, **opts):
1220 # update write() as well if you touch this code
1219 # update write() as well if you touch this code
1221 if not opts.get('keepprogressbar', False):
1220 if not opts.get('keepprogressbar', False):
1222 self._progclear()
1221 self._progclear()
1223 msg = b''.join(args)
1222 msg = b''.join(args)
1224
1223
1225 # opencode timeblockedsection because this is a critical path
1224 # opencode timeblockedsection because this is a critical path
1226 starttime = util.timer()
1225 starttime = util.timer()
1227 try:
1226 try:
1228 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1227 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1229 self._fout.flush()
1228 self._fout.flush()
1230 if getattr(dest, 'structured', False):
1229 if getattr(dest, 'structured', False):
1231 # channel for machine-readable output with metadata, where
1230 # channel for machine-readable output with metadata, where
1232 # no extra colorization is necessary.
1231 # no extra colorization is necessary.
1233 dest.write(msg, **opts)
1232 dest.write(msg, **opts)
1234 elif self._colormode == b'win32':
1233 elif self._colormode == b'win32':
1235 # windows color printing is its own can of crab, defer to
1234 # windows color printing is its own can of crab, defer to
1236 # the color module and that is it.
1235 # the color module and that is it.
1237 color.win32print(self, dest.write, msg, **opts)
1236 color.win32print(self, dest.write, msg, **opts)
1238 else:
1237 else:
1239 if self._colormode is not None:
1238 if self._colormode is not None:
1240 label = opts.get('label', b'')
1239 label = opts.get('label', b'')
1241 msg = self.label(msg, label)
1240 msg = self.label(msg, label)
1242 dest.write(msg)
1241 dest.write(msg)
1243 # stderr may be buffered under win32 when redirected to files,
1242 # stderr may be buffered under win32 when redirected to files,
1244 # including stdout.
1243 # including stdout.
1245 if dest is self._ferr and not getattr(dest, 'closed', False):
1244 if dest is self._ferr and not getattr(dest, 'closed', False):
1246 dest.flush()
1245 dest.flush()
1247 except IOError as err:
1246 except IOError as err:
1248 if dest is self._ferr and err.errno in (
1247 if dest is self._ferr and err.errno in (
1249 errno.EPIPE,
1248 errno.EPIPE,
1250 errno.EIO,
1249 errno.EIO,
1251 errno.EBADF,
1250 errno.EBADF,
1252 ):
1251 ):
1253 # no way to report the error, so ignore it
1252 # no way to report the error, so ignore it
1254 return
1253 return
1255 raise error.StdioError(err)
1254 raise error.StdioError(err)
1256 finally:
1255 finally:
1257 self._blockedtimes[b'stdio_blocked'] += (
1256 self._blockedtimes[b'stdio_blocked'] += (
1258 util.timer() - starttime
1257 util.timer() - starttime
1259 ) * 1000
1258 ) * 1000
1260
1259
1261 def _writemsg(self, dest, *args, **opts):
1260 def _writemsg(self, dest, *args, **opts):
1262 timestamp = self.showtimestamp and opts.get('type') in {
1261 timestamp = self.showtimestamp and opts.get('type') in {
1263 b'debug',
1262 b'debug',
1264 b'error',
1263 b'error',
1265 b'note',
1264 b'note',
1266 b'status',
1265 b'status',
1267 b'warning',
1266 b'warning',
1268 }
1267 }
1269 if timestamp:
1268 if timestamp:
1270 args = (
1269 args = (
1271 b'[%s] '
1270 b'[%s] '
1272 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1271 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1273 ) + args
1272 ) + args
1274 _writemsgwith(self._write, dest, *args, **opts)
1273 _writemsgwith(self._write, dest, *args, **opts)
1275 if timestamp:
1274 if timestamp:
1276 dest.flush()
1275 dest.flush()
1277
1276
1278 def _writemsgnobuf(self, dest, *args, **opts):
1277 def _writemsgnobuf(self, dest, *args, **opts):
1279 _writemsgwith(self._writenobuf, dest, *args, **opts)
1278 _writemsgwith(self._writenobuf, dest, *args, **opts)
1280
1279
1281 def flush(self):
1280 def flush(self):
1282 # opencode timeblockedsection because this is a critical path
1281 # opencode timeblockedsection because this is a critical path
1283 starttime = util.timer()
1282 starttime = util.timer()
1284 try:
1283 try:
1285 try:
1284 try:
1286 self._fout.flush()
1285 self._fout.flush()
1287 except IOError as err:
1286 except IOError as err:
1288 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1287 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1289 raise error.StdioError(err)
1288 raise error.StdioError(err)
1290 finally:
1289 finally:
1291 try:
1290 try:
1292 self._ferr.flush()
1291 self._ferr.flush()
1293 except IOError as err:
1292 except IOError as err:
1294 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1293 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1295 raise error.StdioError(err)
1294 raise error.StdioError(err)
1296 finally:
1295 finally:
1297 self._blockedtimes[b'stdio_blocked'] += (
1296 self._blockedtimes[b'stdio_blocked'] += (
1298 util.timer() - starttime
1297 util.timer() - starttime
1299 ) * 1000
1298 ) * 1000
1300
1299
1301 def _isatty(self, fh):
1300 def _isatty(self, fh):
1302 if self.configbool(b'ui', b'nontty'):
1301 if self.configbool(b'ui', b'nontty'):
1303 return False
1302 return False
1304 return procutil.isatty(fh)
1303 return procutil.isatty(fh)
1305
1304
1306 def protectfinout(self):
1305 def protectfinout(self):
1307 """Duplicate ui streams and redirect original if they are stdio
1306 """Duplicate ui streams and redirect original if they are stdio
1308
1307
1309 Returns (fin, fout) which point to the original ui fds, but may be
1308 Returns (fin, fout) which point to the original ui fds, but may be
1310 copy of them. The returned streams can be considered "owned" in that
1309 copy of them. The returned streams can be considered "owned" in that
1311 print(), exec(), etc. never reach to them.
1310 print(), exec(), etc. never reach to them.
1312 """
1311 """
1313 if self._finoutredirected:
1312 if self._finoutredirected:
1314 # if already redirected, protectstdio() would just create another
1313 # if already redirected, protectstdio() would just create another
1315 # nullfd pair, which is equivalent to returning self._fin/_fout.
1314 # nullfd pair, which is equivalent to returning self._fin/_fout.
1316 return self._fin, self._fout
1315 return self._fin, self._fout
1317 fin, fout = procutil.protectstdio(self._fin, self._fout)
1316 fin, fout = procutil.protectstdio(self._fin, self._fout)
1318 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1317 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1319 return fin, fout
1318 return fin, fout
1320
1319
1321 def restorefinout(self, fin, fout):
1320 def restorefinout(self, fin, fout):
1322 """Restore ui streams from possibly duplicated (fin, fout)"""
1321 """Restore ui streams from possibly duplicated (fin, fout)"""
1323 if (fin, fout) == (self._fin, self._fout):
1322 if (fin, fout) == (self._fin, self._fout):
1324 return
1323 return
1325 procutil.restorestdio(self._fin, self._fout, fin, fout)
1324 procutil.restorestdio(self._fin, self._fout, fin, fout)
1326 # protectfinout() won't create more than one duplicated streams,
1325 # protectfinout() won't create more than one duplicated streams,
1327 # so we can just turn the redirection flag off.
1326 # so we can just turn the redirection flag off.
1328 self._finoutredirected = False
1327 self._finoutredirected = False
1329
1328
1330 @contextlib.contextmanager
1329 @contextlib.contextmanager
1331 def protectedfinout(self):
1330 def protectedfinout(self):
1332 """Run code block with protected standard streams"""
1331 """Run code block with protected standard streams"""
1333 fin, fout = self.protectfinout()
1332 fin, fout = self.protectfinout()
1334 try:
1333 try:
1335 yield fin, fout
1334 yield fin, fout
1336 finally:
1335 finally:
1337 self.restorefinout(fin, fout)
1336 self.restorefinout(fin, fout)
1338
1337
1339 def disablepager(self):
1338 def disablepager(self):
1340 self._disablepager = True
1339 self._disablepager = True
1341
1340
1342 def pager(self, command):
1341 def pager(self, command):
1343 """Start a pager for subsequent command output.
1342 """Start a pager for subsequent command output.
1344
1343
1345 Commands which produce a long stream of output should call
1344 Commands which produce a long stream of output should call
1346 this function to activate the user's preferred pagination
1345 this function to activate the user's preferred pagination
1347 mechanism (which may be no pager). Calling this function
1346 mechanism (which may be no pager). Calling this function
1348 precludes any future use of interactive functionality, such as
1347 precludes any future use of interactive functionality, such as
1349 prompting the user or activating curses.
1348 prompting the user or activating curses.
1350
1349
1351 Args:
1350 Args:
1352 command: The full, non-aliased name of the command. That is, "log"
1351 command: The full, non-aliased name of the command. That is, "log"
1353 not "history, "summary" not "summ", etc.
1352 not "history, "summary" not "summ", etc.
1354 """
1353 """
1355 if self._disablepager or self.pageractive:
1354 if self._disablepager or self.pageractive:
1356 # how pager should do is already determined
1355 # how pager should do is already determined
1357 return
1356 return
1358
1357
1359 if not command.startswith(b'internal-always-') and (
1358 if not command.startswith(b'internal-always-') and (
1360 # explicit --pager=on (= 'internal-always-' prefix) should
1359 # explicit --pager=on (= 'internal-always-' prefix) should
1361 # take precedence over disabling factors below
1360 # take precedence over disabling factors below
1362 command in self.configlist(b'pager', b'ignore')
1361 command in self.configlist(b'pager', b'ignore')
1363 or not self.configbool(b'ui', b'paginate')
1362 or not self.configbool(b'ui', b'paginate')
1364 or not self.configbool(b'pager', b'attend-' + command, True)
1363 or not self.configbool(b'pager', b'attend-' + command, True)
1365 or encoding.environ.get(b'TERM') == b'dumb'
1364 or encoding.environ.get(b'TERM') == b'dumb'
1366 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1365 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1367 # formatted() will need some adjustment.
1366 # formatted() will need some adjustment.
1368 or not self.formatted()
1367 or not self.formatted()
1369 or self.plain()
1368 or self.plain()
1370 or self._buffers
1369 or self._buffers
1371 # TODO: expose debugger-enabled on the UI object
1370 # TODO: expose debugger-enabled on the UI object
1372 or b'--debugger' in pycompat.sysargv
1371 or b'--debugger' in pycompat.sysargv
1373 ):
1372 ):
1374 # We only want to paginate if the ui appears to be
1373 # We only want to paginate if the ui appears to be
1375 # interactive, the user didn't say HGPLAIN or
1374 # interactive, the user didn't say HGPLAIN or
1376 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1375 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1377 return
1376 return
1378
1377
1379 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1378 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1380 if not pagercmd:
1379 if not pagercmd:
1381 return
1380 return
1382
1381
1383 pagerenv = {}
1382 pagerenv = {}
1384 for name, value in rcutil.defaultpagerenv().items():
1383 for name, value in rcutil.defaultpagerenv().items():
1385 if name not in encoding.environ:
1384 if name not in encoding.environ:
1386 pagerenv[name] = value
1385 pagerenv[name] = value
1387
1386
1388 self.debug(
1387 self.debug(
1389 b'starting pager for command %s\n' % stringutil.pprint(command)
1388 b'starting pager for command %s\n' % stringutil.pprint(command)
1390 )
1389 )
1391 self.flush()
1390 self.flush()
1392
1391
1393 wasformatted = self.formatted()
1392 wasformatted = self.formatted()
1394 if util.safehasattr(signal, b"SIGPIPE"):
1393 if util.safehasattr(signal, b"SIGPIPE"):
1395 signal.signal(signal.SIGPIPE, _catchterm)
1394 signal.signal(signal.SIGPIPE, _catchterm)
1396 if self._runpager(pagercmd, pagerenv):
1395 if self._runpager(pagercmd, pagerenv):
1397 self.pageractive = True
1396 self.pageractive = True
1398 # Preserve the formatted-ness of the UI. This is important
1397 # Preserve the formatted-ness of the UI. This is important
1399 # because we mess with stdout, which might confuse
1398 # because we mess with stdout, which might confuse
1400 # auto-detection of things being formatted.
1399 # auto-detection of things being formatted.
1401 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1400 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1402 self.setconfig(b'ui', b'interactive', False, b'pager')
1401 self.setconfig(b'ui', b'interactive', False, b'pager')
1403
1402
1404 # If pagermode differs from color.mode, reconfigure color now that
1403 # If pagermode differs from color.mode, reconfigure color now that
1405 # pageractive is set.
1404 # pageractive is set.
1406 cm = self._colormode
1405 cm = self._colormode
1407 if cm != self.config(b'color', b'pagermode', cm):
1406 if cm != self.config(b'color', b'pagermode', cm):
1408 color.setup(self)
1407 color.setup(self)
1409 else:
1408 else:
1410 # If the pager can't be spawned in dispatch when --pager=on is
1409 # If the pager can't be spawned in dispatch when --pager=on is
1411 # given, don't try again when the command runs, to avoid a duplicate
1410 # given, don't try again when the command runs, to avoid a duplicate
1412 # warning about a missing pager command.
1411 # warning about a missing pager command.
1413 self.disablepager()
1412 self.disablepager()
1414
1413
1415 def _runpager(self, command, env=None):
1414 def _runpager(self, command, env=None):
1416 """Actually start the pager and set up file descriptors.
1415 """Actually start the pager and set up file descriptors.
1417
1416
1418 This is separate in part so that extensions (like chg) can
1417 This is separate in part so that extensions (like chg) can
1419 override how a pager is invoked.
1418 override how a pager is invoked.
1420 """
1419 """
1421 if command == b'cat':
1420 if command == b'cat':
1422 # Save ourselves some work.
1421 # Save ourselves some work.
1423 return False
1422 return False
1424 # If the command doesn't contain any of these characters, we
1423 # If the command doesn't contain any of these characters, we
1425 # assume it's a binary and exec it directly. This means for
1424 # assume it's a binary and exec it directly. This means for
1426 # simple pager command configurations, we can degrade
1425 # simple pager command configurations, we can degrade
1427 # gracefully and tell the user about their broken pager.
1426 # gracefully and tell the user about their broken pager.
1428 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1427 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1429
1428
1430 if pycompat.iswindows and not shell:
1429 if pycompat.iswindows and not shell:
1431 # Window's built-in `more` cannot be invoked with shell=False, but
1430 # Window's built-in `more` cannot be invoked with shell=False, but
1432 # its `more.com` can. Hide this implementation detail from the
1431 # its `more.com` can. Hide this implementation detail from the
1433 # user so we can also get sane bad PAGER behavior. MSYS has
1432 # user so we can also get sane bad PAGER behavior. MSYS has
1434 # `more.exe`, so do a cmd.exe style resolution of the executable to
1433 # `more.exe`, so do a cmd.exe style resolution of the executable to
1435 # determine which one to use.
1434 # determine which one to use.
1436 fullcmd = procutil.findexe(command)
1435 fullcmd = procutil.findexe(command)
1437 if not fullcmd:
1436 if not fullcmd:
1438 self.warn(
1437 self.warn(
1439 _(b"missing pager command '%s', skipping pager\n") % command
1438 _(b"missing pager command '%s', skipping pager\n") % command
1440 )
1439 )
1441 return False
1440 return False
1442
1441
1443 command = fullcmd
1442 command = fullcmd
1444
1443
1445 try:
1444 try:
1446 pager = subprocess.Popen(
1445 pager = subprocess.Popen(
1447 procutil.tonativestr(command),
1446 procutil.tonativestr(command),
1448 shell=shell,
1447 shell=shell,
1449 bufsize=-1,
1448 bufsize=-1,
1450 close_fds=procutil.closefds,
1449 close_fds=procutil.closefds,
1451 stdin=subprocess.PIPE,
1450 stdin=subprocess.PIPE,
1452 stdout=procutil.stdout,
1451 stdout=procutil.stdout,
1453 stderr=procutil.stderr,
1452 stderr=procutil.stderr,
1454 env=procutil.tonativeenv(procutil.shellenviron(env)),
1453 env=procutil.tonativeenv(procutil.shellenviron(env)),
1455 )
1454 )
1456 except OSError as e:
1455 except OSError as e:
1457 if e.errno == errno.ENOENT and not shell:
1456 if e.errno == errno.ENOENT and not shell:
1458 self.warn(
1457 self.warn(
1459 _(b"missing pager command '%s', skipping pager\n") % command
1458 _(b"missing pager command '%s', skipping pager\n") % command
1460 )
1459 )
1461 return False
1460 return False
1462 raise
1461 raise
1463
1462
1464 # back up original file descriptors
1463 # back up original file descriptors
1465 stdoutfd = os.dup(procutil.stdout.fileno())
1464 stdoutfd = os.dup(procutil.stdout.fileno())
1466 stderrfd = os.dup(procutil.stderr.fileno())
1465 stderrfd = os.dup(procutil.stderr.fileno())
1467
1466
1468 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1467 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1469 if self._isatty(procutil.stderr):
1468 if self._isatty(procutil.stderr):
1470 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1469 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1471
1470
1472 @self.atexit
1471 @self.atexit
1473 def killpager():
1472 def killpager():
1474 if util.safehasattr(signal, b"SIGINT"):
1473 if util.safehasattr(signal, b"SIGINT"):
1475 signal.signal(signal.SIGINT, signal.SIG_IGN)
1474 signal.signal(signal.SIGINT, signal.SIG_IGN)
1476 # restore original fds, closing pager.stdin copies in the process
1475 # restore original fds, closing pager.stdin copies in the process
1477 os.dup2(stdoutfd, procutil.stdout.fileno())
1476 os.dup2(stdoutfd, procutil.stdout.fileno())
1478 os.dup2(stderrfd, procutil.stderr.fileno())
1477 os.dup2(stderrfd, procutil.stderr.fileno())
1479 pager.stdin.close()
1478 pager.stdin.close()
1480 pager.wait()
1479 pager.wait()
1481
1480
1482 return True
1481 return True
1483
1482
1484 @property
1483 @property
1485 def _exithandlers(self):
1484 def _exithandlers(self):
1486 return _reqexithandlers
1485 return _reqexithandlers
1487
1486
1488 def atexit(self, func, *args, **kwargs):
1487 def atexit(self, func, *args, **kwargs):
1489 """register a function to run after dispatching a request
1488 """register a function to run after dispatching a request
1490
1489
1491 Handlers do not stay registered across request boundaries."""
1490 Handlers do not stay registered across request boundaries."""
1492 self._exithandlers.append((func, args, kwargs))
1491 self._exithandlers.append((func, args, kwargs))
1493 return func
1492 return func
1494
1493
1495 def interface(self, feature):
1494 def interface(self, feature):
1496 """what interface to use for interactive console features?
1495 """what interface to use for interactive console features?
1497
1496
1498 The interface is controlled by the value of `ui.interface` but also by
1497 The interface is controlled by the value of `ui.interface` but also by
1499 the value of feature-specific configuration. For example:
1498 the value of feature-specific configuration. For example:
1500
1499
1501 ui.interface.histedit = text
1500 ui.interface.histedit = text
1502 ui.interface.chunkselector = curses
1501 ui.interface.chunkselector = curses
1503
1502
1504 Here the features are "histedit" and "chunkselector".
1503 Here the features are "histedit" and "chunkselector".
1505
1504
1506 The configuration above means that the default interfaces for commands
1505 The configuration above means that the default interfaces for commands
1507 is curses, the interface for histedit is text and the interface for
1506 is curses, the interface for histedit is text and the interface for
1508 selecting chunk is crecord (the best curses interface available).
1507 selecting chunk is crecord (the best curses interface available).
1509
1508
1510 Consider the following example:
1509 Consider the following example:
1511 ui.interface = curses
1510 ui.interface = curses
1512 ui.interface.histedit = text
1511 ui.interface.histedit = text
1513
1512
1514 Then histedit will use the text interface and chunkselector will use
1513 Then histedit will use the text interface and chunkselector will use
1515 the default curses interface (crecord at the moment).
1514 the default curses interface (crecord at the moment).
1516 """
1515 """
1517 alldefaults = frozenset([b"text", b"curses"])
1516 alldefaults = frozenset([b"text", b"curses"])
1518
1517
1519 featureinterfaces = {
1518 featureinterfaces = {
1520 b"chunkselector": [
1519 b"chunkselector": [
1521 b"text",
1520 b"text",
1522 b"curses",
1521 b"curses",
1523 ],
1522 ],
1524 b"histedit": [
1523 b"histedit": [
1525 b"text",
1524 b"text",
1526 b"curses",
1525 b"curses",
1527 ],
1526 ],
1528 }
1527 }
1529
1528
1530 # Feature-specific interface
1529 # Feature-specific interface
1531 if feature not in featureinterfaces.keys():
1530 if feature not in featureinterfaces.keys():
1532 # Programming error, not user error
1531 # Programming error, not user error
1533 raise ValueError(b"Unknown feature requested %s" % feature)
1532 raise ValueError(b"Unknown feature requested %s" % feature)
1534
1533
1535 availableinterfaces = frozenset(featureinterfaces[feature])
1534 availableinterfaces = frozenset(featureinterfaces[feature])
1536 if alldefaults > availableinterfaces:
1535 if alldefaults > availableinterfaces:
1537 # Programming error, not user error. We need a use case to
1536 # Programming error, not user error. We need a use case to
1538 # define the right thing to do here.
1537 # define the right thing to do here.
1539 raise ValueError(
1538 raise ValueError(
1540 b"Feature %s does not handle all default interfaces" % feature
1539 b"Feature %s does not handle all default interfaces" % feature
1541 )
1540 )
1542
1541
1543 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1542 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1544 return b"text"
1543 return b"text"
1545
1544
1546 # Default interface for all the features
1545 # Default interface for all the features
1547 defaultinterface = b"text"
1546 defaultinterface = b"text"
1548 i = self.config(b"ui", b"interface")
1547 i = self.config(b"ui", b"interface")
1549 if i in alldefaults:
1548 if i in alldefaults:
1550 defaultinterface = i
1549 defaultinterface = i
1551
1550
1552 choseninterface = defaultinterface
1551 choseninterface = defaultinterface
1553 f = self.config(b"ui", b"interface.%s" % feature)
1552 f = self.config(b"ui", b"interface.%s" % feature)
1554 if f in availableinterfaces:
1553 if f in availableinterfaces:
1555 choseninterface = f
1554 choseninterface = f
1556
1555
1557 if i is not None and defaultinterface != i:
1556 if i is not None and defaultinterface != i:
1558 if f is not None:
1557 if f is not None:
1559 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1558 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1560 else:
1559 else:
1561 self.warn(
1560 self.warn(
1562 _(b"invalid value for ui.interface: %s (using %s)\n")
1561 _(b"invalid value for ui.interface: %s (using %s)\n")
1563 % (i, choseninterface)
1562 % (i, choseninterface)
1564 )
1563 )
1565 if f is not None and choseninterface != f:
1564 if f is not None and choseninterface != f:
1566 self.warn(
1565 self.warn(
1567 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1566 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1568 % (feature, f, choseninterface)
1567 % (feature, f, choseninterface)
1569 )
1568 )
1570
1569
1571 return choseninterface
1570 return choseninterface
1572
1571
1573 def interactive(self):
1572 def interactive(self):
1574 """is interactive input allowed?
1573 """is interactive input allowed?
1575
1574
1576 An interactive session is a session where input can be reasonably read
1575 An interactive session is a session where input can be reasonably read
1577 from `sys.stdin'. If this function returns false, any attempt to read
1576 from `sys.stdin'. If this function returns false, any attempt to read
1578 from stdin should fail with an error, unless a sensible default has been
1577 from stdin should fail with an error, unless a sensible default has been
1579 specified.
1578 specified.
1580
1579
1581 Interactiveness is triggered by the value of the `ui.interactive'
1580 Interactiveness is triggered by the value of the `ui.interactive'
1582 configuration variable or - if it is unset - when `sys.stdin' points
1581 configuration variable or - if it is unset - when `sys.stdin' points
1583 to a terminal device.
1582 to a terminal device.
1584
1583
1585 This function refers to input only; for output, see `ui.formatted()'.
1584 This function refers to input only; for output, see `ui.formatted()'.
1586 """
1585 """
1587 i = self.configbool(b"ui", b"interactive")
1586 i = self.configbool(b"ui", b"interactive")
1588 if i is None:
1587 if i is None:
1589 # some environments replace stdin without implementing isatty
1588 # some environments replace stdin without implementing isatty
1590 # usually those are non-interactive
1589 # usually those are non-interactive
1591 return self._isatty(self._fin)
1590 return self._isatty(self._fin)
1592
1591
1593 return i
1592 return i
1594
1593
1595 def termwidth(self):
1594 def termwidth(self):
1596 """how wide is the terminal in columns?"""
1595 """how wide is the terminal in columns?"""
1597 if b'COLUMNS' in encoding.environ:
1596 if b'COLUMNS' in encoding.environ:
1598 try:
1597 try:
1599 return int(encoding.environ[b'COLUMNS'])
1598 return int(encoding.environ[b'COLUMNS'])
1600 except ValueError:
1599 except ValueError:
1601 pass
1600 pass
1602 return scmutil.termsize(self)[0]
1601 return scmutil.termsize(self)[0]
1603
1602
1604 def formatted(self):
1603 def formatted(self):
1605 """should formatted output be used?
1604 """should formatted output be used?
1606
1605
1607 It is often desirable to format the output to suite the output medium.
1606 It is often desirable to format the output to suite the output medium.
1608 Examples of this are truncating long lines or colorizing messages.
1607 Examples of this are truncating long lines or colorizing messages.
1609 However, this is not often not desirable when piping output into other
1608 However, this is not often not desirable when piping output into other
1610 utilities, e.g. `grep'.
1609 utilities, e.g. `grep'.
1611
1610
1612 Formatted output is triggered by the value of the `ui.formatted'
1611 Formatted output is triggered by the value of the `ui.formatted'
1613 configuration variable or - if it is unset - when `sys.stdout' points
1612 configuration variable or - if it is unset - when `sys.stdout' points
1614 to a terminal device. Please note that `ui.formatted' should be
1613 to a terminal device. Please note that `ui.formatted' should be
1615 considered an implementation detail; it is not intended for use outside
1614 considered an implementation detail; it is not intended for use outside
1616 Mercurial or its extensions.
1615 Mercurial or its extensions.
1617
1616
1618 This function refers to output only; for input, see `ui.interactive()'.
1617 This function refers to output only; for input, see `ui.interactive()'.
1619 This function always returns false when in plain mode, see `ui.plain()'.
1618 This function always returns false when in plain mode, see `ui.plain()'.
1620 """
1619 """
1621 if self.plain():
1620 if self.plain():
1622 return False
1621 return False
1623
1622
1624 i = self.configbool(b"ui", b"formatted")
1623 i = self.configbool(b"ui", b"formatted")
1625 if i is None:
1624 if i is None:
1626 # some environments replace stdout without implementing isatty
1625 # some environments replace stdout without implementing isatty
1627 # usually those are non-interactive
1626 # usually those are non-interactive
1628 return self._isatty(self._fout)
1627 return self._isatty(self._fout)
1629
1628
1630 return i
1629 return i
1631
1630
1632 def _readline(self, prompt=b' ', promptopts=None):
1631 def _readline(self, prompt=b' ', promptopts=None):
1633 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1632 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1634 # because they have to be text streams with *no buffering*. Instead,
1633 # because they have to be text streams with *no buffering*. Instead,
1635 # we use rawinput() only if call_readline() will be invoked by
1634 # we use rawinput() only if call_readline() will be invoked by
1636 # PyOS_Readline(), so no I/O will be made at Python layer.
1635 # PyOS_Readline(), so no I/O will be made at Python layer.
1637 usereadline = (
1636 usereadline = (
1638 self._isatty(self._fin)
1637 self._isatty(self._fin)
1639 and self._isatty(self._fout)
1638 and self._isatty(self._fout)
1640 and procutil.isstdin(self._fin)
1639 and procutil.isstdin(self._fin)
1641 and procutil.isstdout(self._fout)
1640 and procutil.isstdout(self._fout)
1642 )
1641 )
1643 if usereadline:
1642 if usereadline:
1644 try:
1643 try:
1645 # magically add command line editing support, where
1644 # magically add command line editing support, where
1646 # available
1645 # available
1647 import readline
1646 import readline
1648
1647
1649 # force demandimport to really load the module
1648 # force demandimport to really load the module
1650 readline.read_history_file
1649 readline.read_history_file
1651 # windows sometimes raises something other than ImportError
1650 # windows sometimes raises something other than ImportError
1652 except Exception:
1651 except Exception:
1653 usereadline = False
1652 usereadline = False
1654
1653
1655 if self._colormode == b'win32' or not usereadline:
1654 if self._colormode == b'win32' or not usereadline:
1656 if not promptopts:
1655 if not promptopts:
1657 promptopts = {}
1656 promptopts = {}
1658 self._writemsgnobuf(
1657 self._writemsgnobuf(
1659 self._fmsgout, prompt, type=b'prompt', **promptopts
1658 self._fmsgout, prompt, type=b'prompt', **promptopts
1660 )
1659 )
1661 self.flush()
1660 self.flush()
1662 prompt = b' '
1661 prompt = b' '
1663 else:
1662 else:
1664 prompt = self.label(prompt, b'ui.prompt') + b' '
1663 prompt = self.label(prompt, b'ui.prompt') + b' '
1665
1664
1666 # prompt ' ' must exist; otherwise readline may delete entire line
1665 # prompt ' ' must exist; otherwise readline may delete entire line
1667 # - http://bugs.python.org/issue12833
1666 # - http://bugs.python.org/issue12833
1668 with self.timeblockedsection(b'stdio'):
1667 with self.timeblockedsection(b'stdio'):
1669 if usereadline:
1668 if usereadline:
1670 self.flush()
1669 self.flush()
1671 prompt = encoding.strfromlocal(prompt)
1670 prompt = encoding.strfromlocal(prompt)
1672 line = encoding.strtolocal(pycompat.rawinput(prompt))
1671 line = encoding.strtolocal(pycompat.rawinput(prompt))
1673 # When stdin is in binary mode on Windows, it can cause
1672 # When stdin is in binary mode on Windows, it can cause
1674 # raw_input() to emit an extra trailing carriage return
1673 # raw_input() to emit an extra trailing carriage return
1675 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1674 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1676 line = line[:-1]
1675 line = line[:-1]
1677 else:
1676 else:
1678 self._fout.write(pycompat.bytestr(prompt))
1677 self._fout.write(pycompat.bytestr(prompt))
1679 self._fout.flush()
1678 self._fout.flush()
1680 line = self._fin.readline()
1679 line = self._fin.readline()
1681 if not line:
1680 if not line:
1682 raise EOFError
1681 raise EOFError
1683 line = line.rstrip(pycompat.oslinesep)
1682 line = line.rstrip(pycompat.oslinesep)
1684
1683
1685 return line
1684 return line
1686
1685
1687 def prompt(self, msg, default=b"y"):
1686 def prompt(self, msg, default=b"y"):
1688 """Prompt user with msg, read response.
1687 """Prompt user with msg, read response.
1689 If ui is not interactive, the default is returned.
1688 If ui is not interactive, the default is returned.
1690 """
1689 """
1691 return self._prompt(msg, default=default)
1690 return self._prompt(msg, default=default)
1692
1691
1693 def _prompt(self, msg, **opts):
1692 def _prompt(self, msg, **opts):
1694 default = opts['default']
1693 default = opts['default']
1695 if not self.interactive():
1694 if not self.interactive():
1696 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1695 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1697 self._writemsg(
1696 self._writemsg(
1698 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1697 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1699 )
1698 )
1700 return default
1699 return default
1701 try:
1700 try:
1702 r = self._readline(prompt=msg, promptopts=opts)
1701 r = self._readline(prompt=msg, promptopts=opts)
1703 if not r:
1702 if not r:
1704 r = default
1703 r = default
1705 if self.configbool(b'ui', b'promptecho'):
1704 if self.configbool(b'ui', b'promptecho'):
1706 self._writemsg(
1705 self._writemsg(
1707 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1706 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1708 )
1707 )
1709 return r
1708 return r
1710 except EOFError:
1709 except EOFError:
1711 raise error.ResponseExpected()
1710 raise error.ResponseExpected()
1712
1711
1713 @staticmethod
1712 @staticmethod
1714 def extractchoices(prompt):
1713 def extractchoices(prompt):
1715 """Extract prompt message and list of choices from specified prompt.
1714 """Extract prompt message and list of choices from specified prompt.
1716
1715
1717 This returns tuple "(message, choices)", and "choices" is the
1716 This returns tuple "(message, choices)", and "choices" is the
1718 list of tuple "(response character, text without &)".
1717 list of tuple "(response character, text without &)".
1719
1718
1720 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1719 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1721 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1720 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1722 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1721 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1723 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1722 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1724 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1723 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1725 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1724 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1726 """
1725 """
1727
1726
1728 # Sadly, the prompt string may have been built with a filename
1727 # Sadly, the prompt string may have been built with a filename
1729 # containing "$$" so let's try to find the first valid-looking
1728 # containing "$$" so let's try to find the first valid-looking
1730 # prompt to start parsing. Sadly, we also can't rely on
1729 # prompt to start parsing. Sadly, we also can't rely on
1731 # choices containing spaces, ASCII, or basically anything
1730 # choices containing spaces, ASCII, or basically anything
1732 # except an ampersand followed by a character.
1731 # except an ampersand followed by a character.
1733 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1732 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1734 msg = m.group(1)
1733 msg = m.group(1)
1735 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1734 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1736
1735
1737 def choicetuple(s):
1736 def choicetuple(s):
1738 ampidx = s.index(b'&')
1737 ampidx = s.index(b'&')
1739 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1738 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1740
1739
1741 return (msg, [choicetuple(s) for s in choices])
1740 return (msg, [choicetuple(s) for s in choices])
1742
1741
1743 def promptchoice(self, prompt, default=0):
1742 def promptchoice(self, prompt, default=0):
1744 """Prompt user with a message, read response, and ensure it matches
1743 """Prompt user with a message, read response, and ensure it matches
1745 one of the provided choices. The prompt is formatted as follows:
1744 one of the provided choices. The prompt is formatted as follows:
1746
1745
1747 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1746 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1748
1747
1749 The index of the choice is returned. Responses are case
1748 The index of the choice is returned. Responses are case
1750 insensitive. If ui is not interactive, the default is
1749 insensitive. If ui is not interactive, the default is
1751 returned.
1750 returned.
1752 """
1751 """
1753
1752
1754 msg, choices = self.extractchoices(prompt)
1753 msg, choices = self.extractchoices(prompt)
1755 resps = [r for r, t in choices]
1754 resps = [r for r, t in choices]
1756 while True:
1755 while True:
1757 r = self._prompt(msg, default=resps[default], choices=choices)
1756 r = self._prompt(msg, default=resps[default], choices=choices)
1758 if r.lower() in resps:
1757 if r.lower() in resps:
1759 return resps.index(r.lower())
1758 return resps.index(r.lower())
1760 # TODO: shouldn't it be a warning?
1759 # TODO: shouldn't it be a warning?
1761 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1760 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1762
1761
1763 def getpass(self, prompt=None, default=None):
1762 def getpass(self, prompt=None, default=None):
1764 if not self.interactive():
1763 if not self.interactive():
1765 return default
1764 return default
1766 try:
1765 try:
1767 self._writemsg(
1766 self._writemsg(
1768 self._fmsgerr,
1767 self._fmsgerr,
1769 prompt or _(b'password: '),
1768 prompt or _(b'password: '),
1770 type=b'prompt',
1769 type=b'prompt',
1771 password=True,
1770 password=True,
1772 )
1771 )
1773 # disable getpass() only if explicitly specified. it's still valid
1772 # disable getpass() only if explicitly specified. it's still valid
1774 # to interact with tty even if fin is not a tty.
1773 # to interact with tty even if fin is not a tty.
1775 with self.timeblockedsection(b'stdio'):
1774 with self.timeblockedsection(b'stdio'):
1776 if self.configbool(b'ui', b'nontty'):
1775 if self.configbool(b'ui', b'nontty'):
1777 l = self._fin.readline()
1776 l = self._fin.readline()
1778 if not l:
1777 if not l:
1779 raise EOFError
1778 raise EOFError
1780 return l.rstrip(b'\n')
1779 return l.rstrip(b'\n')
1781 else:
1780 else:
1782 return encoding.strtolocal(getpass.getpass(''))
1781 return util.get_password()
1783 except EOFError:
1782 except EOFError:
1784 raise error.ResponseExpected()
1783 raise error.ResponseExpected()
1785
1784
1786 def status(self, *msg, **opts):
1785 def status(self, *msg, **opts):
1787 """write status message to output (if ui.quiet is False)
1786 """write status message to output (if ui.quiet is False)
1788
1787
1789 This adds an output label of "ui.status".
1788 This adds an output label of "ui.status".
1790 """
1789 """
1791 if not self.quiet:
1790 if not self.quiet:
1792 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1791 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1793
1792
1794 def warn(self, *msg, **opts):
1793 def warn(self, *msg, **opts):
1795 """write warning message to output (stderr)
1794 """write warning message to output (stderr)
1796
1795
1797 This adds an output label of "ui.warning".
1796 This adds an output label of "ui.warning".
1798 """
1797 """
1799 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1798 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1800
1799
1801 def error(self, *msg, **opts):
1800 def error(self, *msg, **opts):
1802 """write error message to output (stderr)
1801 """write error message to output (stderr)
1803
1802
1804 This adds an output label of "ui.error".
1803 This adds an output label of "ui.error".
1805 """
1804 """
1806 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1805 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1807
1806
1808 def note(self, *msg, **opts):
1807 def note(self, *msg, **opts):
1809 """write note to output (if ui.verbose is True)
1808 """write note to output (if ui.verbose is True)
1810
1809
1811 This adds an output label of "ui.note".
1810 This adds an output label of "ui.note".
1812 """
1811 """
1813 if self.verbose:
1812 if self.verbose:
1814 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1813 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1815
1814
1816 def debug(self, *msg, **opts):
1815 def debug(self, *msg, **opts):
1817 """write debug message to output (if ui.debugflag is True)
1816 """write debug message to output (if ui.debugflag is True)
1818
1817
1819 This adds an output label of "ui.debug".
1818 This adds an output label of "ui.debug".
1820 """
1819 """
1821 if self.debugflag:
1820 if self.debugflag:
1822 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1821 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1823 self.log(b'debug', b'%s', b''.join(msg))
1822 self.log(b'debug', b'%s', b''.join(msg))
1824
1823
1825 # Aliases to defeat check-code.
1824 # Aliases to defeat check-code.
1826 statusnoi18n = status
1825 statusnoi18n = status
1827 notenoi18n = note
1826 notenoi18n = note
1828 warnnoi18n = warn
1827 warnnoi18n = warn
1829 writenoi18n = write
1828 writenoi18n = write
1830
1829
1831 def edit(
1830 def edit(
1832 self,
1831 self,
1833 text,
1832 text,
1834 user,
1833 user,
1835 extra=None,
1834 extra=None,
1836 editform=None,
1835 editform=None,
1837 pending=None,
1836 pending=None,
1838 repopath=None,
1837 repopath=None,
1839 action=None,
1838 action=None,
1840 ):
1839 ):
1841 if action is None:
1840 if action is None:
1842 self.develwarn(
1841 self.develwarn(
1843 b'action is None but will soon be a required '
1842 b'action is None but will soon be a required '
1844 b'parameter to ui.edit()'
1843 b'parameter to ui.edit()'
1845 )
1844 )
1846 extra_defaults = {
1845 extra_defaults = {
1847 b'prefix': b'editor',
1846 b'prefix': b'editor',
1848 b'suffix': b'.txt',
1847 b'suffix': b'.txt',
1849 }
1848 }
1850 if extra is not None:
1849 if extra is not None:
1851 if extra.get(b'suffix') is not None:
1850 if extra.get(b'suffix') is not None:
1852 self.develwarn(
1851 self.develwarn(
1853 b'extra.suffix is not None but will soon be '
1852 b'extra.suffix is not None but will soon be '
1854 b'ignored by ui.edit()'
1853 b'ignored by ui.edit()'
1855 )
1854 )
1856 extra_defaults.update(extra)
1855 extra_defaults.update(extra)
1857 extra = extra_defaults
1856 extra = extra_defaults
1858
1857
1859 if action == b'diff':
1858 if action == b'diff':
1860 suffix = b'.diff'
1859 suffix = b'.diff'
1861 elif action:
1860 elif action:
1862 suffix = b'.%s.hg.txt' % action
1861 suffix = b'.%s.hg.txt' % action
1863 else:
1862 else:
1864 suffix = extra[b'suffix']
1863 suffix = extra[b'suffix']
1865
1864
1866 rdir = None
1865 rdir = None
1867 if self.configbool(b'experimental', b'editortmpinhg'):
1866 if self.configbool(b'experimental', b'editortmpinhg'):
1868 rdir = repopath
1867 rdir = repopath
1869 (fd, name) = pycompat.mkstemp(
1868 (fd, name) = pycompat.mkstemp(
1870 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1869 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1871 )
1870 )
1872 try:
1871 try:
1873 with os.fdopen(fd, 'wb') as f:
1872 with os.fdopen(fd, 'wb') as f:
1874 f.write(util.tonativeeol(text))
1873 f.write(util.tonativeeol(text))
1875
1874
1876 environ = {b'HGUSER': user}
1875 environ = {b'HGUSER': user}
1877 if b'transplant_source' in extra:
1876 if b'transplant_source' in extra:
1878 environ.update(
1877 environ.update(
1879 {b'HGREVISION': hex(extra[b'transplant_source'])}
1878 {b'HGREVISION': hex(extra[b'transplant_source'])}
1880 )
1879 )
1881 for label in (b'intermediate-source', b'source', b'rebase_source'):
1880 for label in (b'intermediate-source', b'source', b'rebase_source'):
1882 if label in extra:
1881 if label in extra:
1883 environ.update({b'HGREVISION': extra[label]})
1882 environ.update({b'HGREVISION': extra[label]})
1884 break
1883 break
1885 if editform:
1884 if editform:
1886 environ.update({b'HGEDITFORM': editform})
1885 environ.update({b'HGEDITFORM': editform})
1887 if pending:
1886 if pending:
1888 environ.update({b'HG_PENDING': pending})
1887 environ.update({b'HG_PENDING': pending})
1889
1888
1890 editor = self.geteditor()
1889 editor = self.geteditor()
1891
1890
1892 self.system(
1891 self.system(
1893 b"%s \"%s\"" % (editor, name),
1892 b"%s \"%s\"" % (editor, name),
1894 environ=environ,
1893 environ=environ,
1895 onerr=error.CanceledError,
1894 onerr=error.CanceledError,
1896 errprefix=_(b"edit failed"),
1895 errprefix=_(b"edit failed"),
1897 blockedtag=b'editor',
1896 blockedtag=b'editor',
1898 )
1897 )
1899
1898
1900 with open(name, 'rb') as f:
1899 with open(name, 'rb') as f:
1901 t = util.fromnativeeol(f.read())
1900 t = util.fromnativeeol(f.read())
1902 finally:
1901 finally:
1903 os.unlink(name)
1902 os.unlink(name)
1904
1903
1905 return t
1904 return t
1906
1905
1907 def system(
1906 def system(
1908 self,
1907 self,
1909 cmd,
1908 cmd,
1910 environ=None,
1909 environ=None,
1911 cwd=None,
1910 cwd=None,
1912 onerr=None,
1911 onerr=None,
1913 errprefix=None,
1912 errprefix=None,
1914 blockedtag=None,
1913 blockedtag=None,
1915 ):
1914 ):
1916 """execute shell command with appropriate output stream. command
1915 """execute shell command with appropriate output stream. command
1917 output will be redirected if fout is not stdout.
1916 output will be redirected if fout is not stdout.
1918
1917
1919 if command fails and onerr is None, return status, else raise onerr
1918 if command fails and onerr is None, return status, else raise onerr
1920 object as exception.
1919 object as exception.
1921 """
1920 """
1922 if blockedtag is None:
1921 if blockedtag is None:
1923 # Long cmds tend to be because of an absolute path on cmd. Keep
1922 # Long cmds tend to be because of an absolute path on cmd. Keep
1924 # the tail end instead
1923 # the tail end instead
1925 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1924 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1926 blockedtag = b'unknown_system_' + cmdsuffix
1925 blockedtag = b'unknown_system_' + cmdsuffix
1927 out = self._fout
1926 out = self._fout
1928 if any(s[1] for s in self._bufferstates):
1927 if any(s[1] for s in self._bufferstates):
1929 out = self
1928 out = self
1930 with self.timeblockedsection(blockedtag):
1929 with self.timeblockedsection(blockedtag):
1931 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1930 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1932 if rc and onerr:
1931 if rc and onerr:
1933 errmsg = b'%s %s' % (
1932 errmsg = b'%s %s' % (
1934 procutil.shellsplit(cmd)[0],
1933 procutil.shellsplit(cmd)[0],
1935 procutil.explainexit(rc),
1934 procutil.explainexit(rc),
1936 )
1935 )
1937 if errprefix:
1936 if errprefix:
1938 errmsg = b'%s: %s' % (errprefix, errmsg)
1937 errmsg = b'%s: %s' % (errprefix, errmsg)
1939 raise onerr(errmsg)
1938 raise onerr(errmsg)
1940 return rc
1939 return rc
1941
1940
1942 def _runsystem(self, cmd, environ, cwd, out):
1941 def _runsystem(self, cmd, environ, cwd, out):
1943 """actually execute the given shell command (can be overridden by
1942 """actually execute the given shell command (can be overridden by
1944 extensions like chg)"""
1943 extensions like chg)"""
1945 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1944 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1946
1945
1947 def traceback(self, exc=None, force=False):
1946 def traceback(self, exc=None, force=False):
1948 """print exception traceback if traceback printing enabled or forced.
1947 """print exception traceback if traceback printing enabled or forced.
1949 only to call in exception handler. returns true if traceback
1948 only to call in exception handler. returns true if traceback
1950 printed."""
1949 printed."""
1951 if self.tracebackflag or force:
1950 if self.tracebackflag or force:
1952 if exc is None:
1951 if exc is None:
1953 exc = sys.exc_info()
1952 exc = sys.exc_info()
1954 cause = getattr(exc[1], 'cause', None)
1953 cause = getattr(exc[1], 'cause', None)
1955
1954
1956 if cause is not None:
1955 if cause is not None:
1957 causetb = traceback.format_tb(cause[2])
1956 causetb = traceback.format_tb(cause[2])
1958 exctb = traceback.format_tb(exc[2])
1957 exctb = traceback.format_tb(exc[2])
1959 exconly = traceback.format_exception_only(cause[0], cause[1])
1958 exconly = traceback.format_exception_only(cause[0], cause[1])
1960
1959
1961 # exclude frame where 'exc' was chained and rethrown from exctb
1960 # exclude frame where 'exc' was chained and rethrown from exctb
1962 self.write_err(
1961 self.write_err(
1963 b'Traceback (most recent call last):\n',
1962 b'Traceback (most recent call last):\n',
1964 encoding.strtolocal(''.join(exctb[:-1])),
1963 encoding.strtolocal(''.join(exctb[:-1])),
1965 encoding.strtolocal(''.join(causetb)),
1964 encoding.strtolocal(''.join(causetb)),
1966 encoding.strtolocal(''.join(exconly)),
1965 encoding.strtolocal(''.join(exconly)),
1967 )
1966 )
1968 else:
1967 else:
1969 output = traceback.format_exception(exc[0], exc[1], exc[2])
1968 output = traceback.format_exception(exc[0], exc[1], exc[2])
1970 self.write_err(encoding.strtolocal(''.join(output)))
1969 self.write_err(encoding.strtolocal(''.join(output)))
1971 return self.tracebackflag or force
1970 return self.tracebackflag or force
1972
1971
1973 def geteditor(self):
1972 def geteditor(self):
1974 '''return editor to use'''
1973 '''return editor to use'''
1975 if pycompat.sysplatform == b'plan9':
1974 if pycompat.sysplatform == b'plan9':
1976 # vi is the MIPS instruction simulator on Plan 9. We
1975 # vi is the MIPS instruction simulator on Plan 9. We
1977 # instead default to E to plumb commit messages to
1976 # instead default to E to plumb commit messages to
1978 # avoid confusion.
1977 # avoid confusion.
1979 editor = b'E'
1978 editor = b'E'
1980 elif pycompat.isdarwin:
1979 elif pycompat.isdarwin:
1981 # vi on darwin is POSIX compatible to a fault, and that includes
1980 # vi on darwin is POSIX compatible to a fault, and that includes
1982 # exiting non-zero if you make any mistake when running an ex
1981 # exiting non-zero if you make any mistake when running an ex
1983 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
1982 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
1984 # while s/vi/vim/ doesn't.
1983 # while s/vi/vim/ doesn't.
1985 editor = b'vim'
1984 editor = b'vim'
1986 else:
1985 else:
1987 editor = b'vi'
1986 editor = b'vi'
1988 return encoding.environ.get(b"HGEDITOR") or self.config(
1987 return encoding.environ.get(b"HGEDITOR") or self.config(
1989 b"ui", b"editor", editor
1988 b"ui", b"editor", editor
1990 )
1989 )
1991
1990
1992 @util.propertycache
1991 @util.propertycache
1993 def _progbar(self):
1992 def _progbar(self):
1994 """setup the progbar singleton to the ui object"""
1993 """setup the progbar singleton to the ui object"""
1995 if (
1994 if (
1996 self.quiet
1995 self.quiet
1997 or self.debugflag
1996 or self.debugflag
1998 or self.configbool(b'progress', b'disable')
1997 or self.configbool(b'progress', b'disable')
1999 or not progress.shouldprint(self)
1998 or not progress.shouldprint(self)
2000 ):
1999 ):
2001 return None
2000 return None
2002 return getprogbar(self)
2001 return getprogbar(self)
2003
2002
2004 def _progclear(self):
2003 def _progclear(self):
2005 """clear progress bar output if any. use it before any output"""
2004 """clear progress bar output if any. use it before any output"""
2006 if not haveprogbar(): # nothing loaded yet
2005 if not haveprogbar(): # nothing loaded yet
2007 return
2006 return
2008 if self._progbar is not None and self._progbar.printed:
2007 if self._progbar is not None and self._progbar.printed:
2009 self._progbar.clear()
2008 self._progbar.clear()
2010
2009
2011 def makeprogress(self, topic, unit=b"", total=None):
2010 def makeprogress(self, topic, unit=b"", total=None):
2012 """Create a progress helper for the specified topic"""
2011 """Create a progress helper for the specified topic"""
2013 if getattr(self._fmsgerr, 'structured', False):
2012 if getattr(self._fmsgerr, 'structured', False):
2014 # channel for machine-readable output with metadata, just send
2013 # channel for machine-readable output with metadata, just send
2015 # raw information
2014 # raw information
2016 # TODO: consider porting some useful information (e.g. estimated
2015 # TODO: consider porting some useful information (e.g. estimated
2017 # time) from progbar. we might want to support update delay to
2016 # time) from progbar. we might want to support update delay to
2018 # reduce the cost of transferring progress messages.
2017 # reduce the cost of transferring progress messages.
2019 def updatebar(topic, pos, item, unit, total):
2018 def updatebar(topic, pos, item, unit, total):
2020 self._fmsgerr.write(
2019 self._fmsgerr.write(
2021 None,
2020 None,
2022 type=b'progress',
2021 type=b'progress',
2023 topic=topic,
2022 topic=topic,
2024 pos=pos,
2023 pos=pos,
2025 item=item,
2024 item=item,
2026 unit=unit,
2025 unit=unit,
2027 total=total,
2026 total=total,
2028 )
2027 )
2029
2028
2030 elif self._progbar is not None:
2029 elif self._progbar is not None:
2031 updatebar = self._progbar.progress
2030 updatebar = self._progbar.progress
2032 else:
2031 else:
2033
2032
2034 def updatebar(topic, pos, item, unit, total):
2033 def updatebar(topic, pos, item, unit, total):
2035 pass
2034 pass
2036
2035
2037 return scmutil.progress(self, updatebar, topic, unit, total)
2036 return scmutil.progress(self, updatebar, topic, unit, total)
2038
2037
2039 def getlogger(self, name):
2038 def getlogger(self, name):
2040 """Returns a logger of the given name; or None if not registered"""
2039 """Returns a logger of the given name; or None if not registered"""
2041 return self._loggers.get(name)
2040 return self._loggers.get(name)
2042
2041
2043 def setlogger(self, name, logger):
2042 def setlogger(self, name, logger):
2044 """Install logger which can be identified later by the given name
2043 """Install logger which can be identified later by the given name
2045
2044
2046 More than one loggers can be registered. Use extension or module
2045 More than one loggers can be registered. Use extension or module
2047 name to uniquely identify the logger instance.
2046 name to uniquely identify the logger instance.
2048 """
2047 """
2049 self._loggers[name] = logger
2048 self._loggers[name] = logger
2050
2049
2051 def log(self, event, msgfmt, *msgargs, **opts):
2050 def log(self, event, msgfmt, *msgargs, **opts):
2052 """hook for logging facility extensions
2051 """hook for logging facility extensions
2053
2052
2054 event should be a readily-identifiable subsystem, which will
2053 event should be a readily-identifiable subsystem, which will
2055 allow filtering.
2054 allow filtering.
2056
2055
2057 msgfmt should be a newline-terminated format string to log, and
2056 msgfmt should be a newline-terminated format string to log, and
2058 *msgargs are %-formatted into it.
2057 *msgargs are %-formatted into it.
2059
2058
2060 **opts currently has no defined meanings.
2059 **opts currently has no defined meanings.
2061 """
2060 """
2062 if not self._loggers:
2061 if not self._loggers:
2063 return
2062 return
2064 activeloggers = [
2063 activeloggers = [
2065 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
2064 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
2066 ]
2065 ]
2067 if not activeloggers:
2066 if not activeloggers:
2068 return
2067 return
2069 msg = msgfmt % msgargs
2068 msg = msgfmt % msgargs
2070 opts = pycompat.byteskwargs(opts)
2069 opts = pycompat.byteskwargs(opts)
2071 # guard against recursion from e.g. ui.debug()
2070 # guard against recursion from e.g. ui.debug()
2072 registeredloggers = self._loggers
2071 registeredloggers = self._loggers
2073 self._loggers = {}
2072 self._loggers = {}
2074 try:
2073 try:
2075 for logger in activeloggers:
2074 for logger in activeloggers:
2076 logger.log(self, event, msg, opts)
2075 logger.log(self, event, msg, opts)
2077 finally:
2076 finally:
2078 self._loggers = registeredloggers
2077 self._loggers = registeredloggers
2079
2078
2080 def label(self, msg, label):
2079 def label(self, msg, label):
2081 """style msg based on supplied label
2080 """style msg based on supplied label
2082
2081
2083 If some color mode is enabled, this will add the necessary control
2082 If some color mode is enabled, this will add the necessary control
2084 characters to apply such color. In addition, 'debug' color mode adds
2083 characters to apply such color. In addition, 'debug' color mode adds
2085 markup showing which label affects a piece of text.
2084 markup showing which label affects a piece of text.
2086
2085
2087 ui.write(s, 'label') is equivalent to
2086 ui.write(s, 'label') is equivalent to
2088 ui.write(ui.label(s, 'label')).
2087 ui.write(ui.label(s, 'label')).
2089 """
2088 """
2090 if self._colormode is not None:
2089 if self._colormode is not None:
2091 return color.colorlabel(self, msg, label)
2090 return color.colorlabel(self, msg, label)
2092 return msg
2091 return msg
2093
2092
2094 def develwarn(self, msg, stacklevel=1, config=None):
2093 def develwarn(self, msg, stacklevel=1, config=None):
2095 """issue a developer warning message
2094 """issue a developer warning message
2096
2095
2097 Use 'stacklevel' to report the offender some layers further up in the
2096 Use 'stacklevel' to report the offender some layers further up in the
2098 stack.
2097 stack.
2099 """
2098 """
2100 if not self.configbool(b'devel', b'all-warnings'):
2099 if not self.configbool(b'devel', b'all-warnings'):
2101 if config is None or not self.configbool(b'devel', config):
2100 if config is None or not self.configbool(b'devel', config):
2102 return
2101 return
2103 msg = b'devel-warn: ' + msg
2102 msg = b'devel-warn: ' + msg
2104 stacklevel += 1 # get in develwarn
2103 stacklevel += 1 # get in develwarn
2105 if self.tracebackflag:
2104 if self.tracebackflag:
2106 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2105 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2107 self.log(
2106 self.log(
2108 b'develwarn',
2107 b'develwarn',
2109 b'%s at:\n%s'
2108 b'%s at:\n%s'
2110 % (msg, b''.join(util.getstackframes(stacklevel))),
2109 % (msg, b''.join(util.getstackframes(stacklevel))),
2111 )
2110 )
2112 else:
2111 else:
2113 curframe = inspect.currentframe()
2112 curframe = inspect.currentframe()
2114 calframe = inspect.getouterframes(curframe, 2)
2113 calframe = inspect.getouterframes(curframe, 2)
2115 fname, lineno, fmsg = calframe[stacklevel][1:4]
2114 fname, lineno, fmsg = calframe[stacklevel][1:4]
2116 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2115 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2117 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2116 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2118 self.log(
2117 self.log(
2119 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2118 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2120 )
2119 )
2121
2120
2122 # avoid cycles
2121 # avoid cycles
2123 del curframe
2122 del curframe
2124 del calframe
2123 del calframe
2125
2124
2126 def deprecwarn(self, msg, version, stacklevel=2):
2125 def deprecwarn(self, msg, version, stacklevel=2):
2127 """issue a deprecation warning
2126 """issue a deprecation warning
2128
2127
2129 - msg: message explaining what is deprecated and how to upgrade,
2128 - msg: message explaining what is deprecated and how to upgrade,
2130 - version: last version where the API will be supported,
2129 - version: last version where the API will be supported,
2131 """
2130 """
2132 if not (
2131 if not (
2133 self.configbool(b'devel', b'all-warnings')
2132 self.configbool(b'devel', b'all-warnings')
2134 or self.configbool(b'devel', b'deprec-warn')
2133 or self.configbool(b'devel', b'deprec-warn')
2135 ):
2134 ):
2136 return
2135 return
2137 msg += (
2136 msg += (
2138 b"\n(compatibility will be dropped after Mercurial-%s,"
2137 b"\n(compatibility will be dropped after Mercurial-%s,"
2139 b" update your code.)"
2138 b" update your code.)"
2140 ) % version
2139 ) % version
2141 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2140 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2142
2141
2143 def exportableenviron(self):
2142 def exportableenviron(self):
2144 """The environment variables that are safe to export, e.g. through
2143 """The environment variables that are safe to export, e.g. through
2145 hgweb.
2144 hgweb.
2146 """
2145 """
2147 return self._exportableenviron
2146 return self._exportableenviron
2148
2147
2149 @contextlib.contextmanager
2148 @contextlib.contextmanager
2150 def configoverride(self, overrides, source=b""):
2149 def configoverride(self, overrides, source=b""):
2151 """Context manager for temporary config overrides
2150 """Context manager for temporary config overrides
2152 `overrides` must be a dict of the following structure:
2151 `overrides` must be a dict of the following structure:
2153 {(section, name) : value}"""
2152 {(section, name) : value}"""
2154 backups = {}
2153 backups = {}
2155 try:
2154 try:
2156 for (section, name), value in overrides.items():
2155 for (section, name), value in overrides.items():
2157 backups[(section, name)] = self.backupconfig(section, name)
2156 backups[(section, name)] = self.backupconfig(section, name)
2158 self.setconfig(section, name, value, source)
2157 self.setconfig(section, name, value, source)
2159 yield
2158 yield
2160 finally:
2159 finally:
2161 for __, backup in backups.items():
2160 for __, backup in backups.items():
2162 self.restoreconfig(backup)
2161 self.restoreconfig(backup)
2163 # just restoring ui.quiet config to the previous value is not enough
2162 # just restoring ui.quiet config to the previous value is not enough
2164 # as it does not update ui.quiet class member
2163 # as it does not update ui.quiet class member
2165 if (b'ui', b'quiet') in overrides:
2164 if (b'ui', b'quiet') in overrides:
2166 self.fixconfig(section=b'ui')
2165 self.fixconfig(section=b'ui')
2167
2166
2168 def estimatememory(self):
2167 def estimatememory(self):
2169 """Provide an estimate for the available system memory in Bytes.
2168 """Provide an estimate for the available system memory in Bytes.
2170
2169
2171 This can be overriden via ui.available-memory. It returns None, if
2170 This can be overriden via ui.available-memory. It returns None, if
2172 no estimate can be computed.
2171 no estimate can be computed.
2173 """
2172 """
2174 value = self.config(b'ui', b'available-memory')
2173 value = self.config(b'ui', b'available-memory')
2175 if value is not None:
2174 if value is not None:
2176 try:
2175 try:
2177 return util.sizetoint(value)
2176 return util.sizetoint(value)
2178 except error.ParseError:
2177 except error.ParseError:
2179 raise error.ConfigError(
2178 raise error.ConfigError(
2180 _(b"ui.available-memory value is invalid ('%s')") % value
2179 _(b"ui.available-memory value is invalid ('%s')") % value
2181 )
2180 )
2182 return util._estimatememory()
2181 return util._estimatememory()
2183
2182
2184
2183
2185 # we instantiate one globally shared progress bar to avoid
2184 # we instantiate one globally shared progress bar to avoid
2186 # competing progress bars when multiple UI objects get created
2185 # competing progress bars when multiple UI objects get created
2187 _progresssingleton = None
2186 _progresssingleton = None
2188
2187
2189
2188
2190 def getprogbar(ui):
2189 def getprogbar(ui):
2191 global _progresssingleton
2190 global _progresssingleton
2192 if _progresssingleton is None:
2191 if _progresssingleton is None:
2193 # passing 'ui' object to the singleton is fishy,
2192 # passing 'ui' object to the singleton is fishy,
2194 # this is how the extension used to work but feel free to rework it.
2193 # this is how the extension used to work but feel free to rework it.
2195 _progresssingleton = progress.progbar(ui)
2194 _progresssingleton = progress.progbar(ui)
2196 return _progresssingleton
2195 return _progresssingleton
2197
2196
2198
2197
2199 def haveprogbar():
2198 def haveprogbar():
2200 return _progresssingleton is not None
2199 return _progresssingleton is not None
2201
2200
2202
2201
2203 def _selectmsgdests(ui):
2202 def _selectmsgdests(ui):
2204 name = ui.config(b'ui', b'message-output')
2203 name = ui.config(b'ui', b'message-output')
2205 if name == b'channel':
2204 if name == b'channel':
2206 if ui.fmsg:
2205 if ui.fmsg:
2207 return ui.fmsg, ui.fmsg
2206 return ui.fmsg, ui.fmsg
2208 else:
2207 else:
2209 # fall back to ferr if channel isn't ready so that status/error
2208 # fall back to ferr if channel isn't ready so that status/error
2210 # messages can be printed
2209 # messages can be printed
2211 return ui.ferr, ui.ferr
2210 return ui.ferr, ui.ferr
2212 if name == b'stdio':
2211 if name == b'stdio':
2213 return ui.fout, ui.ferr
2212 return ui.fout, ui.ferr
2214 if name == b'stderr':
2213 if name == b'stderr':
2215 return ui.ferr, ui.ferr
2214 return ui.ferr, ui.ferr
2216 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2215 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2217
2216
2218
2217
2219 def _writemsgwith(write, dest, *args, **opts):
2218 def _writemsgwith(write, dest, *args, **opts):
2220 """Write ui message with the given ui._write*() function
2219 """Write ui message with the given ui._write*() function
2221
2220
2222 The specified message type is translated to 'ui.<type>' label if the dest
2221 The specified message type is translated to 'ui.<type>' label if the dest
2223 isn't a structured channel, so that the message will be colorized.
2222 isn't a structured channel, so that the message will be colorized.
2224 """
2223 """
2225 # TODO: maybe change 'type' to a mandatory option
2224 # TODO: maybe change 'type' to a mandatory option
2226 if 'type' in opts and not getattr(dest, 'structured', False):
2225 if 'type' in opts and not getattr(dest, 'structured', False):
2227 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2226 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2228 write(dest, *args, **opts)
2227 write(dest, *args, **opts)
@@ -1,3378 +1,3379 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from __future__ import absolute_import, print_function
16 from __future__ import absolute_import, print_function
17
17
18 import abc
18 import abc
19 import collections
19 import collections
20 import contextlib
20 import contextlib
21 import errno
21 import errno
22 import gc
22 import gc
23 import hashlib
23 import hashlib
24 import itertools
24 import itertools
25 import locale
25 import locale
26 import mmap
26 import mmap
27 import os
27 import os
28 import platform as pyplatform
28 import platform as pyplatform
29 import re as remod
29 import re as remod
30 import shutil
30 import shutil
31 import stat
31 import stat
32 import sys
32 import sys
33 import time
33 import time
34 import traceback
34 import traceback
35 import warnings
35 import warnings
36
36
37 from .thirdparty import attr
37 from .thirdparty import attr
38 from .pycompat import (
38 from .pycompat import (
39 delattr,
39 delattr,
40 getattr,
40 getattr,
41 open,
41 open,
42 setattr,
42 setattr,
43 )
43 )
44 from .node import hex
44 from .node import hex
45 from hgdemandimport import tracing
45 from hgdemandimport import tracing
46 from . import (
46 from . import (
47 encoding,
47 encoding,
48 error,
48 error,
49 i18n,
49 i18n,
50 policy,
50 policy,
51 pycompat,
51 pycompat,
52 urllibcompat,
52 urllibcompat,
53 )
53 )
54 from .utils import (
54 from .utils import (
55 compression,
55 compression,
56 hashutil,
56 hashutil,
57 procutil,
57 procutil,
58 stringutil,
58 stringutil,
59 urlutil,
59 urlutil,
60 )
60 )
61
61
62 if pycompat.TYPE_CHECKING:
62 if pycompat.TYPE_CHECKING:
63 from typing import (
63 from typing import (
64 Iterator,
64 Iterator,
65 List,
65 List,
66 Optional,
66 Optional,
67 Tuple,
67 Tuple,
68 )
68 )
69
69
70
70
71 base85 = policy.importmod('base85')
71 base85 = policy.importmod('base85')
72 osutil = policy.importmod('osutil')
72 osutil = policy.importmod('osutil')
73
73
74 b85decode = base85.b85decode
74 b85decode = base85.b85decode
75 b85encode = base85.b85encode
75 b85encode = base85.b85encode
76
76
77 cookielib = pycompat.cookielib
77 cookielib = pycompat.cookielib
78 httplib = pycompat.httplib
78 httplib = pycompat.httplib
79 pickle = pycompat.pickle
79 pickle = pycompat.pickle
80 safehasattr = pycompat.safehasattr
80 safehasattr = pycompat.safehasattr
81 socketserver = pycompat.socketserver
81 socketserver = pycompat.socketserver
82 bytesio = pycompat.bytesio
82 bytesio = pycompat.bytesio
83 # TODO deprecate stringio name, as it is a lie on Python 3.
83 # TODO deprecate stringio name, as it is a lie on Python 3.
84 stringio = bytesio
84 stringio = bytesio
85 xmlrpclib = pycompat.xmlrpclib
85 xmlrpclib = pycompat.xmlrpclib
86
86
87 httpserver = urllibcompat.httpserver
87 httpserver = urllibcompat.httpserver
88 urlerr = urllibcompat.urlerr
88 urlerr = urllibcompat.urlerr
89 urlreq = urllibcompat.urlreq
89 urlreq = urllibcompat.urlreq
90
90
91 # workaround for win32mbcs
91 # workaround for win32mbcs
92 _filenamebytestr = pycompat.bytestr
92 _filenamebytestr = pycompat.bytestr
93
93
94 if pycompat.iswindows:
94 if pycompat.iswindows:
95 from . import windows as platform
95 from . import windows as platform
96 else:
96 else:
97 from . import posix as platform
97 from . import posix as platform
98
98
99 _ = i18n._
99 _ = i18n._
100
100
101 bindunixsocket = platform.bindunixsocket
101 bindunixsocket = platform.bindunixsocket
102 cachestat = platform.cachestat
102 cachestat = platform.cachestat
103 checkexec = platform.checkexec
103 checkexec = platform.checkexec
104 checklink = platform.checklink
104 checklink = platform.checklink
105 copymode = platform.copymode
105 copymode = platform.copymode
106 expandglobs = platform.expandglobs
106 expandglobs = platform.expandglobs
107 getfsmountpoint = platform.getfsmountpoint
107 getfsmountpoint = platform.getfsmountpoint
108 getfstype = platform.getfstype
108 getfstype = platform.getfstype
109 get_password = platform.get_password
109 groupmembers = platform.groupmembers
110 groupmembers = platform.groupmembers
110 groupname = platform.groupname
111 groupname = platform.groupname
111 isexec = platform.isexec
112 isexec = platform.isexec
112 isowner = platform.isowner
113 isowner = platform.isowner
113 listdir = osutil.listdir
114 listdir = osutil.listdir
114 localpath = platform.localpath
115 localpath = platform.localpath
115 lookupreg = platform.lookupreg
116 lookupreg = platform.lookupreg
116 makedir = platform.makedir
117 makedir = platform.makedir
117 nlinks = platform.nlinks
118 nlinks = platform.nlinks
118 normpath = platform.normpath
119 normpath = platform.normpath
119 normcase = platform.normcase
120 normcase = platform.normcase
120 normcasespec = platform.normcasespec
121 normcasespec = platform.normcasespec
121 normcasefallback = platform.normcasefallback
122 normcasefallback = platform.normcasefallback
122 openhardlinks = platform.openhardlinks
123 openhardlinks = platform.openhardlinks
123 oslink = platform.oslink
124 oslink = platform.oslink
124 parsepatchoutput = platform.parsepatchoutput
125 parsepatchoutput = platform.parsepatchoutput
125 pconvert = platform.pconvert
126 pconvert = platform.pconvert
126 poll = platform.poll
127 poll = platform.poll
127 posixfile = platform.posixfile
128 posixfile = platform.posixfile
128 readlink = platform.readlink
129 readlink = platform.readlink
129 rename = platform.rename
130 rename = platform.rename
130 removedirs = platform.removedirs
131 removedirs = platform.removedirs
131 samedevice = platform.samedevice
132 samedevice = platform.samedevice
132 samefile = platform.samefile
133 samefile = platform.samefile
133 samestat = platform.samestat
134 samestat = platform.samestat
134 setflags = platform.setflags
135 setflags = platform.setflags
135 split = platform.split
136 split = platform.split
136 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
137 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
137 statisexec = platform.statisexec
138 statisexec = platform.statisexec
138 statislink = platform.statislink
139 statislink = platform.statislink
139 umask = platform.umask
140 umask = platform.umask
140 unlink = platform.unlink
141 unlink = platform.unlink
141 username = platform.username
142 username = platform.username
142
143
143
144
144 def setumask(val):
145 def setumask(val):
145 # type: (int) -> None
146 # type: (int) -> None
146 '''updates the umask. used by chg server'''
147 '''updates the umask. used by chg server'''
147 if pycompat.iswindows:
148 if pycompat.iswindows:
148 return
149 return
149 os.umask(val)
150 os.umask(val)
150 global umask
151 global umask
151 platform.umask = umask = val & 0o777
152 platform.umask = umask = val & 0o777
152
153
153
154
154 # small compat layer
155 # small compat layer
155 compengines = compression.compengines
156 compengines = compression.compengines
156 SERVERROLE = compression.SERVERROLE
157 SERVERROLE = compression.SERVERROLE
157 CLIENTROLE = compression.CLIENTROLE
158 CLIENTROLE = compression.CLIENTROLE
158
159
159 try:
160 try:
160 recvfds = osutil.recvfds
161 recvfds = osutil.recvfds
161 except AttributeError:
162 except AttributeError:
162 pass
163 pass
163
164
164 # Python compatibility
165 # Python compatibility
165
166
166 _notset = object()
167 _notset = object()
167
168
168
169
169 def bitsfrom(container):
170 def bitsfrom(container):
170 bits = 0
171 bits = 0
171 for bit in container:
172 for bit in container:
172 bits |= bit
173 bits |= bit
173 return bits
174 return bits
174
175
175
176
176 # python 2.6 still have deprecation warning enabled by default. We do not want
177 # python 2.6 still have deprecation warning enabled by default. We do not want
177 # to display anything to standard user so detect if we are running test and
178 # to display anything to standard user so detect if we are running test and
178 # only use python deprecation warning in this case.
179 # only use python deprecation warning in this case.
179 _dowarn = bool(encoding.environ.get(b'HGEMITWARNINGS'))
180 _dowarn = bool(encoding.environ.get(b'HGEMITWARNINGS'))
180 if _dowarn:
181 if _dowarn:
181 # explicitly unfilter our warning for python 2.7
182 # explicitly unfilter our warning for python 2.7
182 #
183 #
183 # The option of setting PYTHONWARNINGS in the test runner was investigated.
184 # The option of setting PYTHONWARNINGS in the test runner was investigated.
184 # However, module name set through PYTHONWARNINGS was exactly matched, so
185 # However, module name set through PYTHONWARNINGS was exactly matched, so
185 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
186 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
186 # makes the whole PYTHONWARNINGS thing useless for our usecase.
187 # makes the whole PYTHONWARNINGS thing useless for our usecase.
187 warnings.filterwarnings('default', '', DeprecationWarning, 'mercurial')
188 warnings.filterwarnings('default', '', DeprecationWarning, 'mercurial')
188 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext')
189 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext')
189 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext3rd')
190 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext3rd')
190 if _dowarn and pycompat.ispy3:
191 if _dowarn and pycompat.ispy3:
191 # silence warning emitted by passing user string to re.sub()
192 # silence warning emitted by passing user string to re.sub()
192 warnings.filterwarnings(
193 warnings.filterwarnings(
193 'ignore', 'bad escape', DeprecationWarning, 'mercurial'
194 'ignore', 'bad escape', DeprecationWarning, 'mercurial'
194 )
195 )
195 warnings.filterwarnings(
196 warnings.filterwarnings(
196 'ignore', 'invalid escape sequence', DeprecationWarning, 'mercurial'
197 'ignore', 'invalid escape sequence', DeprecationWarning, 'mercurial'
197 )
198 )
198 # TODO: reinvent imp.is_frozen()
199 # TODO: reinvent imp.is_frozen()
199 warnings.filterwarnings(
200 warnings.filterwarnings(
200 'ignore',
201 'ignore',
201 'the imp module is deprecated',
202 'the imp module is deprecated',
202 DeprecationWarning,
203 DeprecationWarning,
203 'mercurial',
204 'mercurial',
204 )
205 )
205
206
206
207
207 def nouideprecwarn(msg, version, stacklevel=1):
208 def nouideprecwarn(msg, version, stacklevel=1):
208 """Issue an python native deprecation warning
209 """Issue an python native deprecation warning
209
210
210 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
211 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
211 """
212 """
212 if _dowarn:
213 if _dowarn:
213 msg += (
214 msg += (
214 b"\n(compatibility will be dropped after Mercurial-%s,"
215 b"\n(compatibility will be dropped after Mercurial-%s,"
215 b" update your code.)"
216 b" update your code.)"
216 ) % version
217 ) % version
217 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
218 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
218 # on python 3 with chg, we will need to explicitly flush the output
219 # on python 3 with chg, we will need to explicitly flush the output
219 sys.stderr.flush()
220 sys.stderr.flush()
220
221
221
222
222 DIGESTS = {
223 DIGESTS = {
223 b'md5': hashlib.md5,
224 b'md5': hashlib.md5,
224 b'sha1': hashutil.sha1,
225 b'sha1': hashutil.sha1,
225 b'sha512': hashlib.sha512,
226 b'sha512': hashlib.sha512,
226 }
227 }
227 # List of digest types from strongest to weakest
228 # List of digest types from strongest to weakest
228 DIGESTS_BY_STRENGTH = [b'sha512', b'sha1', b'md5']
229 DIGESTS_BY_STRENGTH = [b'sha512', b'sha1', b'md5']
229
230
230 for k in DIGESTS_BY_STRENGTH:
231 for k in DIGESTS_BY_STRENGTH:
231 assert k in DIGESTS
232 assert k in DIGESTS
232
233
233
234
234 class digester(object):
235 class digester(object):
235 """helper to compute digests.
236 """helper to compute digests.
236
237
237 This helper can be used to compute one or more digests given their name.
238 This helper can be used to compute one or more digests given their name.
238
239
239 >>> d = digester([b'md5', b'sha1'])
240 >>> d = digester([b'md5', b'sha1'])
240 >>> d.update(b'foo')
241 >>> d.update(b'foo')
241 >>> [k for k in sorted(d)]
242 >>> [k for k in sorted(d)]
242 ['md5', 'sha1']
243 ['md5', 'sha1']
243 >>> d[b'md5']
244 >>> d[b'md5']
244 'acbd18db4cc2f85cedef654fccc4a4d8'
245 'acbd18db4cc2f85cedef654fccc4a4d8'
245 >>> d[b'sha1']
246 >>> d[b'sha1']
246 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
247 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
247 >>> digester.preferred([b'md5', b'sha1'])
248 >>> digester.preferred([b'md5', b'sha1'])
248 'sha1'
249 'sha1'
249 """
250 """
250
251
251 def __init__(self, digests, s=b''):
252 def __init__(self, digests, s=b''):
252 self._hashes = {}
253 self._hashes = {}
253 for k in digests:
254 for k in digests:
254 if k not in DIGESTS:
255 if k not in DIGESTS:
255 raise error.Abort(_(b'unknown digest type: %s') % k)
256 raise error.Abort(_(b'unknown digest type: %s') % k)
256 self._hashes[k] = DIGESTS[k]()
257 self._hashes[k] = DIGESTS[k]()
257 if s:
258 if s:
258 self.update(s)
259 self.update(s)
259
260
260 def update(self, data):
261 def update(self, data):
261 for h in self._hashes.values():
262 for h in self._hashes.values():
262 h.update(data)
263 h.update(data)
263
264
264 def __getitem__(self, key):
265 def __getitem__(self, key):
265 if key not in DIGESTS:
266 if key not in DIGESTS:
266 raise error.Abort(_(b'unknown digest type: %s') % k)
267 raise error.Abort(_(b'unknown digest type: %s') % k)
267 return hex(self._hashes[key].digest())
268 return hex(self._hashes[key].digest())
268
269
269 def __iter__(self):
270 def __iter__(self):
270 return iter(self._hashes)
271 return iter(self._hashes)
271
272
272 @staticmethod
273 @staticmethod
273 def preferred(supported):
274 def preferred(supported):
274 """returns the strongest digest type in both supported and DIGESTS."""
275 """returns the strongest digest type in both supported and DIGESTS."""
275
276
276 for k in DIGESTS_BY_STRENGTH:
277 for k in DIGESTS_BY_STRENGTH:
277 if k in supported:
278 if k in supported:
278 return k
279 return k
279 return None
280 return None
280
281
281
282
282 class digestchecker(object):
283 class digestchecker(object):
283 """file handle wrapper that additionally checks content against a given
284 """file handle wrapper that additionally checks content against a given
284 size and digests.
285 size and digests.
285
286
286 d = digestchecker(fh, size, {'md5': '...'})
287 d = digestchecker(fh, size, {'md5': '...'})
287
288
288 When multiple digests are given, all of them are validated.
289 When multiple digests are given, all of them are validated.
289 """
290 """
290
291
291 def __init__(self, fh, size, digests):
292 def __init__(self, fh, size, digests):
292 self._fh = fh
293 self._fh = fh
293 self._size = size
294 self._size = size
294 self._got = 0
295 self._got = 0
295 self._digests = dict(digests)
296 self._digests = dict(digests)
296 self._digester = digester(self._digests.keys())
297 self._digester = digester(self._digests.keys())
297
298
298 def read(self, length=-1):
299 def read(self, length=-1):
299 content = self._fh.read(length)
300 content = self._fh.read(length)
300 self._digester.update(content)
301 self._digester.update(content)
301 self._got += len(content)
302 self._got += len(content)
302 return content
303 return content
303
304
304 def validate(self):
305 def validate(self):
305 if self._size != self._got:
306 if self._size != self._got:
306 raise error.Abort(
307 raise error.Abort(
307 _(b'size mismatch: expected %d, got %d')
308 _(b'size mismatch: expected %d, got %d')
308 % (self._size, self._got)
309 % (self._size, self._got)
309 )
310 )
310 for k, v in self._digests.items():
311 for k, v in self._digests.items():
311 if v != self._digester[k]:
312 if v != self._digester[k]:
312 # i18n: first parameter is a digest name
313 # i18n: first parameter is a digest name
313 raise error.Abort(
314 raise error.Abort(
314 _(b'%s mismatch: expected %s, got %s')
315 _(b'%s mismatch: expected %s, got %s')
315 % (k, v, self._digester[k])
316 % (k, v, self._digester[k])
316 )
317 )
317
318
318
319
319 try:
320 try:
320 buffer = buffer # pytype: disable=name-error
321 buffer = buffer # pytype: disable=name-error
321 except NameError:
322 except NameError:
322
323
323 def buffer(sliceable, offset=0, length=None):
324 def buffer(sliceable, offset=0, length=None):
324 if length is not None:
325 if length is not None:
325 return memoryview(sliceable)[offset : offset + length]
326 return memoryview(sliceable)[offset : offset + length]
326 return memoryview(sliceable)[offset:]
327 return memoryview(sliceable)[offset:]
327
328
328
329
329 _chunksize = 4096
330 _chunksize = 4096
330
331
331
332
332 class bufferedinputpipe(object):
333 class bufferedinputpipe(object):
333 """a manually buffered input pipe
334 """a manually buffered input pipe
334
335
335 Python will not let us use buffered IO and lazy reading with 'polling' at
336 Python will not let us use buffered IO and lazy reading with 'polling' at
336 the same time. We cannot probe the buffer state and select will not detect
337 the same time. We cannot probe the buffer state and select will not detect
337 that data are ready to read if they are already buffered.
338 that data are ready to read if they are already buffered.
338
339
339 This class let us work around that by implementing its own buffering
340 This class let us work around that by implementing its own buffering
340 (allowing efficient readline) while offering a way to know if the buffer is
341 (allowing efficient readline) while offering a way to know if the buffer is
341 empty from the output (allowing collaboration of the buffer with polling).
342 empty from the output (allowing collaboration of the buffer with polling).
342
343
343 This class lives in the 'util' module because it makes use of the 'os'
344 This class lives in the 'util' module because it makes use of the 'os'
344 module from the python stdlib.
345 module from the python stdlib.
345 """
346 """
346
347
347 def __new__(cls, fh):
348 def __new__(cls, fh):
348 # If we receive a fileobjectproxy, we need to use a variation of this
349 # If we receive a fileobjectproxy, we need to use a variation of this
349 # class that notifies observers about activity.
350 # class that notifies observers about activity.
350 if isinstance(fh, fileobjectproxy):
351 if isinstance(fh, fileobjectproxy):
351 cls = observedbufferedinputpipe
352 cls = observedbufferedinputpipe
352
353
353 return super(bufferedinputpipe, cls).__new__(cls)
354 return super(bufferedinputpipe, cls).__new__(cls)
354
355
355 def __init__(self, input):
356 def __init__(self, input):
356 self._input = input
357 self._input = input
357 self._buffer = []
358 self._buffer = []
358 self._eof = False
359 self._eof = False
359 self._lenbuf = 0
360 self._lenbuf = 0
360
361
361 @property
362 @property
362 def hasbuffer(self):
363 def hasbuffer(self):
363 """True is any data is currently buffered
364 """True is any data is currently buffered
364
365
365 This will be used externally a pre-step for polling IO. If there is
366 This will be used externally a pre-step for polling IO. If there is
366 already data then no polling should be set in place."""
367 already data then no polling should be set in place."""
367 return bool(self._buffer)
368 return bool(self._buffer)
368
369
369 @property
370 @property
370 def closed(self):
371 def closed(self):
371 return self._input.closed
372 return self._input.closed
372
373
373 def fileno(self):
374 def fileno(self):
374 return self._input.fileno()
375 return self._input.fileno()
375
376
376 def close(self):
377 def close(self):
377 return self._input.close()
378 return self._input.close()
378
379
379 def read(self, size):
380 def read(self, size):
380 while (not self._eof) and (self._lenbuf < size):
381 while (not self._eof) and (self._lenbuf < size):
381 self._fillbuffer()
382 self._fillbuffer()
382 return self._frombuffer(size)
383 return self._frombuffer(size)
383
384
384 def unbufferedread(self, size):
385 def unbufferedread(self, size):
385 if not self._eof and self._lenbuf == 0:
386 if not self._eof and self._lenbuf == 0:
386 self._fillbuffer(max(size, _chunksize))
387 self._fillbuffer(max(size, _chunksize))
387 return self._frombuffer(min(self._lenbuf, size))
388 return self._frombuffer(min(self._lenbuf, size))
388
389
389 def readline(self, *args, **kwargs):
390 def readline(self, *args, **kwargs):
390 if len(self._buffer) > 1:
391 if len(self._buffer) > 1:
391 # this should not happen because both read and readline end with a
392 # this should not happen because both read and readline end with a
392 # _frombuffer call that collapse it.
393 # _frombuffer call that collapse it.
393 self._buffer = [b''.join(self._buffer)]
394 self._buffer = [b''.join(self._buffer)]
394 self._lenbuf = len(self._buffer[0])
395 self._lenbuf = len(self._buffer[0])
395 lfi = -1
396 lfi = -1
396 if self._buffer:
397 if self._buffer:
397 lfi = self._buffer[-1].find(b'\n')
398 lfi = self._buffer[-1].find(b'\n')
398 while (not self._eof) and lfi < 0:
399 while (not self._eof) and lfi < 0:
399 self._fillbuffer()
400 self._fillbuffer()
400 if self._buffer:
401 if self._buffer:
401 lfi = self._buffer[-1].find(b'\n')
402 lfi = self._buffer[-1].find(b'\n')
402 size = lfi + 1
403 size = lfi + 1
403 if lfi < 0: # end of file
404 if lfi < 0: # end of file
404 size = self._lenbuf
405 size = self._lenbuf
405 elif len(self._buffer) > 1:
406 elif len(self._buffer) > 1:
406 # we need to take previous chunks into account
407 # we need to take previous chunks into account
407 size += self._lenbuf - len(self._buffer[-1])
408 size += self._lenbuf - len(self._buffer[-1])
408 return self._frombuffer(size)
409 return self._frombuffer(size)
409
410
410 def _frombuffer(self, size):
411 def _frombuffer(self, size):
411 """return at most 'size' data from the buffer
412 """return at most 'size' data from the buffer
412
413
413 The data are removed from the buffer."""
414 The data are removed from the buffer."""
414 if size == 0 or not self._buffer:
415 if size == 0 or not self._buffer:
415 return b''
416 return b''
416 buf = self._buffer[0]
417 buf = self._buffer[0]
417 if len(self._buffer) > 1:
418 if len(self._buffer) > 1:
418 buf = b''.join(self._buffer)
419 buf = b''.join(self._buffer)
419
420
420 data = buf[:size]
421 data = buf[:size]
421 buf = buf[len(data) :]
422 buf = buf[len(data) :]
422 if buf:
423 if buf:
423 self._buffer = [buf]
424 self._buffer = [buf]
424 self._lenbuf = len(buf)
425 self._lenbuf = len(buf)
425 else:
426 else:
426 self._buffer = []
427 self._buffer = []
427 self._lenbuf = 0
428 self._lenbuf = 0
428 return data
429 return data
429
430
430 def _fillbuffer(self, size=_chunksize):
431 def _fillbuffer(self, size=_chunksize):
431 """read data to the buffer"""
432 """read data to the buffer"""
432 data = os.read(self._input.fileno(), size)
433 data = os.read(self._input.fileno(), size)
433 if not data:
434 if not data:
434 self._eof = True
435 self._eof = True
435 else:
436 else:
436 self._lenbuf += len(data)
437 self._lenbuf += len(data)
437 self._buffer.append(data)
438 self._buffer.append(data)
438
439
439 return data
440 return data
440
441
441
442
442 def mmapread(fp, size=None):
443 def mmapread(fp, size=None):
443 if size == 0:
444 if size == 0:
444 # size of 0 to mmap.mmap() means "all data"
445 # size of 0 to mmap.mmap() means "all data"
445 # rather than "zero bytes", so special case that.
446 # rather than "zero bytes", so special case that.
446 return b''
447 return b''
447 elif size is None:
448 elif size is None:
448 size = 0
449 size = 0
449 try:
450 try:
450 fd = getattr(fp, 'fileno', lambda: fp)()
451 fd = getattr(fp, 'fileno', lambda: fp)()
451 return mmap.mmap(fd, size, access=mmap.ACCESS_READ)
452 return mmap.mmap(fd, size, access=mmap.ACCESS_READ)
452 except ValueError:
453 except ValueError:
453 # Empty files cannot be mmapped, but mmapread should still work. Check
454 # Empty files cannot be mmapped, but mmapread should still work. Check
454 # if the file is empty, and if so, return an empty buffer.
455 # if the file is empty, and if so, return an empty buffer.
455 if os.fstat(fd).st_size == 0:
456 if os.fstat(fd).st_size == 0:
456 return b''
457 return b''
457 raise
458 raise
458
459
459
460
460 class fileobjectproxy(object):
461 class fileobjectproxy(object):
461 """A proxy around file objects that tells a watcher when events occur.
462 """A proxy around file objects that tells a watcher when events occur.
462
463
463 This type is intended to only be used for testing purposes. Think hard
464 This type is intended to only be used for testing purposes. Think hard
464 before using it in important code.
465 before using it in important code.
465 """
466 """
466
467
467 __slots__ = (
468 __slots__ = (
468 '_orig',
469 '_orig',
469 '_observer',
470 '_observer',
470 )
471 )
471
472
472 def __init__(self, fh, observer):
473 def __init__(self, fh, observer):
473 object.__setattr__(self, '_orig', fh)
474 object.__setattr__(self, '_orig', fh)
474 object.__setattr__(self, '_observer', observer)
475 object.__setattr__(self, '_observer', observer)
475
476
476 def __getattribute__(self, name):
477 def __getattribute__(self, name):
477 ours = {
478 ours = {
478 '_observer',
479 '_observer',
479 # IOBase
480 # IOBase
480 'close',
481 'close',
481 # closed if a property
482 # closed if a property
482 'fileno',
483 'fileno',
483 'flush',
484 'flush',
484 'isatty',
485 'isatty',
485 'readable',
486 'readable',
486 'readline',
487 'readline',
487 'readlines',
488 'readlines',
488 'seek',
489 'seek',
489 'seekable',
490 'seekable',
490 'tell',
491 'tell',
491 'truncate',
492 'truncate',
492 'writable',
493 'writable',
493 'writelines',
494 'writelines',
494 # RawIOBase
495 # RawIOBase
495 'read',
496 'read',
496 'readall',
497 'readall',
497 'readinto',
498 'readinto',
498 'write',
499 'write',
499 # BufferedIOBase
500 # BufferedIOBase
500 # raw is a property
501 # raw is a property
501 'detach',
502 'detach',
502 # read defined above
503 # read defined above
503 'read1',
504 'read1',
504 # readinto defined above
505 # readinto defined above
505 # write defined above
506 # write defined above
506 }
507 }
507
508
508 # We only observe some methods.
509 # We only observe some methods.
509 if name in ours:
510 if name in ours:
510 return object.__getattribute__(self, name)
511 return object.__getattribute__(self, name)
511
512
512 return getattr(object.__getattribute__(self, '_orig'), name)
513 return getattr(object.__getattribute__(self, '_orig'), name)
513
514
514 def __nonzero__(self):
515 def __nonzero__(self):
515 return bool(object.__getattribute__(self, '_orig'))
516 return bool(object.__getattribute__(self, '_orig'))
516
517
517 __bool__ = __nonzero__
518 __bool__ = __nonzero__
518
519
519 def __delattr__(self, name):
520 def __delattr__(self, name):
520 return delattr(object.__getattribute__(self, '_orig'), name)
521 return delattr(object.__getattribute__(self, '_orig'), name)
521
522
522 def __setattr__(self, name, value):
523 def __setattr__(self, name, value):
523 return setattr(object.__getattribute__(self, '_orig'), name, value)
524 return setattr(object.__getattribute__(self, '_orig'), name, value)
524
525
525 def __iter__(self):
526 def __iter__(self):
526 return object.__getattribute__(self, '_orig').__iter__()
527 return object.__getattribute__(self, '_orig').__iter__()
527
528
528 def _observedcall(self, name, *args, **kwargs):
529 def _observedcall(self, name, *args, **kwargs):
529 # Call the original object.
530 # Call the original object.
530 orig = object.__getattribute__(self, '_orig')
531 orig = object.__getattribute__(self, '_orig')
531 res = getattr(orig, name)(*args, **kwargs)
532 res = getattr(orig, name)(*args, **kwargs)
532
533
533 # Call a method on the observer of the same name with arguments
534 # Call a method on the observer of the same name with arguments
534 # so it can react, log, etc.
535 # so it can react, log, etc.
535 observer = object.__getattribute__(self, '_observer')
536 observer = object.__getattribute__(self, '_observer')
536 fn = getattr(observer, name, None)
537 fn = getattr(observer, name, None)
537 if fn:
538 if fn:
538 fn(res, *args, **kwargs)
539 fn(res, *args, **kwargs)
539
540
540 return res
541 return res
541
542
542 def close(self, *args, **kwargs):
543 def close(self, *args, **kwargs):
543 return object.__getattribute__(self, '_observedcall')(
544 return object.__getattribute__(self, '_observedcall')(
544 'close', *args, **kwargs
545 'close', *args, **kwargs
545 )
546 )
546
547
547 def fileno(self, *args, **kwargs):
548 def fileno(self, *args, **kwargs):
548 return object.__getattribute__(self, '_observedcall')(
549 return object.__getattribute__(self, '_observedcall')(
549 'fileno', *args, **kwargs
550 'fileno', *args, **kwargs
550 )
551 )
551
552
552 def flush(self, *args, **kwargs):
553 def flush(self, *args, **kwargs):
553 return object.__getattribute__(self, '_observedcall')(
554 return object.__getattribute__(self, '_observedcall')(
554 'flush', *args, **kwargs
555 'flush', *args, **kwargs
555 )
556 )
556
557
557 def isatty(self, *args, **kwargs):
558 def isatty(self, *args, **kwargs):
558 return object.__getattribute__(self, '_observedcall')(
559 return object.__getattribute__(self, '_observedcall')(
559 'isatty', *args, **kwargs
560 'isatty', *args, **kwargs
560 )
561 )
561
562
562 def readable(self, *args, **kwargs):
563 def readable(self, *args, **kwargs):
563 return object.__getattribute__(self, '_observedcall')(
564 return object.__getattribute__(self, '_observedcall')(
564 'readable', *args, **kwargs
565 'readable', *args, **kwargs
565 )
566 )
566
567
567 def readline(self, *args, **kwargs):
568 def readline(self, *args, **kwargs):
568 return object.__getattribute__(self, '_observedcall')(
569 return object.__getattribute__(self, '_observedcall')(
569 'readline', *args, **kwargs
570 'readline', *args, **kwargs
570 )
571 )
571
572
572 def readlines(self, *args, **kwargs):
573 def readlines(self, *args, **kwargs):
573 return object.__getattribute__(self, '_observedcall')(
574 return object.__getattribute__(self, '_observedcall')(
574 'readlines', *args, **kwargs
575 'readlines', *args, **kwargs
575 )
576 )
576
577
577 def seek(self, *args, **kwargs):
578 def seek(self, *args, **kwargs):
578 return object.__getattribute__(self, '_observedcall')(
579 return object.__getattribute__(self, '_observedcall')(
579 'seek', *args, **kwargs
580 'seek', *args, **kwargs
580 )
581 )
581
582
582 def seekable(self, *args, **kwargs):
583 def seekable(self, *args, **kwargs):
583 return object.__getattribute__(self, '_observedcall')(
584 return object.__getattribute__(self, '_observedcall')(
584 'seekable', *args, **kwargs
585 'seekable', *args, **kwargs
585 )
586 )
586
587
587 def tell(self, *args, **kwargs):
588 def tell(self, *args, **kwargs):
588 return object.__getattribute__(self, '_observedcall')(
589 return object.__getattribute__(self, '_observedcall')(
589 'tell', *args, **kwargs
590 'tell', *args, **kwargs
590 )
591 )
591
592
592 def truncate(self, *args, **kwargs):
593 def truncate(self, *args, **kwargs):
593 return object.__getattribute__(self, '_observedcall')(
594 return object.__getattribute__(self, '_observedcall')(
594 'truncate', *args, **kwargs
595 'truncate', *args, **kwargs
595 )
596 )
596
597
597 def writable(self, *args, **kwargs):
598 def writable(self, *args, **kwargs):
598 return object.__getattribute__(self, '_observedcall')(
599 return object.__getattribute__(self, '_observedcall')(
599 'writable', *args, **kwargs
600 'writable', *args, **kwargs
600 )
601 )
601
602
602 def writelines(self, *args, **kwargs):
603 def writelines(self, *args, **kwargs):
603 return object.__getattribute__(self, '_observedcall')(
604 return object.__getattribute__(self, '_observedcall')(
604 'writelines', *args, **kwargs
605 'writelines', *args, **kwargs
605 )
606 )
606
607
607 def read(self, *args, **kwargs):
608 def read(self, *args, **kwargs):
608 return object.__getattribute__(self, '_observedcall')(
609 return object.__getattribute__(self, '_observedcall')(
609 'read', *args, **kwargs
610 'read', *args, **kwargs
610 )
611 )
611
612
612 def readall(self, *args, **kwargs):
613 def readall(self, *args, **kwargs):
613 return object.__getattribute__(self, '_observedcall')(
614 return object.__getattribute__(self, '_observedcall')(
614 'readall', *args, **kwargs
615 'readall', *args, **kwargs
615 )
616 )
616
617
617 def readinto(self, *args, **kwargs):
618 def readinto(self, *args, **kwargs):
618 return object.__getattribute__(self, '_observedcall')(
619 return object.__getattribute__(self, '_observedcall')(
619 'readinto', *args, **kwargs
620 'readinto', *args, **kwargs
620 )
621 )
621
622
622 def write(self, *args, **kwargs):
623 def write(self, *args, **kwargs):
623 return object.__getattribute__(self, '_observedcall')(
624 return object.__getattribute__(self, '_observedcall')(
624 'write', *args, **kwargs
625 'write', *args, **kwargs
625 )
626 )
626
627
627 def detach(self, *args, **kwargs):
628 def detach(self, *args, **kwargs):
628 return object.__getattribute__(self, '_observedcall')(
629 return object.__getattribute__(self, '_observedcall')(
629 'detach', *args, **kwargs
630 'detach', *args, **kwargs
630 )
631 )
631
632
632 def read1(self, *args, **kwargs):
633 def read1(self, *args, **kwargs):
633 return object.__getattribute__(self, '_observedcall')(
634 return object.__getattribute__(self, '_observedcall')(
634 'read1', *args, **kwargs
635 'read1', *args, **kwargs
635 )
636 )
636
637
637
638
638 class observedbufferedinputpipe(bufferedinputpipe):
639 class observedbufferedinputpipe(bufferedinputpipe):
639 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
640 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
640
641
641 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
642 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
642 bypass ``fileobjectproxy``. Because of this, we need to make
643 bypass ``fileobjectproxy``. Because of this, we need to make
643 ``bufferedinputpipe`` aware of these operations.
644 ``bufferedinputpipe`` aware of these operations.
644
645
645 This variation of ``bufferedinputpipe`` can notify observers about
646 This variation of ``bufferedinputpipe`` can notify observers about
646 ``os.read()`` events. It also re-publishes other events, such as
647 ``os.read()`` events. It also re-publishes other events, such as
647 ``read()`` and ``readline()``.
648 ``read()`` and ``readline()``.
648 """
649 """
649
650
650 def _fillbuffer(self):
651 def _fillbuffer(self):
651 res = super(observedbufferedinputpipe, self)._fillbuffer()
652 res = super(observedbufferedinputpipe, self)._fillbuffer()
652
653
653 fn = getattr(self._input._observer, 'osread', None)
654 fn = getattr(self._input._observer, 'osread', None)
654 if fn:
655 if fn:
655 fn(res, _chunksize)
656 fn(res, _chunksize)
656
657
657 return res
658 return res
658
659
659 # We use different observer methods because the operation isn't
660 # We use different observer methods because the operation isn't
660 # performed on the actual file object but on us.
661 # performed on the actual file object but on us.
661 def read(self, size):
662 def read(self, size):
662 res = super(observedbufferedinputpipe, self).read(size)
663 res = super(observedbufferedinputpipe, self).read(size)
663
664
664 fn = getattr(self._input._observer, 'bufferedread', None)
665 fn = getattr(self._input._observer, 'bufferedread', None)
665 if fn:
666 if fn:
666 fn(res, size)
667 fn(res, size)
667
668
668 return res
669 return res
669
670
670 def readline(self, *args, **kwargs):
671 def readline(self, *args, **kwargs):
671 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
672 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
672
673
673 fn = getattr(self._input._observer, 'bufferedreadline', None)
674 fn = getattr(self._input._observer, 'bufferedreadline', None)
674 if fn:
675 if fn:
675 fn(res)
676 fn(res)
676
677
677 return res
678 return res
678
679
679
680
680 PROXIED_SOCKET_METHODS = {
681 PROXIED_SOCKET_METHODS = {
681 'makefile',
682 'makefile',
682 'recv',
683 'recv',
683 'recvfrom',
684 'recvfrom',
684 'recvfrom_into',
685 'recvfrom_into',
685 'recv_into',
686 'recv_into',
686 'send',
687 'send',
687 'sendall',
688 'sendall',
688 'sendto',
689 'sendto',
689 'setblocking',
690 'setblocking',
690 'settimeout',
691 'settimeout',
691 'gettimeout',
692 'gettimeout',
692 'setsockopt',
693 'setsockopt',
693 }
694 }
694
695
695
696
696 class socketproxy(object):
697 class socketproxy(object):
697 """A proxy around a socket that tells a watcher when events occur.
698 """A proxy around a socket that tells a watcher when events occur.
698
699
699 This is like ``fileobjectproxy`` except for sockets.
700 This is like ``fileobjectproxy`` except for sockets.
700
701
701 This type is intended to only be used for testing purposes. Think hard
702 This type is intended to only be used for testing purposes. Think hard
702 before using it in important code.
703 before using it in important code.
703 """
704 """
704
705
705 __slots__ = (
706 __slots__ = (
706 '_orig',
707 '_orig',
707 '_observer',
708 '_observer',
708 )
709 )
709
710
710 def __init__(self, sock, observer):
711 def __init__(self, sock, observer):
711 object.__setattr__(self, '_orig', sock)
712 object.__setattr__(self, '_orig', sock)
712 object.__setattr__(self, '_observer', observer)
713 object.__setattr__(self, '_observer', observer)
713
714
714 def __getattribute__(self, name):
715 def __getattribute__(self, name):
715 if name in PROXIED_SOCKET_METHODS:
716 if name in PROXIED_SOCKET_METHODS:
716 return object.__getattribute__(self, name)
717 return object.__getattribute__(self, name)
717
718
718 return getattr(object.__getattribute__(self, '_orig'), name)
719 return getattr(object.__getattribute__(self, '_orig'), name)
719
720
720 def __delattr__(self, name):
721 def __delattr__(self, name):
721 return delattr(object.__getattribute__(self, '_orig'), name)
722 return delattr(object.__getattribute__(self, '_orig'), name)
722
723
723 def __setattr__(self, name, value):
724 def __setattr__(self, name, value):
724 return setattr(object.__getattribute__(self, '_orig'), name, value)
725 return setattr(object.__getattribute__(self, '_orig'), name, value)
725
726
726 def __nonzero__(self):
727 def __nonzero__(self):
727 return bool(object.__getattribute__(self, '_orig'))
728 return bool(object.__getattribute__(self, '_orig'))
728
729
729 __bool__ = __nonzero__
730 __bool__ = __nonzero__
730
731
731 def _observedcall(self, name, *args, **kwargs):
732 def _observedcall(self, name, *args, **kwargs):
732 # Call the original object.
733 # Call the original object.
733 orig = object.__getattribute__(self, '_orig')
734 orig = object.__getattribute__(self, '_orig')
734 res = getattr(orig, name)(*args, **kwargs)
735 res = getattr(orig, name)(*args, **kwargs)
735
736
736 # Call a method on the observer of the same name with arguments
737 # Call a method on the observer of the same name with arguments
737 # so it can react, log, etc.
738 # so it can react, log, etc.
738 observer = object.__getattribute__(self, '_observer')
739 observer = object.__getattribute__(self, '_observer')
739 fn = getattr(observer, name, None)
740 fn = getattr(observer, name, None)
740 if fn:
741 if fn:
741 fn(res, *args, **kwargs)
742 fn(res, *args, **kwargs)
742
743
743 return res
744 return res
744
745
745 def makefile(self, *args, **kwargs):
746 def makefile(self, *args, **kwargs):
746 res = object.__getattribute__(self, '_observedcall')(
747 res = object.__getattribute__(self, '_observedcall')(
747 'makefile', *args, **kwargs
748 'makefile', *args, **kwargs
748 )
749 )
749
750
750 # The file object may be used for I/O. So we turn it into a
751 # The file object may be used for I/O. So we turn it into a
751 # proxy using our observer.
752 # proxy using our observer.
752 observer = object.__getattribute__(self, '_observer')
753 observer = object.__getattribute__(self, '_observer')
753 return makeloggingfileobject(
754 return makeloggingfileobject(
754 observer.fh,
755 observer.fh,
755 res,
756 res,
756 observer.name,
757 observer.name,
757 reads=observer.reads,
758 reads=observer.reads,
758 writes=observer.writes,
759 writes=observer.writes,
759 logdata=observer.logdata,
760 logdata=observer.logdata,
760 logdataapis=observer.logdataapis,
761 logdataapis=observer.logdataapis,
761 )
762 )
762
763
763 def recv(self, *args, **kwargs):
764 def recv(self, *args, **kwargs):
764 return object.__getattribute__(self, '_observedcall')(
765 return object.__getattribute__(self, '_observedcall')(
765 'recv', *args, **kwargs
766 'recv', *args, **kwargs
766 )
767 )
767
768
768 def recvfrom(self, *args, **kwargs):
769 def recvfrom(self, *args, **kwargs):
769 return object.__getattribute__(self, '_observedcall')(
770 return object.__getattribute__(self, '_observedcall')(
770 'recvfrom', *args, **kwargs
771 'recvfrom', *args, **kwargs
771 )
772 )
772
773
773 def recvfrom_into(self, *args, **kwargs):
774 def recvfrom_into(self, *args, **kwargs):
774 return object.__getattribute__(self, '_observedcall')(
775 return object.__getattribute__(self, '_observedcall')(
775 'recvfrom_into', *args, **kwargs
776 'recvfrom_into', *args, **kwargs
776 )
777 )
777
778
778 def recv_into(self, *args, **kwargs):
779 def recv_into(self, *args, **kwargs):
779 return object.__getattribute__(self, '_observedcall')(
780 return object.__getattribute__(self, '_observedcall')(
780 'recv_info', *args, **kwargs
781 'recv_info', *args, **kwargs
781 )
782 )
782
783
783 def send(self, *args, **kwargs):
784 def send(self, *args, **kwargs):
784 return object.__getattribute__(self, '_observedcall')(
785 return object.__getattribute__(self, '_observedcall')(
785 'send', *args, **kwargs
786 'send', *args, **kwargs
786 )
787 )
787
788
788 def sendall(self, *args, **kwargs):
789 def sendall(self, *args, **kwargs):
789 return object.__getattribute__(self, '_observedcall')(
790 return object.__getattribute__(self, '_observedcall')(
790 'sendall', *args, **kwargs
791 'sendall', *args, **kwargs
791 )
792 )
792
793
793 def sendto(self, *args, **kwargs):
794 def sendto(self, *args, **kwargs):
794 return object.__getattribute__(self, '_observedcall')(
795 return object.__getattribute__(self, '_observedcall')(
795 'sendto', *args, **kwargs
796 'sendto', *args, **kwargs
796 )
797 )
797
798
798 def setblocking(self, *args, **kwargs):
799 def setblocking(self, *args, **kwargs):
799 return object.__getattribute__(self, '_observedcall')(
800 return object.__getattribute__(self, '_observedcall')(
800 'setblocking', *args, **kwargs
801 'setblocking', *args, **kwargs
801 )
802 )
802
803
803 def settimeout(self, *args, **kwargs):
804 def settimeout(self, *args, **kwargs):
804 return object.__getattribute__(self, '_observedcall')(
805 return object.__getattribute__(self, '_observedcall')(
805 'settimeout', *args, **kwargs
806 'settimeout', *args, **kwargs
806 )
807 )
807
808
808 def gettimeout(self, *args, **kwargs):
809 def gettimeout(self, *args, **kwargs):
809 return object.__getattribute__(self, '_observedcall')(
810 return object.__getattribute__(self, '_observedcall')(
810 'gettimeout', *args, **kwargs
811 'gettimeout', *args, **kwargs
811 )
812 )
812
813
813 def setsockopt(self, *args, **kwargs):
814 def setsockopt(self, *args, **kwargs):
814 return object.__getattribute__(self, '_observedcall')(
815 return object.__getattribute__(self, '_observedcall')(
815 'setsockopt', *args, **kwargs
816 'setsockopt', *args, **kwargs
816 )
817 )
817
818
818
819
819 class baseproxyobserver(object):
820 class baseproxyobserver(object):
820 def __init__(self, fh, name, logdata, logdataapis):
821 def __init__(self, fh, name, logdata, logdataapis):
821 self.fh = fh
822 self.fh = fh
822 self.name = name
823 self.name = name
823 self.logdata = logdata
824 self.logdata = logdata
824 self.logdataapis = logdataapis
825 self.logdataapis = logdataapis
825
826
826 def _writedata(self, data):
827 def _writedata(self, data):
827 if not self.logdata:
828 if not self.logdata:
828 if self.logdataapis:
829 if self.logdataapis:
829 self.fh.write(b'\n')
830 self.fh.write(b'\n')
830 self.fh.flush()
831 self.fh.flush()
831 return
832 return
832
833
833 # Simple case writes all data on a single line.
834 # Simple case writes all data on a single line.
834 if b'\n' not in data:
835 if b'\n' not in data:
835 if self.logdataapis:
836 if self.logdataapis:
836 self.fh.write(b': %s\n' % stringutil.escapestr(data))
837 self.fh.write(b': %s\n' % stringutil.escapestr(data))
837 else:
838 else:
838 self.fh.write(
839 self.fh.write(
839 b'%s> %s\n' % (self.name, stringutil.escapestr(data))
840 b'%s> %s\n' % (self.name, stringutil.escapestr(data))
840 )
841 )
841 self.fh.flush()
842 self.fh.flush()
842 return
843 return
843
844
844 # Data with newlines is written to multiple lines.
845 # Data with newlines is written to multiple lines.
845 if self.logdataapis:
846 if self.logdataapis:
846 self.fh.write(b':\n')
847 self.fh.write(b':\n')
847
848
848 lines = data.splitlines(True)
849 lines = data.splitlines(True)
849 for line in lines:
850 for line in lines:
850 self.fh.write(
851 self.fh.write(
851 b'%s> %s\n' % (self.name, stringutil.escapestr(line))
852 b'%s> %s\n' % (self.name, stringutil.escapestr(line))
852 )
853 )
853 self.fh.flush()
854 self.fh.flush()
854
855
855
856
856 class fileobjectobserver(baseproxyobserver):
857 class fileobjectobserver(baseproxyobserver):
857 """Logs file object activity."""
858 """Logs file object activity."""
858
859
859 def __init__(
860 def __init__(
860 self, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
861 self, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
861 ):
862 ):
862 super(fileobjectobserver, self).__init__(fh, name, logdata, logdataapis)
863 super(fileobjectobserver, self).__init__(fh, name, logdata, logdataapis)
863 self.reads = reads
864 self.reads = reads
864 self.writes = writes
865 self.writes = writes
865
866
866 def read(self, res, size=-1):
867 def read(self, res, size=-1):
867 if not self.reads:
868 if not self.reads:
868 return
869 return
869 # Python 3 can return None from reads at EOF instead of empty strings.
870 # Python 3 can return None from reads at EOF instead of empty strings.
870 if res is None:
871 if res is None:
871 res = b''
872 res = b''
872
873
873 if size == -1 and res == b'':
874 if size == -1 and res == b'':
874 # Suppress pointless read(-1) calls that return
875 # Suppress pointless read(-1) calls that return
875 # nothing. These happen _a lot_ on Python 3, and there
876 # nothing. These happen _a lot_ on Python 3, and there
876 # doesn't seem to be a better workaround to have matching
877 # doesn't seem to be a better workaround to have matching
877 # Python 2 and 3 behavior. :(
878 # Python 2 and 3 behavior. :(
878 return
879 return
879
880
880 if self.logdataapis:
881 if self.logdataapis:
881 self.fh.write(b'%s> read(%d) -> %d' % (self.name, size, len(res)))
882 self.fh.write(b'%s> read(%d) -> %d' % (self.name, size, len(res)))
882
883
883 self._writedata(res)
884 self._writedata(res)
884
885
885 def readline(self, res, limit=-1):
886 def readline(self, res, limit=-1):
886 if not self.reads:
887 if not self.reads:
887 return
888 return
888
889
889 if self.logdataapis:
890 if self.logdataapis:
890 self.fh.write(b'%s> readline() -> %d' % (self.name, len(res)))
891 self.fh.write(b'%s> readline() -> %d' % (self.name, len(res)))
891
892
892 self._writedata(res)
893 self._writedata(res)
893
894
894 def readinto(self, res, dest):
895 def readinto(self, res, dest):
895 if not self.reads:
896 if not self.reads:
896 return
897 return
897
898
898 if self.logdataapis:
899 if self.logdataapis:
899 self.fh.write(
900 self.fh.write(
900 b'%s> readinto(%d) -> %r' % (self.name, len(dest), res)
901 b'%s> readinto(%d) -> %r' % (self.name, len(dest), res)
901 )
902 )
902
903
903 data = dest[0:res] if res is not None else b''
904 data = dest[0:res] if res is not None else b''
904
905
905 # _writedata() uses "in" operator and is confused by memoryview because
906 # _writedata() uses "in" operator and is confused by memoryview because
906 # characters are ints on Python 3.
907 # characters are ints on Python 3.
907 if isinstance(data, memoryview):
908 if isinstance(data, memoryview):
908 data = data.tobytes()
909 data = data.tobytes()
909
910
910 self._writedata(data)
911 self._writedata(data)
911
912
912 def write(self, res, data):
913 def write(self, res, data):
913 if not self.writes:
914 if not self.writes:
914 return
915 return
915
916
916 # Python 2 returns None from some write() calls. Python 3 (reasonably)
917 # Python 2 returns None from some write() calls. Python 3 (reasonably)
917 # returns the integer bytes written.
918 # returns the integer bytes written.
918 if res is None and data:
919 if res is None and data:
919 res = len(data)
920 res = len(data)
920
921
921 if self.logdataapis:
922 if self.logdataapis:
922 self.fh.write(b'%s> write(%d) -> %r' % (self.name, len(data), res))
923 self.fh.write(b'%s> write(%d) -> %r' % (self.name, len(data), res))
923
924
924 self._writedata(data)
925 self._writedata(data)
925
926
926 def flush(self, res):
927 def flush(self, res):
927 if not self.writes:
928 if not self.writes:
928 return
929 return
929
930
930 self.fh.write(b'%s> flush() -> %r\n' % (self.name, res))
931 self.fh.write(b'%s> flush() -> %r\n' % (self.name, res))
931
932
932 # For observedbufferedinputpipe.
933 # For observedbufferedinputpipe.
933 def bufferedread(self, res, size):
934 def bufferedread(self, res, size):
934 if not self.reads:
935 if not self.reads:
935 return
936 return
936
937
937 if self.logdataapis:
938 if self.logdataapis:
938 self.fh.write(
939 self.fh.write(
939 b'%s> bufferedread(%d) -> %d' % (self.name, size, len(res))
940 b'%s> bufferedread(%d) -> %d' % (self.name, size, len(res))
940 )
941 )
941
942
942 self._writedata(res)
943 self._writedata(res)
943
944
944 def bufferedreadline(self, res):
945 def bufferedreadline(self, res):
945 if not self.reads:
946 if not self.reads:
946 return
947 return
947
948
948 if self.logdataapis:
949 if self.logdataapis:
949 self.fh.write(
950 self.fh.write(
950 b'%s> bufferedreadline() -> %d' % (self.name, len(res))
951 b'%s> bufferedreadline() -> %d' % (self.name, len(res))
951 )
952 )
952
953
953 self._writedata(res)
954 self._writedata(res)
954
955
955
956
956 def makeloggingfileobject(
957 def makeloggingfileobject(
957 logh, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
958 logh, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
958 ):
959 ):
959 """Turn a file object into a logging file object."""
960 """Turn a file object into a logging file object."""
960
961
961 observer = fileobjectobserver(
962 observer = fileobjectobserver(
962 logh,
963 logh,
963 name,
964 name,
964 reads=reads,
965 reads=reads,
965 writes=writes,
966 writes=writes,
966 logdata=logdata,
967 logdata=logdata,
967 logdataapis=logdataapis,
968 logdataapis=logdataapis,
968 )
969 )
969 return fileobjectproxy(fh, observer)
970 return fileobjectproxy(fh, observer)
970
971
971
972
972 class socketobserver(baseproxyobserver):
973 class socketobserver(baseproxyobserver):
973 """Logs socket activity."""
974 """Logs socket activity."""
974
975
975 def __init__(
976 def __init__(
976 self,
977 self,
977 fh,
978 fh,
978 name,
979 name,
979 reads=True,
980 reads=True,
980 writes=True,
981 writes=True,
981 states=True,
982 states=True,
982 logdata=False,
983 logdata=False,
983 logdataapis=True,
984 logdataapis=True,
984 ):
985 ):
985 super(socketobserver, self).__init__(fh, name, logdata, logdataapis)
986 super(socketobserver, self).__init__(fh, name, logdata, logdataapis)
986 self.reads = reads
987 self.reads = reads
987 self.writes = writes
988 self.writes = writes
988 self.states = states
989 self.states = states
989
990
990 def makefile(self, res, mode=None, bufsize=None):
991 def makefile(self, res, mode=None, bufsize=None):
991 if not self.states:
992 if not self.states:
992 return
993 return
993
994
994 self.fh.write(b'%s> makefile(%r, %r)\n' % (self.name, mode, bufsize))
995 self.fh.write(b'%s> makefile(%r, %r)\n' % (self.name, mode, bufsize))
995
996
996 def recv(self, res, size, flags=0):
997 def recv(self, res, size, flags=0):
997 if not self.reads:
998 if not self.reads:
998 return
999 return
999
1000
1000 if self.logdataapis:
1001 if self.logdataapis:
1001 self.fh.write(
1002 self.fh.write(
1002 b'%s> recv(%d, %d) -> %d' % (self.name, size, flags, len(res))
1003 b'%s> recv(%d, %d) -> %d' % (self.name, size, flags, len(res))
1003 )
1004 )
1004 self._writedata(res)
1005 self._writedata(res)
1005
1006
1006 def recvfrom(self, res, size, flags=0):
1007 def recvfrom(self, res, size, flags=0):
1007 if not self.reads:
1008 if not self.reads:
1008 return
1009 return
1009
1010
1010 if self.logdataapis:
1011 if self.logdataapis:
1011 self.fh.write(
1012 self.fh.write(
1012 b'%s> recvfrom(%d, %d) -> %d'
1013 b'%s> recvfrom(%d, %d) -> %d'
1013 % (self.name, size, flags, len(res[0]))
1014 % (self.name, size, flags, len(res[0]))
1014 )
1015 )
1015
1016
1016 self._writedata(res[0])
1017 self._writedata(res[0])
1017
1018
1018 def recvfrom_into(self, res, buf, size, flags=0):
1019 def recvfrom_into(self, res, buf, size, flags=0):
1019 if not self.reads:
1020 if not self.reads:
1020 return
1021 return
1021
1022
1022 if self.logdataapis:
1023 if self.logdataapis:
1023 self.fh.write(
1024 self.fh.write(
1024 b'%s> recvfrom_into(%d, %d) -> %d'
1025 b'%s> recvfrom_into(%d, %d) -> %d'
1025 % (self.name, size, flags, res[0])
1026 % (self.name, size, flags, res[0])
1026 )
1027 )
1027
1028
1028 self._writedata(buf[0 : res[0]])
1029 self._writedata(buf[0 : res[0]])
1029
1030
1030 def recv_into(self, res, buf, size=0, flags=0):
1031 def recv_into(self, res, buf, size=0, flags=0):
1031 if not self.reads:
1032 if not self.reads:
1032 return
1033 return
1033
1034
1034 if self.logdataapis:
1035 if self.logdataapis:
1035 self.fh.write(
1036 self.fh.write(
1036 b'%s> recv_into(%d, %d) -> %d' % (self.name, size, flags, res)
1037 b'%s> recv_into(%d, %d) -> %d' % (self.name, size, flags, res)
1037 )
1038 )
1038
1039
1039 self._writedata(buf[0:res])
1040 self._writedata(buf[0:res])
1040
1041
1041 def send(self, res, data, flags=0):
1042 def send(self, res, data, flags=0):
1042 if not self.writes:
1043 if not self.writes:
1043 return
1044 return
1044
1045
1045 self.fh.write(
1046 self.fh.write(
1046 b'%s> send(%d, %d) -> %d' % (self.name, len(data), flags, len(res))
1047 b'%s> send(%d, %d) -> %d' % (self.name, len(data), flags, len(res))
1047 )
1048 )
1048 self._writedata(data)
1049 self._writedata(data)
1049
1050
1050 def sendall(self, res, data, flags=0):
1051 def sendall(self, res, data, flags=0):
1051 if not self.writes:
1052 if not self.writes:
1052 return
1053 return
1053
1054
1054 if self.logdataapis:
1055 if self.logdataapis:
1055 # Returns None on success. So don't bother reporting return value.
1056 # Returns None on success. So don't bother reporting return value.
1056 self.fh.write(
1057 self.fh.write(
1057 b'%s> sendall(%d, %d)' % (self.name, len(data), flags)
1058 b'%s> sendall(%d, %d)' % (self.name, len(data), flags)
1058 )
1059 )
1059
1060
1060 self._writedata(data)
1061 self._writedata(data)
1061
1062
1062 def sendto(self, res, data, flagsoraddress, address=None):
1063 def sendto(self, res, data, flagsoraddress, address=None):
1063 if not self.writes:
1064 if not self.writes:
1064 return
1065 return
1065
1066
1066 if address:
1067 if address:
1067 flags = flagsoraddress
1068 flags = flagsoraddress
1068 else:
1069 else:
1069 flags = 0
1070 flags = 0
1070
1071
1071 if self.logdataapis:
1072 if self.logdataapis:
1072 self.fh.write(
1073 self.fh.write(
1073 b'%s> sendto(%d, %d, %r) -> %d'
1074 b'%s> sendto(%d, %d, %r) -> %d'
1074 % (self.name, len(data), flags, address, res)
1075 % (self.name, len(data), flags, address, res)
1075 )
1076 )
1076
1077
1077 self._writedata(data)
1078 self._writedata(data)
1078
1079
1079 def setblocking(self, res, flag):
1080 def setblocking(self, res, flag):
1080 if not self.states:
1081 if not self.states:
1081 return
1082 return
1082
1083
1083 self.fh.write(b'%s> setblocking(%r)\n' % (self.name, flag))
1084 self.fh.write(b'%s> setblocking(%r)\n' % (self.name, flag))
1084
1085
1085 def settimeout(self, res, value):
1086 def settimeout(self, res, value):
1086 if not self.states:
1087 if not self.states:
1087 return
1088 return
1088
1089
1089 self.fh.write(b'%s> settimeout(%r)\n' % (self.name, value))
1090 self.fh.write(b'%s> settimeout(%r)\n' % (self.name, value))
1090
1091
1091 def gettimeout(self, res):
1092 def gettimeout(self, res):
1092 if not self.states:
1093 if not self.states:
1093 return
1094 return
1094
1095
1095 self.fh.write(b'%s> gettimeout() -> %f\n' % (self.name, res))
1096 self.fh.write(b'%s> gettimeout() -> %f\n' % (self.name, res))
1096
1097
1097 def setsockopt(self, res, level, optname, value):
1098 def setsockopt(self, res, level, optname, value):
1098 if not self.states:
1099 if not self.states:
1099 return
1100 return
1100
1101
1101 self.fh.write(
1102 self.fh.write(
1102 b'%s> setsockopt(%r, %r, %r) -> %r\n'
1103 b'%s> setsockopt(%r, %r, %r) -> %r\n'
1103 % (self.name, level, optname, value, res)
1104 % (self.name, level, optname, value, res)
1104 )
1105 )
1105
1106
1106
1107
1107 def makeloggingsocket(
1108 def makeloggingsocket(
1108 logh,
1109 logh,
1109 fh,
1110 fh,
1110 name,
1111 name,
1111 reads=True,
1112 reads=True,
1112 writes=True,
1113 writes=True,
1113 states=True,
1114 states=True,
1114 logdata=False,
1115 logdata=False,
1115 logdataapis=True,
1116 logdataapis=True,
1116 ):
1117 ):
1117 """Turn a socket into a logging socket."""
1118 """Turn a socket into a logging socket."""
1118
1119
1119 observer = socketobserver(
1120 observer = socketobserver(
1120 logh,
1121 logh,
1121 name,
1122 name,
1122 reads=reads,
1123 reads=reads,
1123 writes=writes,
1124 writes=writes,
1124 states=states,
1125 states=states,
1125 logdata=logdata,
1126 logdata=logdata,
1126 logdataapis=logdataapis,
1127 logdataapis=logdataapis,
1127 )
1128 )
1128 return socketproxy(fh, observer)
1129 return socketproxy(fh, observer)
1129
1130
1130
1131
1131 def version():
1132 def version():
1132 """Return version information if available."""
1133 """Return version information if available."""
1133 try:
1134 try:
1134 from . import __version__
1135 from . import __version__
1135
1136
1136 return __version__.version
1137 return __version__.version
1137 except ImportError:
1138 except ImportError:
1138 return b'unknown'
1139 return b'unknown'
1139
1140
1140
1141
1141 def versiontuple(v=None, n=4):
1142 def versiontuple(v=None, n=4):
1142 """Parses a Mercurial version string into an N-tuple.
1143 """Parses a Mercurial version string into an N-tuple.
1143
1144
1144 The version string to be parsed is specified with the ``v`` argument.
1145 The version string to be parsed is specified with the ``v`` argument.
1145 If it isn't defined, the current Mercurial version string will be parsed.
1146 If it isn't defined, the current Mercurial version string will be parsed.
1146
1147
1147 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1148 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1148 returned values:
1149 returned values:
1149
1150
1150 >>> v = b'3.6.1+190-df9b73d2d444'
1151 >>> v = b'3.6.1+190-df9b73d2d444'
1151 >>> versiontuple(v, 2)
1152 >>> versiontuple(v, 2)
1152 (3, 6)
1153 (3, 6)
1153 >>> versiontuple(v, 3)
1154 >>> versiontuple(v, 3)
1154 (3, 6, 1)
1155 (3, 6, 1)
1155 >>> versiontuple(v, 4)
1156 >>> versiontuple(v, 4)
1156 (3, 6, 1, '190-df9b73d2d444')
1157 (3, 6, 1, '190-df9b73d2d444')
1157
1158
1158 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1159 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1159 (3, 6, 1, '190-df9b73d2d444+20151118')
1160 (3, 6, 1, '190-df9b73d2d444+20151118')
1160
1161
1161 >>> v = b'3.6'
1162 >>> v = b'3.6'
1162 >>> versiontuple(v, 2)
1163 >>> versiontuple(v, 2)
1163 (3, 6)
1164 (3, 6)
1164 >>> versiontuple(v, 3)
1165 >>> versiontuple(v, 3)
1165 (3, 6, None)
1166 (3, 6, None)
1166 >>> versiontuple(v, 4)
1167 >>> versiontuple(v, 4)
1167 (3, 6, None, None)
1168 (3, 6, None, None)
1168
1169
1169 >>> v = b'3.9-rc'
1170 >>> v = b'3.9-rc'
1170 >>> versiontuple(v, 2)
1171 >>> versiontuple(v, 2)
1171 (3, 9)
1172 (3, 9)
1172 >>> versiontuple(v, 3)
1173 >>> versiontuple(v, 3)
1173 (3, 9, None)
1174 (3, 9, None)
1174 >>> versiontuple(v, 4)
1175 >>> versiontuple(v, 4)
1175 (3, 9, None, 'rc')
1176 (3, 9, None, 'rc')
1176
1177
1177 >>> v = b'3.9-rc+2-02a8fea4289b'
1178 >>> v = b'3.9-rc+2-02a8fea4289b'
1178 >>> versiontuple(v, 2)
1179 >>> versiontuple(v, 2)
1179 (3, 9)
1180 (3, 9)
1180 >>> versiontuple(v, 3)
1181 >>> versiontuple(v, 3)
1181 (3, 9, None)
1182 (3, 9, None)
1182 >>> versiontuple(v, 4)
1183 >>> versiontuple(v, 4)
1183 (3, 9, None, 'rc+2-02a8fea4289b')
1184 (3, 9, None, 'rc+2-02a8fea4289b')
1184
1185
1185 >>> versiontuple(b'4.6rc0')
1186 >>> versiontuple(b'4.6rc0')
1186 (4, 6, None, 'rc0')
1187 (4, 6, None, 'rc0')
1187 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1188 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1188 (4, 6, None, 'rc0+12-425d55e54f98')
1189 (4, 6, None, 'rc0+12-425d55e54f98')
1189 >>> versiontuple(b'.1.2.3')
1190 >>> versiontuple(b'.1.2.3')
1190 (None, None, None, '.1.2.3')
1191 (None, None, None, '.1.2.3')
1191 >>> versiontuple(b'12.34..5')
1192 >>> versiontuple(b'12.34..5')
1192 (12, 34, None, '..5')
1193 (12, 34, None, '..5')
1193 >>> versiontuple(b'1.2.3.4.5.6')
1194 >>> versiontuple(b'1.2.3.4.5.6')
1194 (1, 2, 3, '.4.5.6')
1195 (1, 2, 3, '.4.5.6')
1195 """
1196 """
1196 if not v:
1197 if not v:
1197 v = version()
1198 v = version()
1198 m = remod.match(br'(\d+(?:\.\d+){,2})[+-]?(.*)', v)
1199 m = remod.match(br'(\d+(?:\.\d+){,2})[+-]?(.*)', v)
1199 if not m:
1200 if not m:
1200 vparts, extra = b'', v
1201 vparts, extra = b'', v
1201 elif m.group(2):
1202 elif m.group(2):
1202 vparts, extra = m.groups()
1203 vparts, extra = m.groups()
1203 else:
1204 else:
1204 vparts, extra = m.group(1), None
1205 vparts, extra = m.group(1), None
1205
1206
1206 assert vparts is not None # help pytype
1207 assert vparts is not None # help pytype
1207
1208
1208 vints = []
1209 vints = []
1209 for i in vparts.split(b'.'):
1210 for i in vparts.split(b'.'):
1210 try:
1211 try:
1211 vints.append(int(i))
1212 vints.append(int(i))
1212 except ValueError:
1213 except ValueError:
1213 break
1214 break
1214 # (3, 6) -> (3, 6, None)
1215 # (3, 6) -> (3, 6, None)
1215 while len(vints) < 3:
1216 while len(vints) < 3:
1216 vints.append(None)
1217 vints.append(None)
1217
1218
1218 if n == 2:
1219 if n == 2:
1219 return (vints[0], vints[1])
1220 return (vints[0], vints[1])
1220 if n == 3:
1221 if n == 3:
1221 return (vints[0], vints[1], vints[2])
1222 return (vints[0], vints[1], vints[2])
1222 if n == 4:
1223 if n == 4:
1223 return (vints[0], vints[1], vints[2], extra)
1224 return (vints[0], vints[1], vints[2], extra)
1224
1225
1225
1226
1226 def cachefunc(func):
1227 def cachefunc(func):
1227 '''cache the result of function calls'''
1228 '''cache the result of function calls'''
1228 # XXX doesn't handle keywords args
1229 # XXX doesn't handle keywords args
1229 if func.__code__.co_argcount == 0:
1230 if func.__code__.co_argcount == 0:
1230 listcache = []
1231 listcache = []
1231
1232
1232 def f():
1233 def f():
1233 if len(listcache) == 0:
1234 if len(listcache) == 0:
1234 listcache.append(func())
1235 listcache.append(func())
1235 return listcache[0]
1236 return listcache[0]
1236
1237
1237 return f
1238 return f
1238 cache = {}
1239 cache = {}
1239 if func.__code__.co_argcount == 1:
1240 if func.__code__.co_argcount == 1:
1240 # we gain a small amount of time because
1241 # we gain a small amount of time because
1241 # we don't need to pack/unpack the list
1242 # we don't need to pack/unpack the list
1242 def f(arg):
1243 def f(arg):
1243 if arg not in cache:
1244 if arg not in cache:
1244 cache[arg] = func(arg)
1245 cache[arg] = func(arg)
1245 return cache[arg]
1246 return cache[arg]
1246
1247
1247 else:
1248 else:
1248
1249
1249 def f(*args):
1250 def f(*args):
1250 if args not in cache:
1251 if args not in cache:
1251 cache[args] = func(*args)
1252 cache[args] = func(*args)
1252 return cache[args]
1253 return cache[args]
1253
1254
1254 return f
1255 return f
1255
1256
1256
1257
1257 class cow(object):
1258 class cow(object):
1258 """helper class to make copy-on-write easier
1259 """helper class to make copy-on-write easier
1259
1260
1260 Call preparewrite before doing any writes.
1261 Call preparewrite before doing any writes.
1261 """
1262 """
1262
1263
1263 def preparewrite(self):
1264 def preparewrite(self):
1264 """call this before writes, return self or a copied new object"""
1265 """call this before writes, return self or a copied new object"""
1265 if getattr(self, '_copied', 0):
1266 if getattr(self, '_copied', 0):
1266 self._copied -= 1
1267 self._copied -= 1
1267 # Function cow.__init__ expects 1 arg(s), got 2 [wrong-arg-count]
1268 # Function cow.__init__ expects 1 arg(s), got 2 [wrong-arg-count]
1268 return self.__class__(self) # pytype: disable=wrong-arg-count
1269 return self.__class__(self) # pytype: disable=wrong-arg-count
1269 return self
1270 return self
1270
1271
1271 def copy(self):
1272 def copy(self):
1272 """always do a cheap copy"""
1273 """always do a cheap copy"""
1273 self._copied = getattr(self, '_copied', 0) + 1
1274 self._copied = getattr(self, '_copied', 0) + 1
1274 return self
1275 return self
1275
1276
1276
1277
1277 class sortdict(collections.OrderedDict):
1278 class sortdict(collections.OrderedDict):
1278 """a simple sorted dictionary
1279 """a simple sorted dictionary
1279
1280
1280 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1281 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1281 >>> d2 = d1.copy()
1282 >>> d2 = d1.copy()
1282 >>> d2
1283 >>> d2
1283 sortdict([('a', 0), ('b', 1)])
1284 sortdict([('a', 0), ('b', 1)])
1284 >>> d2.update([(b'a', 2)])
1285 >>> d2.update([(b'a', 2)])
1285 >>> list(d2.keys()) # should still be in last-set order
1286 >>> list(d2.keys()) # should still be in last-set order
1286 ['b', 'a']
1287 ['b', 'a']
1287 >>> d1.insert(1, b'a.5', 0.5)
1288 >>> d1.insert(1, b'a.5', 0.5)
1288 >>> d1
1289 >>> d1
1289 sortdict([('a', 0), ('a.5', 0.5), ('b', 1)])
1290 sortdict([('a', 0), ('a.5', 0.5), ('b', 1)])
1290 """
1291 """
1291
1292
1292 def __setitem__(self, key, value):
1293 def __setitem__(self, key, value):
1293 if key in self:
1294 if key in self:
1294 del self[key]
1295 del self[key]
1295 super(sortdict, self).__setitem__(key, value)
1296 super(sortdict, self).__setitem__(key, value)
1296
1297
1297 if pycompat.ispypy:
1298 if pycompat.ispypy:
1298 # __setitem__() isn't called as of PyPy 5.8.0
1299 # __setitem__() isn't called as of PyPy 5.8.0
1299 def update(self, src, **f):
1300 def update(self, src, **f):
1300 if isinstance(src, dict):
1301 if isinstance(src, dict):
1301 src = pycompat.iteritems(src)
1302 src = pycompat.iteritems(src)
1302 for k, v in src:
1303 for k, v in src:
1303 self[k] = v
1304 self[k] = v
1304 for k in f:
1305 for k in f:
1305 self[k] = f[k]
1306 self[k] = f[k]
1306
1307
1307 def insert(self, position, key, value):
1308 def insert(self, position, key, value):
1308 for (i, (k, v)) in enumerate(list(self.items())):
1309 for (i, (k, v)) in enumerate(list(self.items())):
1309 if i == position:
1310 if i == position:
1310 self[key] = value
1311 self[key] = value
1311 if i >= position:
1312 if i >= position:
1312 del self[k]
1313 del self[k]
1313 self[k] = v
1314 self[k] = v
1314
1315
1315
1316
1316 class cowdict(cow, dict):
1317 class cowdict(cow, dict):
1317 """copy-on-write dict
1318 """copy-on-write dict
1318
1319
1319 Be sure to call d = d.preparewrite() before writing to d.
1320 Be sure to call d = d.preparewrite() before writing to d.
1320
1321
1321 >>> a = cowdict()
1322 >>> a = cowdict()
1322 >>> a is a.preparewrite()
1323 >>> a is a.preparewrite()
1323 True
1324 True
1324 >>> b = a.copy()
1325 >>> b = a.copy()
1325 >>> b is a
1326 >>> b is a
1326 True
1327 True
1327 >>> c = b.copy()
1328 >>> c = b.copy()
1328 >>> c is a
1329 >>> c is a
1329 True
1330 True
1330 >>> a = a.preparewrite()
1331 >>> a = a.preparewrite()
1331 >>> b is a
1332 >>> b is a
1332 False
1333 False
1333 >>> a is a.preparewrite()
1334 >>> a is a.preparewrite()
1334 True
1335 True
1335 >>> c = c.preparewrite()
1336 >>> c = c.preparewrite()
1336 >>> b is c
1337 >>> b is c
1337 False
1338 False
1338 >>> b is b.preparewrite()
1339 >>> b is b.preparewrite()
1339 True
1340 True
1340 """
1341 """
1341
1342
1342
1343
1343 class cowsortdict(cow, sortdict):
1344 class cowsortdict(cow, sortdict):
1344 """copy-on-write sortdict
1345 """copy-on-write sortdict
1345
1346
1346 Be sure to call d = d.preparewrite() before writing to d.
1347 Be sure to call d = d.preparewrite() before writing to d.
1347 """
1348 """
1348
1349
1349
1350
1350 class transactional(object): # pytype: disable=ignored-metaclass
1351 class transactional(object): # pytype: disable=ignored-metaclass
1351 """Base class for making a transactional type into a context manager."""
1352 """Base class for making a transactional type into a context manager."""
1352
1353
1353 __metaclass__ = abc.ABCMeta
1354 __metaclass__ = abc.ABCMeta
1354
1355
1355 @abc.abstractmethod
1356 @abc.abstractmethod
1356 def close(self):
1357 def close(self):
1357 """Successfully closes the transaction."""
1358 """Successfully closes the transaction."""
1358
1359
1359 @abc.abstractmethod
1360 @abc.abstractmethod
1360 def release(self):
1361 def release(self):
1361 """Marks the end of the transaction.
1362 """Marks the end of the transaction.
1362
1363
1363 If the transaction has not been closed, it will be aborted.
1364 If the transaction has not been closed, it will be aborted.
1364 """
1365 """
1365
1366
1366 def __enter__(self):
1367 def __enter__(self):
1367 return self
1368 return self
1368
1369
1369 def __exit__(self, exc_type, exc_val, exc_tb):
1370 def __exit__(self, exc_type, exc_val, exc_tb):
1370 try:
1371 try:
1371 if exc_type is None:
1372 if exc_type is None:
1372 self.close()
1373 self.close()
1373 finally:
1374 finally:
1374 self.release()
1375 self.release()
1375
1376
1376
1377
1377 @contextlib.contextmanager
1378 @contextlib.contextmanager
1378 def acceptintervention(tr=None):
1379 def acceptintervention(tr=None):
1379 """A context manager that closes the transaction on InterventionRequired
1380 """A context manager that closes the transaction on InterventionRequired
1380
1381
1381 If no transaction was provided, this simply runs the body and returns
1382 If no transaction was provided, this simply runs the body and returns
1382 """
1383 """
1383 if not tr:
1384 if not tr:
1384 yield
1385 yield
1385 return
1386 return
1386 try:
1387 try:
1387 yield
1388 yield
1388 tr.close()
1389 tr.close()
1389 except error.InterventionRequired:
1390 except error.InterventionRequired:
1390 tr.close()
1391 tr.close()
1391 raise
1392 raise
1392 finally:
1393 finally:
1393 tr.release()
1394 tr.release()
1394
1395
1395
1396
1396 @contextlib.contextmanager
1397 @contextlib.contextmanager
1397 def nullcontextmanager(enter_result=None):
1398 def nullcontextmanager(enter_result=None):
1398 yield enter_result
1399 yield enter_result
1399
1400
1400
1401
1401 class _lrucachenode(object):
1402 class _lrucachenode(object):
1402 """A node in a doubly linked list.
1403 """A node in a doubly linked list.
1403
1404
1404 Holds a reference to nodes on either side as well as a key-value
1405 Holds a reference to nodes on either side as well as a key-value
1405 pair for the dictionary entry.
1406 pair for the dictionary entry.
1406 """
1407 """
1407
1408
1408 __slots__ = ('next', 'prev', 'key', 'value', 'cost')
1409 __slots__ = ('next', 'prev', 'key', 'value', 'cost')
1409
1410
1410 def __init__(self):
1411 def __init__(self):
1411 self.next = self
1412 self.next = self
1412 self.prev = self
1413 self.prev = self
1413
1414
1414 self.key = _notset
1415 self.key = _notset
1415 self.value = None
1416 self.value = None
1416 self.cost = 0
1417 self.cost = 0
1417
1418
1418 def markempty(self):
1419 def markempty(self):
1419 """Mark the node as emptied."""
1420 """Mark the node as emptied."""
1420 self.key = _notset
1421 self.key = _notset
1421 self.value = None
1422 self.value = None
1422 self.cost = 0
1423 self.cost = 0
1423
1424
1424
1425
1425 class lrucachedict(object):
1426 class lrucachedict(object):
1426 """Dict that caches most recent accesses and sets.
1427 """Dict that caches most recent accesses and sets.
1427
1428
1428 The dict consists of an actual backing dict - indexed by original
1429 The dict consists of an actual backing dict - indexed by original
1429 key - and a doubly linked circular list defining the order of entries in
1430 key - and a doubly linked circular list defining the order of entries in
1430 the cache.
1431 the cache.
1431
1432
1432 The head node is the newest entry in the cache. If the cache is full,
1433 The head node is the newest entry in the cache. If the cache is full,
1433 we recycle head.prev and make it the new head. Cache accesses result in
1434 we recycle head.prev and make it the new head. Cache accesses result in
1434 the node being moved to before the existing head and being marked as the
1435 the node being moved to before the existing head and being marked as the
1435 new head node.
1436 new head node.
1436
1437
1437 Items in the cache can be inserted with an optional "cost" value. This is
1438 Items in the cache can be inserted with an optional "cost" value. This is
1438 simply an integer that is specified by the caller. The cache can be queried
1439 simply an integer that is specified by the caller. The cache can be queried
1439 for the total cost of all items presently in the cache.
1440 for the total cost of all items presently in the cache.
1440
1441
1441 The cache can also define a maximum cost. If a cache insertion would
1442 The cache can also define a maximum cost. If a cache insertion would
1442 cause the total cost of the cache to go beyond the maximum cost limit,
1443 cause the total cost of the cache to go beyond the maximum cost limit,
1443 nodes will be evicted to make room for the new code. This can be used
1444 nodes will be evicted to make room for the new code. This can be used
1444 to e.g. set a max memory limit and associate an estimated bytes size
1445 to e.g. set a max memory limit and associate an estimated bytes size
1445 cost to each item in the cache. By default, no maximum cost is enforced.
1446 cost to each item in the cache. By default, no maximum cost is enforced.
1446 """
1447 """
1447
1448
1448 def __init__(self, max, maxcost=0):
1449 def __init__(self, max, maxcost=0):
1449 self._cache = {}
1450 self._cache = {}
1450
1451
1451 self._head = _lrucachenode()
1452 self._head = _lrucachenode()
1452 self._size = 1
1453 self._size = 1
1453 self.capacity = max
1454 self.capacity = max
1454 self.totalcost = 0
1455 self.totalcost = 0
1455 self.maxcost = maxcost
1456 self.maxcost = maxcost
1456
1457
1457 def __len__(self):
1458 def __len__(self):
1458 return len(self._cache)
1459 return len(self._cache)
1459
1460
1460 def __contains__(self, k):
1461 def __contains__(self, k):
1461 return k in self._cache
1462 return k in self._cache
1462
1463
1463 def __iter__(self):
1464 def __iter__(self):
1464 # We don't have to iterate in cache order, but why not.
1465 # We don't have to iterate in cache order, but why not.
1465 n = self._head
1466 n = self._head
1466 for i in range(len(self._cache)):
1467 for i in range(len(self._cache)):
1467 yield n.key
1468 yield n.key
1468 n = n.next
1469 n = n.next
1469
1470
1470 def __getitem__(self, k):
1471 def __getitem__(self, k):
1471 node = self._cache[k]
1472 node = self._cache[k]
1472 self._movetohead(node)
1473 self._movetohead(node)
1473 return node.value
1474 return node.value
1474
1475
1475 def insert(self, k, v, cost=0):
1476 def insert(self, k, v, cost=0):
1476 """Insert a new item in the cache with optional cost value."""
1477 """Insert a new item in the cache with optional cost value."""
1477 node = self._cache.get(k)
1478 node = self._cache.get(k)
1478 # Replace existing value and mark as newest.
1479 # Replace existing value and mark as newest.
1479 if node is not None:
1480 if node is not None:
1480 self.totalcost -= node.cost
1481 self.totalcost -= node.cost
1481 node.value = v
1482 node.value = v
1482 node.cost = cost
1483 node.cost = cost
1483 self.totalcost += cost
1484 self.totalcost += cost
1484 self._movetohead(node)
1485 self._movetohead(node)
1485
1486
1486 if self.maxcost:
1487 if self.maxcost:
1487 self._enforcecostlimit()
1488 self._enforcecostlimit()
1488
1489
1489 return
1490 return
1490
1491
1491 if self._size < self.capacity:
1492 if self._size < self.capacity:
1492 node = self._addcapacity()
1493 node = self._addcapacity()
1493 else:
1494 else:
1494 # Grab the last/oldest item.
1495 # Grab the last/oldest item.
1495 node = self._head.prev
1496 node = self._head.prev
1496
1497
1497 # At capacity. Kill the old entry.
1498 # At capacity. Kill the old entry.
1498 if node.key is not _notset:
1499 if node.key is not _notset:
1499 self.totalcost -= node.cost
1500 self.totalcost -= node.cost
1500 del self._cache[node.key]
1501 del self._cache[node.key]
1501
1502
1502 node.key = k
1503 node.key = k
1503 node.value = v
1504 node.value = v
1504 node.cost = cost
1505 node.cost = cost
1505 self.totalcost += cost
1506 self.totalcost += cost
1506 self._cache[k] = node
1507 self._cache[k] = node
1507 # And mark it as newest entry. No need to adjust order since it
1508 # And mark it as newest entry. No need to adjust order since it
1508 # is already self._head.prev.
1509 # is already self._head.prev.
1509 self._head = node
1510 self._head = node
1510
1511
1511 if self.maxcost:
1512 if self.maxcost:
1512 self._enforcecostlimit()
1513 self._enforcecostlimit()
1513
1514
1514 def __setitem__(self, k, v):
1515 def __setitem__(self, k, v):
1515 self.insert(k, v)
1516 self.insert(k, v)
1516
1517
1517 def __delitem__(self, k):
1518 def __delitem__(self, k):
1518 self.pop(k)
1519 self.pop(k)
1519
1520
1520 def pop(self, k, default=_notset):
1521 def pop(self, k, default=_notset):
1521 try:
1522 try:
1522 node = self._cache.pop(k)
1523 node = self._cache.pop(k)
1523 except KeyError:
1524 except KeyError:
1524 if default is _notset:
1525 if default is _notset:
1525 raise
1526 raise
1526 return default
1527 return default
1527
1528
1528 assert node is not None # help pytype
1529 assert node is not None # help pytype
1529 value = node.value
1530 value = node.value
1530 self.totalcost -= node.cost
1531 self.totalcost -= node.cost
1531 node.markempty()
1532 node.markempty()
1532
1533
1533 # Temporarily mark as newest item before re-adjusting head to make
1534 # Temporarily mark as newest item before re-adjusting head to make
1534 # this node the oldest item.
1535 # this node the oldest item.
1535 self._movetohead(node)
1536 self._movetohead(node)
1536 self._head = node.next
1537 self._head = node.next
1537
1538
1538 return value
1539 return value
1539
1540
1540 # Additional dict methods.
1541 # Additional dict methods.
1541
1542
1542 def get(self, k, default=None):
1543 def get(self, k, default=None):
1543 try:
1544 try:
1544 return self.__getitem__(k)
1545 return self.__getitem__(k)
1545 except KeyError:
1546 except KeyError:
1546 return default
1547 return default
1547
1548
1548 def peek(self, k, default=_notset):
1549 def peek(self, k, default=_notset):
1549 """Get the specified item without moving it to the head
1550 """Get the specified item without moving it to the head
1550
1551
1551 Unlike get(), this doesn't mutate the internal state. But be aware
1552 Unlike get(), this doesn't mutate the internal state. But be aware
1552 that it doesn't mean peek() is thread safe.
1553 that it doesn't mean peek() is thread safe.
1553 """
1554 """
1554 try:
1555 try:
1555 node = self._cache[k]
1556 node = self._cache[k]
1556 assert node is not None # help pytype
1557 assert node is not None # help pytype
1557 return node.value
1558 return node.value
1558 except KeyError:
1559 except KeyError:
1559 if default is _notset:
1560 if default is _notset:
1560 raise
1561 raise
1561 return default
1562 return default
1562
1563
1563 def clear(self):
1564 def clear(self):
1564 n = self._head
1565 n = self._head
1565 while n.key is not _notset:
1566 while n.key is not _notset:
1566 self.totalcost -= n.cost
1567 self.totalcost -= n.cost
1567 n.markempty()
1568 n.markempty()
1568 n = n.next
1569 n = n.next
1569
1570
1570 self._cache.clear()
1571 self._cache.clear()
1571
1572
1572 def copy(self, capacity=None, maxcost=0):
1573 def copy(self, capacity=None, maxcost=0):
1573 """Create a new cache as a copy of the current one.
1574 """Create a new cache as a copy of the current one.
1574
1575
1575 By default, the new cache has the same capacity as the existing one.
1576 By default, the new cache has the same capacity as the existing one.
1576 But, the cache capacity can be changed as part of performing the
1577 But, the cache capacity can be changed as part of performing the
1577 copy.
1578 copy.
1578
1579
1579 Items in the copy have an insertion/access order matching this
1580 Items in the copy have an insertion/access order matching this
1580 instance.
1581 instance.
1581 """
1582 """
1582
1583
1583 capacity = capacity or self.capacity
1584 capacity = capacity or self.capacity
1584 maxcost = maxcost or self.maxcost
1585 maxcost = maxcost or self.maxcost
1585 result = lrucachedict(capacity, maxcost=maxcost)
1586 result = lrucachedict(capacity, maxcost=maxcost)
1586
1587
1587 # We copy entries by iterating in oldest-to-newest order so the copy
1588 # We copy entries by iterating in oldest-to-newest order so the copy
1588 # has the correct ordering.
1589 # has the correct ordering.
1589
1590
1590 # Find the first non-empty entry.
1591 # Find the first non-empty entry.
1591 n = self._head.prev
1592 n = self._head.prev
1592 while n.key is _notset and n is not self._head:
1593 while n.key is _notset and n is not self._head:
1593 n = n.prev
1594 n = n.prev
1594
1595
1595 # We could potentially skip the first N items when decreasing capacity.
1596 # We could potentially skip the first N items when decreasing capacity.
1596 # But let's keep it simple unless it is a performance problem.
1597 # But let's keep it simple unless it is a performance problem.
1597 for i in range(len(self._cache)):
1598 for i in range(len(self._cache)):
1598 result.insert(n.key, n.value, cost=n.cost)
1599 result.insert(n.key, n.value, cost=n.cost)
1599 n = n.prev
1600 n = n.prev
1600
1601
1601 return result
1602 return result
1602
1603
1603 def popoldest(self):
1604 def popoldest(self):
1604 """Remove the oldest item from the cache.
1605 """Remove the oldest item from the cache.
1605
1606
1606 Returns the (key, value) describing the removed cache entry.
1607 Returns the (key, value) describing the removed cache entry.
1607 """
1608 """
1608 if not self._cache:
1609 if not self._cache:
1609 return
1610 return
1610
1611
1611 # Walk the linked list backwards starting at tail node until we hit
1612 # Walk the linked list backwards starting at tail node until we hit
1612 # a non-empty node.
1613 # a non-empty node.
1613 n = self._head.prev
1614 n = self._head.prev
1614
1615
1615 assert n is not None # help pytype
1616 assert n is not None # help pytype
1616
1617
1617 while n.key is _notset:
1618 while n.key is _notset:
1618 n = n.prev
1619 n = n.prev
1619
1620
1620 assert n is not None # help pytype
1621 assert n is not None # help pytype
1621
1622
1622 key, value = n.key, n.value
1623 key, value = n.key, n.value
1623
1624
1624 # And remove it from the cache and mark it as empty.
1625 # And remove it from the cache and mark it as empty.
1625 del self._cache[n.key]
1626 del self._cache[n.key]
1626 self.totalcost -= n.cost
1627 self.totalcost -= n.cost
1627 n.markempty()
1628 n.markempty()
1628
1629
1629 return key, value
1630 return key, value
1630
1631
1631 def _movetohead(self, node):
1632 def _movetohead(self, node):
1632 """Mark a node as the newest, making it the new head.
1633 """Mark a node as the newest, making it the new head.
1633
1634
1634 When a node is accessed, it becomes the freshest entry in the LRU
1635 When a node is accessed, it becomes the freshest entry in the LRU
1635 list, which is denoted by self._head.
1636 list, which is denoted by self._head.
1636
1637
1637 Visually, let's make ``N`` the new head node (* denotes head):
1638 Visually, let's make ``N`` the new head node (* denotes head):
1638
1639
1639 previous/oldest <-> head <-> next/next newest
1640 previous/oldest <-> head <-> next/next newest
1640
1641
1641 ----<->--- A* ---<->-----
1642 ----<->--- A* ---<->-----
1642 | |
1643 | |
1643 E <-> D <-> N <-> C <-> B
1644 E <-> D <-> N <-> C <-> B
1644
1645
1645 To:
1646 To:
1646
1647
1647 ----<->--- N* ---<->-----
1648 ----<->--- N* ---<->-----
1648 | |
1649 | |
1649 E <-> D <-> C <-> B <-> A
1650 E <-> D <-> C <-> B <-> A
1650
1651
1651 This requires the following moves:
1652 This requires the following moves:
1652
1653
1653 C.next = D (node.prev.next = node.next)
1654 C.next = D (node.prev.next = node.next)
1654 D.prev = C (node.next.prev = node.prev)
1655 D.prev = C (node.next.prev = node.prev)
1655 E.next = N (head.prev.next = node)
1656 E.next = N (head.prev.next = node)
1656 N.prev = E (node.prev = head.prev)
1657 N.prev = E (node.prev = head.prev)
1657 N.next = A (node.next = head)
1658 N.next = A (node.next = head)
1658 A.prev = N (head.prev = node)
1659 A.prev = N (head.prev = node)
1659 """
1660 """
1660 head = self._head
1661 head = self._head
1661 # C.next = D
1662 # C.next = D
1662 node.prev.next = node.next
1663 node.prev.next = node.next
1663 # D.prev = C
1664 # D.prev = C
1664 node.next.prev = node.prev
1665 node.next.prev = node.prev
1665 # N.prev = E
1666 # N.prev = E
1666 node.prev = head.prev
1667 node.prev = head.prev
1667 # N.next = A
1668 # N.next = A
1668 # It is tempting to do just "head" here, however if node is
1669 # It is tempting to do just "head" here, however if node is
1669 # adjacent to head, this will do bad things.
1670 # adjacent to head, this will do bad things.
1670 node.next = head.prev.next
1671 node.next = head.prev.next
1671 # E.next = N
1672 # E.next = N
1672 node.next.prev = node
1673 node.next.prev = node
1673 # A.prev = N
1674 # A.prev = N
1674 node.prev.next = node
1675 node.prev.next = node
1675
1676
1676 self._head = node
1677 self._head = node
1677
1678
1678 def _addcapacity(self):
1679 def _addcapacity(self):
1679 """Add a node to the circular linked list.
1680 """Add a node to the circular linked list.
1680
1681
1681 The new node is inserted before the head node.
1682 The new node is inserted before the head node.
1682 """
1683 """
1683 head = self._head
1684 head = self._head
1684 node = _lrucachenode()
1685 node = _lrucachenode()
1685 head.prev.next = node
1686 head.prev.next = node
1686 node.prev = head.prev
1687 node.prev = head.prev
1687 node.next = head
1688 node.next = head
1688 head.prev = node
1689 head.prev = node
1689 self._size += 1
1690 self._size += 1
1690 return node
1691 return node
1691
1692
1692 def _enforcecostlimit(self):
1693 def _enforcecostlimit(self):
1693 # This should run after an insertion. It should only be called if total
1694 # This should run after an insertion. It should only be called if total
1694 # cost limits are being enforced.
1695 # cost limits are being enforced.
1695 # The most recently inserted node is never evicted.
1696 # The most recently inserted node is never evicted.
1696 if len(self) <= 1 or self.totalcost <= self.maxcost:
1697 if len(self) <= 1 or self.totalcost <= self.maxcost:
1697 return
1698 return
1698
1699
1699 # This is logically equivalent to calling popoldest() until we
1700 # This is logically equivalent to calling popoldest() until we
1700 # free up enough cost. We don't do that since popoldest() needs
1701 # free up enough cost. We don't do that since popoldest() needs
1701 # to walk the linked list and doing this in a loop would be
1702 # to walk the linked list and doing this in a loop would be
1702 # quadratic. So we find the first non-empty node and then
1703 # quadratic. So we find the first non-empty node and then
1703 # walk nodes until we free up enough capacity.
1704 # walk nodes until we free up enough capacity.
1704 #
1705 #
1705 # If we only removed the minimum number of nodes to free enough
1706 # If we only removed the minimum number of nodes to free enough
1706 # cost at insert time, chances are high that the next insert would
1707 # cost at insert time, chances are high that the next insert would
1707 # also require pruning. This would effectively constitute quadratic
1708 # also require pruning. This would effectively constitute quadratic
1708 # behavior for insert-heavy workloads. To mitigate this, we set a
1709 # behavior for insert-heavy workloads. To mitigate this, we set a
1709 # target cost that is a percentage of the max cost. This will tend
1710 # target cost that is a percentage of the max cost. This will tend
1710 # to free more nodes when the high water mark is reached, which
1711 # to free more nodes when the high water mark is reached, which
1711 # lowers the chances of needing to prune on the subsequent insert.
1712 # lowers the chances of needing to prune on the subsequent insert.
1712 targetcost = int(self.maxcost * 0.75)
1713 targetcost = int(self.maxcost * 0.75)
1713
1714
1714 n = self._head.prev
1715 n = self._head.prev
1715 while n.key is _notset:
1716 while n.key is _notset:
1716 n = n.prev
1717 n = n.prev
1717
1718
1718 while len(self) > 1 and self.totalcost > targetcost:
1719 while len(self) > 1 and self.totalcost > targetcost:
1719 del self._cache[n.key]
1720 del self._cache[n.key]
1720 self.totalcost -= n.cost
1721 self.totalcost -= n.cost
1721 n.markempty()
1722 n.markempty()
1722 n = n.prev
1723 n = n.prev
1723
1724
1724
1725
1725 def lrucachefunc(func):
1726 def lrucachefunc(func):
1726 '''cache most recent results of function calls'''
1727 '''cache most recent results of function calls'''
1727 cache = {}
1728 cache = {}
1728 order = collections.deque()
1729 order = collections.deque()
1729 if func.__code__.co_argcount == 1:
1730 if func.__code__.co_argcount == 1:
1730
1731
1731 def f(arg):
1732 def f(arg):
1732 if arg not in cache:
1733 if arg not in cache:
1733 if len(cache) > 20:
1734 if len(cache) > 20:
1734 del cache[order.popleft()]
1735 del cache[order.popleft()]
1735 cache[arg] = func(arg)
1736 cache[arg] = func(arg)
1736 else:
1737 else:
1737 order.remove(arg)
1738 order.remove(arg)
1738 order.append(arg)
1739 order.append(arg)
1739 return cache[arg]
1740 return cache[arg]
1740
1741
1741 else:
1742 else:
1742
1743
1743 def f(*args):
1744 def f(*args):
1744 if args not in cache:
1745 if args not in cache:
1745 if len(cache) > 20:
1746 if len(cache) > 20:
1746 del cache[order.popleft()]
1747 del cache[order.popleft()]
1747 cache[args] = func(*args)
1748 cache[args] = func(*args)
1748 else:
1749 else:
1749 order.remove(args)
1750 order.remove(args)
1750 order.append(args)
1751 order.append(args)
1751 return cache[args]
1752 return cache[args]
1752
1753
1753 return f
1754 return f
1754
1755
1755
1756
1756 class propertycache(object):
1757 class propertycache(object):
1757 def __init__(self, func):
1758 def __init__(self, func):
1758 self.func = func
1759 self.func = func
1759 self.name = func.__name__
1760 self.name = func.__name__
1760
1761
1761 def __get__(self, obj, type=None):
1762 def __get__(self, obj, type=None):
1762 result = self.func(obj)
1763 result = self.func(obj)
1763 self.cachevalue(obj, result)
1764 self.cachevalue(obj, result)
1764 return result
1765 return result
1765
1766
1766 def cachevalue(self, obj, value):
1767 def cachevalue(self, obj, value):
1767 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1768 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1768 obj.__dict__[self.name] = value
1769 obj.__dict__[self.name] = value
1769
1770
1770
1771
1771 def clearcachedproperty(obj, prop):
1772 def clearcachedproperty(obj, prop):
1772 '''clear a cached property value, if one has been set'''
1773 '''clear a cached property value, if one has been set'''
1773 prop = pycompat.sysstr(prop)
1774 prop = pycompat.sysstr(prop)
1774 if prop in obj.__dict__:
1775 if prop in obj.__dict__:
1775 del obj.__dict__[prop]
1776 del obj.__dict__[prop]
1776
1777
1777
1778
1778 def increasingchunks(source, min=1024, max=65536):
1779 def increasingchunks(source, min=1024, max=65536):
1779 """return no less than min bytes per chunk while data remains,
1780 """return no less than min bytes per chunk while data remains,
1780 doubling min after each chunk until it reaches max"""
1781 doubling min after each chunk until it reaches max"""
1781
1782
1782 def log2(x):
1783 def log2(x):
1783 if not x:
1784 if not x:
1784 return 0
1785 return 0
1785 i = 0
1786 i = 0
1786 while x:
1787 while x:
1787 x >>= 1
1788 x >>= 1
1788 i += 1
1789 i += 1
1789 return i - 1
1790 return i - 1
1790
1791
1791 buf = []
1792 buf = []
1792 blen = 0
1793 blen = 0
1793 for chunk in source:
1794 for chunk in source:
1794 buf.append(chunk)
1795 buf.append(chunk)
1795 blen += len(chunk)
1796 blen += len(chunk)
1796 if blen >= min:
1797 if blen >= min:
1797 if min < max:
1798 if min < max:
1798 min = min << 1
1799 min = min << 1
1799 nmin = 1 << log2(blen)
1800 nmin = 1 << log2(blen)
1800 if nmin > min:
1801 if nmin > min:
1801 min = nmin
1802 min = nmin
1802 if min > max:
1803 if min > max:
1803 min = max
1804 min = max
1804 yield b''.join(buf)
1805 yield b''.join(buf)
1805 blen = 0
1806 blen = 0
1806 buf = []
1807 buf = []
1807 if buf:
1808 if buf:
1808 yield b''.join(buf)
1809 yield b''.join(buf)
1809
1810
1810
1811
1811 def always(fn):
1812 def always(fn):
1812 return True
1813 return True
1813
1814
1814
1815
1815 def never(fn):
1816 def never(fn):
1816 return False
1817 return False
1817
1818
1818
1819
1819 def nogc(func):
1820 def nogc(func):
1820 """disable garbage collector
1821 """disable garbage collector
1821
1822
1822 Python's garbage collector triggers a GC each time a certain number of
1823 Python's garbage collector triggers a GC each time a certain number of
1823 container objects (the number being defined by gc.get_threshold()) are
1824 container objects (the number being defined by gc.get_threshold()) are
1824 allocated even when marked not to be tracked by the collector. Tracking has
1825 allocated even when marked not to be tracked by the collector. Tracking has
1825 no effect on when GCs are triggered, only on what objects the GC looks
1826 no effect on when GCs are triggered, only on what objects the GC looks
1826 into. As a workaround, disable GC while building complex (huge)
1827 into. As a workaround, disable GC while building complex (huge)
1827 containers.
1828 containers.
1828
1829
1829 This garbage collector issue have been fixed in 2.7. But it still affect
1830 This garbage collector issue have been fixed in 2.7. But it still affect
1830 CPython's performance.
1831 CPython's performance.
1831 """
1832 """
1832
1833
1833 def wrapper(*args, **kwargs):
1834 def wrapper(*args, **kwargs):
1834 gcenabled = gc.isenabled()
1835 gcenabled = gc.isenabled()
1835 gc.disable()
1836 gc.disable()
1836 try:
1837 try:
1837 return func(*args, **kwargs)
1838 return func(*args, **kwargs)
1838 finally:
1839 finally:
1839 if gcenabled:
1840 if gcenabled:
1840 gc.enable()
1841 gc.enable()
1841
1842
1842 return wrapper
1843 return wrapper
1843
1844
1844
1845
1845 if pycompat.ispypy:
1846 if pycompat.ispypy:
1846 # PyPy runs slower with gc disabled
1847 # PyPy runs slower with gc disabled
1847 nogc = lambda x: x
1848 nogc = lambda x: x
1848
1849
1849
1850
1850 def pathto(root, n1, n2):
1851 def pathto(root, n1, n2):
1851 # type: (bytes, bytes, bytes) -> bytes
1852 # type: (bytes, bytes, bytes) -> bytes
1852 """return the relative path from one place to another.
1853 """return the relative path from one place to another.
1853 root should use os.sep to separate directories
1854 root should use os.sep to separate directories
1854 n1 should use os.sep to separate directories
1855 n1 should use os.sep to separate directories
1855 n2 should use "/" to separate directories
1856 n2 should use "/" to separate directories
1856 returns an os.sep-separated path.
1857 returns an os.sep-separated path.
1857
1858
1858 If n1 is a relative path, it's assumed it's
1859 If n1 is a relative path, it's assumed it's
1859 relative to root.
1860 relative to root.
1860 n2 should always be relative to root.
1861 n2 should always be relative to root.
1861 """
1862 """
1862 if not n1:
1863 if not n1:
1863 return localpath(n2)
1864 return localpath(n2)
1864 if os.path.isabs(n1):
1865 if os.path.isabs(n1):
1865 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1866 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1866 return os.path.join(root, localpath(n2))
1867 return os.path.join(root, localpath(n2))
1867 n2 = b'/'.join((pconvert(root), n2))
1868 n2 = b'/'.join((pconvert(root), n2))
1868 a, b = splitpath(n1), n2.split(b'/')
1869 a, b = splitpath(n1), n2.split(b'/')
1869 a.reverse()
1870 a.reverse()
1870 b.reverse()
1871 b.reverse()
1871 while a and b and a[-1] == b[-1]:
1872 while a and b and a[-1] == b[-1]:
1872 a.pop()
1873 a.pop()
1873 b.pop()
1874 b.pop()
1874 b.reverse()
1875 b.reverse()
1875 return pycompat.ossep.join(([b'..'] * len(a)) + b) or b'.'
1876 return pycompat.ossep.join(([b'..'] * len(a)) + b) or b'.'
1876
1877
1877
1878
1878 def checksignature(func, depth=1):
1879 def checksignature(func, depth=1):
1879 '''wrap a function with code to check for calling errors'''
1880 '''wrap a function with code to check for calling errors'''
1880
1881
1881 def check(*args, **kwargs):
1882 def check(*args, **kwargs):
1882 try:
1883 try:
1883 return func(*args, **kwargs)
1884 return func(*args, **kwargs)
1884 except TypeError:
1885 except TypeError:
1885 if len(traceback.extract_tb(sys.exc_info()[2])) == depth:
1886 if len(traceback.extract_tb(sys.exc_info()[2])) == depth:
1886 raise error.SignatureError
1887 raise error.SignatureError
1887 raise
1888 raise
1888
1889
1889 return check
1890 return check
1890
1891
1891
1892
1892 # a whilelist of known filesystems where hardlink works reliably
1893 # a whilelist of known filesystems where hardlink works reliably
1893 _hardlinkfswhitelist = {
1894 _hardlinkfswhitelist = {
1894 b'apfs',
1895 b'apfs',
1895 b'btrfs',
1896 b'btrfs',
1896 b'ext2',
1897 b'ext2',
1897 b'ext3',
1898 b'ext3',
1898 b'ext4',
1899 b'ext4',
1899 b'hfs',
1900 b'hfs',
1900 b'jfs',
1901 b'jfs',
1901 b'NTFS',
1902 b'NTFS',
1902 b'reiserfs',
1903 b'reiserfs',
1903 b'tmpfs',
1904 b'tmpfs',
1904 b'ufs',
1905 b'ufs',
1905 b'xfs',
1906 b'xfs',
1906 b'zfs',
1907 b'zfs',
1907 }
1908 }
1908
1909
1909
1910
1910 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1911 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1911 """copy a file, preserving mode and optionally other stat info like
1912 """copy a file, preserving mode and optionally other stat info like
1912 atime/mtime
1913 atime/mtime
1913
1914
1914 checkambig argument is used with filestat, and is useful only if
1915 checkambig argument is used with filestat, and is useful only if
1915 destination file is guarded by any lock (e.g. repo.lock or
1916 destination file is guarded by any lock (e.g. repo.lock or
1916 repo.wlock).
1917 repo.wlock).
1917
1918
1918 copystat and checkambig should be exclusive.
1919 copystat and checkambig should be exclusive.
1919 """
1920 """
1920 assert not (copystat and checkambig)
1921 assert not (copystat and checkambig)
1921 oldstat = None
1922 oldstat = None
1922 if os.path.lexists(dest):
1923 if os.path.lexists(dest):
1923 if checkambig:
1924 if checkambig:
1924 oldstat = checkambig and filestat.frompath(dest)
1925 oldstat = checkambig and filestat.frompath(dest)
1925 unlink(dest)
1926 unlink(dest)
1926 if hardlink:
1927 if hardlink:
1927 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1928 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1928 # unless we are confident that dest is on a whitelisted filesystem.
1929 # unless we are confident that dest is on a whitelisted filesystem.
1929 try:
1930 try:
1930 fstype = getfstype(os.path.dirname(dest))
1931 fstype = getfstype(os.path.dirname(dest))
1931 except OSError:
1932 except OSError:
1932 fstype = None
1933 fstype = None
1933 if fstype not in _hardlinkfswhitelist:
1934 if fstype not in _hardlinkfswhitelist:
1934 hardlink = False
1935 hardlink = False
1935 if hardlink:
1936 if hardlink:
1936 try:
1937 try:
1937 oslink(src, dest)
1938 oslink(src, dest)
1938 return
1939 return
1939 except (IOError, OSError):
1940 except (IOError, OSError):
1940 pass # fall back to normal copy
1941 pass # fall back to normal copy
1941 if os.path.islink(src):
1942 if os.path.islink(src):
1942 os.symlink(os.readlink(src), dest)
1943 os.symlink(os.readlink(src), dest)
1943 # copytime is ignored for symlinks, but in general copytime isn't needed
1944 # copytime is ignored for symlinks, but in general copytime isn't needed
1944 # for them anyway
1945 # for them anyway
1945 else:
1946 else:
1946 try:
1947 try:
1947 shutil.copyfile(src, dest)
1948 shutil.copyfile(src, dest)
1948 if copystat:
1949 if copystat:
1949 # copystat also copies mode
1950 # copystat also copies mode
1950 shutil.copystat(src, dest)
1951 shutil.copystat(src, dest)
1951 else:
1952 else:
1952 shutil.copymode(src, dest)
1953 shutil.copymode(src, dest)
1953 if oldstat and oldstat.stat:
1954 if oldstat and oldstat.stat:
1954 newstat = filestat.frompath(dest)
1955 newstat = filestat.frompath(dest)
1955 if newstat.isambig(oldstat):
1956 if newstat.isambig(oldstat):
1956 # stat of copied file is ambiguous to original one
1957 # stat of copied file is ambiguous to original one
1957 advanced = (
1958 advanced = (
1958 oldstat.stat[stat.ST_MTIME] + 1
1959 oldstat.stat[stat.ST_MTIME] + 1
1959 ) & 0x7FFFFFFF
1960 ) & 0x7FFFFFFF
1960 os.utime(dest, (advanced, advanced))
1961 os.utime(dest, (advanced, advanced))
1961 except shutil.Error as inst:
1962 except shutil.Error as inst:
1962 raise error.Abort(stringutil.forcebytestr(inst))
1963 raise error.Abort(stringutil.forcebytestr(inst))
1963
1964
1964
1965
1965 def copyfiles(src, dst, hardlink=None, progress=None):
1966 def copyfiles(src, dst, hardlink=None, progress=None):
1966 """Copy a directory tree using hardlinks if possible."""
1967 """Copy a directory tree using hardlinks if possible."""
1967 num = 0
1968 num = 0
1968
1969
1969 def settopic():
1970 def settopic():
1970 if progress:
1971 if progress:
1971 progress.topic = _(b'linking') if hardlink else _(b'copying')
1972 progress.topic = _(b'linking') if hardlink else _(b'copying')
1972
1973
1973 if os.path.isdir(src):
1974 if os.path.isdir(src):
1974 if hardlink is None:
1975 if hardlink is None:
1975 hardlink = (
1976 hardlink = (
1976 os.stat(src).st_dev == os.stat(os.path.dirname(dst)).st_dev
1977 os.stat(src).st_dev == os.stat(os.path.dirname(dst)).st_dev
1977 )
1978 )
1978 settopic()
1979 settopic()
1979 os.mkdir(dst)
1980 os.mkdir(dst)
1980 for name, kind in listdir(src):
1981 for name, kind in listdir(src):
1981 srcname = os.path.join(src, name)
1982 srcname = os.path.join(src, name)
1982 dstname = os.path.join(dst, name)
1983 dstname = os.path.join(dst, name)
1983 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
1984 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
1984 num += n
1985 num += n
1985 else:
1986 else:
1986 if hardlink is None:
1987 if hardlink is None:
1987 hardlink = (
1988 hardlink = (
1988 os.stat(os.path.dirname(src)).st_dev
1989 os.stat(os.path.dirname(src)).st_dev
1989 == os.stat(os.path.dirname(dst)).st_dev
1990 == os.stat(os.path.dirname(dst)).st_dev
1990 )
1991 )
1991 settopic()
1992 settopic()
1992
1993
1993 if hardlink:
1994 if hardlink:
1994 try:
1995 try:
1995 oslink(src, dst)
1996 oslink(src, dst)
1996 except (IOError, OSError):
1997 except (IOError, OSError):
1997 hardlink = False
1998 hardlink = False
1998 shutil.copy(src, dst)
1999 shutil.copy(src, dst)
1999 else:
2000 else:
2000 shutil.copy(src, dst)
2001 shutil.copy(src, dst)
2001 num += 1
2002 num += 1
2002 if progress:
2003 if progress:
2003 progress.increment()
2004 progress.increment()
2004
2005
2005 return hardlink, num
2006 return hardlink, num
2006
2007
2007
2008
2008 _winreservednames = {
2009 _winreservednames = {
2009 b'con',
2010 b'con',
2010 b'prn',
2011 b'prn',
2011 b'aux',
2012 b'aux',
2012 b'nul',
2013 b'nul',
2013 b'com1',
2014 b'com1',
2014 b'com2',
2015 b'com2',
2015 b'com3',
2016 b'com3',
2016 b'com4',
2017 b'com4',
2017 b'com5',
2018 b'com5',
2018 b'com6',
2019 b'com6',
2019 b'com7',
2020 b'com7',
2020 b'com8',
2021 b'com8',
2021 b'com9',
2022 b'com9',
2022 b'lpt1',
2023 b'lpt1',
2023 b'lpt2',
2024 b'lpt2',
2024 b'lpt3',
2025 b'lpt3',
2025 b'lpt4',
2026 b'lpt4',
2026 b'lpt5',
2027 b'lpt5',
2027 b'lpt6',
2028 b'lpt6',
2028 b'lpt7',
2029 b'lpt7',
2029 b'lpt8',
2030 b'lpt8',
2030 b'lpt9',
2031 b'lpt9',
2031 }
2032 }
2032 _winreservedchars = b':*?"<>|'
2033 _winreservedchars = b':*?"<>|'
2033
2034
2034
2035
2035 def checkwinfilename(path):
2036 def checkwinfilename(path):
2036 # type: (bytes) -> Optional[bytes]
2037 # type: (bytes) -> Optional[bytes]
2037 r"""Check that the base-relative path is a valid filename on Windows.
2038 r"""Check that the base-relative path is a valid filename on Windows.
2038 Returns None if the path is ok, or a UI string describing the problem.
2039 Returns None if the path is ok, or a UI string describing the problem.
2039
2040
2040 >>> checkwinfilename(b"just/a/normal/path")
2041 >>> checkwinfilename(b"just/a/normal/path")
2041 >>> checkwinfilename(b"foo/bar/con.xml")
2042 >>> checkwinfilename(b"foo/bar/con.xml")
2042 "filename contains 'con', which is reserved on Windows"
2043 "filename contains 'con', which is reserved on Windows"
2043 >>> checkwinfilename(b"foo/con.xml/bar")
2044 >>> checkwinfilename(b"foo/con.xml/bar")
2044 "filename contains 'con', which is reserved on Windows"
2045 "filename contains 'con', which is reserved on Windows"
2045 >>> checkwinfilename(b"foo/bar/xml.con")
2046 >>> checkwinfilename(b"foo/bar/xml.con")
2046 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
2047 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
2047 "filename contains 'AUX', which is reserved on Windows"
2048 "filename contains 'AUX', which is reserved on Windows"
2048 >>> checkwinfilename(b"foo/bar/bla:.txt")
2049 >>> checkwinfilename(b"foo/bar/bla:.txt")
2049 "filename contains ':', which is reserved on Windows"
2050 "filename contains ':', which is reserved on Windows"
2050 >>> checkwinfilename(b"foo/bar/b\07la.txt")
2051 >>> checkwinfilename(b"foo/bar/b\07la.txt")
2051 "filename contains '\\x07', which is invalid on Windows"
2052 "filename contains '\\x07', which is invalid on Windows"
2052 >>> checkwinfilename(b"foo/bar/bla ")
2053 >>> checkwinfilename(b"foo/bar/bla ")
2053 "filename ends with ' ', which is not allowed on Windows"
2054 "filename ends with ' ', which is not allowed on Windows"
2054 >>> checkwinfilename(b"../bar")
2055 >>> checkwinfilename(b"../bar")
2055 >>> checkwinfilename(b"foo\\")
2056 >>> checkwinfilename(b"foo\\")
2056 "filename ends with '\\', which is invalid on Windows"
2057 "filename ends with '\\', which is invalid on Windows"
2057 >>> checkwinfilename(b"foo\\/bar")
2058 >>> checkwinfilename(b"foo\\/bar")
2058 "directory name ends with '\\', which is invalid on Windows"
2059 "directory name ends with '\\', which is invalid on Windows"
2059 """
2060 """
2060 if path.endswith(b'\\'):
2061 if path.endswith(b'\\'):
2061 return _(b"filename ends with '\\', which is invalid on Windows")
2062 return _(b"filename ends with '\\', which is invalid on Windows")
2062 if b'\\/' in path:
2063 if b'\\/' in path:
2063 return _(b"directory name ends with '\\', which is invalid on Windows")
2064 return _(b"directory name ends with '\\', which is invalid on Windows")
2064 for n in path.replace(b'\\', b'/').split(b'/'):
2065 for n in path.replace(b'\\', b'/').split(b'/'):
2065 if not n:
2066 if not n:
2066 continue
2067 continue
2067 for c in _filenamebytestr(n):
2068 for c in _filenamebytestr(n):
2068 if c in _winreservedchars:
2069 if c in _winreservedchars:
2069 return (
2070 return (
2070 _(
2071 _(
2071 b"filename contains '%s', which is reserved "
2072 b"filename contains '%s', which is reserved "
2072 b"on Windows"
2073 b"on Windows"
2073 )
2074 )
2074 % c
2075 % c
2075 )
2076 )
2076 if ord(c) <= 31:
2077 if ord(c) <= 31:
2077 return _(
2078 return _(
2078 b"filename contains '%s', which is invalid on Windows"
2079 b"filename contains '%s', which is invalid on Windows"
2079 ) % stringutil.escapestr(c)
2080 ) % stringutil.escapestr(c)
2080 base = n.split(b'.')[0]
2081 base = n.split(b'.')[0]
2081 if base and base.lower() in _winreservednames:
2082 if base and base.lower() in _winreservednames:
2082 return (
2083 return (
2083 _(b"filename contains '%s', which is reserved on Windows")
2084 _(b"filename contains '%s', which is reserved on Windows")
2084 % base
2085 % base
2085 )
2086 )
2086 t = n[-1:]
2087 t = n[-1:]
2087 if t in b'. ' and n not in b'..':
2088 if t in b'. ' and n not in b'..':
2088 return (
2089 return (
2089 _(
2090 _(
2090 b"filename ends with '%s', which is not allowed "
2091 b"filename ends with '%s', which is not allowed "
2091 b"on Windows"
2092 b"on Windows"
2092 )
2093 )
2093 % t
2094 % t
2094 )
2095 )
2095
2096
2096
2097
2097 timer = getattr(time, "perf_counter", None)
2098 timer = getattr(time, "perf_counter", None)
2098
2099
2099 if pycompat.iswindows:
2100 if pycompat.iswindows:
2100 checkosfilename = checkwinfilename
2101 checkosfilename = checkwinfilename
2101 if not timer:
2102 if not timer:
2102 timer = time.clock
2103 timer = time.clock
2103 else:
2104 else:
2104 # mercurial.windows doesn't have platform.checkosfilename
2105 # mercurial.windows doesn't have platform.checkosfilename
2105 checkosfilename = platform.checkosfilename # pytype: disable=module-attr
2106 checkosfilename = platform.checkosfilename # pytype: disable=module-attr
2106 if not timer:
2107 if not timer:
2107 timer = time.time
2108 timer = time.time
2108
2109
2109
2110
2110 def makelock(info, pathname):
2111 def makelock(info, pathname):
2111 """Create a lock file atomically if possible
2112 """Create a lock file atomically if possible
2112
2113
2113 This may leave a stale lock file if symlink isn't supported and signal
2114 This may leave a stale lock file if symlink isn't supported and signal
2114 interrupt is enabled.
2115 interrupt is enabled.
2115 """
2116 """
2116 try:
2117 try:
2117 return os.symlink(info, pathname)
2118 return os.symlink(info, pathname)
2118 except OSError as why:
2119 except OSError as why:
2119 if why.errno == errno.EEXIST:
2120 if why.errno == errno.EEXIST:
2120 raise
2121 raise
2121 except AttributeError: # no symlink in os
2122 except AttributeError: # no symlink in os
2122 pass
2123 pass
2123
2124
2124 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
2125 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
2125 ld = os.open(pathname, flags)
2126 ld = os.open(pathname, flags)
2126 os.write(ld, info)
2127 os.write(ld, info)
2127 os.close(ld)
2128 os.close(ld)
2128
2129
2129
2130
2130 def readlock(pathname):
2131 def readlock(pathname):
2131 # type: (bytes) -> bytes
2132 # type: (bytes) -> bytes
2132 try:
2133 try:
2133 return readlink(pathname)
2134 return readlink(pathname)
2134 except OSError as why:
2135 except OSError as why:
2135 if why.errno not in (errno.EINVAL, errno.ENOSYS):
2136 if why.errno not in (errno.EINVAL, errno.ENOSYS):
2136 raise
2137 raise
2137 except AttributeError: # no symlink in os
2138 except AttributeError: # no symlink in os
2138 pass
2139 pass
2139 with posixfile(pathname, b'rb') as fp:
2140 with posixfile(pathname, b'rb') as fp:
2140 return fp.read()
2141 return fp.read()
2141
2142
2142
2143
2143 def fstat(fp):
2144 def fstat(fp):
2144 '''stat file object that may not have fileno method.'''
2145 '''stat file object that may not have fileno method.'''
2145 try:
2146 try:
2146 return os.fstat(fp.fileno())
2147 return os.fstat(fp.fileno())
2147 except AttributeError:
2148 except AttributeError:
2148 return os.stat(fp.name)
2149 return os.stat(fp.name)
2149
2150
2150
2151
2151 # File system features
2152 # File system features
2152
2153
2153
2154
2154 def fscasesensitive(path):
2155 def fscasesensitive(path):
2155 # type: (bytes) -> bool
2156 # type: (bytes) -> bool
2156 """
2157 """
2157 Return true if the given path is on a case-sensitive filesystem
2158 Return true if the given path is on a case-sensitive filesystem
2158
2159
2159 Requires a path (like /foo/.hg) ending with a foldable final
2160 Requires a path (like /foo/.hg) ending with a foldable final
2160 directory component.
2161 directory component.
2161 """
2162 """
2162 s1 = os.lstat(path)
2163 s1 = os.lstat(path)
2163 d, b = os.path.split(path)
2164 d, b = os.path.split(path)
2164 b2 = b.upper()
2165 b2 = b.upper()
2165 if b == b2:
2166 if b == b2:
2166 b2 = b.lower()
2167 b2 = b.lower()
2167 if b == b2:
2168 if b == b2:
2168 return True # no evidence against case sensitivity
2169 return True # no evidence against case sensitivity
2169 p2 = os.path.join(d, b2)
2170 p2 = os.path.join(d, b2)
2170 try:
2171 try:
2171 s2 = os.lstat(p2)
2172 s2 = os.lstat(p2)
2172 if s2 == s1:
2173 if s2 == s1:
2173 return False
2174 return False
2174 return True
2175 return True
2175 except OSError:
2176 except OSError:
2176 return True
2177 return True
2177
2178
2178
2179
2179 _re2_input = lambda x: x
2180 _re2_input = lambda x: x
2180 try:
2181 try:
2181 import re2 # pytype: disable=import-error
2182 import re2 # pytype: disable=import-error
2182
2183
2183 _re2 = None
2184 _re2 = None
2184 except ImportError:
2185 except ImportError:
2185 _re2 = False
2186 _re2 = False
2186
2187
2187
2188
2188 class _re(object):
2189 class _re(object):
2189 def _checkre2(self):
2190 def _checkre2(self):
2190 global _re2
2191 global _re2
2191 global _re2_input
2192 global _re2_input
2192
2193
2193 check_pattern = br'\[([^\[]+)\]'
2194 check_pattern = br'\[([^\[]+)\]'
2194 check_input = b'[ui]'
2195 check_input = b'[ui]'
2195 try:
2196 try:
2196 # check if match works, see issue3964
2197 # check if match works, see issue3964
2197 _re2 = bool(re2.match(check_pattern, check_input))
2198 _re2 = bool(re2.match(check_pattern, check_input))
2198 except ImportError:
2199 except ImportError:
2199 _re2 = False
2200 _re2 = False
2200 except TypeError:
2201 except TypeError:
2201 # the `pyre-2` project provides a re2 module that accept bytes
2202 # the `pyre-2` project provides a re2 module that accept bytes
2202 # the `fb-re2` project provides a re2 module that acccept sysstr
2203 # the `fb-re2` project provides a re2 module that acccept sysstr
2203 check_pattern = pycompat.sysstr(check_pattern)
2204 check_pattern = pycompat.sysstr(check_pattern)
2204 check_input = pycompat.sysstr(check_input)
2205 check_input = pycompat.sysstr(check_input)
2205 _re2 = bool(re2.match(check_pattern, check_input))
2206 _re2 = bool(re2.match(check_pattern, check_input))
2206 _re2_input = pycompat.sysstr
2207 _re2_input = pycompat.sysstr
2207
2208
2208 def compile(self, pat, flags=0):
2209 def compile(self, pat, flags=0):
2209 """Compile a regular expression, using re2 if possible
2210 """Compile a regular expression, using re2 if possible
2210
2211
2211 For best performance, use only re2-compatible regexp features. The
2212 For best performance, use only re2-compatible regexp features. The
2212 only flags from the re module that are re2-compatible are
2213 only flags from the re module that are re2-compatible are
2213 IGNORECASE and MULTILINE."""
2214 IGNORECASE and MULTILINE."""
2214 if _re2 is None:
2215 if _re2 is None:
2215 self._checkre2()
2216 self._checkre2()
2216 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
2217 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
2217 if flags & remod.IGNORECASE:
2218 if flags & remod.IGNORECASE:
2218 pat = b'(?i)' + pat
2219 pat = b'(?i)' + pat
2219 if flags & remod.MULTILINE:
2220 if flags & remod.MULTILINE:
2220 pat = b'(?m)' + pat
2221 pat = b'(?m)' + pat
2221 try:
2222 try:
2222 return re2.compile(_re2_input(pat))
2223 return re2.compile(_re2_input(pat))
2223 except re2.error:
2224 except re2.error:
2224 pass
2225 pass
2225 return remod.compile(pat, flags)
2226 return remod.compile(pat, flags)
2226
2227
2227 @propertycache
2228 @propertycache
2228 def escape(self):
2229 def escape(self):
2229 """Return the version of escape corresponding to self.compile.
2230 """Return the version of escape corresponding to self.compile.
2230
2231
2231 This is imperfect because whether re2 or re is used for a particular
2232 This is imperfect because whether re2 or re is used for a particular
2232 function depends on the flags, etc, but it's the best we can do.
2233 function depends on the flags, etc, but it's the best we can do.
2233 """
2234 """
2234 global _re2
2235 global _re2
2235 if _re2 is None:
2236 if _re2 is None:
2236 self._checkre2()
2237 self._checkre2()
2237 if _re2:
2238 if _re2:
2238 return re2.escape
2239 return re2.escape
2239 else:
2240 else:
2240 return remod.escape
2241 return remod.escape
2241
2242
2242
2243
2243 re = _re()
2244 re = _re()
2244
2245
2245 _fspathcache = {}
2246 _fspathcache = {}
2246
2247
2247
2248
2248 def fspath(name, root):
2249 def fspath(name, root):
2249 # type: (bytes, bytes) -> bytes
2250 # type: (bytes, bytes) -> bytes
2250 """Get name in the case stored in the filesystem
2251 """Get name in the case stored in the filesystem
2251
2252
2252 The name should be relative to root, and be normcase-ed for efficiency.
2253 The name should be relative to root, and be normcase-ed for efficiency.
2253
2254
2254 Note that this function is unnecessary, and should not be
2255 Note that this function is unnecessary, and should not be
2255 called, for case-sensitive filesystems (simply because it's expensive).
2256 called, for case-sensitive filesystems (simply because it's expensive).
2256
2257
2257 The root should be normcase-ed, too.
2258 The root should be normcase-ed, too.
2258 """
2259 """
2259
2260
2260 def _makefspathcacheentry(dir):
2261 def _makefspathcacheentry(dir):
2261 return {normcase(n): n for n in os.listdir(dir)}
2262 return {normcase(n): n for n in os.listdir(dir)}
2262
2263
2263 seps = pycompat.ossep
2264 seps = pycompat.ossep
2264 if pycompat.osaltsep:
2265 if pycompat.osaltsep:
2265 seps = seps + pycompat.osaltsep
2266 seps = seps + pycompat.osaltsep
2266 # Protect backslashes. This gets silly very quickly.
2267 # Protect backslashes. This gets silly very quickly.
2267 seps.replace(b'\\', b'\\\\')
2268 seps.replace(b'\\', b'\\\\')
2268 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
2269 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
2269 dir = os.path.normpath(root)
2270 dir = os.path.normpath(root)
2270 result = []
2271 result = []
2271 for part, sep in pattern.findall(name):
2272 for part, sep in pattern.findall(name):
2272 if sep:
2273 if sep:
2273 result.append(sep)
2274 result.append(sep)
2274 continue
2275 continue
2275
2276
2276 if dir not in _fspathcache:
2277 if dir not in _fspathcache:
2277 _fspathcache[dir] = _makefspathcacheentry(dir)
2278 _fspathcache[dir] = _makefspathcacheentry(dir)
2278 contents = _fspathcache[dir]
2279 contents = _fspathcache[dir]
2279
2280
2280 found = contents.get(part)
2281 found = contents.get(part)
2281 if not found:
2282 if not found:
2282 # retry "once per directory" per "dirstate.walk" which
2283 # retry "once per directory" per "dirstate.walk" which
2283 # may take place for each patches of "hg qpush", for example
2284 # may take place for each patches of "hg qpush", for example
2284 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2285 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2285 found = contents.get(part)
2286 found = contents.get(part)
2286
2287
2287 result.append(found or part)
2288 result.append(found or part)
2288 dir = os.path.join(dir, part)
2289 dir = os.path.join(dir, part)
2289
2290
2290 return b''.join(result)
2291 return b''.join(result)
2291
2292
2292
2293
2293 def checknlink(testfile):
2294 def checknlink(testfile):
2294 # type: (bytes) -> bool
2295 # type: (bytes) -> bool
2295 '''check whether hardlink count reporting works properly'''
2296 '''check whether hardlink count reporting works properly'''
2296
2297
2297 # testfile may be open, so we need a separate file for checking to
2298 # testfile may be open, so we need a separate file for checking to
2298 # work around issue2543 (or testfile may get lost on Samba shares)
2299 # work around issue2543 (or testfile may get lost on Samba shares)
2299 f1, f2, fp = None, None, None
2300 f1, f2, fp = None, None, None
2300 try:
2301 try:
2301 fd, f1 = pycompat.mkstemp(
2302 fd, f1 = pycompat.mkstemp(
2302 prefix=b'.%s-' % os.path.basename(testfile),
2303 prefix=b'.%s-' % os.path.basename(testfile),
2303 suffix=b'1~',
2304 suffix=b'1~',
2304 dir=os.path.dirname(testfile),
2305 dir=os.path.dirname(testfile),
2305 )
2306 )
2306 os.close(fd)
2307 os.close(fd)
2307 f2 = b'%s2~' % f1[:-2]
2308 f2 = b'%s2~' % f1[:-2]
2308
2309
2309 oslink(f1, f2)
2310 oslink(f1, f2)
2310 # nlinks() may behave differently for files on Windows shares if
2311 # nlinks() may behave differently for files on Windows shares if
2311 # the file is open.
2312 # the file is open.
2312 fp = posixfile(f2)
2313 fp = posixfile(f2)
2313 return nlinks(f2) > 1
2314 return nlinks(f2) > 1
2314 except OSError:
2315 except OSError:
2315 return False
2316 return False
2316 finally:
2317 finally:
2317 if fp is not None:
2318 if fp is not None:
2318 fp.close()
2319 fp.close()
2319 for f in (f1, f2):
2320 for f in (f1, f2):
2320 try:
2321 try:
2321 if f is not None:
2322 if f is not None:
2322 os.unlink(f)
2323 os.unlink(f)
2323 except OSError:
2324 except OSError:
2324 pass
2325 pass
2325
2326
2326
2327
2327 def endswithsep(path):
2328 def endswithsep(path):
2328 # type: (bytes) -> bool
2329 # type: (bytes) -> bool
2329 '''Check path ends with os.sep or os.altsep.'''
2330 '''Check path ends with os.sep or os.altsep.'''
2330 return bool( # help pytype
2331 return bool( # help pytype
2331 path.endswith(pycompat.ossep)
2332 path.endswith(pycompat.ossep)
2332 or pycompat.osaltsep
2333 or pycompat.osaltsep
2333 and path.endswith(pycompat.osaltsep)
2334 and path.endswith(pycompat.osaltsep)
2334 )
2335 )
2335
2336
2336
2337
2337 def splitpath(path):
2338 def splitpath(path):
2338 # type: (bytes) -> List[bytes]
2339 # type: (bytes) -> List[bytes]
2339 """Split path by os.sep.
2340 """Split path by os.sep.
2340 Note that this function does not use os.altsep because this is
2341 Note that this function does not use os.altsep because this is
2341 an alternative of simple "xxx.split(os.sep)".
2342 an alternative of simple "xxx.split(os.sep)".
2342 It is recommended to use os.path.normpath() before using this
2343 It is recommended to use os.path.normpath() before using this
2343 function if need."""
2344 function if need."""
2344 return path.split(pycompat.ossep)
2345 return path.split(pycompat.ossep)
2345
2346
2346
2347
2347 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
2348 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
2348 """Create a temporary file with the same contents from name
2349 """Create a temporary file with the same contents from name
2349
2350
2350 The permission bits are copied from the original file.
2351 The permission bits are copied from the original file.
2351
2352
2352 If the temporary file is going to be truncated immediately, you
2353 If the temporary file is going to be truncated immediately, you
2353 can use emptyok=True as an optimization.
2354 can use emptyok=True as an optimization.
2354
2355
2355 Returns the name of the temporary file.
2356 Returns the name of the temporary file.
2356 """
2357 """
2357 d, fn = os.path.split(name)
2358 d, fn = os.path.split(name)
2358 fd, temp = pycompat.mkstemp(prefix=b'.%s-' % fn, suffix=b'~', dir=d)
2359 fd, temp = pycompat.mkstemp(prefix=b'.%s-' % fn, suffix=b'~', dir=d)
2359 os.close(fd)
2360 os.close(fd)
2360 # Temporary files are created with mode 0600, which is usually not
2361 # Temporary files are created with mode 0600, which is usually not
2361 # what we want. If the original file already exists, just copy
2362 # what we want. If the original file already exists, just copy
2362 # its mode. Otherwise, manually obey umask.
2363 # its mode. Otherwise, manually obey umask.
2363 copymode(name, temp, createmode, enforcewritable)
2364 copymode(name, temp, createmode, enforcewritable)
2364
2365
2365 if emptyok:
2366 if emptyok:
2366 return temp
2367 return temp
2367 try:
2368 try:
2368 try:
2369 try:
2369 ifp = posixfile(name, b"rb")
2370 ifp = posixfile(name, b"rb")
2370 except IOError as inst:
2371 except IOError as inst:
2371 if inst.errno == errno.ENOENT:
2372 if inst.errno == errno.ENOENT:
2372 return temp
2373 return temp
2373 if not getattr(inst, 'filename', None):
2374 if not getattr(inst, 'filename', None):
2374 inst.filename = name
2375 inst.filename = name
2375 raise
2376 raise
2376 ofp = posixfile(temp, b"wb")
2377 ofp = posixfile(temp, b"wb")
2377 for chunk in filechunkiter(ifp):
2378 for chunk in filechunkiter(ifp):
2378 ofp.write(chunk)
2379 ofp.write(chunk)
2379 ifp.close()
2380 ifp.close()
2380 ofp.close()
2381 ofp.close()
2381 except: # re-raises
2382 except: # re-raises
2382 try:
2383 try:
2383 os.unlink(temp)
2384 os.unlink(temp)
2384 except OSError:
2385 except OSError:
2385 pass
2386 pass
2386 raise
2387 raise
2387 return temp
2388 return temp
2388
2389
2389
2390
2390 class filestat(object):
2391 class filestat(object):
2391 """help to exactly detect change of a file
2392 """help to exactly detect change of a file
2392
2393
2393 'stat' attribute is result of 'os.stat()' if specified 'path'
2394 'stat' attribute is result of 'os.stat()' if specified 'path'
2394 exists. Otherwise, it is None. This can avoid preparative
2395 exists. Otherwise, it is None. This can avoid preparative
2395 'exists()' examination on client side of this class.
2396 'exists()' examination on client side of this class.
2396 """
2397 """
2397
2398
2398 def __init__(self, stat):
2399 def __init__(self, stat):
2399 self.stat = stat
2400 self.stat = stat
2400
2401
2401 @classmethod
2402 @classmethod
2402 def frompath(cls, path):
2403 def frompath(cls, path):
2403 try:
2404 try:
2404 stat = os.stat(path)
2405 stat = os.stat(path)
2405 except OSError as err:
2406 except OSError as err:
2406 if err.errno != errno.ENOENT:
2407 if err.errno != errno.ENOENT:
2407 raise
2408 raise
2408 stat = None
2409 stat = None
2409 return cls(stat)
2410 return cls(stat)
2410
2411
2411 @classmethod
2412 @classmethod
2412 def fromfp(cls, fp):
2413 def fromfp(cls, fp):
2413 stat = os.fstat(fp.fileno())
2414 stat = os.fstat(fp.fileno())
2414 return cls(stat)
2415 return cls(stat)
2415
2416
2416 __hash__ = object.__hash__
2417 __hash__ = object.__hash__
2417
2418
2418 def __eq__(self, old):
2419 def __eq__(self, old):
2419 try:
2420 try:
2420 # if ambiguity between stat of new and old file is
2421 # if ambiguity between stat of new and old file is
2421 # avoided, comparison of size, ctime and mtime is enough
2422 # avoided, comparison of size, ctime and mtime is enough
2422 # to exactly detect change of a file regardless of platform
2423 # to exactly detect change of a file regardless of platform
2423 return (
2424 return (
2424 self.stat.st_size == old.stat.st_size
2425 self.stat.st_size == old.stat.st_size
2425 and self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2426 and self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2426 and self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME]
2427 and self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME]
2427 )
2428 )
2428 except AttributeError:
2429 except AttributeError:
2429 pass
2430 pass
2430 try:
2431 try:
2431 return self.stat is None and old.stat is None
2432 return self.stat is None and old.stat is None
2432 except AttributeError:
2433 except AttributeError:
2433 return False
2434 return False
2434
2435
2435 def isambig(self, old):
2436 def isambig(self, old):
2436 """Examine whether new (= self) stat is ambiguous against old one
2437 """Examine whether new (= self) stat is ambiguous against old one
2437
2438
2438 "S[N]" below means stat of a file at N-th change:
2439 "S[N]" below means stat of a file at N-th change:
2439
2440
2440 - S[n-1].ctime < S[n].ctime: can detect change of a file
2441 - S[n-1].ctime < S[n].ctime: can detect change of a file
2441 - S[n-1].ctime == S[n].ctime
2442 - S[n-1].ctime == S[n].ctime
2442 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2443 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2443 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2444 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2444 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2445 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2445 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2446 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2446
2447
2447 Case (*2) above means that a file was changed twice or more at
2448 Case (*2) above means that a file was changed twice or more at
2448 same time in sec (= S[n-1].ctime), and comparison of timestamp
2449 same time in sec (= S[n-1].ctime), and comparison of timestamp
2449 is ambiguous.
2450 is ambiguous.
2450
2451
2451 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2452 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2452 timestamp is ambiguous".
2453 timestamp is ambiguous".
2453
2454
2454 But advancing mtime only in case (*2) doesn't work as
2455 But advancing mtime only in case (*2) doesn't work as
2455 expected, because naturally advanced S[n].mtime in case (*1)
2456 expected, because naturally advanced S[n].mtime in case (*1)
2456 might be equal to manually advanced S[n-1 or earlier].mtime.
2457 might be equal to manually advanced S[n-1 or earlier].mtime.
2457
2458
2458 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2459 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2459 treated as ambiguous regardless of mtime, to avoid overlooking
2460 treated as ambiguous regardless of mtime, to avoid overlooking
2460 by confliction between such mtime.
2461 by confliction between such mtime.
2461
2462
2462 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2463 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2463 S[n].mtime", even if size of a file isn't changed.
2464 S[n].mtime", even if size of a file isn't changed.
2464 """
2465 """
2465 try:
2466 try:
2466 return self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2467 return self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2467 except AttributeError:
2468 except AttributeError:
2468 return False
2469 return False
2469
2470
2470 def avoidambig(self, path, old):
2471 def avoidambig(self, path, old):
2471 """Change file stat of specified path to avoid ambiguity
2472 """Change file stat of specified path to avoid ambiguity
2472
2473
2473 'old' should be previous filestat of 'path'.
2474 'old' should be previous filestat of 'path'.
2474
2475
2475 This skips avoiding ambiguity, if a process doesn't have
2476 This skips avoiding ambiguity, if a process doesn't have
2476 appropriate privileges for 'path'. This returns False in this
2477 appropriate privileges for 'path'. This returns False in this
2477 case.
2478 case.
2478
2479
2479 Otherwise, this returns True, as "ambiguity is avoided".
2480 Otherwise, this returns True, as "ambiguity is avoided".
2480 """
2481 """
2481 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2482 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2482 try:
2483 try:
2483 os.utime(path, (advanced, advanced))
2484 os.utime(path, (advanced, advanced))
2484 except OSError as inst:
2485 except OSError as inst:
2485 if inst.errno == errno.EPERM:
2486 if inst.errno == errno.EPERM:
2486 # utime() on the file created by another user causes EPERM,
2487 # utime() on the file created by another user causes EPERM,
2487 # if a process doesn't have appropriate privileges
2488 # if a process doesn't have appropriate privileges
2488 return False
2489 return False
2489 raise
2490 raise
2490 return True
2491 return True
2491
2492
2492 def __ne__(self, other):
2493 def __ne__(self, other):
2493 return not self == other
2494 return not self == other
2494
2495
2495
2496
2496 class atomictempfile(object):
2497 class atomictempfile(object):
2497 """writable file object that atomically updates a file
2498 """writable file object that atomically updates a file
2498
2499
2499 All writes will go to a temporary copy of the original file. Call
2500 All writes will go to a temporary copy of the original file. Call
2500 close() when you are done writing, and atomictempfile will rename
2501 close() when you are done writing, and atomictempfile will rename
2501 the temporary copy to the original name, making the changes
2502 the temporary copy to the original name, making the changes
2502 visible. If the object is destroyed without being closed, all your
2503 visible. If the object is destroyed without being closed, all your
2503 writes are discarded.
2504 writes are discarded.
2504
2505
2505 checkambig argument of constructor is used with filestat, and is
2506 checkambig argument of constructor is used with filestat, and is
2506 useful only if target file is guarded by any lock (e.g. repo.lock
2507 useful only if target file is guarded by any lock (e.g. repo.lock
2507 or repo.wlock).
2508 or repo.wlock).
2508 """
2509 """
2509
2510
2510 def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
2511 def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
2511 self.__name = name # permanent name
2512 self.__name = name # permanent name
2512 self._tempname = mktempcopy(
2513 self._tempname = mktempcopy(
2513 name,
2514 name,
2514 emptyok=(b'w' in mode),
2515 emptyok=(b'w' in mode),
2515 createmode=createmode,
2516 createmode=createmode,
2516 enforcewritable=(b'w' in mode),
2517 enforcewritable=(b'w' in mode),
2517 )
2518 )
2518
2519
2519 self._fp = posixfile(self._tempname, mode)
2520 self._fp = posixfile(self._tempname, mode)
2520 self._checkambig = checkambig
2521 self._checkambig = checkambig
2521
2522
2522 # delegated methods
2523 # delegated methods
2523 self.read = self._fp.read
2524 self.read = self._fp.read
2524 self.write = self._fp.write
2525 self.write = self._fp.write
2525 self.seek = self._fp.seek
2526 self.seek = self._fp.seek
2526 self.tell = self._fp.tell
2527 self.tell = self._fp.tell
2527 self.fileno = self._fp.fileno
2528 self.fileno = self._fp.fileno
2528
2529
2529 def close(self):
2530 def close(self):
2530 if not self._fp.closed:
2531 if not self._fp.closed:
2531 self._fp.close()
2532 self._fp.close()
2532 filename = localpath(self.__name)
2533 filename = localpath(self.__name)
2533 oldstat = self._checkambig and filestat.frompath(filename)
2534 oldstat = self._checkambig and filestat.frompath(filename)
2534 if oldstat and oldstat.stat:
2535 if oldstat and oldstat.stat:
2535 rename(self._tempname, filename)
2536 rename(self._tempname, filename)
2536 newstat = filestat.frompath(filename)
2537 newstat = filestat.frompath(filename)
2537 if newstat.isambig(oldstat):
2538 if newstat.isambig(oldstat):
2538 # stat of changed file is ambiguous to original one
2539 # stat of changed file is ambiguous to original one
2539 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2540 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2540 os.utime(filename, (advanced, advanced))
2541 os.utime(filename, (advanced, advanced))
2541 else:
2542 else:
2542 rename(self._tempname, filename)
2543 rename(self._tempname, filename)
2543
2544
2544 def discard(self):
2545 def discard(self):
2545 if not self._fp.closed:
2546 if not self._fp.closed:
2546 try:
2547 try:
2547 os.unlink(self._tempname)
2548 os.unlink(self._tempname)
2548 except OSError:
2549 except OSError:
2549 pass
2550 pass
2550 self._fp.close()
2551 self._fp.close()
2551
2552
2552 def __del__(self):
2553 def __del__(self):
2553 if safehasattr(self, '_fp'): # constructor actually did something
2554 if safehasattr(self, '_fp'): # constructor actually did something
2554 self.discard()
2555 self.discard()
2555
2556
2556 def __enter__(self):
2557 def __enter__(self):
2557 return self
2558 return self
2558
2559
2559 def __exit__(self, exctype, excvalue, traceback):
2560 def __exit__(self, exctype, excvalue, traceback):
2560 if exctype is not None:
2561 if exctype is not None:
2561 self.discard()
2562 self.discard()
2562 else:
2563 else:
2563 self.close()
2564 self.close()
2564
2565
2565
2566
2566 def unlinkpath(f, ignoremissing=False, rmdir=True):
2567 def unlinkpath(f, ignoremissing=False, rmdir=True):
2567 # type: (bytes, bool, bool) -> None
2568 # type: (bytes, bool, bool) -> None
2568 """unlink and remove the directory if it is empty"""
2569 """unlink and remove the directory if it is empty"""
2569 if ignoremissing:
2570 if ignoremissing:
2570 tryunlink(f)
2571 tryunlink(f)
2571 else:
2572 else:
2572 unlink(f)
2573 unlink(f)
2573 if rmdir:
2574 if rmdir:
2574 # try removing directories that might now be empty
2575 # try removing directories that might now be empty
2575 try:
2576 try:
2576 removedirs(os.path.dirname(f))
2577 removedirs(os.path.dirname(f))
2577 except OSError:
2578 except OSError:
2578 pass
2579 pass
2579
2580
2580
2581
2581 def tryunlink(f):
2582 def tryunlink(f):
2582 # type: (bytes) -> None
2583 # type: (bytes) -> None
2583 """Attempt to remove a file, ignoring ENOENT errors."""
2584 """Attempt to remove a file, ignoring ENOENT errors."""
2584 try:
2585 try:
2585 unlink(f)
2586 unlink(f)
2586 except OSError as e:
2587 except OSError as e:
2587 if e.errno != errno.ENOENT:
2588 if e.errno != errno.ENOENT:
2588 raise
2589 raise
2589
2590
2590
2591
2591 def makedirs(name, mode=None, notindexed=False):
2592 def makedirs(name, mode=None, notindexed=False):
2592 # type: (bytes, Optional[int], bool) -> None
2593 # type: (bytes, Optional[int], bool) -> None
2593 """recursive directory creation with parent mode inheritance
2594 """recursive directory creation with parent mode inheritance
2594
2595
2595 Newly created directories are marked as "not to be indexed by
2596 Newly created directories are marked as "not to be indexed by
2596 the content indexing service", if ``notindexed`` is specified
2597 the content indexing service", if ``notindexed`` is specified
2597 for "write" mode access.
2598 for "write" mode access.
2598 """
2599 """
2599 try:
2600 try:
2600 makedir(name, notindexed)
2601 makedir(name, notindexed)
2601 except OSError as err:
2602 except OSError as err:
2602 if err.errno == errno.EEXIST:
2603 if err.errno == errno.EEXIST:
2603 return
2604 return
2604 if err.errno != errno.ENOENT or not name:
2605 if err.errno != errno.ENOENT or not name:
2605 raise
2606 raise
2606 parent = os.path.dirname(os.path.abspath(name))
2607 parent = os.path.dirname(os.path.abspath(name))
2607 if parent == name:
2608 if parent == name:
2608 raise
2609 raise
2609 makedirs(parent, mode, notindexed)
2610 makedirs(parent, mode, notindexed)
2610 try:
2611 try:
2611 makedir(name, notindexed)
2612 makedir(name, notindexed)
2612 except OSError as err:
2613 except OSError as err:
2613 # Catch EEXIST to handle races
2614 # Catch EEXIST to handle races
2614 if err.errno == errno.EEXIST:
2615 if err.errno == errno.EEXIST:
2615 return
2616 return
2616 raise
2617 raise
2617 if mode is not None:
2618 if mode is not None:
2618 os.chmod(name, mode)
2619 os.chmod(name, mode)
2619
2620
2620
2621
2621 def readfile(path):
2622 def readfile(path):
2622 # type: (bytes) -> bytes
2623 # type: (bytes) -> bytes
2623 with open(path, b'rb') as fp:
2624 with open(path, b'rb') as fp:
2624 return fp.read()
2625 return fp.read()
2625
2626
2626
2627
2627 def writefile(path, text):
2628 def writefile(path, text):
2628 # type: (bytes, bytes) -> None
2629 # type: (bytes, bytes) -> None
2629 with open(path, b'wb') as fp:
2630 with open(path, b'wb') as fp:
2630 fp.write(text)
2631 fp.write(text)
2631
2632
2632
2633
2633 def appendfile(path, text):
2634 def appendfile(path, text):
2634 # type: (bytes, bytes) -> None
2635 # type: (bytes, bytes) -> None
2635 with open(path, b'ab') as fp:
2636 with open(path, b'ab') as fp:
2636 fp.write(text)
2637 fp.write(text)
2637
2638
2638
2639
2639 class chunkbuffer(object):
2640 class chunkbuffer(object):
2640 """Allow arbitrary sized chunks of data to be efficiently read from an
2641 """Allow arbitrary sized chunks of data to be efficiently read from an
2641 iterator over chunks of arbitrary size."""
2642 iterator over chunks of arbitrary size."""
2642
2643
2643 def __init__(self, in_iter):
2644 def __init__(self, in_iter):
2644 """in_iter is the iterator that's iterating over the input chunks."""
2645 """in_iter is the iterator that's iterating over the input chunks."""
2645
2646
2646 def splitbig(chunks):
2647 def splitbig(chunks):
2647 for chunk in chunks:
2648 for chunk in chunks:
2648 if len(chunk) > 2 ** 20:
2649 if len(chunk) > 2 ** 20:
2649 pos = 0
2650 pos = 0
2650 while pos < len(chunk):
2651 while pos < len(chunk):
2651 end = pos + 2 ** 18
2652 end = pos + 2 ** 18
2652 yield chunk[pos:end]
2653 yield chunk[pos:end]
2653 pos = end
2654 pos = end
2654 else:
2655 else:
2655 yield chunk
2656 yield chunk
2656
2657
2657 self.iter = splitbig(in_iter)
2658 self.iter = splitbig(in_iter)
2658 self._queue = collections.deque()
2659 self._queue = collections.deque()
2659 self._chunkoffset = 0
2660 self._chunkoffset = 0
2660
2661
2661 def read(self, l=None):
2662 def read(self, l=None):
2662 """Read L bytes of data from the iterator of chunks of data.
2663 """Read L bytes of data from the iterator of chunks of data.
2663 Returns less than L bytes if the iterator runs dry.
2664 Returns less than L bytes if the iterator runs dry.
2664
2665
2665 If size parameter is omitted, read everything"""
2666 If size parameter is omitted, read everything"""
2666 if l is None:
2667 if l is None:
2667 return b''.join(self.iter)
2668 return b''.join(self.iter)
2668
2669
2669 left = l
2670 left = l
2670 buf = []
2671 buf = []
2671 queue = self._queue
2672 queue = self._queue
2672 while left > 0:
2673 while left > 0:
2673 # refill the queue
2674 # refill the queue
2674 if not queue:
2675 if not queue:
2675 target = 2 ** 18
2676 target = 2 ** 18
2676 for chunk in self.iter:
2677 for chunk in self.iter:
2677 queue.append(chunk)
2678 queue.append(chunk)
2678 target -= len(chunk)
2679 target -= len(chunk)
2679 if target <= 0:
2680 if target <= 0:
2680 break
2681 break
2681 if not queue:
2682 if not queue:
2682 break
2683 break
2683
2684
2684 # The easy way to do this would be to queue.popleft(), modify the
2685 # The easy way to do this would be to queue.popleft(), modify the
2685 # chunk (if necessary), then queue.appendleft(). However, for cases
2686 # chunk (if necessary), then queue.appendleft(). However, for cases
2686 # where we read partial chunk content, this incurs 2 dequeue
2687 # where we read partial chunk content, this incurs 2 dequeue
2687 # mutations and creates a new str for the remaining chunk in the
2688 # mutations and creates a new str for the remaining chunk in the
2688 # queue. Our code below avoids this overhead.
2689 # queue. Our code below avoids this overhead.
2689
2690
2690 chunk = queue[0]
2691 chunk = queue[0]
2691 chunkl = len(chunk)
2692 chunkl = len(chunk)
2692 offset = self._chunkoffset
2693 offset = self._chunkoffset
2693
2694
2694 # Use full chunk.
2695 # Use full chunk.
2695 if offset == 0 and left >= chunkl:
2696 if offset == 0 and left >= chunkl:
2696 left -= chunkl
2697 left -= chunkl
2697 queue.popleft()
2698 queue.popleft()
2698 buf.append(chunk)
2699 buf.append(chunk)
2699 # self._chunkoffset remains at 0.
2700 # self._chunkoffset remains at 0.
2700 continue
2701 continue
2701
2702
2702 chunkremaining = chunkl - offset
2703 chunkremaining = chunkl - offset
2703
2704
2704 # Use all of unconsumed part of chunk.
2705 # Use all of unconsumed part of chunk.
2705 if left >= chunkremaining:
2706 if left >= chunkremaining:
2706 left -= chunkremaining
2707 left -= chunkremaining
2707 queue.popleft()
2708 queue.popleft()
2708 # offset == 0 is enabled by block above, so this won't merely
2709 # offset == 0 is enabled by block above, so this won't merely
2709 # copy via ``chunk[0:]``.
2710 # copy via ``chunk[0:]``.
2710 buf.append(chunk[offset:])
2711 buf.append(chunk[offset:])
2711 self._chunkoffset = 0
2712 self._chunkoffset = 0
2712
2713
2713 # Partial chunk needed.
2714 # Partial chunk needed.
2714 else:
2715 else:
2715 buf.append(chunk[offset : offset + left])
2716 buf.append(chunk[offset : offset + left])
2716 self._chunkoffset += left
2717 self._chunkoffset += left
2717 left -= chunkremaining
2718 left -= chunkremaining
2718
2719
2719 return b''.join(buf)
2720 return b''.join(buf)
2720
2721
2721
2722
2722 def filechunkiter(f, size=131072, limit=None):
2723 def filechunkiter(f, size=131072, limit=None):
2723 """Create a generator that produces the data in the file size
2724 """Create a generator that produces the data in the file size
2724 (default 131072) bytes at a time, up to optional limit (default is
2725 (default 131072) bytes at a time, up to optional limit (default is
2725 to read all data). Chunks may be less than size bytes if the
2726 to read all data). Chunks may be less than size bytes if the
2726 chunk is the last chunk in the file, or the file is a socket or
2727 chunk is the last chunk in the file, or the file is a socket or
2727 some other type of file that sometimes reads less data than is
2728 some other type of file that sometimes reads less data than is
2728 requested."""
2729 requested."""
2729 assert size >= 0
2730 assert size >= 0
2730 assert limit is None or limit >= 0
2731 assert limit is None or limit >= 0
2731 while True:
2732 while True:
2732 if limit is None:
2733 if limit is None:
2733 nbytes = size
2734 nbytes = size
2734 else:
2735 else:
2735 nbytes = min(limit, size)
2736 nbytes = min(limit, size)
2736 s = nbytes and f.read(nbytes)
2737 s = nbytes and f.read(nbytes)
2737 if not s:
2738 if not s:
2738 break
2739 break
2739 if limit:
2740 if limit:
2740 limit -= len(s)
2741 limit -= len(s)
2741 yield s
2742 yield s
2742
2743
2743
2744
2744 class cappedreader(object):
2745 class cappedreader(object):
2745 """A file object proxy that allows reading up to N bytes.
2746 """A file object proxy that allows reading up to N bytes.
2746
2747
2747 Given a source file object, instances of this type allow reading up to
2748 Given a source file object, instances of this type allow reading up to
2748 N bytes from that source file object. Attempts to read past the allowed
2749 N bytes from that source file object. Attempts to read past the allowed
2749 limit are treated as EOF.
2750 limit are treated as EOF.
2750
2751
2751 It is assumed that I/O is not performed on the original file object
2752 It is assumed that I/O is not performed on the original file object
2752 in addition to I/O that is performed by this instance. If there is,
2753 in addition to I/O that is performed by this instance. If there is,
2753 state tracking will get out of sync and unexpected results will ensue.
2754 state tracking will get out of sync and unexpected results will ensue.
2754 """
2755 """
2755
2756
2756 def __init__(self, fh, limit):
2757 def __init__(self, fh, limit):
2757 """Allow reading up to <limit> bytes from <fh>."""
2758 """Allow reading up to <limit> bytes from <fh>."""
2758 self._fh = fh
2759 self._fh = fh
2759 self._left = limit
2760 self._left = limit
2760
2761
2761 def read(self, n=-1):
2762 def read(self, n=-1):
2762 if not self._left:
2763 if not self._left:
2763 return b''
2764 return b''
2764
2765
2765 if n < 0:
2766 if n < 0:
2766 n = self._left
2767 n = self._left
2767
2768
2768 data = self._fh.read(min(n, self._left))
2769 data = self._fh.read(min(n, self._left))
2769 self._left -= len(data)
2770 self._left -= len(data)
2770 assert self._left >= 0
2771 assert self._left >= 0
2771
2772
2772 return data
2773 return data
2773
2774
2774 def readinto(self, b):
2775 def readinto(self, b):
2775 res = self.read(len(b))
2776 res = self.read(len(b))
2776 if res is None:
2777 if res is None:
2777 return None
2778 return None
2778
2779
2779 b[0 : len(res)] = res
2780 b[0 : len(res)] = res
2780 return len(res)
2781 return len(res)
2781
2782
2782
2783
2783 def unitcountfn(*unittable):
2784 def unitcountfn(*unittable):
2784 '''return a function that renders a readable count of some quantity'''
2785 '''return a function that renders a readable count of some quantity'''
2785
2786
2786 def go(count):
2787 def go(count):
2787 for multiplier, divisor, format in unittable:
2788 for multiplier, divisor, format in unittable:
2788 if abs(count) >= divisor * multiplier:
2789 if abs(count) >= divisor * multiplier:
2789 return format % (count / float(divisor))
2790 return format % (count / float(divisor))
2790 return unittable[-1][2] % count
2791 return unittable[-1][2] % count
2791
2792
2792 return go
2793 return go
2793
2794
2794
2795
2795 def processlinerange(fromline, toline):
2796 def processlinerange(fromline, toline):
2796 # type: (int, int) -> Tuple[int, int]
2797 # type: (int, int) -> Tuple[int, int]
2797 """Check that linerange <fromline>:<toline> makes sense and return a
2798 """Check that linerange <fromline>:<toline> makes sense and return a
2798 0-based range.
2799 0-based range.
2799
2800
2800 >>> processlinerange(10, 20)
2801 >>> processlinerange(10, 20)
2801 (9, 20)
2802 (9, 20)
2802 >>> processlinerange(2, 1)
2803 >>> processlinerange(2, 1)
2803 Traceback (most recent call last):
2804 Traceback (most recent call last):
2804 ...
2805 ...
2805 ParseError: line range must be positive
2806 ParseError: line range must be positive
2806 >>> processlinerange(0, 5)
2807 >>> processlinerange(0, 5)
2807 Traceback (most recent call last):
2808 Traceback (most recent call last):
2808 ...
2809 ...
2809 ParseError: fromline must be strictly positive
2810 ParseError: fromline must be strictly positive
2810 """
2811 """
2811 if toline - fromline < 0:
2812 if toline - fromline < 0:
2812 raise error.ParseError(_(b"line range must be positive"))
2813 raise error.ParseError(_(b"line range must be positive"))
2813 if fromline < 1:
2814 if fromline < 1:
2814 raise error.ParseError(_(b"fromline must be strictly positive"))
2815 raise error.ParseError(_(b"fromline must be strictly positive"))
2815 return fromline - 1, toline
2816 return fromline - 1, toline
2816
2817
2817
2818
2818 bytecount = unitcountfn(
2819 bytecount = unitcountfn(
2819 (100, 1 << 30, _(b'%.0f GB')),
2820 (100, 1 << 30, _(b'%.0f GB')),
2820 (10, 1 << 30, _(b'%.1f GB')),
2821 (10, 1 << 30, _(b'%.1f GB')),
2821 (1, 1 << 30, _(b'%.2f GB')),
2822 (1, 1 << 30, _(b'%.2f GB')),
2822 (100, 1 << 20, _(b'%.0f MB')),
2823 (100, 1 << 20, _(b'%.0f MB')),
2823 (10, 1 << 20, _(b'%.1f MB')),
2824 (10, 1 << 20, _(b'%.1f MB')),
2824 (1, 1 << 20, _(b'%.2f MB')),
2825 (1, 1 << 20, _(b'%.2f MB')),
2825 (100, 1 << 10, _(b'%.0f KB')),
2826 (100, 1 << 10, _(b'%.0f KB')),
2826 (10, 1 << 10, _(b'%.1f KB')),
2827 (10, 1 << 10, _(b'%.1f KB')),
2827 (1, 1 << 10, _(b'%.2f KB')),
2828 (1, 1 << 10, _(b'%.2f KB')),
2828 (1, 1, _(b'%.0f bytes')),
2829 (1, 1, _(b'%.0f bytes')),
2829 )
2830 )
2830
2831
2831
2832
2832 class transformingwriter(object):
2833 class transformingwriter(object):
2833 """Writable file wrapper to transform data by function"""
2834 """Writable file wrapper to transform data by function"""
2834
2835
2835 def __init__(self, fp, encode):
2836 def __init__(self, fp, encode):
2836 self._fp = fp
2837 self._fp = fp
2837 self._encode = encode
2838 self._encode = encode
2838
2839
2839 def close(self):
2840 def close(self):
2840 self._fp.close()
2841 self._fp.close()
2841
2842
2842 def flush(self):
2843 def flush(self):
2843 self._fp.flush()
2844 self._fp.flush()
2844
2845
2845 def write(self, data):
2846 def write(self, data):
2846 return self._fp.write(self._encode(data))
2847 return self._fp.write(self._encode(data))
2847
2848
2848
2849
2849 # Matches a single EOL which can either be a CRLF where repeated CR
2850 # Matches a single EOL which can either be a CRLF where repeated CR
2850 # are removed or a LF. We do not care about old Macintosh files, so a
2851 # are removed or a LF. We do not care about old Macintosh files, so a
2851 # stray CR is an error.
2852 # stray CR is an error.
2852 _eolre = remod.compile(br'\r*\n')
2853 _eolre = remod.compile(br'\r*\n')
2853
2854
2854
2855
2855 def tolf(s):
2856 def tolf(s):
2856 # type: (bytes) -> bytes
2857 # type: (bytes) -> bytes
2857 return _eolre.sub(b'\n', s)
2858 return _eolre.sub(b'\n', s)
2858
2859
2859
2860
2860 def tocrlf(s):
2861 def tocrlf(s):
2861 # type: (bytes) -> bytes
2862 # type: (bytes) -> bytes
2862 return _eolre.sub(b'\r\n', s)
2863 return _eolre.sub(b'\r\n', s)
2863
2864
2864
2865
2865 def _crlfwriter(fp):
2866 def _crlfwriter(fp):
2866 return transformingwriter(fp, tocrlf)
2867 return transformingwriter(fp, tocrlf)
2867
2868
2868
2869
2869 if pycompat.oslinesep == b'\r\n':
2870 if pycompat.oslinesep == b'\r\n':
2870 tonativeeol = tocrlf
2871 tonativeeol = tocrlf
2871 fromnativeeol = tolf
2872 fromnativeeol = tolf
2872 nativeeolwriter = _crlfwriter
2873 nativeeolwriter = _crlfwriter
2873 else:
2874 else:
2874 tonativeeol = pycompat.identity
2875 tonativeeol = pycompat.identity
2875 fromnativeeol = pycompat.identity
2876 fromnativeeol = pycompat.identity
2876 nativeeolwriter = pycompat.identity
2877 nativeeolwriter = pycompat.identity
2877
2878
2878 if pyplatform.python_implementation() == b'CPython' and sys.version_info < (
2879 if pyplatform.python_implementation() == b'CPython' and sys.version_info < (
2879 3,
2880 3,
2880 0,
2881 0,
2881 ):
2882 ):
2882 # There is an issue in CPython that some IO methods do not handle EINTR
2883 # There is an issue in CPython that some IO methods do not handle EINTR
2883 # correctly. The following table shows what CPython version (and functions)
2884 # correctly. The following table shows what CPython version (and functions)
2884 # are affected (buggy: has the EINTR bug, okay: otherwise):
2885 # are affected (buggy: has the EINTR bug, okay: otherwise):
2885 #
2886 #
2886 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2887 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2887 # --------------------------------------------------
2888 # --------------------------------------------------
2888 # fp.__iter__ | buggy | buggy | okay
2889 # fp.__iter__ | buggy | buggy | okay
2889 # fp.read* | buggy | okay [1] | okay
2890 # fp.read* | buggy | okay [1] | okay
2890 #
2891 #
2891 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2892 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2892 #
2893 #
2893 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2894 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2894 # like "read*" work fine, as we do not support Python < 2.7.4.
2895 # like "read*" work fine, as we do not support Python < 2.7.4.
2895 #
2896 #
2896 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2897 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2897 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2898 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2898 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2899 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2899 # fp.__iter__ but not other fp.read* methods.
2900 # fp.__iter__ but not other fp.read* methods.
2900 #
2901 #
2901 # On modern systems like Linux, the "read" syscall cannot be interrupted
2902 # On modern systems like Linux, the "read" syscall cannot be interrupted
2902 # when reading "fast" files like on-disk files. So the EINTR issue only
2903 # when reading "fast" files like on-disk files. So the EINTR issue only
2903 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2904 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2904 # files approximately as "fast" files and use the fast (unsafe) code path,
2905 # files approximately as "fast" files and use the fast (unsafe) code path,
2905 # to minimize the performance impact.
2906 # to minimize the performance impact.
2906
2907
2907 def iterfile(fp):
2908 def iterfile(fp):
2908 fastpath = True
2909 fastpath = True
2909 if type(fp) is file:
2910 if type(fp) is file:
2910 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2911 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2911 if fastpath:
2912 if fastpath:
2912 return fp
2913 return fp
2913 else:
2914 else:
2914 # fp.readline deals with EINTR correctly, use it as a workaround.
2915 # fp.readline deals with EINTR correctly, use it as a workaround.
2915 return iter(fp.readline, b'')
2916 return iter(fp.readline, b'')
2916
2917
2917
2918
2918 else:
2919 else:
2919 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2920 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2920 def iterfile(fp):
2921 def iterfile(fp):
2921 return fp
2922 return fp
2922
2923
2923
2924
2924 def iterlines(iterator):
2925 def iterlines(iterator):
2925 # type: (Iterator[bytes]) -> Iterator[bytes]
2926 # type: (Iterator[bytes]) -> Iterator[bytes]
2926 for chunk in iterator:
2927 for chunk in iterator:
2927 for line in chunk.splitlines():
2928 for line in chunk.splitlines():
2928 yield line
2929 yield line
2929
2930
2930
2931
2931 def expandpath(path):
2932 def expandpath(path):
2932 # type: (bytes) -> bytes
2933 # type: (bytes) -> bytes
2933 return os.path.expanduser(os.path.expandvars(path))
2934 return os.path.expanduser(os.path.expandvars(path))
2934
2935
2935
2936
2936 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2937 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2937 """Return the result of interpolating items in the mapping into string s.
2938 """Return the result of interpolating items in the mapping into string s.
2938
2939
2939 prefix is a single character string, or a two character string with
2940 prefix is a single character string, or a two character string with
2940 a backslash as the first character if the prefix needs to be escaped in
2941 a backslash as the first character if the prefix needs to be escaped in
2941 a regular expression.
2942 a regular expression.
2942
2943
2943 fn is an optional function that will be applied to the replacement text
2944 fn is an optional function that will be applied to the replacement text
2944 just before replacement.
2945 just before replacement.
2945
2946
2946 escape_prefix is an optional flag that allows using doubled prefix for
2947 escape_prefix is an optional flag that allows using doubled prefix for
2947 its escaping.
2948 its escaping.
2948 """
2949 """
2949 fn = fn or (lambda s: s)
2950 fn = fn or (lambda s: s)
2950 patterns = b'|'.join(mapping.keys())
2951 patterns = b'|'.join(mapping.keys())
2951 if escape_prefix:
2952 if escape_prefix:
2952 patterns += b'|' + prefix
2953 patterns += b'|' + prefix
2953 if len(prefix) > 1:
2954 if len(prefix) > 1:
2954 prefix_char = prefix[1:]
2955 prefix_char = prefix[1:]
2955 else:
2956 else:
2956 prefix_char = prefix
2957 prefix_char = prefix
2957 mapping[prefix_char] = prefix_char
2958 mapping[prefix_char] = prefix_char
2958 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2959 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2959 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2960 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2960
2961
2961
2962
2962 def getport(*args, **kwargs):
2963 def getport(*args, **kwargs):
2963 msg = b'getport(...) moved to mercurial.utils.urlutil'
2964 msg = b'getport(...) moved to mercurial.utils.urlutil'
2964 nouideprecwarn(msg, b'6.0', stacklevel=2)
2965 nouideprecwarn(msg, b'6.0', stacklevel=2)
2965 return urlutil.getport(*args, **kwargs)
2966 return urlutil.getport(*args, **kwargs)
2966
2967
2967
2968
2968 def url(*args, **kwargs):
2969 def url(*args, **kwargs):
2969 msg = b'url(...) moved to mercurial.utils.urlutil'
2970 msg = b'url(...) moved to mercurial.utils.urlutil'
2970 nouideprecwarn(msg, b'6.0', stacklevel=2)
2971 nouideprecwarn(msg, b'6.0', stacklevel=2)
2971 return urlutil.url(*args, **kwargs)
2972 return urlutil.url(*args, **kwargs)
2972
2973
2973
2974
2974 def hasscheme(*args, **kwargs):
2975 def hasscheme(*args, **kwargs):
2975 msg = b'hasscheme(...) moved to mercurial.utils.urlutil'
2976 msg = b'hasscheme(...) moved to mercurial.utils.urlutil'
2976 nouideprecwarn(msg, b'6.0', stacklevel=2)
2977 nouideprecwarn(msg, b'6.0', stacklevel=2)
2977 return urlutil.hasscheme(*args, **kwargs)
2978 return urlutil.hasscheme(*args, **kwargs)
2978
2979
2979
2980
2980 def hasdriveletter(*args, **kwargs):
2981 def hasdriveletter(*args, **kwargs):
2981 msg = b'hasdriveletter(...) moved to mercurial.utils.urlutil'
2982 msg = b'hasdriveletter(...) moved to mercurial.utils.urlutil'
2982 nouideprecwarn(msg, b'6.0', stacklevel=2)
2983 nouideprecwarn(msg, b'6.0', stacklevel=2)
2983 return urlutil.hasdriveletter(*args, **kwargs)
2984 return urlutil.hasdriveletter(*args, **kwargs)
2984
2985
2985
2986
2986 def urllocalpath(*args, **kwargs):
2987 def urllocalpath(*args, **kwargs):
2987 msg = b'urllocalpath(...) moved to mercurial.utils.urlutil'
2988 msg = b'urllocalpath(...) moved to mercurial.utils.urlutil'
2988 nouideprecwarn(msg, b'6.0', stacklevel=2)
2989 nouideprecwarn(msg, b'6.0', stacklevel=2)
2989 return urlutil.urllocalpath(*args, **kwargs)
2990 return urlutil.urllocalpath(*args, **kwargs)
2990
2991
2991
2992
2992 def checksafessh(*args, **kwargs):
2993 def checksafessh(*args, **kwargs):
2993 msg = b'checksafessh(...) moved to mercurial.utils.urlutil'
2994 msg = b'checksafessh(...) moved to mercurial.utils.urlutil'
2994 nouideprecwarn(msg, b'6.0', stacklevel=2)
2995 nouideprecwarn(msg, b'6.0', stacklevel=2)
2995 return urlutil.checksafessh(*args, **kwargs)
2996 return urlutil.checksafessh(*args, **kwargs)
2996
2997
2997
2998
2998 def hidepassword(*args, **kwargs):
2999 def hidepassword(*args, **kwargs):
2999 msg = b'hidepassword(...) moved to mercurial.utils.urlutil'
3000 msg = b'hidepassword(...) moved to mercurial.utils.urlutil'
3000 nouideprecwarn(msg, b'6.0', stacklevel=2)
3001 nouideprecwarn(msg, b'6.0', stacklevel=2)
3001 return urlutil.hidepassword(*args, **kwargs)
3002 return urlutil.hidepassword(*args, **kwargs)
3002
3003
3003
3004
3004 def removeauth(*args, **kwargs):
3005 def removeauth(*args, **kwargs):
3005 msg = b'removeauth(...) moved to mercurial.utils.urlutil'
3006 msg = b'removeauth(...) moved to mercurial.utils.urlutil'
3006 nouideprecwarn(msg, b'6.0', stacklevel=2)
3007 nouideprecwarn(msg, b'6.0', stacklevel=2)
3007 return urlutil.removeauth(*args, **kwargs)
3008 return urlutil.removeauth(*args, **kwargs)
3008
3009
3009
3010
3010 timecount = unitcountfn(
3011 timecount = unitcountfn(
3011 (1, 1e3, _(b'%.0f s')),
3012 (1, 1e3, _(b'%.0f s')),
3012 (100, 1, _(b'%.1f s')),
3013 (100, 1, _(b'%.1f s')),
3013 (10, 1, _(b'%.2f s')),
3014 (10, 1, _(b'%.2f s')),
3014 (1, 1, _(b'%.3f s')),
3015 (1, 1, _(b'%.3f s')),
3015 (100, 0.001, _(b'%.1f ms')),
3016 (100, 0.001, _(b'%.1f ms')),
3016 (10, 0.001, _(b'%.2f ms')),
3017 (10, 0.001, _(b'%.2f ms')),
3017 (1, 0.001, _(b'%.3f ms')),
3018 (1, 0.001, _(b'%.3f ms')),
3018 (100, 0.000001, _(b'%.1f us')),
3019 (100, 0.000001, _(b'%.1f us')),
3019 (10, 0.000001, _(b'%.2f us')),
3020 (10, 0.000001, _(b'%.2f us')),
3020 (1, 0.000001, _(b'%.3f us')),
3021 (1, 0.000001, _(b'%.3f us')),
3021 (100, 0.000000001, _(b'%.1f ns')),
3022 (100, 0.000000001, _(b'%.1f ns')),
3022 (10, 0.000000001, _(b'%.2f ns')),
3023 (10, 0.000000001, _(b'%.2f ns')),
3023 (1, 0.000000001, _(b'%.3f ns')),
3024 (1, 0.000000001, _(b'%.3f ns')),
3024 )
3025 )
3025
3026
3026
3027
3027 @attr.s
3028 @attr.s
3028 class timedcmstats(object):
3029 class timedcmstats(object):
3029 """Stats information produced by the timedcm context manager on entering."""
3030 """Stats information produced by the timedcm context manager on entering."""
3030
3031
3031 # the starting value of the timer as a float (meaning and resulution is
3032 # the starting value of the timer as a float (meaning and resulution is
3032 # platform dependent, see util.timer)
3033 # platform dependent, see util.timer)
3033 start = attr.ib(default=attr.Factory(lambda: timer()))
3034 start = attr.ib(default=attr.Factory(lambda: timer()))
3034 # the number of seconds as a floating point value; starts at 0, updated when
3035 # the number of seconds as a floating point value; starts at 0, updated when
3035 # the context is exited.
3036 # the context is exited.
3036 elapsed = attr.ib(default=0)
3037 elapsed = attr.ib(default=0)
3037 # the number of nested timedcm context managers.
3038 # the number of nested timedcm context managers.
3038 level = attr.ib(default=1)
3039 level = attr.ib(default=1)
3039
3040
3040 def __bytes__(self):
3041 def __bytes__(self):
3041 return timecount(self.elapsed) if self.elapsed else b'<unknown>'
3042 return timecount(self.elapsed) if self.elapsed else b'<unknown>'
3042
3043
3043 __str__ = encoding.strmethod(__bytes__)
3044 __str__ = encoding.strmethod(__bytes__)
3044
3045
3045
3046
3046 @contextlib.contextmanager
3047 @contextlib.contextmanager
3047 def timedcm(whencefmt, *whenceargs):
3048 def timedcm(whencefmt, *whenceargs):
3048 """A context manager that produces timing information for a given context.
3049 """A context manager that produces timing information for a given context.
3049
3050
3050 On entering a timedcmstats instance is produced.
3051 On entering a timedcmstats instance is produced.
3051
3052
3052 This context manager is reentrant.
3053 This context manager is reentrant.
3053
3054
3054 """
3055 """
3055 # track nested context managers
3056 # track nested context managers
3056 timedcm._nested += 1
3057 timedcm._nested += 1
3057 timing_stats = timedcmstats(level=timedcm._nested)
3058 timing_stats = timedcmstats(level=timedcm._nested)
3058 try:
3059 try:
3059 with tracing.log(whencefmt, *whenceargs):
3060 with tracing.log(whencefmt, *whenceargs):
3060 yield timing_stats
3061 yield timing_stats
3061 finally:
3062 finally:
3062 timing_stats.elapsed = timer() - timing_stats.start
3063 timing_stats.elapsed = timer() - timing_stats.start
3063 timedcm._nested -= 1
3064 timedcm._nested -= 1
3064
3065
3065
3066
3066 timedcm._nested = 0
3067 timedcm._nested = 0
3067
3068
3068
3069
3069 def timed(func):
3070 def timed(func):
3070 """Report the execution time of a function call to stderr.
3071 """Report the execution time of a function call to stderr.
3071
3072
3072 During development, use as a decorator when you need to measure
3073 During development, use as a decorator when you need to measure
3073 the cost of a function, e.g. as follows:
3074 the cost of a function, e.g. as follows:
3074
3075
3075 @util.timed
3076 @util.timed
3076 def foo(a, b, c):
3077 def foo(a, b, c):
3077 pass
3078 pass
3078 """
3079 """
3079
3080
3080 def wrapper(*args, **kwargs):
3081 def wrapper(*args, **kwargs):
3081 with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
3082 with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
3082 result = func(*args, **kwargs)
3083 result = func(*args, **kwargs)
3083 stderr = procutil.stderr
3084 stderr = procutil.stderr
3084 stderr.write(
3085 stderr.write(
3085 b'%s%s: %s\n'
3086 b'%s%s: %s\n'
3086 % (
3087 % (
3087 b' ' * time_stats.level * 2,
3088 b' ' * time_stats.level * 2,
3088 pycompat.bytestr(func.__name__),
3089 pycompat.bytestr(func.__name__),
3089 time_stats,
3090 time_stats,
3090 )
3091 )
3091 )
3092 )
3092 return result
3093 return result
3093
3094
3094 return wrapper
3095 return wrapper
3095
3096
3096
3097
3097 _sizeunits = (
3098 _sizeunits = (
3098 (b'm', 2 ** 20),
3099 (b'm', 2 ** 20),
3099 (b'k', 2 ** 10),
3100 (b'k', 2 ** 10),
3100 (b'g', 2 ** 30),
3101 (b'g', 2 ** 30),
3101 (b'kb', 2 ** 10),
3102 (b'kb', 2 ** 10),
3102 (b'mb', 2 ** 20),
3103 (b'mb', 2 ** 20),
3103 (b'gb', 2 ** 30),
3104 (b'gb', 2 ** 30),
3104 (b'b', 1),
3105 (b'b', 1),
3105 )
3106 )
3106
3107
3107
3108
3108 def sizetoint(s):
3109 def sizetoint(s):
3109 # type: (bytes) -> int
3110 # type: (bytes) -> int
3110 """Convert a space specifier to a byte count.
3111 """Convert a space specifier to a byte count.
3111
3112
3112 >>> sizetoint(b'30')
3113 >>> sizetoint(b'30')
3113 30
3114 30
3114 >>> sizetoint(b'2.2kb')
3115 >>> sizetoint(b'2.2kb')
3115 2252
3116 2252
3116 >>> sizetoint(b'6M')
3117 >>> sizetoint(b'6M')
3117 6291456
3118 6291456
3118 """
3119 """
3119 t = s.strip().lower()
3120 t = s.strip().lower()
3120 try:
3121 try:
3121 for k, u in _sizeunits:
3122 for k, u in _sizeunits:
3122 if t.endswith(k):
3123 if t.endswith(k):
3123 return int(float(t[: -len(k)]) * u)
3124 return int(float(t[: -len(k)]) * u)
3124 return int(t)
3125 return int(t)
3125 except ValueError:
3126 except ValueError:
3126 raise error.ParseError(_(b"couldn't parse size: %s") % s)
3127 raise error.ParseError(_(b"couldn't parse size: %s") % s)
3127
3128
3128
3129
3129 class hooks(object):
3130 class hooks(object):
3130 """A collection of hook functions that can be used to extend a
3131 """A collection of hook functions that can be used to extend a
3131 function's behavior. Hooks are called in lexicographic order,
3132 function's behavior. Hooks are called in lexicographic order,
3132 based on the names of their sources."""
3133 based on the names of their sources."""
3133
3134
3134 def __init__(self):
3135 def __init__(self):
3135 self._hooks = []
3136 self._hooks = []
3136
3137
3137 def add(self, source, hook):
3138 def add(self, source, hook):
3138 self._hooks.append((source, hook))
3139 self._hooks.append((source, hook))
3139
3140
3140 def __call__(self, *args):
3141 def __call__(self, *args):
3141 self._hooks.sort(key=lambda x: x[0])
3142 self._hooks.sort(key=lambda x: x[0])
3142 results = []
3143 results = []
3143 for source, hook in self._hooks:
3144 for source, hook in self._hooks:
3144 results.append(hook(*args))
3145 results.append(hook(*args))
3145 return results
3146 return results
3146
3147
3147
3148
3148 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
3149 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
3149 """Yields lines for a nicely formatted stacktrace.
3150 """Yields lines for a nicely formatted stacktrace.
3150 Skips the 'skip' last entries, then return the last 'depth' entries.
3151 Skips the 'skip' last entries, then return the last 'depth' entries.
3151 Each file+linenumber is formatted according to fileline.
3152 Each file+linenumber is formatted according to fileline.
3152 Each line is formatted according to line.
3153 Each line is formatted according to line.
3153 If line is None, it yields:
3154 If line is None, it yields:
3154 length of longest filepath+line number,
3155 length of longest filepath+line number,
3155 filepath+linenumber,
3156 filepath+linenumber,
3156 function
3157 function
3157
3158
3158 Not be used in production code but very convenient while developing.
3159 Not be used in production code but very convenient while developing.
3159 """
3160 """
3160 entries = [
3161 entries = [
3161 (fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3162 (fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3162 for fn, ln, func, _text in traceback.extract_stack()[: -skip - 1]
3163 for fn, ln, func, _text in traceback.extract_stack()[: -skip - 1]
3163 ][-depth:]
3164 ][-depth:]
3164 if entries:
3165 if entries:
3165 fnmax = max(len(entry[0]) for entry in entries)
3166 fnmax = max(len(entry[0]) for entry in entries)
3166 for fnln, func in entries:
3167 for fnln, func in entries:
3167 if line is None:
3168 if line is None:
3168 yield (fnmax, fnln, func)
3169 yield (fnmax, fnln, func)
3169 else:
3170 else:
3170 yield line % (fnmax, fnln, func)
3171 yield line % (fnmax, fnln, func)
3171
3172
3172
3173
3173 def debugstacktrace(
3174 def debugstacktrace(
3174 msg=b'stacktrace',
3175 msg=b'stacktrace',
3175 skip=0,
3176 skip=0,
3176 f=procutil.stderr,
3177 f=procutil.stderr,
3177 otherf=procutil.stdout,
3178 otherf=procutil.stdout,
3178 depth=0,
3179 depth=0,
3179 prefix=b'',
3180 prefix=b'',
3180 ):
3181 ):
3181 """Writes a message to f (stderr) with a nicely formatted stacktrace.
3182 """Writes a message to f (stderr) with a nicely formatted stacktrace.
3182 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3183 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3183 By default it will flush stdout first.
3184 By default it will flush stdout first.
3184 It can be used everywhere and intentionally does not require an ui object.
3185 It can be used everywhere and intentionally does not require an ui object.
3185 Not be used in production code but very convenient while developing.
3186 Not be used in production code but very convenient while developing.
3186 """
3187 """
3187 if otherf:
3188 if otherf:
3188 otherf.flush()
3189 otherf.flush()
3189 f.write(b'%s%s at:\n' % (prefix, msg.rstrip()))
3190 f.write(b'%s%s at:\n' % (prefix, msg.rstrip()))
3190 for line in getstackframes(skip + 1, depth=depth):
3191 for line in getstackframes(skip + 1, depth=depth):
3191 f.write(prefix + line)
3192 f.write(prefix + line)
3192 f.flush()
3193 f.flush()
3193
3194
3194
3195
3195 # convenient shortcut
3196 # convenient shortcut
3196 dst = debugstacktrace
3197 dst = debugstacktrace
3197
3198
3198
3199
3199 def safename(f, tag, ctx, others=None):
3200 def safename(f, tag, ctx, others=None):
3200 """
3201 """
3201 Generate a name that it is safe to rename f to in the given context.
3202 Generate a name that it is safe to rename f to in the given context.
3202
3203
3203 f: filename to rename
3204 f: filename to rename
3204 tag: a string tag that will be included in the new name
3205 tag: a string tag that will be included in the new name
3205 ctx: a context, in which the new name must not exist
3206 ctx: a context, in which the new name must not exist
3206 others: a set of other filenames that the new name must not be in
3207 others: a set of other filenames that the new name must not be in
3207
3208
3208 Returns a file name of the form oldname~tag[~number] which does not exist
3209 Returns a file name of the form oldname~tag[~number] which does not exist
3209 in the provided context and is not in the set of other names.
3210 in the provided context and is not in the set of other names.
3210 """
3211 """
3211 if others is None:
3212 if others is None:
3212 others = set()
3213 others = set()
3213
3214
3214 fn = b'%s~%s' % (f, tag)
3215 fn = b'%s~%s' % (f, tag)
3215 if fn not in ctx and fn not in others:
3216 if fn not in ctx and fn not in others:
3216 return fn
3217 return fn
3217 for n in itertools.count(1):
3218 for n in itertools.count(1):
3218 fn = b'%s~%s~%s' % (f, tag, n)
3219 fn = b'%s~%s~%s' % (f, tag, n)
3219 if fn not in ctx and fn not in others:
3220 if fn not in ctx and fn not in others:
3220 return fn
3221 return fn
3221
3222
3222
3223
3223 def readexactly(stream, n):
3224 def readexactly(stream, n):
3224 '''read n bytes from stream.read and abort if less was available'''
3225 '''read n bytes from stream.read and abort if less was available'''
3225 s = stream.read(n)
3226 s = stream.read(n)
3226 if len(s) < n:
3227 if len(s) < n:
3227 raise error.Abort(
3228 raise error.Abort(
3228 _(b"stream ended unexpectedly (got %d bytes, expected %d)")
3229 _(b"stream ended unexpectedly (got %d bytes, expected %d)")
3229 % (len(s), n)
3230 % (len(s), n)
3230 )
3231 )
3231 return s
3232 return s
3232
3233
3233
3234
3234 def uvarintencode(value):
3235 def uvarintencode(value):
3235 """Encode an unsigned integer value to a varint.
3236 """Encode an unsigned integer value to a varint.
3236
3237
3237 A varint is a variable length integer of 1 or more bytes. Each byte
3238 A varint is a variable length integer of 1 or more bytes. Each byte
3238 except the last has the most significant bit set. The lower 7 bits of
3239 except the last has the most significant bit set. The lower 7 bits of
3239 each byte store the 2's complement representation, least significant group
3240 each byte store the 2's complement representation, least significant group
3240 first.
3241 first.
3241
3242
3242 >>> uvarintencode(0)
3243 >>> uvarintencode(0)
3243 '\\x00'
3244 '\\x00'
3244 >>> uvarintencode(1)
3245 >>> uvarintencode(1)
3245 '\\x01'
3246 '\\x01'
3246 >>> uvarintencode(127)
3247 >>> uvarintencode(127)
3247 '\\x7f'
3248 '\\x7f'
3248 >>> uvarintencode(1337)
3249 >>> uvarintencode(1337)
3249 '\\xb9\\n'
3250 '\\xb9\\n'
3250 >>> uvarintencode(65536)
3251 >>> uvarintencode(65536)
3251 '\\x80\\x80\\x04'
3252 '\\x80\\x80\\x04'
3252 >>> uvarintencode(-1)
3253 >>> uvarintencode(-1)
3253 Traceback (most recent call last):
3254 Traceback (most recent call last):
3254 ...
3255 ...
3255 ProgrammingError: negative value for uvarint: -1
3256 ProgrammingError: negative value for uvarint: -1
3256 """
3257 """
3257 if value < 0:
3258 if value < 0:
3258 raise error.ProgrammingError(b'negative value for uvarint: %d' % value)
3259 raise error.ProgrammingError(b'negative value for uvarint: %d' % value)
3259 bits = value & 0x7F
3260 bits = value & 0x7F
3260 value >>= 7
3261 value >>= 7
3261 bytes = []
3262 bytes = []
3262 while value:
3263 while value:
3263 bytes.append(pycompat.bytechr(0x80 | bits))
3264 bytes.append(pycompat.bytechr(0x80 | bits))
3264 bits = value & 0x7F
3265 bits = value & 0x7F
3265 value >>= 7
3266 value >>= 7
3266 bytes.append(pycompat.bytechr(bits))
3267 bytes.append(pycompat.bytechr(bits))
3267
3268
3268 return b''.join(bytes)
3269 return b''.join(bytes)
3269
3270
3270
3271
3271 def uvarintdecodestream(fh):
3272 def uvarintdecodestream(fh):
3272 """Decode an unsigned variable length integer from a stream.
3273 """Decode an unsigned variable length integer from a stream.
3273
3274
3274 The passed argument is anything that has a ``.read(N)`` method.
3275 The passed argument is anything that has a ``.read(N)`` method.
3275
3276
3276 >>> try:
3277 >>> try:
3277 ... from StringIO import StringIO as BytesIO
3278 ... from StringIO import StringIO as BytesIO
3278 ... except ImportError:
3279 ... except ImportError:
3279 ... from io import BytesIO
3280 ... from io import BytesIO
3280 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3281 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3281 0
3282 0
3282 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3283 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3283 1
3284 1
3284 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3285 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3285 127
3286 127
3286 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3287 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3287 1337
3288 1337
3288 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3289 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3289 65536
3290 65536
3290 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3291 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3291 Traceback (most recent call last):
3292 Traceback (most recent call last):
3292 ...
3293 ...
3293 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3294 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3294 """
3295 """
3295 result = 0
3296 result = 0
3296 shift = 0
3297 shift = 0
3297 while True:
3298 while True:
3298 byte = ord(readexactly(fh, 1))
3299 byte = ord(readexactly(fh, 1))
3299 result |= (byte & 0x7F) << shift
3300 result |= (byte & 0x7F) << shift
3300 if not (byte & 0x80):
3301 if not (byte & 0x80):
3301 return result
3302 return result
3302 shift += 7
3303 shift += 7
3303
3304
3304
3305
3305 # Passing the '' locale means that the locale should be set according to the
3306 # Passing the '' locale means that the locale should be set according to the
3306 # user settings (environment variables).
3307 # user settings (environment variables).
3307 # Python sometimes avoids setting the global locale settings. When interfacing
3308 # Python sometimes avoids setting the global locale settings. When interfacing
3308 # with C code (e.g. the curses module or the Subversion bindings), the global
3309 # with C code (e.g. the curses module or the Subversion bindings), the global
3309 # locale settings must be initialized correctly. Python 2 does not initialize
3310 # locale settings must be initialized correctly. Python 2 does not initialize
3310 # the global locale settings on interpreter startup. Python 3 sometimes
3311 # the global locale settings on interpreter startup. Python 3 sometimes
3311 # initializes LC_CTYPE, but not consistently at least on Windows. Therefore we
3312 # initializes LC_CTYPE, but not consistently at least on Windows. Therefore we
3312 # explicitly initialize it to get consistent behavior if it's not already
3313 # explicitly initialize it to get consistent behavior if it's not already
3313 # initialized. Since CPython commit 177d921c8c03d30daa32994362023f777624b10d,
3314 # initialized. Since CPython commit 177d921c8c03d30daa32994362023f777624b10d,
3314 # LC_CTYPE is always initialized. If we require Python 3.8+, we should re-check
3315 # LC_CTYPE is always initialized. If we require Python 3.8+, we should re-check
3315 # if we can remove this code.
3316 # if we can remove this code.
3316 @contextlib.contextmanager
3317 @contextlib.contextmanager
3317 def with_lc_ctype():
3318 def with_lc_ctype():
3318 oldloc = locale.setlocale(locale.LC_CTYPE, None)
3319 oldloc = locale.setlocale(locale.LC_CTYPE, None)
3319 if oldloc == 'C':
3320 if oldloc == 'C':
3320 try:
3321 try:
3321 try:
3322 try:
3322 locale.setlocale(locale.LC_CTYPE, '')
3323 locale.setlocale(locale.LC_CTYPE, '')
3323 except locale.Error:
3324 except locale.Error:
3324 # The likely case is that the locale from the environment
3325 # The likely case is that the locale from the environment
3325 # variables is unknown.
3326 # variables is unknown.
3326 pass
3327 pass
3327 yield
3328 yield
3328 finally:
3329 finally:
3329 locale.setlocale(locale.LC_CTYPE, oldloc)
3330 locale.setlocale(locale.LC_CTYPE, oldloc)
3330 else:
3331 else:
3331 yield
3332 yield
3332
3333
3333
3334
3334 def _estimatememory():
3335 def _estimatememory():
3335 # type: () -> Optional[int]
3336 # type: () -> Optional[int]
3336 """Provide an estimate for the available system memory in Bytes.
3337 """Provide an estimate for the available system memory in Bytes.
3337
3338
3338 If no estimate can be provided on the platform, returns None.
3339 If no estimate can be provided on the platform, returns None.
3339 """
3340 """
3340 if pycompat.sysplatform.startswith(b'win'):
3341 if pycompat.sysplatform.startswith(b'win'):
3341 # On Windows, use the GlobalMemoryStatusEx kernel function directly.
3342 # On Windows, use the GlobalMemoryStatusEx kernel function directly.
3342 from ctypes import c_long as DWORD, c_ulonglong as DWORDLONG
3343 from ctypes import c_long as DWORD, c_ulonglong as DWORDLONG
3343 from ctypes.wintypes import ( # pytype: disable=import-error
3344 from ctypes.wintypes import ( # pytype: disable=import-error
3344 Structure,
3345 Structure,
3345 byref,
3346 byref,
3346 sizeof,
3347 sizeof,
3347 windll,
3348 windll,
3348 )
3349 )
3349
3350
3350 class MEMORYSTATUSEX(Structure):
3351 class MEMORYSTATUSEX(Structure):
3351 _fields_ = [
3352 _fields_ = [
3352 ('dwLength', DWORD),
3353 ('dwLength', DWORD),
3353 ('dwMemoryLoad', DWORD),
3354 ('dwMemoryLoad', DWORD),
3354 ('ullTotalPhys', DWORDLONG),
3355 ('ullTotalPhys', DWORDLONG),
3355 ('ullAvailPhys', DWORDLONG),
3356 ('ullAvailPhys', DWORDLONG),
3356 ('ullTotalPageFile', DWORDLONG),
3357 ('ullTotalPageFile', DWORDLONG),
3357 ('ullAvailPageFile', DWORDLONG),
3358 ('ullAvailPageFile', DWORDLONG),
3358 ('ullTotalVirtual', DWORDLONG),
3359 ('ullTotalVirtual', DWORDLONG),
3359 ('ullAvailVirtual', DWORDLONG),
3360 ('ullAvailVirtual', DWORDLONG),
3360 ('ullExtendedVirtual', DWORDLONG),
3361 ('ullExtendedVirtual', DWORDLONG),
3361 ]
3362 ]
3362
3363
3363 x = MEMORYSTATUSEX()
3364 x = MEMORYSTATUSEX()
3364 x.dwLength = sizeof(x)
3365 x.dwLength = sizeof(x)
3365 windll.kernel32.GlobalMemoryStatusEx(byref(x))
3366 windll.kernel32.GlobalMemoryStatusEx(byref(x))
3366 return x.ullAvailPhys
3367 return x.ullAvailPhys
3367
3368
3368 # On newer Unix-like systems and Mac OSX, the sysconf interface
3369 # On newer Unix-like systems and Mac OSX, the sysconf interface
3369 # can be used. _SC_PAGE_SIZE is part of POSIX; _SC_PHYS_PAGES
3370 # can be used. _SC_PAGE_SIZE is part of POSIX; _SC_PHYS_PAGES
3370 # seems to be implemented on most systems.
3371 # seems to be implemented on most systems.
3371 try:
3372 try:
3372 pagesize = os.sysconf(os.sysconf_names['SC_PAGE_SIZE'])
3373 pagesize = os.sysconf(os.sysconf_names['SC_PAGE_SIZE'])
3373 pages = os.sysconf(os.sysconf_names['SC_PHYS_PAGES'])
3374 pages = os.sysconf(os.sysconf_names['SC_PHYS_PAGES'])
3374 return pagesize * pages
3375 return pagesize * pages
3375 except OSError: # sysconf can fail
3376 except OSError: # sysconf can fail
3376 pass
3377 pass
3377 except KeyError: # unknown parameter
3378 except KeyError: # unknown parameter
3378 pass
3379 pass
@@ -1,691 +1,713 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
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 getpass
11 import getpass
12 import msvcrt
12 import msvcrt
13 import os
13 import os
14 import re
14 import re
15 import stat
15 import stat
16 import string
16 import string
17 import sys
17 import sys
18
18
19 from .i18n import _
19 from .i18n import _
20 from .pycompat import getattr
20 from .pycompat import getattr
21 from . import (
21 from . import (
22 encoding,
22 encoding,
23 error,
23 error,
24 policy,
24 policy,
25 pycompat,
25 pycompat,
26 win32,
26 win32,
27 )
27 )
28
28
29 try:
29 try:
30 import _winreg as winreg # pytype: disable=import-error
30 import _winreg as winreg # pytype: disable=import-error
31
31
32 winreg.CloseKey
32 winreg.CloseKey
33 except ImportError:
33 except ImportError:
34 # py2 only
34 # py2 only
35 import winreg # pytype: disable=import-error
35 import winreg # pytype: disable=import-error
36
36
37 osutil = policy.importmod('osutil')
37 osutil = policy.importmod('osutil')
38
38
39 getfsmountpoint = win32.getvolumename
39 getfsmountpoint = win32.getvolumename
40 getfstype = win32.getfstype
40 getfstype = win32.getfstype
41 getuser = win32.getuser
41 getuser = win32.getuser
42 hidewindow = win32.hidewindow
42 hidewindow = win32.hidewindow
43 makedir = win32.makedir
43 makedir = win32.makedir
44 nlinks = win32.nlinks
44 nlinks = win32.nlinks
45 oslink = win32.oslink
45 oslink = win32.oslink
46 samedevice = win32.samedevice
46 samedevice = win32.samedevice
47 samefile = win32.samefile
47 samefile = win32.samefile
48 setsignalhandler = win32.setsignalhandler
48 setsignalhandler = win32.setsignalhandler
49 spawndetached = win32.spawndetached
49 spawndetached = win32.spawndetached
50 split = os.path.split
50 split = os.path.split
51 testpid = win32.testpid
51 testpid = win32.testpid
52 unlink = win32.unlink
52 unlink = win32.unlink
53
53
54 umask = 0o022
54 umask = 0o022
55
55
56
56
57 class mixedfilemodewrapper(object):
57 class mixedfilemodewrapper(object):
58 """Wraps a file handle when it is opened in read/write mode.
58 """Wraps a file handle when it is opened in read/write mode.
59
59
60 fopen() and fdopen() on Windows have a specific-to-Windows requirement
60 fopen() and fdopen() on Windows have a specific-to-Windows requirement
61 that files opened with mode r+, w+, or a+ make a call to a file positioning
61 that files opened with mode r+, w+, or a+ make a call to a file positioning
62 function when switching between reads and writes. Without this extra call,
62 function when switching between reads and writes. Without this extra call,
63 Python will raise a not very intuitive "IOError: [Errno 0] Error."
63 Python will raise a not very intuitive "IOError: [Errno 0] Error."
64
64
65 This class wraps posixfile instances when the file is opened in read/write
65 This class wraps posixfile instances when the file is opened in read/write
66 mode and automatically adds checks or inserts appropriate file positioning
66 mode and automatically adds checks or inserts appropriate file positioning
67 calls when necessary.
67 calls when necessary.
68 """
68 """
69
69
70 OPNONE = 0
70 OPNONE = 0
71 OPREAD = 1
71 OPREAD = 1
72 OPWRITE = 2
72 OPWRITE = 2
73
73
74 def __init__(self, fp):
74 def __init__(self, fp):
75 object.__setattr__(self, '_fp', fp)
75 object.__setattr__(self, '_fp', fp)
76 object.__setattr__(self, '_lastop', 0)
76 object.__setattr__(self, '_lastop', 0)
77
77
78 def __enter__(self):
78 def __enter__(self):
79 self._fp.__enter__()
79 self._fp.__enter__()
80 return self
80 return self
81
81
82 def __exit__(self, exc_type, exc_val, exc_tb):
82 def __exit__(self, exc_type, exc_val, exc_tb):
83 self._fp.__exit__(exc_type, exc_val, exc_tb)
83 self._fp.__exit__(exc_type, exc_val, exc_tb)
84
84
85 def __getattr__(self, name):
85 def __getattr__(self, name):
86 return getattr(self._fp, name)
86 return getattr(self._fp, name)
87
87
88 def __setattr__(self, name, value):
88 def __setattr__(self, name, value):
89 return self._fp.__setattr__(name, value)
89 return self._fp.__setattr__(name, value)
90
90
91 def _noopseek(self):
91 def _noopseek(self):
92 self._fp.seek(0, os.SEEK_CUR)
92 self._fp.seek(0, os.SEEK_CUR)
93
93
94 def seek(self, *args, **kwargs):
94 def seek(self, *args, **kwargs):
95 object.__setattr__(self, '_lastop', self.OPNONE)
95 object.__setattr__(self, '_lastop', self.OPNONE)
96 return self._fp.seek(*args, **kwargs)
96 return self._fp.seek(*args, **kwargs)
97
97
98 def write(self, d):
98 def write(self, d):
99 if self._lastop == self.OPREAD:
99 if self._lastop == self.OPREAD:
100 self._noopseek()
100 self._noopseek()
101
101
102 object.__setattr__(self, '_lastop', self.OPWRITE)
102 object.__setattr__(self, '_lastop', self.OPWRITE)
103 return self._fp.write(d)
103 return self._fp.write(d)
104
104
105 def writelines(self, *args, **kwargs):
105 def writelines(self, *args, **kwargs):
106 if self._lastop == self.OPREAD:
106 if self._lastop == self.OPREAD:
107 self._noopeseek()
107 self._noopeseek()
108
108
109 object.__setattr__(self, '_lastop', self.OPWRITE)
109 object.__setattr__(self, '_lastop', self.OPWRITE)
110 return self._fp.writelines(*args, **kwargs)
110 return self._fp.writelines(*args, **kwargs)
111
111
112 def read(self, *args, **kwargs):
112 def read(self, *args, **kwargs):
113 if self._lastop == self.OPWRITE:
113 if self._lastop == self.OPWRITE:
114 self._noopseek()
114 self._noopseek()
115
115
116 object.__setattr__(self, '_lastop', self.OPREAD)
116 object.__setattr__(self, '_lastop', self.OPREAD)
117 return self._fp.read(*args, **kwargs)
117 return self._fp.read(*args, **kwargs)
118
118
119 def readline(self, *args, **kwargs):
119 def readline(self, *args, **kwargs):
120 if self._lastop == self.OPWRITE:
120 if self._lastop == self.OPWRITE:
121 self._noopseek()
121 self._noopseek()
122
122
123 object.__setattr__(self, '_lastop', self.OPREAD)
123 object.__setattr__(self, '_lastop', self.OPREAD)
124 return self._fp.readline(*args, **kwargs)
124 return self._fp.readline(*args, **kwargs)
125
125
126 def readlines(self, *args, **kwargs):
126 def readlines(self, *args, **kwargs):
127 if self._lastop == self.OPWRITE:
127 if self._lastop == self.OPWRITE:
128 self._noopseek()
128 self._noopseek()
129
129
130 object.__setattr__(self, '_lastop', self.OPREAD)
130 object.__setattr__(self, '_lastop', self.OPREAD)
131 return self._fp.readlines(*args, **kwargs)
131 return self._fp.readlines(*args, **kwargs)
132
132
133
133
134 class fdproxy(object):
134 class fdproxy(object):
135 """Wraps osutil.posixfile() to override the name attribute to reflect the
135 """Wraps osutil.posixfile() to override the name attribute to reflect the
136 underlying file name.
136 underlying file name.
137 """
137 """
138
138
139 def __init__(self, name, fp):
139 def __init__(self, name, fp):
140 self.name = name
140 self.name = name
141 self._fp = fp
141 self._fp = fp
142
142
143 def __enter__(self):
143 def __enter__(self):
144 self._fp.__enter__()
144 self._fp.__enter__()
145 # Return this wrapper for the context manager so that the name is
145 # Return this wrapper for the context manager so that the name is
146 # still available.
146 # still available.
147 return self
147 return self
148
148
149 def __exit__(self, exc_type, exc_value, traceback):
149 def __exit__(self, exc_type, exc_value, traceback):
150 self._fp.__exit__(exc_type, exc_value, traceback)
150 self._fp.__exit__(exc_type, exc_value, traceback)
151
151
152 def __iter__(self):
152 def __iter__(self):
153 return iter(self._fp)
153 return iter(self._fp)
154
154
155 def __getattr__(self, name):
155 def __getattr__(self, name):
156 return getattr(self._fp, name)
156 return getattr(self._fp, name)
157
157
158
158
159 def posixfile(name, mode=b'r', buffering=-1):
159 def posixfile(name, mode=b'r', buffering=-1):
160 '''Open a file with even more POSIX-like semantics'''
160 '''Open a file with even more POSIX-like semantics'''
161 try:
161 try:
162 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
162 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
163
163
164 # PyFile_FromFd() ignores the name, and seems to report fp.name as the
164 # PyFile_FromFd() ignores the name, and seems to report fp.name as the
165 # underlying file descriptor.
165 # underlying file descriptor.
166 if pycompat.ispy3:
166 if pycompat.ispy3:
167 fp = fdproxy(name, fp)
167 fp = fdproxy(name, fp)
168
168
169 # The position when opening in append mode is implementation defined, so
169 # The position when opening in append mode is implementation defined, so
170 # make it consistent with other platforms, which position at EOF.
170 # make it consistent with other platforms, which position at EOF.
171 if b'a' in mode:
171 if b'a' in mode:
172 fp.seek(0, os.SEEK_END)
172 fp.seek(0, os.SEEK_END)
173
173
174 if b'+' in mode:
174 if b'+' in mode:
175 return mixedfilemodewrapper(fp)
175 return mixedfilemodewrapper(fp)
176
176
177 return fp
177 return fp
178 except WindowsError as err:
178 except WindowsError as err:
179 # convert to a friendlier exception
179 # convert to a friendlier exception
180 raise IOError(
180 raise IOError(
181 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
181 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
182 )
182 )
183
183
184
184
185 # may be wrapped by win32mbcs extension
185 # may be wrapped by win32mbcs extension
186 listdir = osutil.listdir
186 listdir = osutil.listdir
187
187
188
188
189 # copied from .utils.procutil, remove after Python 2 support was dropped
189 # copied from .utils.procutil, remove after Python 2 support was dropped
190 def _isatty(fp):
190 def _isatty(fp):
191 try:
191 try:
192 return fp.isatty()
192 return fp.isatty()
193 except AttributeError:
193 except AttributeError:
194 return False
194 return False
195
195
196
196
197 def get_password():
198 """Prompt for password with echo off, using Windows getch().
199
200 This shouldn't be called directly- use ``ui.getpass()`` instead, which
201 checks if the session is interactive first.
202 """
203 pw = ""
204 while True:
205 c = msvcrt.getwch()
206 if c == '\r' or c == '\n':
207 break
208 if c == '\003':
209 raise KeyboardInterrupt
210 if c == '\b':
211 pw = pw[:-1]
212 else:
213 pw = pw + c
214 msvcrt.putwch('\r')
215 msvcrt.putwch('\n')
216 return encoding.strtolocal(pw)
217
218
197 class winstdout(object):
219 class winstdout(object):
198 """Some files on Windows misbehave.
220 """Some files on Windows misbehave.
199
221
200 When writing to a broken pipe, EINVAL instead of EPIPE may be raised.
222 When writing to a broken pipe, EINVAL instead of EPIPE may be raised.
201
223
202 When writing too many bytes to a console at the same, a "Not enough space"
224 When writing too many bytes to a console at the same, a "Not enough space"
203 error may happen. Python 3 already works around that.
225 error may happen. Python 3 already works around that.
204 """
226 """
205
227
206 def __init__(self, fp):
228 def __init__(self, fp):
207 self.fp = fp
229 self.fp = fp
208 self.throttle = not pycompat.ispy3 and _isatty(fp)
230 self.throttle = not pycompat.ispy3 and _isatty(fp)
209
231
210 def __getattr__(self, key):
232 def __getattr__(self, key):
211 return getattr(self.fp, key)
233 return getattr(self.fp, key)
212
234
213 def close(self):
235 def close(self):
214 try:
236 try:
215 self.fp.close()
237 self.fp.close()
216 except IOError:
238 except IOError:
217 pass
239 pass
218
240
219 def write(self, s):
241 def write(self, s):
220 try:
242 try:
221 if not self.throttle:
243 if not self.throttle:
222 return self.fp.write(s)
244 return self.fp.write(s)
223 # This is workaround for "Not enough space" error on
245 # This is workaround for "Not enough space" error on
224 # writing large size of data to console.
246 # writing large size of data to console.
225 limit = 16000
247 limit = 16000
226 l = len(s)
248 l = len(s)
227 start = 0
249 start = 0
228 while start < l:
250 while start < l:
229 end = start + limit
251 end = start + limit
230 self.fp.write(s[start:end])
252 self.fp.write(s[start:end])
231 start = end
253 start = end
232 except IOError as inst:
254 except IOError as inst:
233 if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
255 if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
234 raise
256 raise
235 self.close()
257 self.close()
236 raise IOError(errno.EPIPE, 'Broken pipe')
258 raise IOError(errno.EPIPE, 'Broken pipe')
237
259
238 def flush(self):
260 def flush(self):
239 try:
261 try:
240 return self.fp.flush()
262 return self.fp.flush()
241 except IOError as inst:
263 except IOError as inst:
242 if not win32.lasterrorwaspipeerror(inst):
264 if not win32.lasterrorwaspipeerror(inst):
243 raise
265 raise
244 raise IOError(errno.EPIPE, 'Broken pipe')
266 raise IOError(errno.EPIPE, 'Broken pipe')
245
267
246
268
247 def openhardlinks():
269 def openhardlinks():
248 return True
270 return True
249
271
250
272
251 def parsepatchoutput(output_line):
273 def parsepatchoutput(output_line):
252 """parses the output produced by patch and returns the filename"""
274 """parses the output produced by patch and returns the filename"""
253 pf = output_line[14:]
275 pf = output_line[14:]
254 if pf[0] == b'`':
276 if pf[0] == b'`':
255 pf = pf[1:-1] # Remove the quotes
277 pf = pf[1:-1] # Remove the quotes
256 return pf
278 return pf
257
279
258
280
259 def sshargs(sshcmd, host, user, port):
281 def sshargs(sshcmd, host, user, port):
260 '''Build argument list for ssh or Plink'''
282 '''Build argument list for ssh or Plink'''
261 pflag = b'plink' in sshcmd.lower() and b'-P' or b'-p'
283 pflag = b'plink' in sshcmd.lower() and b'-P' or b'-p'
262 args = user and (b"%s@%s" % (user, host)) or host
284 args = user and (b"%s@%s" % (user, host)) or host
263 if args.startswith(b'-') or args.startswith(b'/'):
285 if args.startswith(b'-') or args.startswith(b'/'):
264 raise error.Abort(
286 raise error.Abort(
265 _(b'illegal ssh hostname or username starting with - or /: %s')
287 _(b'illegal ssh hostname or username starting with - or /: %s')
266 % args
288 % args
267 )
289 )
268 args = shellquote(args)
290 args = shellquote(args)
269 if port:
291 if port:
270 args = b'%s %s %s' % (pflag, shellquote(port), args)
292 args = b'%s %s %s' % (pflag, shellquote(port), args)
271 return args
293 return args
272
294
273
295
274 def setflags(f, l, x):
296 def setflags(f, l, x):
275 pass
297 pass
276
298
277
299
278 def copymode(src, dst, mode=None, enforcewritable=False):
300 def copymode(src, dst, mode=None, enforcewritable=False):
279 pass
301 pass
280
302
281
303
282 def checkexec(path):
304 def checkexec(path):
283 return False
305 return False
284
306
285
307
286 def checklink(path):
308 def checklink(path):
287 return False
309 return False
288
310
289
311
290 def setbinary(fd):
312 def setbinary(fd):
291 # When run without console, pipes may expose invalid
313 # When run without console, pipes may expose invalid
292 # fileno(), usually set to -1.
314 # fileno(), usually set to -1.
293 fno = getattr(fd, 'fileno', None)
315 fno = getattr(fd, 'fileno', None)
294 if fno is not None and fno() >= 0:
316 if fno is not None and fno() >= 0:
295 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
317 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
296
318
297
319
298 def pconvert(path):
320 def pconvert(path):
299 return path.replace(pycompat.ossep, b'/')
321 return path.replace(pycompat.ossep, b'/')
300
322
301
323
302 def localpath(path):
324 def localpath(path):
303 return path.replace(b'/', b'\\')
325 return path.replace(b'/', b'\\')
304
326
305
327
306 def normpath(path):
328 def normpath(path):
307 return pconvert(os.path.normpath(path))
329 return pconvert(os.path.normpath(path))
308
330
309
331
310 def normcase(path):
332 def normcase(path):
311 return encoding.upper(path) # NTFS compares via upper()
333 return encoding.upper(path) # NTFS compares via upper()
312
334
313
335
314 # see posix.py for definitions
336 # see posix.py for definitions
315 normcasespec = encoding.normcasespecs.upper
337 normcasespec = encoding.normcasespecs.upper
316 normcasefallback = encoding.upperfallback
338 normcasefallback = encoding.upperfallback
317
339
318
340
319 def samestat(s1, s2):
341 def samestat(s1, s2):
320 return False
342 return False
321
343
322
344
323 def shelltocmdexe(path, env):
345 def shelltocmdexe(path, env):
324 r"""Convert shell variables in the form $var and ${var} inside ``path``
346 r"""Convert shell variables in the form $var and ${var} inside ``path``
325 to %var% form. Existing Windows style variables are left unchanged.
347 to %var% form. Existing Windows style variables are left unchanged.
326
348
327 The variables are limited to the given environment. Unknown variables are
349 The variables are limited to the given environment. Unknown variables are
328 left unchanged.
350 left unchanged.
329
351
330 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
352 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
331 >>> # Only valid values are expanded
353 >>> # Only valid values are expanded
332 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
354 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
333 ... e)
355 ... e)
334 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
356 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
335 >>> # Single quote prevents expansion, as does \$ escaping
357 >>> # Single quote prevents expansion, as does \$ escaping
336 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
358 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
337 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
359 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
338 >>> # $$ is not special. %% is not special either, but can be the end and
360 >>> # $$ is not special. %% is not special either, but can be the end and
339 >>> # start of consecutive variables
361 >>> # start of consecutive variables
340 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
362 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
341 'cmd $$ %% %var1%%var2%'
363 'cmd $$ %% %var1%%var2%'
342 >>> # No double substitution
364 >>> # No double substitution
343 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
365 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
344 '%var1% %var1%'
366 '%var1% %var1%'
345 >>> # Tilde expansion
367 >>> # Tilde expansion
346 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
368 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
347 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
369 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
348 """
370 """
349 if not any(c in path for c in b"$'~"):
371 if not any(c in path for c in b"$'~"):
350 return path
372 return path
351
373
352 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
374 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
353
375
354 res = b''
376 res = b''
355 index = 0
377 index = 0
356 pathlen = len(path)
378 pathlen = len(path)
357 while index < pathlen:
379 while index < pathlen:
358 c = path[index : index + 1]
380 c = path[index : index + 1]
359 if c == b'\'': # no expansion within single quotes
381 if c == b'\'': # no expansion within single quotes
360 path = path[index + 1 :]
382 path = path[index + 1 :]
361 pathlen = len(path)
383 pathlen = len(path)
362 try:
384 try:
363 index = path.index(b'\'')
385 index = path.index(b'\'')
364 res += b'"' + path[:index] + b'"'
386 res += b'"' + path[:index] + b'"'
365 except ValueError:
387 except ValueError:
366 res += c + path
388 res += c + path
367 index = pathlen - 1
389 index = pathlen - 1
368 elif c == b'%': # variable
390 elif c == b'%': # variable
369 path = path[index + 1 :]
391 path = path[index + 1 :]
370 pathlen = len(path)
392 pathlen = len(path)
371 try:
393 try:
372 index = path.index(b'%')
394 index = path.index(b'%')
373 except ValueError:
395 except ValueError:
374 res += b'%' + path
396 res += b'%' + path
375 index = pathlen - 1
397 index = pathlen - 1
376 else:
398 else:
377 var = path[:index]
399 var = path[:index]
378 res += b'%' + var + b'%'
400 res += b'%' + var + b'%'
379 elif c == b'$': # variable
401 elif c == b'$': # variable
380 if path[index + 1 : index + 2] == b'{':
402 if path[index + 1 : index + 2] == b'{':
381 path = path[index + 2 :]
403 path = path[index + 2 :]
382 pathlen = len(path)
404 pathlen = len(path)
383 try:
405 try:
384 index = path.index(b'}')
406 index = path.index(b'}')
385 var = path[:index]
407 var = path[:index]
386
408
387 # See below for why empty variables are handled specially
409 # See below for why empty variables are handled specially
388 if env.get(var, b'') != b'':
410 if env.get(var, b'') != b'':
389 res += b'%' + var + b'%'
411 res += b'%' + var + b'%'
390 else:
412 else:
391 res += b'${' + var + b'}'
413 res += b'${' + var + b'}'
392 except ValueError:
414 except ValueError:
393 res += b'${' + path
415 res += b'${' + path
394 index = pathlen - 1
416 index = pathlen - 1
395 else:
417 else:
396 var = b''
418 var = b''
397 index += 1
419 index += 1
398 c = path[index : index + 1]
420 c = path[index : index + 1]
399 while c != b'' and c in varchars:
421 while c != b'' and c in varchars:
400 var += c
422 var += c
401 index += 1
423 index += 1
402 c = path[index : index + 1]
424 c = path[index : index + 1]
403 # Some variables (like HG_OLDNODE) may be defined, but have an
425 # Some variables (like HG_OLDNODE) may be defined, but have an
404 # empty value. Those need to be skipped because when spawning
426 # empty value. Those need to be skipped because when spawning
405 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
427 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
406 # VAR, and that really confuses things like revset expressions.
428 # VAR, and that really confuses things like revset expressions.
407 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
429 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
408 # will substitute to an empty string, and everything is happy.
430 # will substitute to an empty string, and everything is happy.
409 if env.get(var, b'') != b'':
431 if env.get(var, b'') != b'':
410 res += b'%' + var + b'%'
432 res += b'%' + var + b'%'
411 else:
433 else:
412 res += b'$' + var
434 res += b'$' + var
413
435
414 if c != b'':
436 if c != b'':
415 index -= 1
437 index -= 1
416 elif (
438 elif (
417 c == b'~'
439 c == b'~'
418 and index + 1 < pathlen
440 and index + 1 < pathlen
419 and path[index + 1 : index + 2] in (b'\\', b'/')
441 and path[index + 1 : index + 2] in (b'\\', b'/')
420 ):
442 ):
421 res += b"%USERPROFILE%"
443 res += b"%USERPROFILE%"
422 elif (
444 elif (
423 c == b'\\'
445 c == b'\\'
424 and index + 1 < pathlen
446 and index + 1 < pathlen
425 and path[index + 1 : index + 2] in (b'$', b'~')
447 and path[index + 1 : index + 2] in (b'$', b'~')
426 ):
448 ):
427 # Skip '\', but only if it is escaping $ or ~
449 # Skip '\', but only if it is escaping $ or ~
428 res += path[index + 1 : index + 2]
450 res += path[index + 1 : index + 2]
429 index += 1
451 index += 1
430 else:
452 else:
431 res += c
453 res += c
432
454
433 index += 1
455 index += 1
434 return res
456 return res
435
457
436
458
437 # A sequence of backslashes is special iff it precedes a double quote:
459 # A sequence of backslashes is special iff it precedes a double quote:
438 # - if there's an even number of backslashes, the double quote is not
460 # - if there's an even number of backslashes, the double quote is not
439 # quoted (i.e. it ends the quoted region)
461 # quoted (i.e. it ends the quoted region)
440 # - if there's an odd number of backslashes, the double quote is quoted
462 # - if there's an odd number of backslashes, the double quote is quoted
441 # - in both cases, every pair of backslashes is unquoted into a single
463 # - in both cases, every pair of backslashes is unquoted into a single
442 # backslash
464 # backslash
443 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
465 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
444 # So, to quote a string, we must surround it in double quotes, double
466 # So, to quote a string, we must surround it in double quotes, double
445 # the number of backslashes that precede double quotes and add another
467 # the number of backslashes that precede double quotes and add another
446 # backslash before every double quote (being careful with the double
468 # backslash before every double quote (being careful with the double
447 # quote we've appended to the end)
469 # quote we've appended to the end)
448 _quotere = None
470 _quotere = None
449 _needsshellquote = None
471 _needsshellquote = None
450
472
451
473
452 def shellquote(s):
474 def shellquote(s):
453 r"""
475 r"""
454 >>> shellquote(br'C:\Users\xyz')
476 >>> shellquote(br'C:\Users\xyz')
455 '"C:\\Users\\xyz"'
477 '"C:\\Users\\xyz"'
456 >>> shellquote(br'C:\Users\xyz/mixed')
478 >>> shellquote(br'C:\Users\xyz/mixed')
457 '"C:\\Users\\xyz/mixed"'
479 '"C:\\Users\\xyz/mixed"'
458 >>> # Would be safe not to quote too, since it is all double backslashes
480 >>> # Would be safe not to quote too, since it is all double backslashes
459 >>> shellquote(br'C:\\Users\\xyz')
481 >>> shellquote(br'C:\\Users\\xyz')
460 '"C:\\\\Users\\\\xyz"'
482 '"C:\\\\Users\\\\xyz"'
461 >>> # But this must be quoted
483 >>> # But this must be quoted
462 >>> shellquote(br'C:\\Users\\xyz/abc')
484 >>> shellquote(br'C:\\Users\\xyz/abc')
463 '"C:\\\\Users\\\\xyz/abc"'
485 '"C:\\\\Users\\\\xyz/abc"'
464 """
486 """
465 global _quotere
487 global _quotere
466 if _quotere is None:
488 if _quotere is None:
467 _quotere = re.compile(br'(\\*)("|\\$)')
489 _quotere = re.compile(br'(\\*)("|\\$)')
468 global _needsshellquote
490 global _needsshellquote
469 if _needsshellquote is None:
491 if _needsshellquote is None:
470 # ":" is also treated as "safe character", because it is used as a part
492 # ":" is also treated as "safe character", because it is used as a part
471 # of path name on Windows. "\" is also part of a path name, but isn't
493 # of path name on Windows. "\" is also part of a path name, but isn't
472 # safe because shlex.split() (kind of) treats it as an escape char and
494 # safe because shlex.split() (kind of) treats it as an escape char and
473 # drops it. It will leave the next character, even if it is another
495 # drops it. It will leave the next character, even if it is another
474 # "\".
496 # "\".
475 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
497 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
476 if s and not _needsshellquote(s) and not _quotere.search(s):
498 if s and not _needsshellquote(s) and not _quotere.search(s):
477 # "s" shouldn't have to be quoted
499 # "s" shouldn't have to be quoted
478 return s
500 return s
479 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
501 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
480
502
481
503
482 def _unquote(s):
504 def _unquote(s):
483 if s.startswith(b'"') and s.endswith(b'"'):
505 if s.startswith(b'"') and s.endswith(b'"'):
484 return s[1:-1]
506 return s[1:-1]
485 return s
507 return s
486
508
487
509
488 def shellsplit(s):
510 def shellsplit(s):
489 """Parse a command string in cmd.exe way (best-effort)"""
511 """Parse a command string in cmd.exe way (best-effort)"""
490 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
512 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
491
513
492
514
493 # if you change this stub into a real check, please try to implement the
515 # if you change this stub into a real check, please try to implement the
494 # username and groupname functions above, too.
516 # username and groupname functions above, too.
495 def isowner(st):
517 def isowner(st):
496 return True
518 return True
497
519
498
520
499 def findexe(command):
521 def findexe(command):
500 """Find executable for command searching like cmd.exe does.
522 """Find executable for command searching like cmd.exe does.
501 If command is a basename then PATH is searched for command.
523 If command is a basename then PATH is searched for command.
502 PATH isn't searched if command is an absolute or relative path.
524 PATH isn't searched if command is an absolute or relative path.
503 An extension from PATHEXT is found and added if not present.
525 An extension from PATHEXT is found and added if not present.
504 If command isn't found None is returned."""
526 If command isn't found None is returned."""
505 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
527 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
506 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
528 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
507 if os.path.splitext(command)[1].lower() in pathexts:
529 if os.path.splitext(command)[1].lower() in pathexts:
508 pathexts = [b'']
530 pathexts = [b'']
509
531
510 def findexisting(pathcommand):
532 def findexisting(pathcommand):
511 """Will append extension (if needed) and return existing file"""
533 """Will append extension (if needed) and return existing file"""
512 for ext in pathexts:
534 for ext in pathexts:
513 executable = pathcommand + ext
535 executable = pathcommand + ext
514 if os.path.exists(executable):
536 if os.path.exists(executable):
515 return executable
537 return executable
516 return None
538 return None
517
539
518 if pycompat.ossep in command:
540 if pycompat.ossep in command:
519 return findexisting(command)
541 return findexisting(command)
520
542
521 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
543 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
522 executable = findexisting(os.path.join(path, command))
544 executable = findexisting(os.path.join(path, command))
523 if executable is not None:
545 if executable is not None:
524 return executable
546 return executable
525 return findexisting(os.path.expanduser(os.path.expandvars(command)))
547 return findexisting(os.path.expanduser(os.path.expandvars(command)))
526
548
527
549
528 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
550 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
529
551
530
552
531 def statfiles(files):
553 def statfiles(files):
532 """Stat each file in files. Yield each stat, or None if a file
554 """Stat each file in files. Yield each stat, or None if a file
533 does not exist or has a type we don't care about.
555 does not exist or has a type we don't care about.
534
556
535 Cluster and cache stat per directory to minimize number of OS stat calls."""
557 Cluster and cache stat per directory to minimize number of OS stat calls."""
536 dircache = {} # dirname -> filename -> status | None if file does not exist
558 dircache = {} # dirname -> filename -> status | None if file does not exist
537 getkind = stat.S_IFMT
559 getkind = stat.S_IFMT
538 for nf in files:
560 for nf in files:
539 nf = normcase(nf)
561 nf = normcase(nf)
540 dir, base = os.path.split(nf)
562 dir, base = os.path.split(nf)
541 if not dir:
563 if not dir:
542 dir = b'.'
564 dir = b'.'
543 cache = dircache.get(dir, None)
565 cache = dircache.get(dir, None)
544 if cache is None:
566 if cache is None:
545 try:
567 try:
546 dmap = {
568 dmap = {
547 normcase(n): s
569 normcase(n): s
548 for n, k, s in listdir(dir, True)
570 for n, k, s in listdir(dir, True)
549 if getkind(s.st_mode) in _wantedkinds
571 if getkind(s.st_mode) in _wantedkinds
550 }
572 }
551 except OSError as err:
573 except OSError as err:
552 # Python >= 2.5 returns ENOENT and adds winerror field
574 # Python >= 2.5 returns ENOENT and adds winerror field
553 # EINVAL is raised if dir is not a directory.
575 # EINVAL is raised if dir is not a directory.
554 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
576 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
555 raise
577 raise
556 dmap = {}
578 dmap = {}
557 cache = dircache.setdefault(dir, dmap)
579 cache = dircache.setdefault(dir, dmap)
558 yield cache.get(base, None)
580 yield cache.get(base, None)
559
581
560
582
561 def username(uid=None):
583 def username(uid=None):
562 """Return the name of the user with the given uid.
584 """Return the name of the user with the given uid.
563
585
564 If uid is None, return the name of the current user."""
586 If uid is None, return the name of the current user."""
565 if not uid:
587 if not uid:
566 return pycompat.fsencode(getpass.getuser())
588 return pycompat.fsencode(getpass.getuser())
567 return None
589 return None
568
590
569
591
570 def groupname(gid=None):
592 def groupname(gid=None):
571 """Return the name of the group with the given gid.
593 """Return the name of the group with the given gid.
572
594
573 If gid is None, return the name of the current group."""
595 If gid is None, return the name of the current group."""
574 return None
596 return None
575
597
576
598
577 def readlink(pathname):
599 def readlink(pathname):
578 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
600 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
579
601
580
602
581 def removedirs(name):
603 def removedirs(name):
582 """special version of os.removedirs that does not remove symlinked
604 """special version of os.removedirs that does not remove symlinked
583 directories or junction points if they actually contain files"""
605 directories or junction points if they actually contain files"""
584 if listdir(name):
606 if listdir(name):
585 return
607 return
586 os.rmdir(name)
608 os.rmdir(name)
587 head, tail = os.path.split(name)
609 head, tail = os.path.split(name)
588 if not tail:
610 if not tail:
589 head, tail = os.path.split(head)
611 head, tail = os.path.split(head)
590 while head and tail:
612 while head and tail:
591 try:
613 try:
592 if listdir(head):
614 if listdir(head):
593 return
615 return
594 os.rmdir(head)
616 os.rmdir(head)
595 except (ValueError, OSError):
617 except (ValueError, OSError):
596 break
618 break
597 head, tail = os.path.split(head)
619 head, tail = os.path.split(head)
598
620
599
621
600 def rename(src, dst):
622 def rename(src, dst):
601 '''atomically rename file src to dst, replacing dst if it exists'''
623 '''atomically rename file src to dst, replacing dst if it exists'''
602 try:
624 try:
603 os.rename(src, dst)
625 os.rename(src, dst)
604 except OSError as e:
626 except OSError as e:
605 if e.errno != errno.EEXIST:
627 if e.errno != errno.EEXIST:
606 raise
628 raise
607 unlink(dst)
629 unlink(dst)
608 os.rename(src, dst)
630 os.rename(src, dst)
609
631
610
632
611 def gethgcmd():
633 def gethgcmd():
612 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
634 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
613
635
614
636
615 def groupmembers(name):
637 def groupmembers(name):
616 # Don't support groups on Windows for now
638 # Don't support groups on Windows for now
617 raise KeyError
639 raise KeyError
618
640
619
641
620 def isexec(f):
642 def isexec(f):
621 return False
643 return False
622
644
623
645
624 class cachestat(object):
646 class cachestat(object):
625 def __init__(self, path):
647 def __init__(self, path):
626 pass
648 pass
627
649
628 def cacheable(self):
650 def cacheable(self):
629 return False
651 return False
630
652
631
653
632 def lookupreg(key, valname=None, scope=None):
654 def lookupreg(key, valname=None, scope=None):
633 """Look up a key/value name in the Windows registry.
655 """Look up a key/value name in the Windows registry.
634
656
635 valname: value name. If unspecified, the default value for the key
657 valname: value name. If unspecified, the default value for the key
636 is used.
658 is used.
637 scope: optionally specify scope for registry lookup, this can be
659 scope: optionally specify scope for registry lookup, this can be
638 a sequence of scopes to look up in order. Default (CURRENT_USER,
660 a sequence of scopes to look up in order. Default (CURRENT_USER,
639 LOCAL_MACHINE).
661 LOCAL_MACHINE).
640 """
662 """
641 if scope is None:
663 if scope is None:
642 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
664 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
643 elif not isinstance(scope, (list, tuple)):
665 elif not isinstance(scope, (list, tuple)):
644 scope = (scope,)
666 scope = (scope,)
645 for s in scope:
667 for s in scope:
646 try:
668 try:
647 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
669 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
648 name = valname and encoding.strfromlocal(valname) or valname
670 name = valname and encoding.strfromlocal(valname) or valname
649 val = winreg.QueryValueEx(hkey, name)[0]
671 val = winreg.QueryValueEx(hkey, name)[0]
650 # never let a Unicode string escape into the wild
672 # never let a Unicode string escape into the wild
651 return encoding.unitolocal(val)
673 return encoding.unitolocal(val)
652 except EnvironmentError:
674 except EnvironmentError:
653 pass
675 pass
654
676
655
677
656 expandglobs = True
678 expandglobs = True
657
679
658
680
659 def statislink(st):
681 def statislink(st):
660 '''check whether a stat result is a symlink'''
682 '''check whether a stat result is a symlink'''
661 return False
683 return False
662
684
663
685
664 def statisexec(st):
686 def statisexec(st):
665 '''check whether a stat result is an executable file'''
687 '''check whether a stat result is an executable file'''
666 return False
688 return False
667
689
668
690
669 def poll(fds):
691 def poll(fds):
670 # see posix.py for description
692 # see posix.py for description
671 raise NotImplementedError()
693 raise NotImplementedError()
672
694
673
695
674 def readpipe(pipe):
696 def readpipe(pipe):
675 """Read all available data from a pipe."""
697 """Read all available data from a pipe."""
676 chunks = []
698 chunks = []
677 while True:
699 while True:
678 size = win32.peekpipe(pipe)
700 size = win32.peekpipe(pipe)
679 if not size:
701 if not size:
680 break
702 break
681
703
682 s = pipe.read(size)
704 s = pipe.read(size)
683 if not s:
705 if not s:
684 break
706 break
685 chunks.append(s)
707 chunks.append(s)
686
708
687 return b''.join(chunks)
709 return b''.join(chunks)
688
710
689
711
690 def bindunixsocket(sock, path):
712 def bindunixsocket(sock, path):
691 raise NotImplementedError('unsupported platform')
713 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now