##// END OF EJS Templates
fsmonitor: don't write out state if identity has changed (issue5581)...
Siddharth Agarwal -
r32816:1b25c648 default
parent child Browse files
Show More
@@ -1,119 +1,135 b''
1 1 # state.py - fsmonitor persistent state
2 2 #
3 3 # Copyright 2013-2016 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import socket
13 13 import struct
14 14
15 15 from mercurial.i18n import _
16 from mercurial import pathutil
16 from mercurial import (
17 pathutil,
18 util,
19 )
17 20
18 21 _version = 4
19 22 _versionformat = ">I"
20 23
21 24 class state(object):
22 25 def __init__(self, repo):
23 26 self._vfs = repo.vfs
24 27 self._ui = repo.ui
25 28 self._rootdir = pathutil.normasprefix(repo.root)
26 29 self._lastclock = None
30 self._identity = util.filestat(None)
27 31
28 32 self.mode = self._ui.config('fsmonitor', 'mode', default='on')
29 33 self.walk_on_invalidate = self._ui.configbool(
30 34 'fsmonitor', 'walk_on_invalidate', False)
31 35 self.timeout = float(self._ui.config(
32 36 'fsmonitor', 'timeout', default='2'))
33 37
34 38 def get(self):
35 39 try:
36 40 file = self._vfs('fsmonitor.state', 'rb')
37 41 except IOError as inst:
42 self._identity = util.filestat(None)
38 43 if inst.errno != errno.ENOENT:
39 44 raise
40 45 return None, None, None
41 46
47 self._identity = util.filestat.fromfp(file)
48
42 49 versionbytes = file.read(4)
43 50 if len(versionbytes) < 4:
44 51 self._ui.log(
45 52 'fsmonitor', 'fsmonitor: state file only has %d bytes, '
46 53 'nuking state\n' % len(versionbytes))
47 54 self.invalidate()
48 55 return None, None, None
49 56 try:
50 57 diskversion = struct.unpack(_versionformat, versionbytes)[0]
51 58 if diskversion != _version:
52 59 # different version, nuke state and start over
53 60 self._ui.log(
54 61 'fsmonitor', 'fsmonitor: version switch from %d to '
55 62 '%d, nuking state\n' % (diskversion, _version))
56 63 self.invalidate()
57 64 return None, None, None
58 65
59 66 state = file.read().split('\0')
60 67 # state = hostname\0clock\0ignorehash\0 + list of files, each
61 68 # followed by a \0
62 69 if len(state) < 3:
63 70 self._ui.log(
64 71 'fsmonitor', 'fsmonitor: state file truncated (expected '
65 72 '3 chunks, found %d), nuking state\n', len(state))
66 73 self.invalidate()
67 74 return None, None, None
68 75 diskhostname = state[0]
69 76 hostname = socket.gethostname()
70 77 if diskhostname != hostname:
71 78 # file got moved to a different host
72 79 self._ui.log('fsmonitor', 'fsmonitor: stored hostname "%s" '
73 80 'different from current "%s", nuking state\n' %
74 81 (diskhostname, hostname))
75 82 self.invalidate()
76 83 return None, None, None
77 84
78 85 clock = state[1]
79 86 ignorehash = state[2]
80 87 # discard the value after the last \0
81 88 notefiles = state[3:-1]
82 89
83 90 finally:
84 91 file.close()
85 92
86 93 return clock, ignorehash, notefiles
87 94
88 95 def set(self, clock, ignorehash, notefiles):
89 96 if clock is None:
90 97 self.invalidate()
91 98 return
92 99
100 # Read the identity from the file on disk rather than from the open file
101 # pointer below, because the latter is actually a brand new file.
102 identity = util.filestat.frompath(self._vfs.join('fsmonitor.state'))
103 if identity != self._identity:
104 self._ui.debug('skip updating fsmonitor.state: identity mismatch\n')
105 return
106
93 107 try:
94 file = self._vfs('fsmonitor.state', 'wb', atomictemp=True)
108 file = self._vfs('fsmonitor.state', 'wb', atomictemp=True,
109 checkambig=True)
95 110 except (IOError, OSError):
96 111 self._ui.warn(_("warning: unable to write out fsmonitor state\n"))
97 112 return
98 113
99 114 with file:
100 115 file.write(struct.pack(_versionformat, _version))
101 116 file.write(socket.gethostname() + '\0')
102 117 file.write(clock + '\0')
103 118 file.write(ignorehash + '\0')
104 119 if notefiles:
105 120 file.write('\0'.join(notefiles))
106 121 file.write('\0')
107 122
108 123 def invalidate(self):
109 124 try:
110 125 os.unlink(os.path.join(self._rootdir, '.hg', 'fsmonitor.state'))
111 126 except OSError as inst:
112 127 if inst.errno != errno.ENOENT:
113 128 raise
129 self._identity = util.filestat(None)
114 130
115 131 def setlastclock(self, clock):
116 132 self._lastclock = clock
117 133
118 134 def getlastclock(self):
119 135 return self._lastclock
@@ -1,3747 +1,3752 b''
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from __future__ import absolute_import
17 17
18 18 import bz2
19 19 import calendar
20 20 import codecs
21 21 import collections
22 22 import datetime
23 23 import errno
24 24 import gc
25 25 import hashlib
26 26 import imp
27 27 import os
28 28 import platform as pyplatform
29 29 import re as remod
30 30 import shutil
31 31 import signal
32 32 import socket
33 33 import stat
34 34 import string
35 35 import subprocess
36 36 import sys
37 37 import tempfile
38 38 import textwrap
39 39 import time
40 40 import traceback
41 41 import warnings
42 42 import zlib
43 43
44 44 from . import (
45 45 encoding,
46 46 error,
47 47 i18n,
48 48 policy,
49 49 pycompat,
50 50 )
51 51
52 52 base85 = policy.importmod(r'base85')
53 53 osutil = policy.importmod(r'osutil')
54 54 parsers = policy.importmod(r'parsers')
55 55
56 56 b85decode = base85.b85decode
57 57 b85encode = base85.b85encode
58 58
59 59 cookielib = pycompat.cookielib
60 60 empty = pycompat.empty
61 61 httplib = pycompat.httplib
62 62 httpserver = pycompat.httpserver
63 63 pickle = pycompat.pickle
64 64 queue = pycompat.queue
65 65 socketserver = pycompat.socketserver
66 66 stderr = pycompat.stderr
67 67 stdin = pycompat.stdin
68 68 stdout = pycompat.stdout
69 69 stringio = pycompat.stringio
70 70 urlerr = pycompat.urlerr
71 71 urlreq = pycompat.urlreq
72 72 xmlrpclib = pycompat.xmlrpclib
73 73
74 74 # workaround for win32mbcs
75 75 _filenamebytestr = pycompat.bytestr
76 76
77 77 def isatty(fp):
78 78 try:
79 79 return fp.isatty()
80 80 except AttributeError:
81 81 return False
82 82
83 83 # glibc determines buffering on first write to stdout - if we replace a TTY
84 84 # destined stdout with a pipe destined stdout (e.g. pager), we want line
85 85 # buffering
86 86 if isatty(stdout):
87 87 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
88 88
89 89 if pycompat.osname == 'nt':
90 90 from . import windows as platform
91 91 stdout = platform.winstdout(stdout)
92 92 else:
93 93 from . import posix as platform
94 94
95 95 _ = i18n._
96 96
97 97 bindunixsocket = platform.bindunixsocket
98 98 cachestat = platform.cachestat
99 99 checkexec = platform.checkexec
100 100 checklink = platform.checklink
101 101 copymode = platform.copymode
102 102 executablepath = platform.executablepath
103 103 expandglobs = platform.expandglobs
104 104 explainexit = platform.explainexit
105 105 findexe = platform.findexe
106 106 gethgcmd = platform.gethgcmd
107 107 getuser = platform.getuser
108 108 getpid = os.getpid
109 109 groupmembers = platform.groupmembers
110 110 groupname = platform.groupname
111 111 hidewindow = platform.hidewindow
112 112 isexec = platform.isexec
113 113 isowner = platform.isowner
114 114 listdir = osutil.listdir
115 115 localpath = platform.localpath
116 116 lookupreg = platform.lookupreg
117 117 makedir = platform.makedir
118 118 nlinks = platform.nlinks
119 119 normpath = platform.normpath
120 120 normcase = platform.normcase
121 121 normcasespec = platform.normcasespec
122 122 normcasefallback = platform.normcasefallback
123 123 openhardlinks = platform.openhardlinks
124 124 oslink = platform.oslink
125 125 parsepatchoutput = platform.parsepatchoutput
126 126 pconvert = platform.pconvert
127 127 poll = platform.poll
128 128 popen = platform.popen
129 129 posixfile = platform.posixfile
130 130 quotecommand = platform.quotecommand
131 131 readpipe = platform.readpipe
132 132 rename = platform.rename
133 133 removedirs = platform.removedirs
134 134 samedevice = platform.samedevice
135 135 samefile = platform.samefile
136 136 samestat = platform.samestat
137 137 setbinary = platform.setbinary
138 138 setflags = platform.setflags
139 139 setsignalhandler = platform.setsignalhandler
140 140 shellquote = platform.shellquote
141 141 spawndetached = platform.spawndetached
142 142 split = platform.split
143 143 sshargs = platform.sshargs
144 144 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
145 145 statisexec = platform.statisexec
146 146 statislink = platform.statislink
147 147 testpid = platform.testpid
148 148 umask = platform.umask
149 149 unlink = platform.unlink
150 150 username = platform.username
151 151
152 152 try:
153 153 recvfds = osutil.recvfds
154 154 except AttributeError:
155 155 pass
156 156 try:
157 157 setprocname = osutil.setprocname
158 158 except AttributeError:
159 159 pass
160 160
161 161 # Python compatibility
162 162
163 163 _notset = object()
164 164
165 165 # disable Python's problematic floating point timestamps (issue4836)
166 166 # (Python hypocritically says you shouldn't change this behavior in
167 167 # libraries, and sure enough Mercurial is not a library.)
168 168 os.stat_float_times(False)
169 169
170 170 def safehasattr(thing, attr):
171 171 return getattr(thing, attr, _notset) is not _notset
172 172
173 173 def bitsfrom(container):
174 174 bits = 0
175 175 for bit in container:
176 176 bits |= bit
177 177 return bits
178 178
179 179 # python 2.6 still have deprecation warning enabled by default. We do not want
180 180 # to display anything to standard user so detect if we are running test and
181 181 # only use python deprecation warning in this case.
182 182 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
183 183 if _dowarn:
184 184 # explicitly unfilter our warning for python 2.7
185 185 #
186 186 # The option of setting PYTHONWARNINGS in the test runner was investigated.
187 187 # However, module name set through PYTHONWARNINGS was exactly matched, so
188 188 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
189 189 # makes the whole PYTHONWARNINGS thing useless for our usecase.
190 190 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
191 191 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
192 192 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
193 193
194 194 def nouideprecwarn(msg, version, stacklevel=1):
195 195 """Issue an python native deprecation warning
196 196
197 197 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
198 198 """
199 199 if _dowarn:
200 200 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
201 201 " update your code.)") % version
202 202 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
203 203
204 204 DIGESTS = {
205 205 'md5': hashlib.md5,
206 206 'sha1': hashlib.sha1,
207 207 'sha512': hashlib.sha512,
208 208 }
209 209 # List of digest types from strongest to weakest
210 210 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
211 211
212 212 for k in DIGESTS_BY_STRENGTH:
213 213 assert k in DIGESTS
214 214
215 215 class digester(object):
216 216 """helper to compute digests.
217 217
218 218 This helper can be used to compute one or more digests given their name.
219 219
220 220 >>> d = digester(['md5', 'sha1'])
221 221 >>> d.update('foo')
222 222 >>> [k for k in sorted(d)]
223 223 ['md5', 'sha1']
224 224 >>> d['md5']
225 225 'acbd18db4cc2f85cedef654fccc4a4d8'
226 226 >>> d['sha1']
227 227 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
228 228 >>> digester.preferred(['md5', 'sha1'])
229 229 'sha1'
230 230 """
231 231
232 232 def __init__(self, digests, s=''):
233 233 self._hashes = {}
234 234 for k in digests:
235 235 if k not in DIGESTS:
236 236 raise Abort(_('unknown digest type: %s') % k)
237 237 self._hashes[k] = DIGESTS[k]()
238 238 if s:
239 239 self.update(s)
240 240
241 241 def update(self, data):
242 242 for h in self._hashes.values():
243 243 h.update(data)
244 244
245 245 def __getitem__(self, key):
246 246 if key not in DIGESTS:
247 247 raise Abort(_('unknown digest type: %s') % k)
248 248 return self._hashes[key].hexdigest()
249 249
250 250 def __iter__(self):
251 251 return iter(self._hashes)
252 252
253 253 @staticmethod
254 254 def preferred(supported):
255 255 """returns the strongest digest type in both supported and DIGESTS."""
256 256
257 257 for k in DIGESTS_BY_STRENGTH:
258 258 if k in supported:
259 259 return k
260 260 return None
261 261
262 262 class digestchecker(object):
263 263 """file handle wrapper that additionally checks content against a given
264 264 size and digests.
265 265
266 266 d = digestchecker(fh, size, {'md5': '...'})
267 267
268 268 When multiple digests are given, all of them are validated.
269 269 """
270 270
271 271 def __init__(self, fh, size, digests):
272 272 self._fh = fh
273 273 self._size = size
274 274 self._got = 0
275 275 self._digests = dict(digests)
276 276 self._digester = digester(self._digests.keys())
277 277
278 278 def read(self, length=-1):
279 279 content = self._fh.read(length)
280 280 self._digester.update(content)
281 281 self._got += len(content)
282 282 return content
283 283
284 284 def validate(self):
285 285 if self._size != self._got:
286 286 raise Abort(_('size mismatch: expected %d, got %d') %
287 287 (self._size, self._got))
288 288 for k, v in self._digests.items():
289 289 if v != self._digester[k]:
290 290 # i18n: first parameter is a digest name
291 291 raise Abort(_('%s mismatch: expected %s, got %s') %
292 292 (k, v, self._digester[k]))
293 293
294 294 try:
295 295 buffer = buffer
296 296 except NameError:
297 297 if not pycompat.ispy3:
298 298 def buffer(sliceable, offset=0, length=None):
299 299 if length is not None:
300 300 return sliceable[offset:offset + length]
301 301 return sliceable[offset:]
302 302 else:
303 303 def buffer(sliceable, offset=0, length=None):
304 304 if length is not None:
305 305 return memoryview(sliceable)[offset:offset + length]
306 306 return memoryview(sliceable)[offset:]
307 307
308 308 closefds = pycompat.osname == 'posix'
309 309
310 310 _chunksize = 4096
311 311
312 312 class bufferedinputpipe(object):
313 313 """a manually buffered input pipe
314 314
315 315 Python will not let us use buffered IO and lazy reading with 'polling' at
316 316 the same time. We cannot probe the buffer state and select will not detect
317 317 that data are ready to read if they are already buffered.
318 318
319 319 This class let us work around that by implementing its own buffering
320 320 (allowing efficient readline) while offering a way to know if the buffer is
321 321 empty from the output (allowing collaboration of the buffer with polling).
322 322
323 323 This class lives in the 'util' module because it makes use of the 'os'
324 324 module from the python stdlib.
325 325 """
326 326
327 327 def __init__(self, input):
328 328 self._input = input
329 329 self._buffer = []
330 330 self._eof = False
331 331 self._lenbuf = 0
332 332
333 333 @property
334 334 def hasbuffer(self):
335 335 """True is any data is currently buffered
336 336
337 337 This will be used externally a pre-step for polling IO. If there is
338 338 already data then no polling should be set in place."""
339 339 return bool(self._buffer)
340 340
341 341 @property
342 342 def closed(self):
343 343 return self._input.closed
344 344
345 345 def fileno(self):
346 346 return self._input.fileno()
347 347
348 348 def close(self):
349 349 return self._input.close()
350 350
351 351 def read(self, size):
352 352 while (not self._eof) and (self._lenbuf < size):
353 353 self._fillbuffer()
354 354 return self._frombuffer(size)
355 355
356 356 def readline(self, *args, **kwargs):
357 357 if 1 < len(self._buffer):
358 358 # this should not happen because both read and readline end with a
359 359 # _frombuffer call that collapse it.
360 360 self._buffer = [''.join(self._buffer)]
361 361 self._lenbuf = len(self._buffer[0])
362 362 lfi = -1
363 363 if self._buffer:
364 364 lfi = self._buffer[-1].find('\n')
365 365 while (not self._eof) and lfi < 0:
366 366 self._fillbuffer()
367 367 if self._buffer:
368 368 lfi = self._buffer[-1].find('\n')
369 369 size = lfi + 1
370 370 if lfi < 0: # end of file
371 371 size = self._lenbuf
372 372 elif 1 < len(self._buffer):
373 373 # we need to take previous chunks into account
374 374 size += self._lenbuf - len(self._buffer[-1])
375 375 return self._frombuffer(size)
376 376
377 377 def _frombuffer(self, size):
378 378 """return at most 'size' data from the buffer
379 379
380 380 The data are removed from the buffer."""
381 381 if size == 0 or not self._buffer:
382 382 return ''
383 383 buf = self._buffer[0]
384 384 if 1 < len(self._buffer):
385 385 buf = ''.join(self._buffer)
386 386
387 387 data = buf[:size]
388 388 buf = buf[len(data):]
389 389 if buf:
390 390 self._buffer = [buf]
391 391 self._lenbuf = len(buf)
392 392 else:
393 393 self._buffer = []
394 394 self._lenbuf = 0
395 395 return data
396 396
397 397 def _fillbuffer(self):
398 398 """read data to the buffer"""
399 399 data = os.read(self._input.fileno(), _chunksize)
400 400 if not data:
401 401 self._eof = True
402 402 else:
403 403 self._lenbuf += len(data)
404 404 self._buffer.append(data)
405 405
406 406 def popen2(cmd, env=None, newlines=False):
407 407 # Setting bufsize to -1 lets the system decide the buffer size.
408 408 # The default for bufsize is 0, meaning unbuffered. This leads to
409 409 # poor performance on Mac OS X: http://bugs.python.org/issue4194
410 410 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
411 411 close_fds=closefds,
412 412 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
413 413 universal_newlines=newlines,
414 414 env=env)
415 415 return p.stdin, p.stdout
416 416
417 417 def popen3(cmd, env=None, newlines=False):
418 418 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
419 419 return stdin, stdout, stderr
420 420
421 421 def popen4(cmd, env=None, newlines=False, bufsize=-1):
422 422 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
423 423 close_fds=closefds,
424 424 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
425 425 stderr=subprocess.PIPE,
426 426 universal_newlines=newlines,
427 427 env=env)
428 428 return p.stdin, p.stdout, p.stderr, p
429 429
430 430 def version():
431 431 """Return version information if available."""
432 432 try:
433 433 from . import __version__
434 434 return __version__.version
435 435 except ImportError:
436 436 return 'unknown'
437 437
438 438 def versiontuple(v=None, n=4):
439 439 """Parses a Mercurial version string into an N-tuple.
440 440
441 441 The version string to be parsed is specified with the ``v`` argument.
442 442 If it isn't defined, the current Mercurial version string will be parsed.
443 443
444 444 ``n`` can be 2, 3, or 4. Here is how some version strings map to
445 445 returned values:
446 446
447 447 >>> v = '3.6.1+190-df9b73d2d444'
448 448 >>> versiontuple(v, 2)
449 449 (3, 6)
450 450 >>> versiontuple(v, 3)
451 451 (3, 6, 1)
452 452 >>> versiontuple(v, 4)
453 453 (3, 6, 1, '190-df9b73d2d444')
454 454
455 455 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
456 456 (3, 6, 1, '190-df9b73d2d444+20151118')
457 457
458 458 >>> v = '3.6'
459 459 >>> versiontuple(v, 2)
460 460 (3, 6)
461 461 >>> versiontuple(v, 3)
462 462 (3, 6, None)
463 463 >>> versiontuple(v, 4)
464 464 (3, 6, None, None)
465 465
466 466 >>> v = '3.9-rc'
467 467 >>> versiontuple(v, 2)
468 468 (3, 9)
469 469 >>> versiontuple(v, 3)
470 470 (3, 9, None)
471 471 >>> versiontuple(v, 4)
472 472 (3, 9, None, 'rc')
473 473
474 474 >>> v = '3.9-rc+2-02a8fea4289b'
475 475 >>> versiontuple(v, 2)
476 476 (3, 9)
477 477 >>> versiontuple(v, 3)
478 478 (3, 9, None)
479 479 >>> versiontuple(v, 4)
480 480 (3, 9, None, 'rc+2-02a8fea4289b')
481 481 """
482 482 if not v:
483 483 v = version()
484 484 parts = remod.split('[\+-]', v, 1)
485 485 if len(parts) == 1:
486 486 vparts, extra = parts[0], None
487 487 else:
488 488 vparts, extra = parts
489 489
490 490 vints = []
491 491 for i in vparts.split('.'):
492 492 try:
493 493 vints.append(int(i))
494 494 except ValueError:
495 495 break
496 496 # (3, 6) -> (3, 6, None)
497 497 while len(vints) < 3:
498 498 vints.append(None)
499 499
500 500 if n == 2:
501 501 return (vints[0], vints[1])
502 502 if n == 3:
503 503 return (vints[0], vints[1], vints[2])
504 504 if n == 4:
505 505 return (vints[0], vints[1], vints[2], extra)
506 506
507 507 # used by parsedate
508 508 defaultdateformats = (
509 509 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
510 510 '%Y-%m-%dT%H:%M', # without seconds
511 511 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
512 512 '%Y-%m-%dT%H%M', # without seconds
513 513 '%Y-%m-%d %H:%M:%S', # our common legal variant
514 514 '%Y-%m-%d %H:%M', # without seconds
515 515 '%Y-%m-%d %H%M%S', # without :
516 516 '%Y-%m-%d %H%M', # without seconds
517 517 '%Y-%m-%d %I:%M:%S%p',
518 518 '%Y-%m-%d %H:%M',
519 519 '%Y-%m-%d %I:%M%p',
520 520 '%Y-%m-%d',
521 521 '%m-%d',
522 522 '%m/%d',
523 523 '%m/%d/%y',
524 524 '%m/%d/%Y',
525 525 '%a %b %d %H:%M:%S %Y',
526 526 '%a %b %d %I:%M:%S%p %Y',
527 527 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
528 528 '%b %d %H:%M:%S %Y',
529 529 '%b %d %I:%M:%S%p %Y',
530 530 '%b %d %H:%M:%S',
531 531 '%b %d %I:%M:%S%p',
532 532 '%b %d %H:%M',
533 533 '%b %d %I:%M%p',
534 534 '%b %d %Y',
535 535 '%b %d',
536 536 '%H:%M:%S',
537 537 '%I:%M:%S%p',
538 538 '%H:%M',
539 539 '%I:%M%p',
540 540 )
541 541
542 542 extendeddateformats = defaultdateformats + (
543 543 "%Y",
544 544 "%Y-%m",
545 545 "%b",
546 546 "%b %Y",
547 547 )
548 548
549 549 def cachefunc(func):
550 550 '''cache the result of function calls'''
551 551 # XXX doesn't handle keywords args
552 552 if func.__code__.co_argcount == 0:
553 553 cache = []
554 554 def f():
555 555 if len(cache) == 0:
556 556 cache.append(func())
557 557 return cache[0]
558 558 return f
559 559 cache = {}
560 560 if func.__code__.co_argcount == 1:
561 561 # we gain a small amount of time because
562 562 # we don't need to pack/unpack the list
563 563 def f(arg):
564 564 if arg not in cache:
565 565 cache[arg] = func(arg)
566 566 return cache[arg]
567 567 else:
568 568 def f(*args):
569 569 if args not in cache:
570 570 cache[args] = func(*args)
571 571 return cache[args]
572 572
573 573 return f
574 574
575 575 class sortdict(collections.OrderedDict):
576 576 '''a simple sorted dictionary
577 577
578 578 >>> d1 = sortdict([('a', 0), ('b', 1)])
579 579 >>> d2 = d1.copy()
580 580 >>> d2
581 581 sortdict([('a', 0), ('b', 1)])
582 582 >>> d2.update([('a', 2)])
583 583 >>> d2.keys() # should still be in last-set order
584 584 ['b', 'a']
585 585 '''
586 586
587 587 def __setitem__(self, key, value):
588 588 if key in self:
589 589 del self[key]
590 590 super(sortdict, self).__setitem__(key, value)
591 591
592 592 class _lrucachenode(object):
593 593 """A node in a doubly linked list.
594 594
595 595 Holds a reference to nodes on either side as well as a key-value
596 596 pair for the dictionary entry.
597 597 """
598 598 __slots__ = (u'next', u'prev', u'key', u'value')
599 599
600 600 def __init__(self):
601 601 self.next = None
602 602 self.prev = None
603 603
604 604 self.key = _notset
605 605 self.value = None
606 606
607 607 def markempty(self):
608 608 """Mark the node as emptied."""
609 609 self.key = _notset
610 610
611 611 class lrucachedict(object):
612 612 """Dict that caches most recent accesses and sets.
613 613
614 614 The dict consists of an actual backing dict - indexed by original
615 615 key - and a doubly linked circular list defining the order of entries in
616 616 the cache.
617 617
618 618 The head node is the newest entry in the cache. If the cache is full,
619 619 we recycle head.prev and make it the new head. Cache accesses result in
620 620 the node being moved to before the existing head and being marked as the
621 621 new head node.
622 622 """
623 623 def __init__(self, max):
624 624 self._cache = {}
625 625
626 626 self._head = head = _lrucachenode()
627 627 head.prev = head
628 628 head.next = head
629 629 self._size = 1
630 630 self._capacity = max
631 631
632 632 def __len__(self):
633 633 return len(self._cache)
634 634
635 635 def __contains__(self, k):
636 636 return k in self._cache
637 637
638 638 def __iter__(self):
639 639 # We don't have to iterate in cache order, but why not.
640 640 n = self._head
641 641 for i in range(len(self._cache)):
642 642 yield n.key
643 643 n = n.next
644 644
645 645 def __getitem__(self, k):
646 646 node = self._cache[k]
647 647 self._movetohead(node)
648 648 return node.value
649 649
650 650 def __setitem__(self, k, v):
651 651 node = self._cache.get(k)
652 652 # Replace existing value and mark as newest.
653 653 if node is not None:
654 654 node.value = v
655 655 self._movetohead(node)
656 656 return
657 657
658 658 if self._size < self._capacity:
659 659 node = self._addcapacity()
660 660 else:
661 661 # Grab the last/oldest item.
662 662 node = self._head.prev
663 663
664 664 # At capacity. Kill the old entry.
665 665 if node.key is not _notset:
666 666 del self._cache[node.key]
667 667
668 668 node.key = k
669 669 node.value = v
670 670 self._cache[k] = node
671 671 # And mark it as newest entry. No need to adjust order since it
672 672 # is already self._head.prev.
673 673 self._head = node
674 674
675 675 def __delitem__(self, k):
676 676 node = self._cache.pop(k)
677 677 node.markempty()
678 678
679 679 # Temporarily mark as newest item before re-adjusting head to make
680 680 # this node the oldest item.
681 681 self._movetohead(node)
682 682 self._head = node.next
683 683
684 684 # Additional dict methods.
685 685
686 686 def get(self, k, default=None):
687 687 try:
688 688 return self._cache[k].value
689 689 except KeyError:
690 690 return default
691 691
692 692 def clear(self):
693 693 n = self._head
694 694 while n.key is not _notset:
695 695 n.markempty()
696 696 n = n.next
697 697
698 698 self._cache.clear()
699 699
700 700 def copy(self):
701 701 result = lrucachedict(self._capacity)
702 702 n = self._head.prev
703 703 # Iterate in oldest-to-newest order, so the copy has the right ordering
704 704 for i in range(len(self._cache)):
705 705 result[n.key] = n.value
706 706 n = n.prev
707 707 return result
708 708
709 709 def _movetohead(self, node):
710 710 """Mark a node as the newest, making it the new head.
711 711
712 712 When a node is accessed, it becomes the freshest entry in the LRU
713 713 list, which is denoted by self._head.
714 714
715 715 Visually, let's make ``N`` the new head node (* denotes head):
716 716
717 717 previous/oldest <-> head <-> next/next newest
718 718
719 719 ----<->--- A* ---<->-----
720 720 | |
721 721 E <-> D <-> N <-> C <-> B
722 722
723 723 To:
724 724
725 725 ----<->--- N* ---<->-----
726 726 | |
727 727 E <-> D <-> C <-> B <-> A
728 728
729 729 This requires the following moves:
730 730
731 731 C.next = D (node.prev.next = node.next)
732 732 D.prev = C (node.next.prev = node.prev)
733 733 E.next = N (head.prev.next = node)
734 734 N.prev = E (node.prev = head.prev)
735 735 N.next = A (node.next = head)
736 736 A.prev = N (head.prev = node)
737 737 """
738 738 head = self._head
739 739 # C.next = D
740 740 node.prev.next = node.next
741 741 # D.prev = C
742 742 node.next.prev = node.prev
743 743 # N.prev = E
744 744 node.prev = head.prev
745 745 # N.next = A
746 746 # It is tempting to do just "head" here, however if node is
747 747 # adjacent to head, this will do bad things.
748 748 node.next = head.prev.next
749 749 # E.next = N
750 750 node.next.prev = node
751 751 # A.prev = N
752 752 node.prev.next = node
753 753
754 754 self._head = node
755 755
756 756 def _addcapacity(self):
757 757 """Add a node to the circular linked list.
758 758
759 759 The new node is inserted before the head node.
760 760 """
761 761 head = self._head
762 762 node = _lrucachenode()
763 763 head.prev.next = node
764 764 node.prev = head.prev
765 765 node.next = head
766 766 head.prev = node
767 767 self._size += 1
768 768 return node
769 769
770 770 def lrucachefunc(func):
771 771 '''cache most recent results of function calls'''
772 772 cache = {}
773 773 order = collections.deque()
774 774 if func.__code__.co_argcount == 1:
775 775 def f(arg):
776 776 if arg not in cache:
777 777 if len(cache) > 20:
778 778 del cache[order.popleft()]
779 779 cache[arg] = func(arg)
780 780 else:
781 781 order.remove(arg)
782 782 order.append(arg)
783 783 return cache[arg]
784 784 else:
785 785 def f(*args):
786 786 if args not in cache:
787 787 if len(cache) > 20:
788 788 del cache[order.popleft()]
789 789 cache[args] = func(*args)
790 790 else:
791 791 order.remove(args)
792 792 order.append(args)
793 793 return cache[args]
794 794
795 795 return f
796 796
797 797 class propertycache(object):
798 798 def __init__(self, func):
799 799 self.func = func
800 800 self.name = func.__name__
801 801 def __get__(self, obj, type=None):
802 802 result = self.func(obj)
803 803 self.cachevalue(obj, result)
804 804 return result
805 805
806 806 def cachevalue(self, obj, value):
807 807 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
808 808 obj.__dict__[self.name] = value
809 809
810 810 def pipefilter(s, cmd):
811 811 '''filter string S through command CMD, returning its output'''
812 812 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
813 813 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
814 814 pout, perr = p.communicate(s)
815 815 return pout
816 816
817 817 def tempfilter(s, cmd):
818 818 '''filter string S through a pair of temporary files with CMD.
819 819 CMD is used as a template to create the real command to be run,
820 820 with the strings INFILE and OUTFILE replaced by the real names of
821 821 the temporary files generated.'''
822 822 inname, outname = None, None
823 823 try:
824 824 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
825 825 fp = os.fdopen(infd, pycompat.sysstr('wb'))
826 826 fp.write(s)
827 827 fp.close()
828 828 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
829 829 os.close(outfd)
830 830 cmd = cmd.replace('INFILE', inname)
831 831 cmd = cmd.replace('OUTFILE', outname)
832 832 code = os.system(cmd)
833 833 if pycompat.sysplatform == 'OpenVMS' and code & 1:
834 834 code = 0
835 835 if code:
836 836 raise Abort(_("command '%s' failed: %s") %
837 837 (cmd, explainexit(code)))
838 838 return readfile(outname)
839 839 finally:
840 840 try:
841 841 if inname:
842 842 os.unlink(inname)
843 843 except OSError:
844 844 pass
845 845 try:
846 846 if outname:
847 847 os.unlink(outname)
848 848 except OSError:
849 849 pass
850 850
851 851 filtertable = {
852 852 'tempfile:': tempfilter,
853 853 'pipe:': pipefilter,
854 854 }
855 855
856 856 def filter(s, cmd):
857 857 "filter a string through a command that transforms its input to its output"
858 858 for name, fn in filtertable.iteritems():
859 859 if cmd.startswith(name):
860 860 return fn(s, cmd[len(name):].lstrip())
861 861 return pipefilter(s, cmd)
862 862
863 863 def binary(s):
864 864 """return true if a string is binary data"""
865 865 return bool(s and '\0' in s)
866 866
867 867 def increasingchunks(source, min=1024, max=65536):
868 868 '''return no less than min bytes per chunk while data remains,
869 869 doubling min after each chunk until it reaches max'''
870 870 def log2(x):
871 871 if not x:
872 872 return 0
873 873 i = 0
874 874 while x:
875 875 x >>= 1
876 876 i += 1
877 877 return i - 1
878 878
879 879 buf = []
880 880 blen = 0
881 881 for chunk in source:
882 882 buf.append(chunk)
883 883 blen += len(chunk)
884 884 if blen >= min:
885 885 if min < max:
886 886 min = min << 1
887 887 nmin = 1 << log2(blen)
888 888 if nmin > min:
889 889 min = nmin
890 890 if min > max:
891 891 min = max
892 892 yield ''.join(buf)
893 893 blen = 0
894 894 buf = []
895 895 if buf:
896 896 yield ''.join(buf)
897 897
898 898 Abort = error.Abort
899 899
900 900 def always(fn):
901 901 return True
902 902
903 903 def never(fn):
904 904 return False
905 905
906 906 def nogc(func):
907 907 """disable garbage collector
908 908
909 909 Python's garbage collector triggers a GC each time a certain number of
910 910 container objects (the number being defined by gc.get_threshold()) are
911 911 allocated even when marked not to be tracked by the collector. Tracking has
912 912 no effect on when GCs are triggered, only on what objects the GC looks
913 913 into. As a workaround, disable GC while building complex (huge)
914 914 containers.
915 915
916 916 This garbage collector issue have been fixed in 2.7.
917 917 """
918 918 if sys.version_info >= (2, 7):
919 919 return func
920 920 def wrapper(*args, **kwargs):
921 921 gcenabled = gc.isenabled()
922 922 gc.disable()
923 923 try:
924 924 return func(*args, **kwargs)
925 925 finally:
926 926 if gcenabled:
927 927 gc.enable()
928 928 return wrapper
929 929
930 930 def pathto(root, n1, n2):
931 931 '''return the relative path from one place to another.
932 932 root should use os.sep to separate directories
933 933 n1 should use os.sep to separate directories
934 934 n2 should use "/" to separate directories
935 935 returns an os.sep-separated path.
936 936
937 937 If n1 is a relative path, it's assumed it's
938 938 relative to root.
939 939 n2 should always be relative to root.
940 940 '''
941 941 if not n1:
942 942 return localpath(n2)
943 943 if os.path.isabs(n1):
944 944 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
945 945 return os.path.join(root, localpath(n2))
946 946 n2 = '/'.join((pconvert(root), n2))
947 947 a, b = splitpath(n1), n2.split('/')
948 948 a.reverse()
949 949 b.reverse()
950 950 while a and b and a[-1] == b[-1]:
951 951 a.pop()
952 952 b.pop()
953 953 b.reverse()
954 954 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
955 955
956 956 def mainfrozen():
957 957 """return True if we are a frozen executable.
958 958
959 959 The code supports py2exe (most common, Windows only) and tools/freeze
960 960 (portable, not much used).
961 961 """
962 962 return (safehasattr(sys, "frozen") or # new py2exe
963 963 safehasattr(sys, "importers") or # old py2exe
964 964 imp.is_frozen(u"__main__")) # tools/freeze
965 965
966 966 # the location of data files matching the source code
967 967 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
968 968 # executable version (py2exe) doesn't support __file__
969 969 datapath = os.path.dirname(pycompat.sysexecutable)
970 970 else:
971 971 datapath = os.path.dirname(pycompat.fsencode(__file__))
972 972
973 973 i18n.setdatapath(datapath)
974 974
975 975 _hgexecutable = None
976 976
977 977 def hgexecutable():
978 978 """return location of the 'hg' executable.
979 979
980 980 Defaults to $HG or 'hg' in the search path.
981 981 """
982 982 if _hgexecutable is None:
983 983 hg = encoding.environ.get('HG')
984 984 mainmod = sys.modules[pycompat.sysstr('__main__')]
985 985 if hg:
986 986 _sethgexecutable(hg)
987 987 elif mainfrozen():
988 988 if getattr(sys, 'frozen', None) == 'macosx_app':
989 989 # Env variable set by py2app
990 990 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
991 991 else:
992 992 _sethgexecutable(pycompat.sysexecutable)
993 993 elif (os.path.basename(
994 994 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
995 995 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
996 996 else:
997 997 exe = findexe('hg') or os.path.basename(sys.argv[0])
998 998 _sethgexecutable(exe)
999 999 return _hgexecutable
1000 1000
1001 1001 def _sethgexecutable(path):
1002 1002 """set location of the 'hg' executable"""
1003 1003 global _hgexecutable
1004 1004 _hgexecutable = path
1005 1005
1006 1006 def _isstdout(f):
1007 1007 fileno = getattr(f, 'fileno', None)
1008 1008 return fileno and fileno() == sys.__stdout__.fileno()
1009 1009
1010 1010 def shellenviron(environ=None):
1011 1011 """return environ with optional override, useful for shelling out"""
1012 1012 def py2shell(val):
1013 1013 'convert python object into string that is useful to shell'
1014 1014 if val is None or val is False:
1015 1015 return '0'
1016 1016 if val is True:
1017 1017 return '1'
1018 1018 return str(val)
1019 1019 env = dict(encoding.environ)
1020 1020 if environ:
1021 1021 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1022 1022 env['HG'] = hgexecutable()
1023 1023 return env
1024 1024
1025 1025 def system(cmd, environ=None, cwd=None, out=None):
1026 1026 '''enhanced shell command execution.
1027 1027 run with environment maybe modified, maybe in different dir.
1028 1028
1029 1029 if out is specified, it is assumed to be a file-like object that has a
1030 1030 write() method. stdout and stderr will be redirected to out.'''
1031 1031 try:
1032 1032 stdout.flush()
1033 1033 except Exception:
1034 1034 pass
1035 1035 cmd = quotecommand(cmd)
1036 1036 if pycompat.sysplatform == 'plan9' and (sys.version_info[0] == 2
1037 1037 and sys.version_info[1] < 7):
1038 1038 # subprocess kludge to work around issues in half-baked Python
1039 1039 # ports, notably bichued/python:
1040 1040 if not cwd is None:
1041 1041 os.chdir(cwd)
1042 1042 rc = os.system(cmd)
1043 1043 else:
1044 1044 env = shellenviron(environ)
1045 1045 if out is None or _isstdout(out):
1046 1046 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1047 1047 env=env, cwd=cwd)
1048 1048 else:
1049 1049 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1050 1050 env=env, cwd=cwd, stdout=subprocess.PIPE,
1051 1051 stderr=subprocess.STDOUT)
1052 1052 for line in iter(proc.stdout.readline, ''):
1053 1053 out.write(line)
1054 1054 proc.wait()
1055 1055 rc = proc.returncode
1056 1056 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1057 1057 rc = 0
1058 1058 return rc
1059 1059
1060 1060 def checksignature(func):
1061 1061 '''wrap a function with code to check for calling errors'''
1062 1062 def check(*args, **kwargs):
1063 1063 try:
1064 1064 return func(*args, **kwargs)
1065 1065 except TypeError:
1066 1066 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1067 1067 raise error.SignatureError
1068 1068 raise
1069 1069
1070 1070 return check
1071 1071
1072 1072 # a whilelist of known filesystems where hardlink works reliably
1073 1073 _hardlinkfswhitelist = {
1074 1074 'btrfs',
1075 1075 'ext2',
1076 1076 'ext3',
1077 1077 'ext4',
1078 1078 'hfs',
1079 1079 'jfs',
1080 1080 'reiserfs',
1081 1081 'tmpfs',
1082 1082 'ufs',
1083 1083 'xfs',
1084 1084 'zfs',
1085 1085 }
1086 1086
1087 1087 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1088 1088 '''copy a file, preserving mode and optionally other stat info like
1089 1089 atime/mtime
1090 1090
1091 1091 checkambig argument is used with filestat, and is useful only if
1092 1092 destination file is guarded by any lock (e.g. repo.lock or
1093 1093 repo.wlock).
1094 1094
1095 1095 copystat and checkambig should be exclusive.
1096 1096 '''
1097 1097 assert not (copystat and checkambig)
1098 1098 oldstat = None
1099 1099 if os.path.lexists(dest):
1100 1100 if checkambig:
1101 1101 oldstat = checkambig and filestat.frompath(dest)
1102 1102 unlink(dest)
1103 1103 if hardlink:
1104 1104 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1105 1105 # unless we are confident that dest is on a whitelisted filesystem.
1106 1106 try:
1107 1107 fstype = getfstype(os.path.dirname(dest))
1108 1108 except OSError:
1109 1109 fstype = None
1110 1110 if fstype not in _hardlinkfswhitelist:
1111 1111 hardlink = False
1112 1112 if hardlink:
1113 1113 try:
1114 1114 oslink(src, dest)
1115 1115 return
1116 1116 except (IOError, OSError):
1117 1117 pass # fall back to normal copy
1118 1118 if os.path.islink(src):
1119 1119 os.symlink(os.readlink(src), dest)
1120 1120 # copytime is ignored for symlinks, but in general copytime isn't needed
1121 1121 # for them anyway
1122 1122 else:
1123 1123 try:
1124 1124 shutil.copyfile(src, dest)
1125 1125 if copystat:
1126 1126 # copystat also copies mode
1127 1127 shutil.copystat(src, dest)
1128 1128 else:
1129 1129 shutil.copymode(src, dest)
1130 1130 if oldstat and oldstat.stat:
1131 1131 newstat = filestat.frompath(dest)
1132 1132 if newstat.isambig(oldstat):
1133 1133 # stat of copied file is ambiguous to original one
1134 1134 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1135 1135 os.utime(dest, (advanced, advanced))
1136 1136 except shutil.Error as inst:
1137 1137 raise Abort(str(inst))
1138 1138
1139 1139 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1140 1140 """Copy a directory tree using hardlinks if possible."""
1141 1141 num = 0
1142 1142
1143 1143 gettopic = lambda: hardlink and _('linking') or _('copying')
1144 1144
1145 1145 if os.path.isdir(src):
1146 1146 if hardlink is None:
1147 1147 hardlink = (os.stat(src).st_dev ==
1148 1148 os.stat(os.path.dirname(dst)).st_dev)
1149 1149 topic = gettopic()
1150 1150 os.mkdir(dst)
1151 1151 for name, kind in listdir(src):
1152 1152 srcname = os.path.join(src, name)
1153 1153 dstname = os.path.join(dst, name)
1154 1154 def nprog(t, pos):
1155 1155 if pos is not None:
1156 1156 return progress(t, pos + num)
1157 1157 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1158 1158 num += n
1159 1159 else:
1160 1160 if hardlink is None:
1161 1161 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1162 1162 os.stat(os.path.dirname(dst)).st_dev)
1163 1163 topic = gettopic()
1164 1164
1165 1165 if hardlink:
1166 1166 try:
1167 1167 oslink(src, dst)
1168 1168 except (IOError, OSError):
1169 1169 hardlink = False
1170 1170 shutil.copy(src, dst)
1171 1171 else:
1172 1172 shutil.copy(src, dst)
1173 1173 num += 1
1174 1174 progress(topic, num)
1175 1175 progress(topic, None)
1176 1176
1177 1177 return hardlink, num
1178 1178
1179 1179 _winreservednames = '''con prn aux nul
1180 1180 com1 com2 com3 com4 com5 com6 com7 com8 com9
1181 1181 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1182 1182 _winreservedchars = ':*?"<>|'
1183 1183 def checkwinfilename(path):
1184 1184 r'''Check that the base-relative path is a valid filename on Windows.
1185 1185 Returns None if the path is ok, or a UI string describing the problem.
1186 1186
1187 1187 >>> checkwinfilename("just/a/normal/path")
1188 1188 >>> checkwinfilename("foo/bar/con.xml")
1189 1189 "filename contains 'con', which is reserved on Windows"
1190 1190 >>> checkwinfilename("foo/con.xml/bar")
1191 1191 "filename contains 'con', which is reserved on Windows"
1192 1192 >>> checkwinfilename("foo/bar/xml.con")
1193 1193 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1194 1194 "filename contains 'AUX', which is reserved on Windows"
1195 1195 >>> checkwinfilename("foo/bar/bla:.txt")
1196 1196 "filename contains ':', which is reserved on Windows"
1197 1197 >>> checkwinfilename("foo/bar/b\07la.txt")
1198 1198 "filename contains '\\x07', which is invalid on Windows"
1199 1199 >>> checkwinfilename("foo/bar/bla ")
1200 1200 "filename ends with ' ', which is not allowed on Windows"
1201 1201 >>> checkwinfilename("../bar")
1202 1202 >>> checkwinfilename("foo\\")
1203 1203 "filename ends with '\\', which is invalid on Windows"
1204 1204 >>> checkwinfilename("foo\\/bar")
1205 1205 "directory name ends with '\\', which is invalid on Windows"
1206 1206 '''
1207 1207 if path.endswith('\\'):
1208 1208 return _("filename ends with '\\', which is invalid on Windows")
1209 1209 if '\\/' in path:
1210 1210 return _("directory name ends with '\\', which is invalid on Windows")
1211 1211 for n in path.replace('\\', '/').split('/'):
1212 1212 if not n:
1213 1213 continue
1214 1214 for c in _filenamebytestr(n):
1215 1215 if c in _winreservedchars:
1216 1216 return _("filename contains '%s', which is reserved "
1217 1217 "on Windows") % c
1218 1218 if ord(c) <= 31:
1219 1219 return _("filename contains %r, which is invalid "
1220 1220 "on Windows") % c
1221 1221 base = n.split('.')[0]
1222 1222 if base and base.lower() in _winreservednames:
1223 1223 return _("filename contains '%s', which is reserved "
1224 1224 "on Windows") % base
1225 1225 t = n[-1]
1226 1226 if t in '. ' and n not in '..':
1227 1227 return _("filename ends with '%s', which is not allowed "
1228 1228 "on Windows") % t
1229 1229
1230 1230 if pycompat.osname == 'nt':
1231 1231 checkosfilename = checkwinfilename
1232 1232 timer = time.clock
1233 1233 else:
1234 1234 checkosfilename = platform.checkosfilename
1235 1235 timer = time.time
1236 1236
1237 1237 if safehasattr(time, "perf_counter"):
1238 1238 timer = time.perf_counter
1239 1239
1240 1240 def makelock(info, pathname):
1241 1241 try:
1242 1242 return os.symlink(info, pathname)
1243 1243 except OSError as why:
1244 1244 if why.errno == errno.EEXIST:
1245 1245 raise
1246 1246 except AttributeError: # no symlink in os
1247 1247 pass
1248 1248
1249 1249 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1250 1250 os.write(ld, info)
1251 1251 os.close(ld)
1252 1252
1253 1253 def readlock(pathname):
1254 1254 try:
1255 1255 return os.readlink(pathname)
1256 1256 except OSError as why:
1257 1257 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1258 1258 raise
1259 1259 except AttributeError: # no symlink in os
1260 1260 pass
1261 1261 fp = posixfile(pathname)
1262 1262 r = fp.read()
1263 1263 fp.close()
1264 1264 return r
1265 1265
1266 1266 def fstat(fp):
1267 1267 '''stat file object that may not have fileno method.'''
1268 1268 try:
1269 1269 return os.fstat(fp.fileno())
1270 1270 except AttributeError:
1271 1271 return os.stat(fp.name)
1272 1272
1273 1273 # File system features
1274 1274
1275 1275 def fscasesensitive(path):
1276 1276 """
1277 1277 Return true if the given path is on a case-sensitive filesystem
1278 1278
1279 1279 Requires a path (like /foo/.hg) ending with a foldable final
1280 1280 directory component.
1281 1281 """
1282 1282 s1 = os.lstat(path)
1283 1283 d, b = os.path.split(path)
1284 1284 b2 = b.upper()
1285 1285 if b == b2:
1286 1286 b2 = b.lower()
1287 1287 if b == b2:
1288 1288 return True # no evidence against case sensitivity
1289 1289 p2 = os.path.join(d, b2)
1290 1290 try:
1291 1291 s2 = os.lstat(p2)
1292 1292 if s2 == s1:
1293 1293 return False
1294 1294 return True
1295 1295 except OSError:
1296 1296 return True
1297 1297
1298 1298 try:
1299 1299 import re2
1300 1300 _re2 = None
1301 1301 except ImportError:
1302 1302 _re2 = False
1303 1303
1304 1304 class _re(object):
1305 1305 def _checkre2(self):
1306 1306 global _re2
1307 1307 try:
1308 1308 # check if match works, see issue3964
1309 1309 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1310 1310 except ImportError:
1311 1311 _re2 = False
1312 1312
1313 1313 def compile(self, pat, flags=0):
1314 1314 '''Compile a regular expression, using re2 if possible
1315 1315
1316 1316 For best performance, use only re2-compatible regexp features. The
1317 1317 only flags from the re module that are re2-compatible are
1318 1318 IGNORECASE and MULTILINE.'''
1319 1319 if _re2 is None:
1320 1320 self._checkre2()
1321 1321 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1322 1322 if flags & remod.IGNORECASE:
1323 1323 pat = '(?i)' + pat
1324 1324 if flags & remod.MULTILINE:
1325 1325 pat = '(?m)' + pat
1326 1326 try:
1327 1327 return re2.compile(pat)
1328 1328 except re2.error:
1329 1329 pass
1330 1330 return remod.compile(pat, flags)
1331 1331
1332 1332 @propertycache
1333 1333 def escape(self):
1334 1334 '''Return the version of escape corresponding to self.compile.
1335 1335
1336 1336 This is imperfect because whether re2 or re is used for a particular
1337 1337 function depends on the flags, etc, but it's the best we can do.
1338 1338 '''
1339 1339 global _re2
1340 1340 if _re2 is None:
1341 1341 self._checkre2()
1342 1342 if _re2:
1343 1343 return re2.escape
1344 1344 else:
1345 1345 return remod.escape
1346 1346
1347 1347 re = _re()
1348 1348
1349 1349 _fspathcache = {}
1350 1350 def fspath(name, root):
1351 1351 '''Get name in the case stored in the filesystem
1352 1352
1353 1353 The name should be relative to root, and be normcase-ed for efficiency.
1354 1354
1355 1355 Note that this function is unnecessary, and should not be
1356 1356 called, for case-sensitive filesystems (simply because it's expensive).
1357 1357
1358 1358 The root should be normcase-ed, too.
1359 1359 '''
1360 1360 def _makefspathcacheentry(dir):
1361 1361 return dict((normcase(n), n) for n in os.listdir(dir))
1362 1362
1363 1363 seps = pycompat.ossep
1364 1364 if pycompat.osaltsep:
1365 1365 seps = seps + pycompat.osaltsep
1366 1366 # Protect backslashes. This gets silly very quickly.
1367 1367 seps.replace('\\','\\\\')
1368 1368 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1369 1369 dir = os.path.normpath(root)
1370 1370 result = []
1371 1371 for part, sep in pattern.findall(name):
1372 1372 if sep:
1373 1373 result.append(sep)
1374 1374 continue
1375 1375
1376 1376 if dir not in _fspathcache:
1377 1377 _fspathcache[dir] = _makefspathcacheentry(dir)
1378 1378 contents = _fspathcache[dir]
1379 1379
1380 1380 found = contents.get(part)
1381 1381 if not found:
1382 1382 # retry "once per directory" per "dirstate.walk" which
1383 1383 # may take place for each patches of "hg qpush", for example
1384 1384 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1385 1385 found = contents.get(part)
1386 1386
1387 1387 result.append(found or part)
1388 1388 dir = os.path.join(dir, part)
1389 1389
1390 1390 return ''.join(result)
1391 1391
1392 1392 def getfstype(dirpath):
1393 1393 '''Get the filesystem type name from a directory (best-effort)
1394 1394
1395 1395 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1396 1396 '''
1397 1397 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1398 1398
1399 1399 def checknlink(testfile):
1400 1400 '''check whether hardlink count reporting works properly'''
1401 1401
1402 1402 # testfile may be open, so we need a separate file for checking to
1403 1403 # work around issue2543 (or testfile may get lost on Samba shares)
1404 1404 f1 = testfile + ".hgtmp1"
1405 1405 if os.path.lexists(f1):
1406 1406 return False
1407 1407 try:
1408 1408 posixfile(f1, 'w').close()
1409 1409 except IOError:
1410 1410 try:
1411 1411 os.unlink(f1)
1412 1412 except OSError:
1413 1413 pass
1414 1414 return False
1415 1415
1416 1416 f2 = testfile + ".hgtmp2"
1417 1417 fd = None
1418 1418 try:
1419 1419 oslink(f1, f2)
1420 1420 # nlinks() may behave differently for files on Windows shares if
1421 1421 # the file is open.
1422 1422 fd = posixfile(f2)
1423 1423 return nlinks(f2) > 1
1424 1424 except OSError:
1425 1425 return False
1426 1426 finally:
1427 1427 if fd is not None:
1428 1428 fd.close()
1429 1429 for f in (f1, f2):
1430 1430 try:
1431 1431 os.unlink(f)
1432 1432 except OSError:
1433 1433 pass
1434 1434
1435 1435 def endswithsep(path):
1436 1436 '''Check path ends with os.sep or os.altsep.'''
1437 1437 return (path.endswith(pycompat.ossep)
1438 1438 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1439 1439
1440 1440 def splitpath(path):
1441 1441 '''Split path by os.sep.
1442 1442 Note that this function does not use os.altsep because this is
1443 1443 an alternative of simple "xxx.split(os.sep)".
1444 1444 It is recommended to use os.path.normpath() before using this
1445 1445 function if need.'''
1446 1446 return path.split(pycompat.ossep)
1447 1447
1448 1448 def gui():
1449 1449 '''Are we running in a GUI?'''
1450 1450 if pycompat.sysplatform == 'darwin':
1451 1451 if 'SSH_CONNECTION' in encoding.environ:
1452 1452 # handle SSH access to a box where the user is logged in
1453 1453 return False
1454 1454 elif getattr(osutil, 'isgui', None):
1455 1455 # check if a CoreGraphics session is available
1456 1456 return osutil.isgui()
1457 1457 else:
1458 1458 # pure build; use a safe default
1459 1459 return True
1460 1460 else:
1461 1461 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1462 1462
1463 1463 def mktempcopy(name, emptyok=False, createmode=None):
1464 1464 """Create a temporary file with the same contents from name
1465 1465
1466 1466 The permission bits are copied from the original file.
1467 1467
1468 1468 If the temporary file is going to be truncated immediately, you
1469 1469 can use emptyok=True as an optimization.
1470 1470
1471 1471 Returns the name of the temporary file.
1472 1472 """
1473 1473 d, fn = os.path.split(name)
1474 1474 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1475 1475 os.close(fd)
1476 1476 # Temporary files are created with mode 0600, which is usually not
1477 1477 # what we want. If the original file already exists, just copy
1478 1478 # its mode. Otherwise, manually obey umask.
1479 1479 copymode(name, temp, createmode)
1480 1480 if emptyok:
1481 1481 return temp
1482 1482 try:
1483 1483 try:
1484 1484 ifp = posixfile(name, "rb")
1485 1485 except IOError as inst:
1486 1486 if inst.errno == errno.ENOENT:
1487 1487 return temp
1488 1488 if not getattr(inst, 'filename', None):
1489 1489 inst.filename = name
1490 1490 raise
1491 1491 ofp = posixfile(temp, "wb")
1492 1492 for chunk in filechunkiter(ifp):
1493 1493 ofp.write(chunk)
1494 1494 ifp.close()
1495 1495 ofp.close()
1496 1496 except: # re-raises
1497 1497 try: os.unlink(temp)
1498 1498 except OSError: pass
1499 1499 raise
1500 1500 return temp
1501 1501
1502 1502 class filestat(object):
1503 1503 """help to exactly detect change of a file
1504 1504
1505 1505 'stat' attribute is result of 'os.stat()' if specified 'path'
1506 1506 exists. Otherwise, it is None. This can avoid preparative
1507 1507 'exists()' examination on client side of this class.
1508 1508 """
1509 1509 def __init__(self, stat):
1510 1510 self.stat = stat
1511 1511
1512 1512 @classmethod
1513 1513 def frompath(cls, path):
1514 1514 try:
1515 1515 stat = os.stat(path)
1516 1516 except OSError as err:
1517 1517 if err.errno != errno.ENOENT:
1518 1518 raise
1519 1519 stat = None
1520 1520 return cls(stat)
1521 1521
1522 @classmethod
1523 def fromfp(cls, fp):
1524 stat = os.fstat(fp.fileno())
1525 return cls(stat)
1526
1522 1527 __hash__ = object.__hash__
1523 1528
1524 1529 def __eq__(self, old):
1525 1530 try:
1526 1531 # if ambiguity between stat of new and old file is
1527 1532 # avoided, comparison of size, ctime and mtime is enough
1528 1533 # to exactly detect change of a file regardless of platform
1529 1534 return (self.stat.st_size == old.stat.st_size and
1530 1535 self.stat.st_ctime == old.stat.st_ctime and
1531 1536 self.stat.st_mtime == old.stat.st_mtime)
1532 1537 except AttributeError:
1533 1538 pass
1534 1539 try:
1535 1540 return self.stat is None and old.stat is None
1536 1541 except AttributeError:
1537 1542 return False
1538 1543
1539 1544 def isambig(self, old):
1540 1545 """Examine whether new (= self) stat is ambiguous against old one
1541 1546
1542 1547 "S[N]" below means stat of a file at N-th change:
1543 1548
1544 1549 - S[n-1].ctime < S[n].ctime: can detect change of a file
1545 1550 - S[n-1].ctime == S[n].ctime
1546 1551 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1547 1552 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1548 1553 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1549 1554 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1550 1555
1551 1556 Case (*2) above means that a file was changed twice or more at
1552 1557 same time in sec (= S[n-1].ctime), and comparison of timestamp
1553 1558 is ambiguous.
1554 1559
1555 1560 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1556 1561 timestamp is ambiguous".
1557 1562
1558 1563 But advancing mtime only in case (*2) doesn't work as
1559 1564 expected, because naturally advanced S[n].mtime in case (*1)
1560 1565 might be equal to manually advanced S[n-1 or earlier].mtime.
1561 1566
1562 1567 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1563 1568 treated as ambiguous regardless of mtime, to avoid overlooking
1564 1569 by confliction between such mtime.
1565 1570
1566 1571 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1567 1572 S[n].mtime", even if size of a file isn't changed.
1568 1573 """
1569 1574 try:
1570 1575 return (self.stat.st_ctime == old.stat.st_ctime)
1571 1576 except AttributeError:
1572 1577 return False
1573 1578
1574 1579 def avoidambig(self, path, old):
1575 1580 """Change file stat of specified path to avoid ambiguity
1576 1581
1577 1582 'old' should be previous filestat of 'path'.
1578 1583
1579 1584 This skips avoiding ambiguity, if a process doesn't have
1580 1585 appropriate privileges for 'path'. This returns False in this
1581 1586 case.
1582 1587
1583 1588 Otherwise, this returns True, as "ambiguity is avoided".
1584 1589 """
1585 1590 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1586 1591 try:
1587 1592 os.utime(path, (advanced, advanced))
1588 1593 except OSError as inst:
1589 1594 if inst.errno == errno.EPERM:
1590 1595 # utime() on the file created by another user causes EPERM,
1591 1596 # if a process doesn't have appropriate privileges
1592 1597 return False
1593 1598 raise
1594 1599 return True
1595 1600
1596 1601 def __ne__(self, other):
1597 1602 return not self == other
1598 1603
1599 1604 class atomictempfile(object):
1600 1605 '''writable file object that atomically updates a file
1601 1606
1602 1607 All writes will go to a temporary copy of the original file. Call
1603 1608 close() when you are done writing, and atomictempfile will rename
1604 1609 the temporary copy to the original name, making the changes
1605 1610 visible. If the object is destroyed without being closed, all your
1606 1611 writes are discarded.
1607 1612
1608 1613 checkambig argument of constructor is used with filestat, and is
1609 1614 useful only if target file is guarded by any lock (e.g. repo.lock
1610 1615 or repo.wlock).
1611 1616 '''
1612 1617 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1613 1618 self.__name = name # permanent name
1614 1619 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1615 1620 createmode=createmode)
1616 1621 self._fp = posixfile(self._tempname, mode)
1617 1622 self._checkambig = checkambig
1618 1623
1619 1624 # delegated methods
1620 1625 self.read = self._fp.read
1621 1626 self.write = self._fp.write
1622 1627 self.seek = self._fp.seek
1623 1628 self.tell = self._fp.tell
1624 1629 self.fileno = self._fp.fileno
1625 1630
1626 1631 def close(self):
1627 1632 if not self._fp.closed:
1628 1633 self._fp.close()
1629 1634 filename = localpath(self.__name)
1630 1635 oldstat = self._checkambig and filestat.frompath(filename)
1631 1636 if oldstat and oldstat.stat:
1632 1637 rename(self._tempname, filename)
1633 1638 newstat = filestat.frompath(filename)
1634 1639 if newstat.isambig(oldstat):
1635 1640 # stat of changed file is ambiguous to original one
1636 1641 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1637 1642 os.utime(filename, (advanced, advanced))
1638 1643 else:
1639 1644 rename(self._tempname, filename)
1640 1645
1641 1646 def discard(self):
1642 1647 if not self._fp.closed:
1643 1648 try:
1644 1649 os.unlink(self._tempname)
1645 1650 except OSError:
1646 1651 pass
1647 1652 self._fp.close()
1648 1653
1649 1654 def __del__(self):
1650 1655 if safehasattr(self, '_fp'): # constructor actually did something
1651 1656 self.discard()
1652 1657
1653 1658 def __enter__(self):
1654 1659 return self
1655 1660
1656 1661 def __exit__(self, exctype, excvalue, traceback):
1657 1662 if exctype is not None:
1658 1663 self.discard()
1659 1664 else:
1660 1665 self.close()
1661 1666
1662 1667 def unlinkpath(f, ignoremissing=False):
1663 1668 """unlink and remove the directory if it is empty"""
1664 1669 if ignoremissing:
1665 1670 tryunlink(f)
1666 1671 else:
1667 1672 unlink(f)
1668 1673 # try removing directories that might now be empty
1669 1674 try:
1670 1675 removedirs(os.path.dirname(f))
1671 1676 except OSError:
1672 1677 pass
1673 1678
1674 1679 def tryunlink(f):
1675 1680 """Attempt to remove a file, ignoring ENOENT errors."""
1676 1681 try:
1677 1682 unlink(f)
1678 1683 except OSError as e:
1679 1684 if e.errno != errno.ENOENT:
1680 1685 raise
1681 1686
1682 1687 def makedirs(name, mode=None, notindexed=False):
1683 1688 """recursive directory creation with parent mode inheritance
1684 1689
1685 1690 Newly created directories are marked as "not to be indexed by
1686 1691 the content indexing service", if ``notindexed`` is specified
1687 1692 for "write" mode access.
1688 1693 """
1689 1694 try:
1690 1695 makedir(name, notindexed)
1691 1696 except OSError as err:
1692 1697 if err.errno == errno.EEXIST:
1693 1698 return
1694 1699 if err.errno != errno.ENOENT or not name:
1695 1700 raise
1696 1701 parent = os.path.dirname(os.path.abspath(name))
1697 1702 if parent == name:
1698 1703 raise
1699 1704 makedirs(parent, mode, notindexed)
1700 1705 try:
1701 1706 makedir(name, notindexed)
1702 1707 except OSError as err:
1703 1708 # Catch EEXIST to handle races
1704 1709 if err.errno == errno.EEXIST:
1705 1710 return
1706 1711 raise
1707 1712 if mode is not None:
1708 1713 os.chmod(name, mode)
1709 1714
1710 1715 def readfile(path):
1711 1716 with open(path, 'rb') as fp:
1712 1717 return fp.read()
1713 1718
1714 1719 def writefile(path, text):
1715 1720 with open(path, 'wb') as fp:
1716 1721 fp.write(text)
1717 1722
1718 1723 def appendfile(path, text):
1719 1724 with open(path, 'ab') as fp:
1720 1725 fp.write(text)
1721 1726
1722 1727 class chunkbuffer(object):
1723 1728 """Allow arbitrary sized chunks of data to be efficiently read from an
1724 1729 iterator over chunks of arbitrary size."""
1725 1730
1726 1731 def __init__(self, in_iter):
1727 1732 """in_iter is the iterator that's iterating over the input chunks."""
1728 1733 def splitbig(chunks):
1729 1734 for chunk in chunks:
1730 1735 if len(chunk) > 2**20:
1731 1736 pos = 0
1732 1737 while pos < len(chunk):
1733 1738 end = pos + 2 ** 18
1734 1739 yield chunk[pos:end]
1735 1740 pos = end
1736 1741 else:
1737 1742 yield chunk
1738 1743 self.iter = splitbig(in_iter)
1739 1744 self._queue = collections.deque()
1740 1745 self._chunkoffset = 0
1741 1746
1742 1747 def read(self, l=None):
1743 1748 """Read L bytes of data from the iterator of chunks of data.
1744 1749 Returns less than L bytes if the iterator runs dry.
1745 1750
1746 1751 If size parameter is omitted, read everything"""
1747 1752 if l is None:
1748 1753 return ''.join(self.iter)
1749 1754
1750 1755 left = l
1751 1756 buf = []
1752 1757 queue = self._queue
1753 1758 while left > 0:
1754 1759 # refill the queue
1755 1760 if not queue:
1756 1761 target = 2**18
1757 1762 for chunk in self.iter:
1758 1763 queue.append(chunk)
1759 1764 target -= len(chunk)
1760 1765 if target <= 0:
1761 1766 break
1762 1767 if not queue:
1763 1768 break
1764 1769
1765 1770 # The easy way to do this would be to queue.popleft(), modify the
1766 1771 # chunk (if necessary), then queue.appendleft(). However, for cases
1767 1772 # where we read partial chunk content, this incurs 2 dequeue
1768 1773 # mutations and creates a new str for the remaining chunk in the
1769 1774 # queue. Our code below avoids this overhead.
1770 1775
1771 1776 chunk = queue[0]
1772 1777 chunkl = len(chunk)
1773 1778 offset = self._chunkoffset
1774 1779
1775 1780 # Use full chunk.
1776 1781 if offset == 0 and left >= chunkl:
1777 1782 left -= chunkl
1778 1783 queue.popleft()
1779 1784 buf.append(chunk)
1780 1785 # self._chunkoffset remains at 0.
1781 1786 continue
1782 1787
1783 1788 chunkremaining = chunkl - offset
1784 1789
1785 1790 # Use all of unconsumed part of chunk.
1786 1791 if left >= chunkremaining:
1787 1792 left -= chunkremaining
1788 1793 queue.popleft()
1789 1794 # offset == 0 is enabled by block above, so this won't merely
1790 1795 # copy via ``chunk[0:]``.
1791 1796 buf.append(chunk[offset:])
1792 1797 self._chunkoffset = 0
1793 1798
1794 1799 # Partial chunk needed.
1795 1800 else:
1796 1801 buf.append(chunk[offset:offset + left])
1797 1802 self._chunkoffset += left
1798 1803 left -= chunkremaining
1799 1804
1800 1805 return ''.join(buf)
1801 1806
1802 1807 def filechunkiter(f, size=131072, limit=None):
1803 1808 """Create a generator that produces the data in the file size
1804 1809 (default 131072) bytes at a time, up to optional limit (default is
1805 1810 to read all data). Chunks may be less than size bytes if the
1806 1811 chunk is the last chunk in the file, or the file is a socket or
1807 1812 some other type of file that sometimes reads less data than is
1808 1813 requested."""
1809 1814 assert size >= 0
1810 1815 assert limit is None or limit >= 0
1811 1816 while True:
1812 1817 if limit is None:
1813 1818 nbytes = size
1814 1819 else:
1815 1820 nbytes = min(limit, size)
1816 1821 s = nbytes and f.read(nbytes)
1817 1822 if not s:
1818 1823 break
1819 1824 if limit:
1820 1825 limit -= len(s)
1821 1826 yield s
1822 1827
1823 1828 def makedate(timestamp=None):
1824 1829 '''Return a unix timestamp (or the current time) as a (unixtime,
1825 1830 offset) tuple based off the local timezone.'''
1826 1831 if timestamp is None:
1827 1832 timestamp = time.time()
1828 1833 if timestamp < 0:
1829 1834 hint = _("check your clock")
1830 1835 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1831 1836 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1832 1837 datetime.datetime.fromtimestamp(timestamp))
1833 1838 tz = delta.days * 86400 + delta.seconds
1834 1839 return timestamp, tz
1835 1840
1836 1841 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1837 1842 """represent a (unixtime, offset) tuple as a localized time.
1838 1843 unixtime is seconds since the epoch, and offset is the time zone's
1839 1844 number of seconds away from UTC.
1840 1845
1841 1846 >>> datestr((0, 0))
1842 1847 'Thu Jan 01 00:00:00 1970 +0000'
1843 1848 >>> datestr((42, 0))
1844 1849 'Thu Jan 01 00:00:42 1970 +0000'
1845 1850 >>> datestr((-42, 0))
1846 1851 'Wed Dec 31 23:59:18 1969 +0000'
1847 1852 >>> datestr((0x7fffffff, 0))
1848 1853 'Tue Jan 19 03:14:07 2038 +0000'
1849 1854 >>> datestr((-0x80000000, 0))
1850 1855 'Fri Dec 13 20:45:52 1901 +0000'
1851 1856 """
1852 1857 t, tz = date or makedate()
1853 1858 if "%1" in format or "%2" in format or "%z" in format:
1854 1859 sign = (tz > 0) and "-" or "+"
1855 1860 minutes = abs(tz) // 60
1856 1861 q, r = divmod(minutes, 60)
1857 1862 format = format.replace("%z", "%1%2")
1858 1863 format = format.replace("%1", "%c%02d" % (sign, q))
1859 1864 format = format.replace("%2", "%02d" % r)
1860 1865 d = t - tz
1861 1866 if d > 0x7fffffff:
1862 1867 d = 0x7fffffff
1863 1868 elif d < -0x80000000:
1864 1869 d = -0x80000000
1865 1870 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1866 1871 # because they use the gmtime() system call which is buggy on Windows
1867 1872 # for negative values.
1868 1873 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1869 1874 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1870 1875 return s
1871 1876
1872 1877 def shortdate(date=None):
1873 1878 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1874 1879 return datestr(date, format='%Y-%m-%d')
1875 1880
1876 1881 def parsetimezone(s):
1877 1882 """find a trailing timezone, if any, in string, and return a
1878 1883 (offset, remainder) pair"""
1879 1884
1880 1885 if s.endswith("GMT") or s.endswith("UTC"):
1881 1886 return 0, s[:-3].rstrip()
1882 1887
1883 1888 # Unix-style timezones [+-]hhmm
1884 1889 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1885 1890 sign = (s[-5] == "+") and 1 or -1
1886 1891 hours = int(s[-4:-2])
1887 1892 minutes = int(s[-2:])
1888 1893 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1889 1894
1890 1895 # ISO8601 trailing Z
1891 1896 if s.endswith("Z") and s[-2:-1].isdigit():
1892 1897 return 0, s[:-1]
1893 1898
1894 1899 # ISO8601-style [+-]hh:mm
1895 1900 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1896 1901 s[-5:-3].isdigit() and s[-2:].isdigit()):
1897 1902 sign = (s[-6] == "+") and 1 or -1
1898 1903 hours = int(s[-5:-3])
1899 1904 minutes = int(s[-2:])
1900 1905 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1901 1906
1902 1907 return None, s
1903 1908
1904 1909 def strdate(string, format, defaults=None):
1905 1910 """parse a localized time string and return a (unixtime, offset) tuple.
1906 1911 if the string cannot be parsed, ValueError is raised."""
1907 1912 if defaults is None:
1908 1913 defaults = {}
1909 1914
1910 1915 # NOTE: unixtime = localunixtime + offset
1911 1916 offset, date = parsetimezone(string)
1912 1917
1913 1918 # add missing elements from defaults
1914 1919 usenow = False # default to using biased defaults
1915 1920 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1916 1921 part = pycompat.bytestr(part)
1917 1922 found = [True for p in part if ("%"+p) in format]
1918 1923 if not found:
1919 1924 date += "@" + defaults[part][usenow]
1920 1925 format += "@%" + part[0]
1921 1926 else:
1922 1927 # We've found a specific time element, less specific time
1923 1928 # elements are relative to today
1924 1929 usenow = True
1925 1930
1926 1931 timetuple = time.strptime(encoding.strfromlocal(date),
1927 1932 encoding.strfromlocal(format))
1928 1933 localunixtime = int(calendar.timegm(timetuple))
1929 1934 if offset is None:
1930 1935 # local timezone
1931 1936 unixtime = int(time.mktime(timetuple))
1932 1937 offset = unixtime - localunixtime
1933 1938 else:
1934 1939 unixtime = localunixtime + offset
1935 1940 return unixtime, offset
1936 1941
1937 1942 def parsedate(date, formats=None, bias=None):
1938 1943 """parse a localized date/time and return a (unixtime, offset) tuple.
1939 1944
1940 1945 The date may be a "unixtime offset" string or in one of the specified
1941 1946 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1942 1947
1943 1948 >>> parsedate(' today ') == parsedate(\
1944 1949 datetime.date.today().strftime('%b %d'))
1945 1950 True
1946 1951 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1947 1952 datetime.timedelta(days=1)\
1948 1953 ).strftime('%b %d'))
1949 1954 True
1950 1955 >>> now, tz = makedate()
1951 1956 >>> strnow, strtz = parsedate('now')
1952 1957 >>> (strnow - now) < 1
1953 1958 True
1954 1959 >>> tz == strtz
1955 1960 True
1956 1961 """
1957 1962 if bias is None:
1958 1963 bias = {}
1959 1964 if not date:
1960 1965 return 0, 0
1961 1966 if isinstance(date, tuple) and len(date) == 2:
1962 1967 return date
1963 1968 if not formats:
1964 1969 formats = defaultdateformats
1965 1970 date = date.strip()
1966 1971
1967 1972 if date == 'now' or date == _('now'):
1968 1973 return makedate()
1969 1974 if date == 'today' or date == _('today'):
1970 1975 date = datetime.date.today().strftime('%b %d')
1971 1976 elif date == 'yesterday' or date == _('yesterday'):
1972 1977 date = (datetime.date.today() -
1973 1978 datetime.timedelta(days=1)).strftime('%b %d')
1974 1979
1975 1980 try:
1976 1981 when, offset = map(int, date.split(' '))
1977 1982 except ValueError:
1978 1983 # fill out defaults
1979 1984 now = makedate()
1980 1985 defaults = {}
1981 1986 for part in ("d", "mb", "yY", "HI", "M", "S"):
1982 1987 # this piece is for rounding the specific end of unknowns
1983 1988 b = bias.get(part)
1984 1989 if b is None:
1985 1990 if part[0:1] in "HMS":
1986 1991 b = "00"
1987 1992 else:
1988 1993 b = "0"
1989 1994
1990 1995 # this piece is for matching the generic end to today's date
1991 1996 n = datestr(now, "%" + part[0:1])
1992 1997
1993 1998 defaults[part] = (b, n)
1994 1999
1995 2000 for format in formats:
1996 2001 try:
1997 2002 when, offset = strdate(date, format, defaults)
1998 2003 except (ValueError, OverflowError):
1999 2004 pass
2000 2005 else:
2001 2006 break
2002 2007 else:
2003 2008 raise error.ParseError(_('invalid date: %r') % date)
2004 2009 # validate explicit (probably user-specified) date and
2005 2010 # time zone offset. values must fit in signed 32 bits for
2006 2011 # current 32-bit linux runtimes. timezones go from UTC-12
2007 2012 # to UTC+14
2008 2013 if when < -0x80000000 or when > 0x7fffffff:
2009 2014 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2010 2015 if offset < -50400 or offset > 43200:
2011 2016 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2012 2017 return when, offset
2013 2018
2014 2019 def matchdate(date):
2015 2020 """Return a function that matches a given date match specifier
2016 2021
2017 2022 Formats include:
2018 2023
2019 2024 '{date}' match a given date to the accuracy provided
2020 2025
2021 2026 '<{date}' on or before a given date
2022 2027
2023 2028 '>{date}' on or after a given date
2024 2029
2025 2030 >>> p1 = parsedate("10:29:59")
2026 2031 >>> p2 = parsedate("10:30:00")
2027 2032 >>> p3 = parsedate("10:30:59")
2028 2033 >>> p4 = parsedate("10:31:00")
2029 2034 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2030 2035 >>> f = matchdate("10:30")
2031 2036 >>> f(p1[0])
2032 2037 False
2033 2038 >>> f(p2[0])
2034 2039 True
2035 2040 >>> f(p3[0])
2036 2041 True
2037 2042 >>> f(p4[0])
2038 2043 False
2039 2044 >>> f(p5[0])
2040 2045 False
2041 2046 """
2042 2047
2043 2048 def lower(date):
2044 2049 d = {'mb': "1", 'd': "1"}
2045 2050 return parsedate(date, extendeddateformats, d)[0]
2046 2051
2047 2052 def upper(date):
2048 2053 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2049 2054 for days in ("31", "30", "29"):
2050 2055 try:
2051 2056 d["d"] = days
2052 2057 return parsedate(date, extendeddateformats, d)[0]
2053 2058 except Abort:
2054 2059 pass
2055 2060 d["d"] = "28"
2056 2061 return parsedate(date, extendeddateformats, d)[0]
2057 2062
2058 2063 date = date.strip()
2059 2064
2060 2065 if not date:
2061 2066 raise Abort(_("dates cannot consist entirely of whitespace"))
2062 2067 elif date[0] == "<":
2063 2068 if not date[1:]:
2064 2069 raise Abort(_("invalid day spec, use '<DATE'"))
2065 2070 when = upper(date[1:])
2066 2071 return lambda x: x <= when
2067 2072 elif date[0] == ">":
2068 2073 if not date[1:]:
2069 2074 raise Abort(_("invalid day spec, use '>DATE'"))
2070 2075 when = lower(date[1:])
2071 2076 return lambda x: x >= when
2072 2077 elif date[0] == "-":
2073 2078 try:
2074 2079 days = int(date[1:])
2075 2080 except ValueError:
2076 2081 raise Abort(_("invalid day spec: %s") % date[1:])
2077 2082 if days < 0:
2078 2083 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2079 2084 % date[1:])
2080 2085 when = makedate()[0] - days * 3600 * 24
2081 2086 return lambda x: x >= when
2082 2087 elif " to " in date:
2083 2088 a, b = date.split(" to ")
2084 2089 start, stop = lower(a), upper(b)
2085 2090 return lambda x: x >= start and x <= stop
2086 2091 else:
2087 2092 start, stop = lower(date), upper(date)
2088 2093 return lambda x: x >= start and x <= stop
2089 2094
2090 2095 def stringmatcher(pattern, casesensitive=True):
2091 2096 """
2092 2097 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2093 2098 returns the matcher name, pattern, and matcher function.
2094 2099 missing or unknown prefixes are treated as literal matches.
2095 2100
2096 2101 helper for tests:
2097 2102 >>> def test(pattern, *tests):
2098 2103 ... kind, pattern, matcher = stringmatcher(pattern)
2099 2104 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2100 2105 >>> def itest(pattern, *tests):
2101 2106 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2102 2107 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2103 2108
2104 2109 exact matching (no prefix):
2105 2110 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2106 2111 ('literal', 'abcdefg', [False, False, True])
2107 2112
2108 2113 regex matching ('re:' prefix)
2109 2114 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2110 2115 ('re', 'a.+b', [False, False, True])
2111 2116
2112 2117 force exact matches ('literal:' prefix)
2113 2118 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2114 2119 ('literal', 're:foobar', [False, True])
2115 2120
2116 2121 unknown prefixes are ignored and treated as literals
2117 2122 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2118 2123 ('literal', 'foo:bar', [False, False, True])
2119 2124
2120 2125 case insensitive regex matches
2121 2126 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2122 2127 ('re', 'A.+b', [False, False, True])
2123 2128
2124 2129 case insensitive literal matches
2125 2130 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2126 2131 ('literal', 'ABCDEFG', [False, False, True])
2127 2132 """
2128 2133 if pattern.startswith('re:'):
2129 2134 pattern = pattern[3:]
2130 2135 try:
2131 2136 flags = 0
2132 2137 if not casesensitive:
2133 2138 flags = remod.I
2134 2139 regex = remod.compile(pattern, flags)
2135 2140 except remod.error as e:
2136 2141 raise error.ParseError(_('invalid regular expression: %s')
2137 2142 % e)
2138 2143 return 're', pattern, regex.search
2139 2144 elif pattern.startswith('literal:'):
2140 2145 pattern = pattern[8:]
2141 2146
2142 2147 match = pattern.__eq__
2143 2148
2144 2149 if not casesensitive:
2145 2150 ipat = encoding.lower(pattern)
2146 2151 match = lambda s: ipat == encoding.lower(s)
2147 2152 return 'literal', pattern, match
2148 2153
2149 2154 def shortuser(user):
2150 2155 """Return a short representation of a user name or email address."""
2151 2156 f = user.find('@')
2152 2157 if f >= 0:
2153 2158 user = user[:f]
2154 2159 f = user.find('<')
2155 2160 if f >= 0:
2156 2161 user = user[f + 1:]
2157 2162 f = user.find(' ')
2158 2163 if f >= 0:
2159 2164 user = user[:f]
2160 2165 f = user.find('.')
2161 2166 if f >= 0:
2162 2167 user = user[:f]
2163 2168 return user
2164 2169
2165 2170 def emailuser(user):
2166 2171 """Return the user portion of an email address."""
2167 2172 f = user.find('@')
2168 2173 if f >= 0:
2169 2174 user = user[:f]
2170 2175 f = user.find('<')
2171 2176 if f >= 0:
2172 2177 user = user[f + 1:]
2173 2178 return user
2174 2179
2175 2180 def email(author):
2176 2181 '''get email of author.'''
2177 2182 r = author.find('>')
2178 2183 if r == -1:
2179 2184 r = None
2180 2185 return author[author.find('<') + 1:r]
2181 2186
2182 2187 def ellipsis(text, maxlength=400):
2183 2188 """Trim string to at most maxlength (default: 400) columns in display."""
2184 2189 return encoding.trim(text, maxlength, ellipsis='...')
2185 2190
2186 2191 def unitcountfn(*unittable):
2187 2192 '''return a function that renders a readable count of some quantity'''
2188 2193
2189 2194 def go(count):
2190 2195 for multiplier, divisor, format in unittable:
2191 2196 if abs(count) >= divisor * multiplier:
2192 2197 return format % (count / float(divisor))
2193 2198 return unittable[-1][2] % count
2194 2199
2195 2200 return go
2196 2201
2197 2202 def processlinerange(fromline, toline):
2198 2203 """Check that linerange <fromline>:<toline> makes sense and return a
2199 2204 0-based range.
2200 2205
2201 2206 >>> processlinerange(10, 20)
2202 2207 (9, 20)
2203 2208 >>> processlinerange(2, 1)
2204 2209 Traceback (most recent call last):
2205 2210 ...
2206 2211 ParseError: line range must be positive
2207 2212 >>> processlinerange(0, 5)
2208 2213 Traceback (most recent call last):
2209 2214 ...
2210 2215 ParseError: fromline must be strictly positive
2211 2216 """
2212 2217 if toline - fromline < 0:
2213 2218 raise error.ParseError(_("line range must be positive"))
2214 2219 if fromline < 1:
2215 2220 raise error.ParseError(_("fromline must be strictly positive"))
2216 2221 return fromline - 1, toline
2217 2222
2218 2223 bytecount = unitcountfn(
2219 2224 (100, 1 << 30, _('%.0f GB')),
2220 2225 (10, 1 << 30, _('%.1f GB')),
2221 2226 (1, 1 << 30, _('%.2f GB')),
2222 2227 (100, 1 << 20, _('%.0f MB')),
2223 2228 (10, 1 << 20, _('%.1f MB')),
2224 2229 (1, 1 << 20, _('%.2f MB')),
2225 2230 (100, 1 << 10, _('%.0f KB')),
2226 2231 (10, 1 << 10, _('%.1f KB')),
2227 2232 (1, 1 << 10, _('%.2f KB')),
2228 2233 (1, 1, _('%.0f bytes')),
2229 2234 )
2230 2235
2231 2236 # Matches a single EOL which can either be a CRLF where repeated CR
2232 2237 # are removed or a LF. We do not care about old Macintosh files, so a
2233 2238 # stray CR is an error.
2234 2239 _eolre = remod.compile(br'\r*\n')
2235 2240
2236 2241 def tolf(s):
2237 2242 return _eolre.sub('\n', s)
2238 2243
2239 2244 def tocrlf(s):
2240 2245 return _eolre.sub('\r\n', s)
2241 2246
2242 2247 if pycompat.oslinesep == '\r\n':
2243 2248 tonativeeol = tocrlf
2244 2249 fromnativeeol = tolf
2245 2250 else:
2246 2251 tonativeeol = pycompat.identity
2247 2252 fromnativeeol = pycompat.identity
2248 2253
2249 2254 def escapestr(s):
2250 2255 # call underlying function of s.encode('string_escape') directly for
2251 2256 # Python 3 compatibility
2252 2257 return codecs.escape_encode(s)[0]
2253 2258
2254 2259 def unescapestr(s):
2255 2260 return codecs.escape_decode(s)[0]
2256 2261
2257 2262 def uirepr(s):
2258 2263 # Avoid double backslash in Windows path repr()
2259 2264 return repr(s).replace('\\\\', '\\')
2260 2265
2261 2266 # delay import of textwrap
2262 2267 def MBTextWrapper(**kwargs):
2263 2268 class tw(textwrap.TextWrapper):
2264 2269 """
2265 2270 Extend TextWrapper for width-awareness.
2266 2271
2267 2272 Neither number of 'bytes' in any encoding nor 'characters' is
2268 2273 appropriate to calculate terminal columns for specified string.
2269 2274
2270 2275 Original TextWrapper implementation uses built-in 'len()' directly,
2271 2276 so overriding is needed to use width information of each characters.
2272 2277
2273 2278 In addition, characters classified into 'ambiguous' width are
2274 2279 treated as wide in East Asian area, but as narrow in other.
2275 2280
2276 2281 This requires use decision to determine width of such characters.
2277 2282 """
2278 2283 def _cutdown(self, ucstr, space_left):
2279 2284 l = 0
2280 2285 colwidth = encoding.ucolwidth
2281 2286 for i in xrange(len(ucstr)):
2282 2287 l += colwidth(ucstr[i])
2283 2288 if space_left < l:
2284 2289 return (ucstr[:i], ucstr[i:])
2285 2290 return ucstr, ''
2286 2291
2287 2292 # overriding of base class
2288 2293 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2289 2294 space_left = max(width - cur_len, 1)
2290 2295
2291 2296 if self.break_long_words:
2292 2297 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2293 2298 cur_line.append(cut)
2294 2299 reversed_chunks[-1] = res
2295 2300 elif not cur_line:
2296 2301 cur_line.append(reversed_chunks.pop())
2297 2302
2298 2303 # this overriding code is imported from TextWrapper of Python 2.6
2299 2304 # to calculate columns of string by 'encoding.ucolwidth()'
2300 2305 def _wrap_chunks(self, chunks):
2301 2306 colwidth = encoding.ucolwidth
2302 2307
2303 2308 lines = []
2304 2309 if self.width <= 0:
2305 2310 raise ValueError("invalid width %r (must be > 0)" % self.width)
2306 2311
2307 2312 # Arrange in reverse order so items can be efficiently popped
2308 2313 # from a stack of chucks.
2309 2314 chunks.reverse()
2310 2315
2311 2316 while chunks:
2312 2317
2313 2318 # Start the list of chunks that will make up the current line.
2314 2319 # cur_len is just the length of all the chunks in cur_line.
2315 2320 cur_line = []
2316 2321 cur_len = 0
2317 2322
2318 2323 # Figure out which static string will prefix this line.
2319 2324 if lines:
2320 2325 indent = self.subsequent_indent
2321 2326 else:
2322 2327 indent = self.initial_indent
2323 2328
2324 2329 # Maximum width for this line.
2325 2330 width = self.width - len(indent)
2326 2331
2327 2332 # First chunk on line is whitespace -- drop it, unless this
2328 2333 # is the very beginning of the text (i.e. no lines started yet).
2329 2334 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2330 2335 del chunks[-1]
2331 2336
2332 2337 while chunks:
2333 2338 l = colwidth(chunks[-1])
2334 2339
2335 2340 # Can at least squeeze this chunk onto the current line.
2336 2341 if cur_len + l <= width:
2337 2342 cur_line.append(chunks.pop())
2338 2343 cur_len += l
2339 2344
2340 2345 # Nope, this line is full.
2341 2346 else:
2342 2347 break
2343 2348
2344 2349 # The current line is full, and the next chunk is too big to
2345 2350 # fit on *any* line (not just this one).
2346 2351 if chunks and colwidth(chunks[-1]) > width:
2347 2352 self._handle_long_word(chunks, cur_line, cur_len, width)
2348 2353
2349 2354 # If the last chunk on this line is all whitespace, drop it.
2350 2355 if (self.drop_whitespace and
2351 2356 cur_line and cur_line[-1].strip() == r''):
2352 2357 del cur_line[-1]
2353 2358
2354 2359 # Convert current line back to a string and store it in list
2355 2360 # of all lines (return value).
2356 2361 if cur_line:
2357 2362 lines.append(indent + r''.join(cur_line))
2358 2363
2359 2364 return lines
2360 2365
2361 2366 global MBTextWrapper
2362 2367 MBTextWrapper = tw
2363 2368 return tw(**kwargs)
2364 2369
2365 2370 def wrap(line, width, initindent='', hangindent=''):
2366 2371 maxindent = max(len(hangindent), len(initindent))
2367 2372 if width <= maxindent:
2368 2373 # adjust for weird terminal size
2369 2374 width = max(78, maxindent + 1)
2370 2375 line = line.decode(pycompat.sysstr(encoding.encoding),
2371 2376 pycompat.sysstr(encoding.encodingmode))
2372 2377 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2373 2378 pycompat.sysstr(encoding.encodingmode))
2374 2379 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2375 2380 pycompat.sysstr(encoding.encodingmode))
2376 2381 wrapper = MBTextWrapper(width=width,
2377 2382 initial_indent=initindent,
2378 2383 subsequent_indent=hangindent)
2379 2384 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2380 2385
2381 2386 if (pyplatform.python_implementation() == 'CPython' and
2382 2387 sys.version_info < (3, 0)):
2383 2388 # There is an issue in CPython that some IO methods do not handle EINTR
2384 2389 # correctly. The following table shows what CPython version (and functions)
2385 2390 # are affected (buggy: has the EINTR bug, okay: otherwise):
2386 2391 #
2387 2392 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2388 2393 # --------------------------------------------------
2389 2394 # fp.__iter__ | buggy | buggy | okay
2390 2395 # fp.read* | buggy | okay [1] | okay
2391 2396 #
2392 2397 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2393 2398 #
2394 2399 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2395 2400 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2396 2401 #
2397 2402 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2398 2403 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2399 2404 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2400 2405 # fp.__iter__ but not other fp.read* methods.
2401 2406 #
2402 2407 # On modern systems like Linux, the "read" syscall cannot be interrupted
2403 2408 # when reading "fast" files like on-disk files. So the EINTR issue only
2404 2409 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2405 2410 # files approximately as "fast" files and use the fast (unsafe) code path,
2406 2411 # to minimize the performance impact.
2407 2412 if sys.version_info >= (2, 7, 4):
2408 2413 # fp.readline deals with EINTR correctly, use it as a workaround.
2409 2414 def _safeiterfile(fp):
2410 2415 return iter(fp.readline, '')
2411 2416 else:
2412 2417 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2413 2418 # note: this may block longer than necessary because of bufsize.
2414 2419 def _safeiterfile(fp, bufsize=4096):
2415 2420 fd = fp.fileno()
2416 2421 line = ''
2417 2422 while True:
2418 2423 try:
2419 2424 buf = os.read(fd, bufsize)
2420 2425 except OSError as ex:
2421 2426 # os.read only raises EINTR before any data is read
2422 2427 if ex.errno == errno.EINTR:
2423 2428 continue
2424 2429 else:
2425 2430 raise
2426 2431 line += buf
2427 2432 if '\n' in buf:
2428 2433 splitted = line.splitlines(True)
2429 2434 line = ''
2430 2435 for l in splitted:
2431 2436 if l[-1] == '\n':
2432 2437 yield l
2433 2438 else:
2434 2439 line = l
2435 2440 if not buf:
2436 2441 break
2437 2442 if line:
2438 2443 yield line
2439 2444
2440 2445 def iterfile(fp):
2441 2446 fastpath = True
2442 2447 if type(fp) is file:
2443 2448 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2444 2449 if fastpath:
2445 2450 return fp
2446 2451 else:
2447 2452 return _safeiterfile(fp)
2448 2453 else:
2449 2454 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2450 2455 def iterfile(fp):
2451 2456 return fp
2452 2457
2453 2458 def iterlines(iterator):
2454 2459 for chunk in iterator:
2455 2460 for line in chunk.splitlines():
2456 2461 yield line
2457 2462
2458 2463 def expandpath(path):
2459 2464 return os.path.expanduser(os.path.expandvars(path))
2460 2465
2461 2466 def hgcmd():
2462 2467 """Return the command used to execute current hg
2463 2468
2464 2469 This is different from hgexecutable() because on Windows we want
2465 2470 to avoid things opening new shell windows like batch files, so we
2466 2471 get either the python call or current executable.
2467 2472 """
2468 2473 if mainfrozen():
2469 2474 if getattr(sys, 'frozen', None) == 'macosx_app':
2470 2475 # Env variable set by py2app
2471 2476 return [encoding.environ['EXECUTABLEPATH']]
2472 2477 else:
2473 2478 return [pycompat.sysexecutable]
2474 2479 return gethgcmd()
2475 2480
2476 2481 def rundetached(args, condfn):
2477 2482 """Execute the argument list in a detached process.
2478 2483
2479 2484 condfn is a callable which is called repeatedly and should return
2480 2485 True once the child process is known to have started successfully.
2481 2486 At this point, the child process PID is returned. If the child
2482 2487 process fails to start or finishes before condfn() evaluates to
2483 2488 True, return -1.
2484 2489 """
2485 2490 # Windows case is easier because the child process is either
2486 2491 # successfully starting and validating the condition or exiting
2487 2492 # on failure. We just poll on its PID. On Unix, if the child
2488 2493 # process fails to start, it will be left in a zombie state until
2489 2494 # the parent wait on it, which we cannot do since we expect a long
2490 2495 # running process on success. Instead we listen for SIGCHLD telling
2491 2496 # us our child process terminated.
2492 2497 terminated = set()
2493 2498 def handler(signum, frame):
2494 2499 terminated.add(os.wait())
2495 2500 prevhandler = None
2496 2501 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2497 2502 if SIGCHLD is not None:
2498 2503 prevhandler = signal.signal(SIGCHLD, handler)
2499 2504 try:
2500 2505 pid = spawndetached(args)
2501 2506 while not condfn():
2502 2507 if ((pid in terminated or not testpid(pid))
2503 2508 and not condfn()):
2504 2509 return -1
2505 2510 time.sleep(0.1)
2506 2511 return pid
2507 2512 finally:
2508 2513 if prevhandler is not None:
2509 2514 signal.signal(signal.SIGCHLD, prevhandler)
2510 2515
2511 2516 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2512 2517 """Return the result of interpolating items in the mapping into string s.
2513 2518
2514 2519 prefix is a single character string, or a two character string with
2515 2520 a backslash as the first character if the prefix needs to be escaped in
2516 2521 a regular expression.
2517 2522
2518 2523 fn is an optional function that will be applied to the replacement text
2519 2524 just before replacement.
2520 2525
2521 2526 escape_prefix is an optional flag that allows using doubled prefix for
2522 2527 its escaping.
2523 2528 """
2524 2529 fn = fn or (lambda s: s)
2525 2530 patterns = '|'.join(mapping.keys())
2526 2531 if escape_prefix:
2527 2532 patterns += '|' + prefix
2528 2533 if len(prefix) > 1:
2529 2534 prefix_char = prefix[1:]
2530 2535 else:
2531 2536 prefix_char = prefix
2532 2537 mapping[prefix_char] = prefix_char
2533 2538 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2534 2539 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2535 2540
2536 2541 def getport(port):
2537 2542 """Return the port for a given network service.
2538 2543
2539 2544 If port is an integer, it's returned as is. If it's a string, it's
2540 2545 looked up using socket.getservbyname(). If there's no matching
2541 2546 service, error.Abort is raised.
2542 2547 """
2543 2548 try:
2544 2549 return int(port)
2545 2550 except ValueError:
2546 2551 pass
2547 2552
2548 2553 try:
2549 2554 return socket.getservbyname(port)
2550 2555 except socket.error:
2551 2556 raise Abort(_("no port number associated with service '%s'") % port)
2552 2557
2553 2558 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2554 2559 '0': False, 'no': False, 'false': False, 'off': False,
2555 2560 'never': False}
2556 2561
2557 2562 def parsebool(s):
2558 2563 """Parse s into a boolean.
2559 2564
2560 2565 If s is not a valid boolean, returns None.
2561 2566 """
2562 2567 return _booleans.get(s.lower(), None)
2563 2568
2564 2569 _hextochr = dict((a + b, chr(int(a + b, 16)))
2565 2570 for a in string.hexdigits for b in string.hexdigits)
2566 2571
2567 2572 class url(object):
2568 2573 r"""Reliable URL parser.
2569 2574
2570 2575 This parses URLs and provides attributes for the following
2571 2576 components:
2572 2577
2573 2578 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2574 2579
2575 2580 Missing components are set to None. The only exception is
2576 2581 fragment, which is set to '' if present but empty.
2577 2582
2578 2583 If parsefragment is False, fragment is included in query. If
2579 2584 parsequery is False, query is included in path. If both are
2580 2585 False, both fragment and query are included in path.
2581 2586
2582 2587 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2583 2588
2584 2589 Note that for backward compatibility reasons, bundle URLs do not
2585 2590 take host names. That means 'bundle://../' has a path of '../'.
2586 2591
2587 2592 Examples:
2588 2593
2589 2594 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2590 2595 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2591 2596 >>> url('ssh://[::1]:2200//home/joe/repo')
2592 2597 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2593 2598 >>> url('file:///home/joe/repo')
2594 2599 <url scheme: 'file', path: '/home/joe/repo'>
2595 2600 >>> url('file:///c:/temp/foo/')
2596 2601 <url scheme: 'file', path: 'c:/temp/foo/'>
2597 2602 >>> url('bundle:foo')
2598 2603 <url scheme: 'bundle', path: 'foo'>
2599 2604 >>> url('bundle://../foo')
2600 2605 <url scheme: 'bundle', path: '../foo'>
2601 2606 >>> url(r'c:\foo\bar')
2602 2607 <url path: 'c:\\foo\\bar'>
2603 2608 >>> url(r'\\blah\blah\blah')
2604 2609 <url path: '\\\\blah\\blah\\blah'>
2605 2610 >>> url(r'\\blah\blah\blah#baz')
2606 2611 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2607 2612 >>> url(r'file:///C:\users\me')
2608 2613 <url scheme: 'file', path: 'C:\\users\\me'>
2609 2614
2610 2615 Authentication credentials:
2611 2616
2612 2617 >>> url('ssh://joe:xyz@x/repo')
2613 2618 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2614 2619 >>> url('ssh://joe@x/repo')
2615 2620 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2616 2621
2617 2622 Query strings and fragments:
2618 2623
2619 2624 >>> url('http://host/a?b#c')
2620 2625 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2621 2626 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2622 2627 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2623 2628
2624 2629 Empty path:
2625 2630
2626 2631 >>> url('')
2627 2632 <url path: ''>
2628 2633 >>> url('#a')
2629 2634 <url path: '', fragment: 'a'>
2630 2635 >>> url('http://host/')
2631 2636 <url scheme: 'http', host: 'host', path: ''>
2632 2637 >>> url('http://host/#a')
2633 2638 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2634 2639
2635 2640 Only scheme:
2636 2641
2637 2642 >>> url('http:')
2638 2643 <url scheme: 'http'>
2639 2644 """
2640 2645
2641 2646 _safechars = "!~*'()+"
2642 2647 _safepchars = "/!~*'()+:\\"
2643 2648 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2644 2649
2645 2650 def __init__(self, path, parsequery=True, parsefragment=True):
2646 2651 # We slowly chomp away at path until we have only the path left
2647 2652 self.scheme = self.user = self.passwd = self.host = None
2648 2653 self.port = self.path = self.query = self.fragment = None
2649 2654 self._localpath = True
2650 2655 self._hostport = ''
2651 2656 self._origpath = path
2652 2657
2653 2658 if parsefragment and '#' in path:
2654 2659 path, self.fragment = path.split('#', 1)
2655 2660
2656 2661 # special case for Windows drive letters and UNC paths
2657 2662 if hasdriveletter(path) or path.startswith('\\\\'):
2658 2663 self.path = path
2659 2664 return
2660 2665
2661 2666 # For compatibility reasons, we can't handle bundle paths as
2662 2667 # normal URLS
2663 2668 if path.startswith('bundle:'):
2664 2669 self.scheme = 'bundle'
2665 2670 path = path[7:]
2666 2671 if path.startswith('//'):
2667 2672 path = path[2:]
2668 2673 self.path = path
2669 2674 return
2670 2675
2671 2676 if self._matchscheme(path):
2672 2677 parts = path.split(':', 1)
2673 2678 if parts[0]:
2674 2679 self.scheme, path = parts
2675 2680 self._localpath = False
2676 2681
2677 2682 if not path:
2678 2683 path = None
2679 2684 if self._localpath:
2680 2685 self.path = ''
2681 2686 return
2682 2687 else:
2683 2688 if self._localpath:
2684 2689 self.path = path
2685 2690 return
2686 2691
2687 2692 if parsequery and '?' in path:
2688 2693 path, self.query = path.split('?', 1)
2689 2694 if not path:
2690 2695 path = None
2691 2696 if not self.query:
2692 2697 self.query = None
2693 2698
2694 2699 # // is required to specify a host/authority
2695 2700 if path and path.startswith('//'):
2696 2701 parts = path[2:].split('/', 1)
2697 2702 if len(parts) > 1:
2698 2703 self.host, path = parts
2699 2704 else:
2700 2705 self.host = parts[0]
2701 2706 path = None
2702 2707 if not self.host:
2703 2708 self.host = None
2704 2709 # path of file:///d is /d
2705 2710 # path of file:///d:/ is d:/, not /d:/
2706 2711 if path and not hasdriveletter(path):
2707 2712 path = '/' + path
2708 2713
2709 2714 if self.host and '@' in self.host:
2710 2715 self.user, self.host = self.host.rsplit('@', 1)
2711 2716 if ':' in self.user:
2712 2717 self.user, self.passwd = self.user.split(':', 1)
2713 2718 if not self.host:
2714 2719 self.host = None
2715 2720
2716 2721 # Don't split on colons in IPv6 addresses without ports
2717 2722 if (self.host and ':' in self.host and
2718 2723 not (self.host.startswith('[') and self.host.endswith(']'))):
2719 2724 self._hostport = self.host
2720 2725 self.host, self.port = self.host.rsplit(':', 1)
2721 2726 if not self.host:
2722 2727 self.host = None
2723 2728
2724 2729 if (self.host and self.scheme == 'file' and
2725 2730 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2726 2731 raise Abort(_('file:// URLs can only refer to localhost'))
2727 2732
2728 2733 self.path = path
2729 2734
2730 2735 # leave the query string escaped
2731 2736 for a in ('user', 'passwd', 'host', 'port',
2732 2737 'path', 'fragment'):
2733 2738 v = getattr(self, a)
2734 2739 if v is not None:
2735 2740 setattr(self, a, urlreq.unquote(v))
2736 2741
2737 2742 def __repr__(self):
2738 2743 attrs = []
2739 2744 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2740 2745 'query', 'fragment'):
2741 2746 v = getattr(self, a)
2742 2747 if v is not None:
2743 2748 attrs.append('%s: %r' % (a, v))
2744 2749 return '<url %s>' % ', '.join(attrs)
2745 2750
2746 2751 def __str__(self):
2747 2752 r"""Join the URL's components back into a URL string.
2748 2753
2749 2754 Examples:
2750 2755
2751 2756 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2752 2757 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2753 2758 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2754 2759 'http://user:pw@host:80/?foo=bar&baz=42'
2755 2760 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2756 2761 'http://user:pw@host:80/?foo=bar%3dbaz'
2757 2762 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2758 2763 'ssh://user:pw@[::1]:2200//home/joe#'
2759 2764 >>> str(url('http://localhost:80//'))
2760 2765 'http://localhost:80//'
2761 2766 >>> str(url('http://localhost:80/'))
2762 2767 'http://localhost:80/'
2763 2768 >>> str(url('http://localhost:80'))
2764 2769 'http://localhost:80/'
2765 2770 >>> str(url('bundle:foo'))
2766 2771 'bundle:foo'
2767 2772 >>> str(url('bundle://../foo'))
2768 2773 'bundle:../foo'
2769 2774 >>> str(url('path'))
2770 2775 'path'
2771 2776 >>> str(url('file:///tmp/foo/bar'))
2772 2777 'file:///tmp/foo/bar'
2773 2778 >>> str(url('file:///c:/tmp/foo/bar'))
2774 2779 'file:///c:/tmp/foo/bar'
2775 2780 >>> print url(r'bundle:foo\bar')
2776 2781 bundle:foo\bar
2777 2782 >>> print url(r'file:///D:\data\hg')
2778 2783 file:///D:\data\hg
2779 2784 """
2780 2785 return encoding.strfromlocal(self.__bytes__())
2781 2786
2782 2787 def __bytes__(self):
2783 2788 if self._localpath:
2784 2789 s = self.path
2785 2790 if self.scheme == 'bundle':
2786 2791 s = 'bundle:' + s
2787 2792 if self.fragment:
2788 2793 s += '#' + self.fragment
2789 2794 return s
2790 2795
2791 2796 s = self.scheme + ':'
2792 2797 if self.user or self.passwd or self.host:
2793 2798 s += '//'
2794 2799 elif self.scheme and (not self.path or self.path.startswith('/')
2795 2800 or hasdriveletter(self.path)):
2796 2801 s += '//'
2797 2802 if hasdriveletter(self.path):
2798 2803 s += '/'
2799 2804 if self.user:
2800 2805 s += urlreq.quote(self.user, safe=self._safechars)
2801 2806 if self.passwd:
2802 2807 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2803 2808 if self.user or self.passwd:
2804 2809 s += '@'
2805 2810 if self.host:
2806 2811 if not (self.host.startswith('[') and self.host.endswith(']')):
2807 2812 s += urlreq.quote(self.host)
2808 2813 else:
2809 2814 s += self.host
2810 2815 if self.port:
2811 2816 s += ':' + urlreq.quote(self.port)
2812 2817 if self.host:
2813 2818 s += '/'
2814 2819 if self.path:
2815 2820 # TODO: similar to the query string, we should not unescape the
2816 2821 # path when we store it, the path might contain '%2f' = '/',
2817 2822 # which we should *not* escape.
2818 2823 s += urlreq.quote(self.path, safe=self._safepchars)
2819 2824 if self.query:
2820 2825 # we store the query in escaped form.
2821 2826 s += '?' + self.query
2822 2827 if self.fragment is not None:
2823 2828 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2824 2829 return s
2825 2830
2826 2831 def authinfo(self):
2827 2832 user, passwd = self.user, self.passwd
2828 2833 try:
2829 2834 self.user, self.passwd = None, None
2830 2835 s = bytes(self)
2831 2836 finally:
2832 2837 self.user, self.passwd = user, passwd
2833 2838 if not self.user:
2834 2839 return (s, None)
2835 2840 # authinfo[1] is passed to urllib2 password manager, and its
2836 2841 # URIs must not contain credentials. The host is passed in the
2837 2842 # URIs list because Python < 2.4.3 uses only that to search for
2838 2843 # a password.
2839 2844 return (s, (None, (s, self.host),
2840 2845 self.user, self.passwd or ''))
2841 2846
2842 2847 def isabs(self):
2843 2848 if self.scheme and self.scheme != 'file':
2844 2849 return True # remote URL
2845 2850 if hasdriveletter(self.path):
2846 2851 return True # absolute for our purposes - can't be joined()
2847 2852 if self.path.startswith(r'\\'):
2848 2853 return True # Windows UNC path
2849 2854 if self.path.startswith('/'):
2850 2855 return True # POSIX-style
2851 2856 return False
2852 2857
2853 2858 def localpath(self):
2854 2859 if self.scheme == 'file' or self.scheme == 'bundle':
2855 2860 path = self.path or '/'
2856 2861 # For Windows, we need to promote hosts containing drive
2857 2862 # letters to paths with drive letters.
2858 2863 if hasdriveletter(self._hostport):
2859 2864 path = self._hostport + '/' + self.path
2860 2865 elif (self.host is not None and self.path
2861 2866 and not hasdriveletter(path)):
2862 2867 path = '/' + path
2863 2868 return path
2864 2869 return self._origpath
2865 2870
2866 2871 def islocal(self):
2867 2872 '''whether localpath will return something that posixfile can open'''
2868 2873 return (not self.scheme or self.scheme == 'file'
2869 2874 or self.scheme == 'bundle')
2870 2875
2871 2876 def hasscheme(path):
2872 2877 return bool(url(path).scheme)
2873 2878
2874 2879 def hasdriveletter(path):
2875 2880 return path and path[1:2] == ':' and path[0:1].isalpha()
2876 2881
2877 2882 def urllocalpath(path):
2878 2883 return url(path, parsequery=False, parsefragment=False).localpath()
2879 2884
2880 2885 def hidepassword(u):
2881 2886 '''hide user credential in a url string'''
2882 2887 u = url(u)
2883 2888 if u.passwd:
2884 2889 u.passwd = '***'
2885 2890 return bytes(u)
2886 2891
2887 2892 def removeauth(u):
2888 2893 '''remove all authentication information from a url string'''
2889 2894 u = url(u)
2890 2895 u.user = u.passwd = None
2891 2896 return str(u)
2892 2897
2893 2898 timecount = unitcountfn(
2894 2899 (1, 1e3, _('%.0f s')),
2895 2900 (100, 1, _('%.1f s')),
2896 2901 (10, 1, _('%.2f s')),
2897 2902 (1, 1, _('%.3f s')),
2898 2903 (100, 0.001, _('%.1f ms')),
2899 2904 (10, 0.001, _('%.2f ms')),
2900 2905 (1, 0.001, _('%.3f ms')),
2901 2906 (100, 0.000001, _('%.1f us')),
2902 2907 (10, 0.000001, _('%.2f us')),
2903 2908 (1, 0.000001, _('%.3f us')),
2904 2909 (100, 0.000000001, _('%.1f ns')),
2905 2910 (10, 0.000000001, _('%.2f ns')),
2906 2911 (1, 0.000000001, _('%.3f ns')),
2907 2912 )
2908 2913
2909 2914 _timenesting = [0]
2910 2915
2911 2916 def timed(func):
2912 2917 '''Report the execution time of a function call to stderr.
2913 2918
2914 2919 During development, use as a decorator when you need to measure
2915 2920 the cost of a function, e.g. as follows:
2916 2921
2917 2922 @util.timed
2918 2923 def foo(a, b, c):
2919 2924 pass
2920 2925 '''
2921 2926
2922 2927 def wrapper(*args, **kwargs):
2923 2928 start = timer()
2924 2929 indent = 2
2925 2930 _timenesting[0] += indent
2926 2931 try:
2927 2932 return func(*args, **kwargs)
2928 2933 finally:
2929 2934 elapsed = timer() - start
2930 2935 _timenesting[0] -= indent
2931 2936 stderr.write('%s%s: %s\n' %
2932 2937 (' ' * _timenesting[0], func.__name__,
2933 2938 timecount(elapsed)))
2934 2939 return wrapper
2935 2940
2936 2941 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2937 2942 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2938 2943
2939 2944 def sizetoint(s):
2940 2945 '''Convert a space specifier to a byte count.
2941 2946
2942 2947 >>> sizetoint('30')
2943 2948 30
2944 2949 >>> sizetoint('2.2kb')
2945 2950 2252
2946 2951 >>> sizetoint('6M')
2947 2952 6291456
2948 2953 '''
2949 2954 t = s.strip().lower()
2950 2955 try:
2951 2956 for k, u in _sizeunits:
2952 2957 if t.endswith(k):
2953 2958 return int(float(t[:-len(k)]) * u)
2954 2959 return int(t)
2955 2960 except ValueError:
2956 2961 raise error.ParseError(_("couldn't parse size: %s") % s)
2957 2962
2958 2963 class hooks(object):
2959 2964 '''A collection of hook functions that can be used to extend a
2960 2965 function's behavior. Hooks are called in lexicographic order,
2961 2966 based on the names of their sources.'''
2962 2967
2963 2968 def __init__(self):
2964 2969 self._hooks = []
2965 2970
2966 2971 def add(self, source, hook):
2967 2972 self._hooks.append((source, hook))
2968 2973
2969 2974 def __call__(self, *args):
2970 2975 self._hooks.sort(key=lambda x: x[0])
2971 2976 results = []
2972 2977 for source, hook in self._hooks:
2973 2978 results.append(hook(*args))
2974 2979 return results
2975 2980
2976 2981 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
2977 2982 '''Yields lines for a nicely formatted stacktrace.
2978 2983 Skips the 'skip' last entries, then return the last 'depth' entries.
2979 2984 Each file+linenumber is formatted according to fileline.
2980 2985 Each line is formatted according to line.
2981 2986 If line is None, it yields:
2982 2987 length of longest filepath+line number,
2983 2988 filepath+linenumber,
2984 2989 function
2985 2990
2986 2991 Not be used in production code but very convenient while developing.
2987 2992 '''
2988 2993 entries = [(fileline % (fn, ln), func)
2989 2994 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
2990 2995 ][-depth:]
2991 2996 if entries:
2992 2997 fnmax = max(len(entry[0]) for entry in entries)
2993 2998 for fnln, func in entries:
2994 2999 if line is None:
2995 3000 yield (fnmax, fnln, func)
2996 3001 else:
2997 3002 yield line % (fnmax, fnln, func)
2998 3003
2999 3004 def debugstacktrace(msg='stacktrace', skip=0,
3000 3005 f=stderr, otherf=stdout, depth=0):
3001 3006 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3002 3007 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3003 3008 By default it will flush stdout first.
3004 3009 It can be used everywhere and intentionally does not require an ui object.
3005 3010 Not be used in production code but very convenient while developing.
3006 3011 '''
3007 3012 if otherf:
3008 3013 otherf.flush()
3009 3014 f.write('%s at:\n' % msg.rstrip())
3010 3015 for line in getstackframes(skip + 1, depth=depth):
3011 3016 f.write(line)
3012 3017 f.flush()
3013 3018
3014 3019 class dirs(object):
3015 3020 '''a multiset of directory names from a dirstate or manifest'''
3016 3021
3017 3022 def __init__(self, map, skip=None):
3018 3023 self._dirs = {}
3019 3024 addpath = self.addpath
3020 3025 if safehasattr(map, 'iteritems') and skip is not None:
3021 3026 for f, s in map.iteritems():
3022 3027 if s[0] != skip:
3023 3028 addpath(f)
3024 3029 else:
3025 3030 for f in map:
3026 3031 addpath(f)
3027 3032
3028 3033 def addpath(self, path):
3029 3034 dirs = self._dirs
3030 3035 for base in finddirs(path):
3031 3036 if base in dirs:
3032 3037 dirs[base] += 1
3033 3038 return
3034 3039 dirs[base] = 1
3035 3040
3036 3041 def delpath(self, path):
3037 3042 dirs = self._dirs
3038 3043 for base in finddirs(path):
3039 3044 if dirs[base] > 1:
3040 3045 dirs[base] -= 1
3041 3046 return
3042 3047 del dirs[base]
3043 3048
3044 3049 def __iter__(self):
3045 3050 return iter(self._dirs)
3046 3051
3047 3052 def __contains__(self, d):
3048 3053 return d in self._dirs
3049 3054
3050 3055 if safehasattr(parsers, 'dirs'):
3051 3056 dirs = parsers.dirs
3052 3057
3053 3058 def finddirs(path):
3054 3059 pos = path.rfind('/')
3055 3060 while pos != -1:
3056 3061 yield path[:pos]
3057 3062 pos = path.rfind('/', 0, pos)
3058 3063
3059 3064 class ctxmanager(object):
3060 3065 '''A context manager for use in 'with' blocks to allow multiple
3061 3066 contexts to be entered at once. This is both safer and more
3062 3067 flexible than contextlib.nested.
3063 3068
3064 3069 Once Mercurial supports Python 2.7+, this will become mostly
3065 3070 unnecessary.
3066 3071 '''
3067 3072
3068 3073 def __init__(self, *args):
3069 3074 '''Accepts a list of no-argument functions that return context
3070 3075 managers. These will be invoked at __call__ time.'''
3071 3076 self._pending = args
3072 3077 self._atexit = []
3073 3078
3074 3079 def __enter__(self):
3075 3080 return self
3076 3081
3077 3082 def enter(self):
3078 3083 '''Create and enter context managers in the order in which they were
3079 3084 passed to the constructor.'''
3080 3085 values = []
3081 3086 for func in self._pending:
3082 3087 obj = func()
3083 3088 values.append(obj.__enter__())
3084 3089 self._atexit.append(obj.__exit__)
3085 3090 del self._pending
3086 3091 return values
3087 3092
3088 3093 def atexit(self, func, *args, **kwargs):
3089 3094 '''Add a function to call when this context manager exits. The
3090 3095 ordering of multiple atexit calls is unspecified, save that
3091 3096 they will happen before any __exit__ functions.'''
3092 3097 def wrapper(exc_type, exc_val, exc_tb):
3093 3098 func(*args, **kwargs)
3094 3099 self._atexit.append(wrapper)
3095 3100 return func
3096 3101
3097 3102 def __exit__(self, exc_type, exc_val, exc_tb):
3098 3103 '''Context managers are exited in the reverse order from which
3099 3104 they were created.'''
3100 3105 received = exc_type is not None
3101 3106 suppressed = False
3102 3107 pending = None
3103 3108 self._atexit.reverse()
3104 3109 for exitfunc in self._atexit:
3105 3110 try:
3106 3111 if exitfunc(exc_type, exc_val, exc_tb):
3107 3112 suppressed = True
3108 3113 exc_type = None
3109 3114 exc_val = None
3110 3115 exc_tb = None
3111 3116 except BaseException:
3112 3117 pending = sys.exc_info()
3113 3118 exc_type, exc_val, exc_tb = pending = sys.exc_info()
3114 3119 del self._atexit
3115 3120 if pending:
3116 3121 raise exc_val
3117 3122 return received and suppressed
3118 3123
3119 3124 # compression code
3120 3125
3121 3126 SERVERROLE = 'server'
3122 3127 CLIENTROLE = 'client'
3123 3128
3124 3129 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3125 3130 (u'name', u'serverpriority',
3126 3131 u'clientpriority'))
3127 3132
3128 3133 class compressormanager(object):
3129 3134 """Holds registrations of various compression engines.
3130 3135
3131 3136 This class essentially abstracts the differences between compression
3132 3137 engines to allow new compression formats to be added easily, possibly from
3133 3138 extensions.
3134 3139
3135 3140 Compressors are registered against the global instance by calling its
3136 3141 ``register()`` method.
3137 3142 """
3138 3143 def __init__(self):
3139 3144 self._engines = {}
3140 3145 # Bundle spec human name to engine name.
3141 3146 self._bundlenames = {}
3142 3147 # Internal bundle identifier to engine name.
3143 3148 self._bundletypes = {}
3144 3149 # Revlog header to engine name.
3145 3150 self._revlogheaders = {}
3146 3151 # Wire proto identifier to engine name.
3147 3152 self._wiretypes = {}
3148 3153
3149 3154 def __getitem__(self, key):
3150 3155 return self._engines[key]
3151 3156
3152 3157 def __contains__(self, key):
3153 3158 return key in self._engines
3154 3159
3155 3160 def __iter__(self):
3156 3161 return iter(self._engines.keys())
3157 3162
3158 3163 def register(self, engine):
3159 3164 """Register a compression engine with the manager.
3160 3165
3161 3166 The argument must be a ``compressionengine`` instance.
3162 3167 """
3163 3168 if not isinstance(engine, compressionengine):
3164 3169 raise ValueError(_('argument must be a compressionengine'))
3165 3170
3166 3171 name = engine.name()
3167 3172
3168 3173 if name in self._engines:
3169 3174 raise error.Abort(_('compression engine %s already registered') %
3170 3175 name)
3171 3176
3172 3177 bundleinfo = engine.bundletype()
3173 3178 if bundleinfo:
3174 3179 bundlename, bundletype = bundleinfo
3175 3180
3176 3181 if bundlename in self._bundlenames:
3177 3182 raise error.Abort(_('bundle name %s already registered') %
3178 3183 bundlename)
3179 3184 if bundletype in self._bundletypes:
3180 3185 raise error.Abort(_('bundle type %s already registered by %s') %
3181 3186 (bundletype, self._bundletypes[bundletype]))
3182 3187
3183 3188 # No external facing name declared.
3184 3189 if bundlename:
3185 3190 self._bundlenames[bundlename] = name
3186 3191
3187 3192 self._bundletypes[bundletype] = name
3188 3193
3189 3194 wiresupport = engine.wireprotosupport()
3190 3195 if wiresupport:
3191 3196 wiretype = wiresupport.name
3192 3197 if wiretype in self._wiretypes:
3193 3198 raise error.Abort(_('wire protocol compression %s already '
3194 3199 'registered by %s') %
3195 3200 (wiretype, self._wiretypes[wiretype]))
3196 3201
3197 3202 self._wiretypes[wiretype] = name
3198 3203
3199 3204 revlogheader = engine.revlogheader()
3200 3205 if revlogheader and revlogheader in self._revlogheaders:
3201 3206 raise error.Abort(_('revlog header %s already registered by %s') %
3202 3207 (revlogheader, self._revlogheaders[revlogheader]))
3203 3208
3204 3209 if revlogheader:
3205 3210 self._revlogheaders[revlogheader] = name
3206 3211
3207 3212 self._engines[name] = engine
3208 3213
3209 3214 @property
3210 3215 def supportedbundlenames(self):
3211 3216 return set(self._bundlenames.keys())
3212 3217
3213 3218 @property
3214 3219 def supportedbundletypes(self):
3215 3220 return set(self._bundletypes.keys())
3216 3221
3217 3222 def forbundlename(self, bundlename):
3218 3223 """Obtain a compression engine registered to a bundle name.
3219 3224
3220 3225 Will raise KeyError if the bundle type isn't registered.
3221 3226
3222 3227 Will abort if the engine is known but not available.
3223 3228 """
3224 3229 engine = self._engines[self._bundlenames[bundlename]]
3225 3230 if not engine.available():
3226 3231 raise error.Abort(_('compression engine %s could not be loaded') %
3227 3232 engine.name())
3228 3233 return engine
3229 3234
3230 3235 def forbundletype(self, bundletype):
3231 3236 """Obtain a compression engine registered to a bundle type.
3232 3237
3233 3238 Will raise KeyError if the bundle type isn't registered.
3234 3239
3235 3240 Will abort if the engine is known but not available.
3236 3241 """
3237 3242 engine = self._engines[self._bundletypes[bundletype]]
3238 3243 if not engine.available():
3239 3244 raise error.Abort(_('compression engine %s could not be loaded') %
3240 3245 engine.name())
3241 3246 return engine
3242 3247
3243 3248 def supportedwireengines(self, role, onlyavailable=True):
3244 3249 """Obtain compression engines that support the wire protocol.
3245 3250
3246 3251 Returns a list of engines in prioritized order, most desired first.
3247 3252
3248 3253 If ``onlyavailable`` is set, filter out engines that can't be
3249 3254 loaded.
3250 3255 """
3251 3256 assert role in (SERVERROLE, CLIENTROLE)
3252 3257
3253 3258 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3254 3259
3255 3260 engines = [self._engines[e] for e in self._wiretypes.values()]
3256 3261 if onlyavailable:
3257 3262 engines = [e for e in engines if e.available()]
3258 3263
3259 3264 def getkey(e):
3260 3265 # Sort first by priority, highest first. In case of tie, sort
3261 3266 # alphabetically. This is arbitrary, but ensures output is
3262 3267 # stable.
3263 3268 w = e.wireprotosupport()
3264 3269 return -1 * getattr(w, attr), w.name
3265 3270
3266 3271 return list(sorted(engines, key=getkey))
3267 3272
3268 3273 def forwiretype(self, wiretype):
3269 3274 engine = self._engines[self._wiretypes[wiretype]]
3270 3275 if not engine.available():
3271 3276 raise error.Abort(_('compression engine %s could not be loaded') %
3272 3277 engine.name())
3273 3278 return engine
3274 3279
3275 3280 def forrevlogheader(self, header):
3276 3281 """Obtain a compression engine registered to a revlog header.
3277 3282
3278 3283 Will raise KeyError if the revlog header value isn't registered.
3279 3284 """
3280 3285 return self._engines[self._revlogheaders[header]]
3281 3286
3282 3287 compengines = compressormanager()
3283 3288
3284 3289 class compressionengine(object):
3285 3290 """Base class for compression engines.
3286 3291
3287 3292 Compression engines must implement the interface defined by this class.
3288 3293 """
3289 3294 def name(self):
3290 3295 """Returns the name of the compression engine.
3291 3296
3292 3297 This is the key the engine is registered under.
3293 3298
3294 3299 This method must be implemented.
3295 3300 """
3296 3301 raise NotImplementedError()
3297 3302
3298 3303 def available(self):
3299 3304 """Whether the compression engine is available.
3300 3305
3301 3306 The intent of this method is to allow optional compression engines
3302 3307 that may not be available in all installations (such as engines relying
3303 3308 on C extensions that may not be present).
3304 3309 """
3305 3310 return True
3306 3311
3307 3312 def bundletype(self):
3308 3313 """Describes bundle identifiers for this engine.
3309 3314
3310 3315 If this compression engine isn't supported for bundles, returns None.
3311 3316
3312 3317 If this engine can be used for bundles, returns a 2-tuple of strings of
3313 3318 the user-facing "bundle spec" compression name and an internal
3314 3319 identifier used to denote the compression format within bundles. To
3315 3320 exclude the name from external usage, set the first element to ``None``.
3316 3321
3317 3322 If bundle compression is supported, the class must also implement
3318 3323 ``compressstream`` and `decompressorreader``.
3319 3324
3320 3325 The docstring of this method is used in the help system to tell users
3321 3326 about this engine.
3322 3327 """
3323 3328 return None
3324 3329
3325 3330 def wireprotosupport(self):
3326 3331 """Declare support for this compression format on the wire protocol.
3327 3332
3328 3333 If this compression engine isn't supported for compressing wire
3329 3334 protocol payloads, returns None.
3330 3335
3331 3336 Otherwise, returns ``compenginewireprotosupport`` with the following
3332 3337 fields:
3333 3338
3334 3339 * String format identifier
3335 3340 * Integer priority for the server
3336 3341 * Integer priority for the client
3337 3342
3338 3343 The integer priorities are used to order the advertisement of format
3339 3344 support by server and client. The highest integer is advertised
3340 3345 first. Integers with non-positive values aren't advertised.
3341 3346
3342 3347 The priority values are somewhat arbitrary and only used for default
3343 3348 ordering. The relative order can be changed via config options.
3344 3349
3345 3350 If wire protocol compression is supported, the class must also implement
3346 3351 ``compressstream`` and ``decompressorreader``.
3347 3352 """
3348 3353 return None
3349 3354
3350 3355 def revlogheader(self):
3351 3356 """Header added to revlog chunks that identifies this engine.
3352 3357
3353 3358 If this engine can be used to compress revlogs, this method should
3354 3359 return the bytes used to identify chunks compressed with this engine.
3355 3360 Else, the method should return ``None`` to indicate it does not
3356 3361 participate in revlog compression.
3357 3362 """
3358 3363 return None
3359 3364
3360 3365 def compressstream(self, it, opts=None):
3361 3366 """Compress an iterator of chunks.
3362 3367
3363 3368 The method receives an iterator (ideally a generator) of chunks of
3364 3369 bytes to be compressed. It returns an iterator (ideally a generator)
3365 3370 of bytes of chunks representing the compressed output.
3366 3371
3367 3372 Optionally accepts an argument defining how to perform compression.
3368 3373 Each engine treats this argument differently.
3369 3374 """
3370 3375 raise NotImplementedError()
3371 3376
3372 3377 def decompressorreader(self, fh):
3373 3378 """Perform decompression on a file object.
3374 3379
3375 3380 Argument is an object with a ``read(size)`` method that returns
3376 3381 compressed data. Return value is an object with a ``read(size)`` that
3377 3382 returns uncompressed data.
3378 3383 """
3379 3384 raise NotImplementedError()
3380 3385
3381 3386 def revlogcompressor(self, opts=None):
3382 3387 """Obtain an object that can be used to compress revlog entries.
3383 3388
3384 3389 The object has a ``compress(data)`` method that compresses binary
3385 3390 data. This method returns compressed binary data or ``None`` if
3386 3391 the data could not be compressed (too small, not compressible, etc).
3387 3392 The returned data should have a header uniquely identifying this
3388 3393 compression format so decompression can be routed to this engine.
3389 3394 This header should be identified by the ``revlogheader()`` return
3390 3395 value.
3391 3396
3392 3397 The object has a ``decompress(data)`` method that decompresses
3393 3398 data. The method will only be called if ``data`` begins with
3394 3399 ``revlogheader()``. The method should return the raw, uncompressed
3395 3400 data or raise a ``RevlogError``.
3396 3401
3397 3402 The object is reusable but is not thread safe.
3398 3403 """
3399 3404 raise NotImplementedError()
3400 3405
3401 3406 class _zlibengine(compressionengine):
3402 3407 def name(self):
3403 3408 return 'zlib'
3404 3409
3405 3410 def bundletype(self):
3406 3411 """zlib compression using the DEFLATE algorithm.
3407 3412
3408 3413 All Mercurial clients should support this format. The compression
3409 3414 algorithm strikes a reasonable balance between compression ratio
3410 3415 and size.
3411 3416 """
3412 3417 return 'gzip', 'GZ'
3413 3418
3414 3419 def wireprotosupport(self):
3415 3420 return compewireprotosupport('zlib', 20, 20)
3416 3421
3417 3422 def revlogheader(self):
3418 3423 return 'x'
3419 3424
3420 3425 def compressstream(self, it, opts=None):
3421 3426 opts = opts or {}
3422 3427
3423 3428 z = zlib.compressobj(opts.get('level', -1))
3424 3429 for chunk in it:
3425 3430 data = z.compress(chunk)
3426 3431 # Not all calls to compress emit data. It is cheaper to inspect
3427 3432 # here than to feed empty chunks through generator.
3428 3433 if data:
3429 3434 yield data
3430 3435
3431 3436 yield z.flush()
3432 3437
3433 3438 def decompressorreader(self, fh):
3434 3439 def gen():
3435 3440 d = zlib.decompressobj()
3436 3441 for chunk in filechunkiter(fh):
3437 3442 while chunk:
3438 3443 # Limit output size to limit memory.
3439 3444 yield d.decompress(chunk, 2 ** 18)
3440 3445 chunk = d.unconsumed_tail
3441 3446
3442 3447 return chunkbuffer(gen())
3443 3448
3444 3449 class zlibrevlogcompressor(object):
3445 3450 def compress(self, data):
3446 3451 insize = len(data)
3447 3452 # Caller handles empty input case.
3448 3453 assert insize > 0
3449 3454
3450 3455 if insize < 44:
3451 3456 return None
3452 3457
3453 3458 elif insize <= 1000000:
3454 3459 compressed = zlib.compress(data)
3455 3460 if len(compressed) < insize:
3456 3461 return compressed
3457 3462 return None
3458 3463
3459 3464 # zlib makes an internal copy of the input buffer, doubling
3460 3465 # memory usage for large inputs. So do streaming compression
3461 3466 # on large inputs.
3462 3467 else:
3463 3468 z = zlib.compressobj()
3464 3469 parts = []
3465 3470 pos = 0
3466 3471 while pos < insize:
3467 3472 pos2 = pos + 2**20
3468 3473 parts.append(z.compress(data[pos:pos2]))
3469 3474 pos = pos2
3470 3475 parts.append(z.flush())
3471 3476
3472 3477 if sum(map(len, parts)) < insize:
3473 3478 return ''.join(parts)
3474 3479 return None
3475 3480
3476 3481 def decompress(self, data):
3477 3482 try:
3478 3483 return zlib.decompress(data)
3479 3484 except zlib.error as e:
3480 3485 raise error.RevlogError(_('revlog decompress error: %s') %
3481 3486 str(e))
3482 3487
3483 3488 def revlogcompressor(self, opts=None):
3484 3489 return self.zlibrevlogcompressor()
3485 3490
3486 3491 compengines.register(_zlibengine())
3487 3492
3488 3493 class _bz2engine(compressionengine):
3489 3494 def name(self):
3490 3495 return 'bz2'
3491 3496
3492 3497 def bundletype(self):
3493 3498 """An algorithm that produces smaller bundles than ``gzip``.
3494 3499
3495 3500 All Mercurial clients should support this format.
3496 3501
3497 3502 This engine will likely produce smaller bundles than ``gzip`` but
3498 3503 will be significantly slower, both during compression and
3499 3504 decompression.
3500 3505
3501 3506 If available, the ``zstd`` engine can yield similar or better
3502 3507 compression at much higher speeds.
3503 3508 """
3504 3509 return 'bzip2', 'BZ'
3505 3510
3506 3511 # We declare a protocol name but don't advertise by default because
3507 3512 # it is slow.
3508 3513 def wireprotosupport(self):
3509 3514 return compewireprotosupport('bzip2', 0, 0)
3510 3515
3511 3516 def compressstream(self, it, opts=None):
3512 3517 opts = opts or {}
3513 3518 z = bz2.BZ2Compressor(opts.get('level', 9))
3514 3519 for chunk in it:
3515 3520 data = z.compress(chunk)
3516 3521 if data:
3517 3522 yield data
3518 3523
3519 3524 yield z.flush()
3520 3525
3521 3526 def decompressorreader(self, fh):
3522 3527 def gen():
3523 3528 d = bz2.BZ2Decompressor()
3524 3529 for chunk in filechunkiter(fh):
3525 3530 yield d.decompress(chunk)
3526 3531
3527 3532 return chunkbuffer(gen())
3528 3533
3529 3534 compengines.register(_bz2engine())
3530 3535
3531 3536 class _truncatedbz2engine(compressionengine):
3532 3537 def name(self):
3533 3538 return 'bz2truncated'
3534 3539
3535 3540 def bundletype(self):
3536 3541 return None, '_truncatedBZ'
3537 3542
3538 3543 # We don't implement compressstream because it is hackily handled elsewhere.
3539 3544
3540 3545 def decompressorreader(self, fh):
3541 3546 def gen():
3542 3547 # The input stream doesn't have the 'BZ' header. So add it back.
3543 3548 d = bz2.BZ2Decompressor()
3544 3549 d.decompress('BZ')
3545 3550 for chunk in filechunkiter(fh):
3546 3551 yield d.decompress(chunk)
3547 3552
3548 3553 return chunkbuffer(gen())
3549 3554
3550 3555 compengines.register(_truncatedbz2engine())
3551 3556
3552 3557 class _noopengine(compressionengine):
3553 3558 def name(self):
3554 3559 return 'none'
3555 3560
3556 3561 def bundletype(self):
3557 3562 """No compression is performed.
3558 3563
3559 3564 Use this compression engine to explicitly disable compression.
3560 3565 """
3561 3566 return 'none', 'UN'
3562 3567
3563 3568 # Clients always support uncompressed payloads. Servers don't because
3564 3569 # unless you are on a fast network, uncompressed payloads can easily
3565 3570 # saturate your network pipe.
3566 3571 def wireprotosupport(self):
3567 3572 return compewireprotosupport('none', 0, 10)
3568 3573
3569 3574 # We don't implement revlogheader because it is handled specially
3570 3575 # in the revlog class.
3571 3576
3572 3577 def compressstream(self, it, opts=None):
3573 3578 return it
3574 3579
3575 3580 def decompressorreader(self, fh):
3576 3581 return fh
3577 3582
3578 3583 class nooprevlogcompressor(object):
3579 3584 def compress(self, data):
3580 3585 return None
3581 3586
3582 3587 def revlogcompressor(self, opts=None):
3583 3588 return self.nooprevlogcompressor()
3584 3589
3585 3590 compengines.register(_noopengine())
3586 3591
3587 3592 class _zstdengine(compressionengine):
3588 3593 def name(self):
3589 3594 return 'zstd'
3590 3595
3591 3596 @propertycache
3592 3597 def _module(self):
3593 3598 # Not all installs have the zstd module available. So defer importing
3594 3599 # until first access.
3595 3600 try:
3596 3601 from . import zstd
3597 3602 # Force delayed import.
3598 3603 zstd.__version__
3599 3604 return zstd
3600 3605 except ImportError:
3601 3606 return None
3602 3607
3603 3608 def available(self):
3604 3609 return bool(self._module)
3605 3610
3606 3611 def bundletype(self):
3607 3612 """A modern compression algorithm that is fast and highly flexible.
3608 3613
3609 3614 Only supported by Mercurial 4.1 and newer clients.
3610 3615
3611 3616 With the default settings, zstd compression is both faster and yields
3612 3617 better compression than ``gzip``. It also frequently yields better
3613 3618 compression than ``bzip2`` while operating at much higher speeds.
3614 3619
3615 3620 If this engine is available and backwards compatibility is not a
3616 3621 concern, it is likely the best available engine.
3617 3622 """
3618 3623 return 'zstd', 'ZS'
3619 3624
3620 3625 def wireprotosupport(self):
3621 3626 return compewireprotosupport('zstd', 50, 50)
3622 3627
3623 3628 def revlogheader(self):
3624 3629 return '\x28'
3625 3630
3626 3631 def compressstream(self, it, opts=None):
3627 3632 opts = opts or {}
3628 3633 # zstd level 3 is almost always significantly faster than zlib
3629 3634 # while providing no worse compression. It strikes a good balance
3630 3635 # between speed and compression.
3631 3636 level = opts.get('level', 3)
3632 3637
3633 3638 zstd = self._module
3634 3639 z = zstd.ZstdCompressor(level=level).compressobj()
3635 3640 for chunk in it:
3636 3641 data = z.compress(chunk)
3637 3642 if data:
3638 3643 yield data
3639 3644
3640 3645 yield z.flush()
3641 3646
3642 3647 def decompressorreader(self, fh):
3643 3648 zstd = self._module
3644 3649 dctx = zstd.ZstdDecompressor()
3645 3650 return chunkbuffer(dctx.read_from(fh))
3646 3651
3647 3652 class zstdrevlogcompressor(object):
3648 3653 def __init__(self, zstd, level=3):
3649 3654 # Writing the content size adds a few bytes to the output. However,
3650 3655 # it allows decompression to be more optimal since we can
3651 3656 # pre-allocate a buffer to hold the result.
3652 3657 self._cctx = zstd.ZstdCompressor(level=level,
3653 3658 write_content_size=True)
3654 3659 self._dctx = zstd.ZstdDecompressor()
3655 3660 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3656 3661 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3657 3662
3658 3663 def compress(self, data):
3659 3664 insize = len(data)
3660 3665 # Caller handles empty input case.
3661 3666 assert insize > 0
3662 3667
3663 3668 if insize < 50:
3664 3669 return None
3665 3670
3666 3671 elif insize <= 1000000:
3667 3672 compressed = self._cctx.compress(data)
3668 3673 if len(compressed) < insize:
3669 3674 return compressed
3670 3675 return None
3671 3676 else:
3672 3677 z = self._cctx.compressobj()
3673 3678 chunks = []
3674 3679 pos = 0
3675 3680 while pos < insize:
3676 3681 pos2 = pos + self._compinsize
3677 3682 chunk = z.compress(data[pos:pos2])
3678 3683 if chunk:
3679 3684 chunks.append(chunk)
3680 3685 pos = pos2
3681 3686 chunks.append(z.flush())
3682 3687
3683 3688 if sum(map(len, chunks)) < insize:
3684 3689 return ''.join(chunks)
3685 3690 return None
3686 3691
3687 3692 def decompress(self, data):
3688 3693 insize = len(data)
3689 3694
3690 3695 try:
3691 3696 # This was measured to be faster than other streaming
3692 3697 # decompressors.
3693 3698 dobj = self._dctx.decompressobj()
3694 3699 chunks = []
3695 3700 pos = 0
3696 3701 while pos < insize:
3697 3702 pos2 = pos + self._decompinsize
3698 3703 chunk = dobj.decompress(data[pos:pos2])
3699 3704 if chunk:
3700 3705 chunks.append(chunk)
3701 3706 pos = pos2
3702 3707 # Frame should be exhausted, so no finish() API.
3703 3708
3704 3709 return ''.join(chunks)
3705 3710 except Exception as e:
3706 3711 raise error.RevlogError(_('revlog decompress error: %s') %
3707 3712 str(e))
3708 3713
3709 3714 def revlogcompressor(self, opts=None):
3710 3715 opts = opts or {}
3711 3716 return self.zstdrevlogcompressor(self._module,
3712 3717 level=opts.get('level', 3))
3713 3718
3714 3719 compengines.register(_zstdengine())
3715 3720
3716 3721 def bundlecompressiontopics():
3717 3722 """Obtains a list of available bundle compressions for use in help."""
3718 3723 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3719 3724 items = {}
3720 3725
3721 3726 # We need to format the docstring. So use a dummy object/type to hold it
3722 3727 # rather than mutating the original.
3723 3728 class docobject(object):
3724 3729 pass
3725 3730
3726 3731 for name in compengines:
3727 3732 engine = compengines[name]
3728 3733
3729 3734 if not engine.available():
3730 3735 continue
3731 3736
3732 3737 bt = engine.bundletype()
3733 3738 if not bt or not bt[0]:
3734 3739 continue
3735 3740
3736 3741 doc = pycompat.sysstr('``%s``\n %s') % (
3737 3742 bt[0], engine.bundletype.__doc__)
3738 3743
3739 3744 value = docobject()
3740 3745 value.__doc__ = doc
3741 3746
3742 3747 items[bt[0]] = value
3743 3748
3744 3749 return items
3745 3750
3746 3751 # convenient shortcut
3747 3752 dst = debugstacktrace
@@ -1,206 +1,234 b''
1 1 $ hg init repo
2 2 $ cd repo
3 3 $ echo a > a
4 4 $ hg add a
5 5 $ hg commit -m test
6 6
7 7 Do we ever miss a sub-second change?:
8 8
9 9 $ for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
10 10 > hg co -qC 0
11 11 > echo b > a
12 12 > hg st
13 13 > done
14 14 M a
15 15 M a
16 16 M a
17 17 M a
18 18 M a
19 19 M a
20 20 M a
21 21 M a
22 22 M a
23 23 M a
24 24 M a
25 25 M a
26 26 M a
27 27 M a
28 28 M a
29 29 M a
30 30 M a
31 31 M a
32 32 M a
33 33 M a
34 34
35 35 $ echo test > b
36 36 $ mkdir dir1
37 37 $ echo test > dir1/c
38 38 $ echo test > d
39 39
40 40 $ echo test > e
41 41 #if execbit
42 42 A directory will typically have the execute bit -- make sure it doesn't get
43 43 confused with a file with the exec bit set
44 44 $ chmod +x e
45 45 #endif
46 46
47 47 $ hg add b dir1 d e
48 48 adding dir1/c (glob)
49 49 $ hg commit -m test2
50 50
51 51 $ cat >> $TESTTMP/dirstaterace.py << EOF
52 52 > from mercurial import (
53 53 > context,
54 54 > extensions,
55 55 > )
56 56 > def extsetup():
57 57 > extensions.wrapfunction(context.workingctx, '_checklookup', overridechecklookup)
58 58 > def overridechecklookup(orig, self, files):
59 59 > # make an update that changes the dirstate from underneath
60 60 > self._repo.ui.system(r"sh '$TESTTMP/dirstaterace.sh'",
61 61 > cwd=self._repo.root)
62 62 > return orig(self, files)
63 63 > EOF
64 64
65 65 $ hg debugrebuilddirstate
66 66 $ hg debugdirstate
67 67 n 0 -1 unset a
68 68 n 0 -1 unset b
69 69 n 0 -1 unset d
70 70 n 0 -1 unset dir1/c
71 71 n 0 -1 unset e
72 72
73 73 XXX Note that this returns M for files that got replaced by directories. This is
74 74 definitely a bug, but the fix for that is hard and the next status run is fine
75 75 anyway.
76 76
77 77 $ cat > $TESTTMP/dirstaterace.sh <<EOF
78 78 > rm b && rm -r dir1 && rm d && mkdir d && rm e && mkdir e
79 79 > EOF
80 80
81 81 $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py
82 82 M d
83 83 M e
84 84 ! b
85 85 ! dir1/c
86 86 $ hg debugdirstate
87 87 n 644 2 * a (glob)
88 88 n 0 -1 unset b
89 89 n 0 -1 unset d
90 90 n 0 -1 unset dir1/c
91 91 n 0 -1 unset e
92 92
93 93 $ hg status
94 94 ! b
95 95 ! d
96 96 ! dir1/c
97 97 ! e
98 98
99 99 $ rmdir d e
100 100 $ hg update -C -q .
101 101
102 102 Test that dirstate changes aren't written out at the end of "hg
103 103 status", if .hg/dirstate is already changed simultaneously before
104 104 acquisition of wlock in workingctx._poststatusfixup().
105 105
106 106 This avoidance is important to keep consistency of dirstate in race
107 107 condition (see issue5584 for detail).
108 108
109 109 $ hg parents -q
110 110 1:* (glob)
111 111
112 112 $ hg debugrebuilddirstate
113 113 $ hg debugdirstate
114 114 n 0 -1 unset a
115 115 n 0 -1 unset b
116 116 n 0 -1 unset d
117 117 n 0 -1 unset dir1/c
118 118 n 0 -1 unset e
119 119
120 120 $ cat > $TESTTMP/dirstaterace.sh <<EOF
121 121 > # This script assumes timetable of typical issue5584 case below:
122 122 > #
123 123 > # 1. "hg status" loads .hg/dirstate
124 124 > # 2. "hg status" confirms clean-ness of FILE
125 125 > # 3. "hg update -C 0" updates the working directory simultaneously
126 126 > # (FILE is removed, and FILE is dropped from .hg/dirstate)
127 127 > # 4. "hg status" acquires wlock
128 128 > # (.hg/dirstate is re-loaded = no FILE entry in dirstate)
129 129 > # 5. "hg status" marks FILE in dirstate as clean
130 130 > # (FILE entry is added to in-memory dirstate)
131 131 > # 6. "hg status" writes dirstate changes into .hg/dirstate
132 132 > # (FILE entry is written into .hg/dirstate)
133 133 > #
134 134 > # To reproduce similar situation easily and certainly, #2 and #3
135 135 > # are swapped. "hg cat" below ensures #2 on "hg status" side.
136 136 >
137 137 > hg update -q -C 0
138 138 > hg cat -r 1 b > b
139 139 > EOF
140 140
141 141 "hg status" below should excludes "e", of which exec flag is set, for
142 142 portability of test scenario, because unsure but missing "e" is
143 143 treated differently in _checklookup() according to runtime platform.
144 144
145 145 - "missing(!)" on POSIX, "pctx[f].cmp(self[f])" raises ENOENT
146 146 - "modified(M)" on Windows, "self.flags(f) != pctx.flags(f)" is True
147 147
148 148 $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py --debug -X path:e
149 149 skip updating dirstate: identity mismatch
150 150 M a
151 151 ! d
152 152 ! dir1/c
153 153
154 154 $ hg parents -q
155 155 0:* (glob)
156 156 $ hg files
157 157 a
158 158 $ hg debugdirstate
159 159 n * * * a (glob)
160 160
161 161 $ rm b
162 162
163 #if fsmonitor
164
165 Create fsmonitor state.
166
167 $ hg status
168 $ f --type .hg/fsmonitor.state
169 .hg/fsmonitor.state: file
170
171 Test that invalidating fsmonitor state in the middle (which doesn't require the
172 wlock) causes the fsmonitor update to be skipped.
173 hg debugrebuilddirstate ensures that the dirstaterace hook will be called, but
174 it also invalidates the fsmonitor state. So back it up and restore it.
175
176 $ mv .hg/fsmonitor.state .hg/fsmonitor.state.tmp
177 $ hg debugrebuilddirstate
178 $ mv .hg/fsmonitor.state.tmp .hg/fsmonitor.state
179
180 $ cat > $TESTTMP/dirstaterace.sh <<EOF
181 > rm .hg/fsmonitor.state
182 > EOF
183
184 $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py --debug
185 skip updating fsmonitor.state: identity mismatch
186 $ f .hg/fsmonitor.state
187 .hg/fsmonitor.state: file not found
188
189 #endif
190
163 191 Set up a rebase situation for issue5581.
164 192
165 193 $ echo c2 > a
166 194 $ echo c2 > b
167 195 $ hg add b
168 196 $ hg commit -m c2
169 197 created new head
170 198 $ echo c3 >> a
171 199 $ hg commit -m c3
172 200 $ hg update 2
173 201 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 202 $ echo c4 >> a
175 203 $ echo c4 >> b
176 204 $ hg commit -m c4
177 205 created new head
178 206
179 207 Configure a merge tool that runs status in the middle of the rebase.
180 208
181 209 $ cat >> $TESTTMP/mergetool-race.sh << EOF
182 210 > echo "custom merge tool"
183 211 > printf "c2\nc3\nc4\n" > \$1
184 212 > hg --cwd $TESTTMP/repo status
185 213 > echo "custom merge tool end"
186 214 > EOF
187 215 $ cat >> $HGRCPATH << EOF
188 216 > [extensions]
189 217 > rebase =
190 218 > [merge-tools]
191 219 > test.executable=sh
192 220 > test.args=$TESTTMP/mergetool-race.sh \$output
193 221 > EOF
194 222
195 223 $ hg rebase -s . -d 3 --tool test
196 224 rebasing 4:b08445fd6b2a "c4" (tip)
197 225 merging a
198 226 custom merge tool
199 227 M a
200 228 ? a.orig
201 229 custom merge tool end
202 230 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/* (glob)
203 231
204 232 This hg status should be empty, whether or not fsmonitor is enabled (issue5581).
205 233
206 234 $ hg status
General Comments 0
You need to be logged in to leave comments. Login now