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