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