##// END OF EJS Templates
vfs: add a NotImplementedError implementation of __call__...
Augie Fackler -
r43768:b16912f8 default
parent child Browse files
Show More
@@ -1,733 +1,736 b''
1 # vfs.py - Mercurial 'vfs' classes
1 # vfs.py - Mercurial 'vfs' classes
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import errno
10 import errno
11 import os
11 import os
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import threading
14 import threading
15
15
16 from .i18n import _
16 from .i18n import _
17 from .pycompat import (
17 from .pycompat import (
18 delattr,
18 delattr,
19 getattr,
19 getattr,
20 setattr,
20 setattr,
21 )
21 )
22 from . import (
22 from . import (
23 encoding,
23 encoding,
24 error,
24 error,
25 pathutil,
25 pathutil,
26 pycompat,
26 pycompat,
27 util,
27 util,
28 )
28 )
29
29
30
30
31 def _avoidambig(path, oldstat):
31 def _avoidambig(path, oldstat):
32 """Avoid file stat ambiguity forcibly
32 """Avoid file stat ambiguity forcibly
33
33
34 This function causes copying ``path`` file, if it is owned by
34 This function causes copying ``path`` file, if it is owned by
35 another (see issue5418 and issue5584 for detail).
35 another (see issue5418 and issue5584 for detail).
36 """
36 """
37
37
38 def checkandavoid():
38 def checkandavoid():
39 newstat = util.filestat.frompath(path)
39 newstat = util.filestat.frompath(path)
40 # return whether file stat ambiguity is (already) avoided
40 # return whether file stat ambiguity is (already) avoided
41 return not newstat.isambig(oldstat) or newstat.avoidambig(path, oldstat)
41 return not newstat.isambig(oldstat) or newstat.avoidambig(path, oldstat)
42
42
43 if not checkandavoid():
43 if not checkandavoid():
44 # simply copy to change owner of path to get privilege to
44 # simply copy to change owner of path to get privilege to
45 # advance mtime (see issue5418)
45 # advance mtime (see issue5418)
46 util.rename(util.mktempcopy(path), path)
46 util.rename(util.mktempcopy(path), path)
47 checkandavoid()
47 checkandavoid()
48
48
49
49
50 class abstractvfs(object):
50 class abstractvfs(object):
51 """Abstract base class; cannot be instantiated"""
51 """Abstract base class; cannot be instantiated"""
52
52
53 def __init__(self, *args, **kwargs):
53 def __init__(self, *args, **kwargs):
54 '''Prevent instantiation; don't call this from subclasses.'''
54 '''Prevent instantiation; don't call this from subclasses.'''
55 raise NotImplementedError('attempted instantiating ' + str(type(self)))
55 raise NotImplementedError('attempted instantiating ' + str(type(self)))
56
56
57 def __call__(self, path, mode=b'rb', **kwargs):
58 raise NotImplementedError
59
57 def _auditpath(self, path, mode):
60 def _auditpath(self, path, mode):
58 raise NotImplementedError
61 raise NotImplementedError
59
62
60 def tryread(self, path):
63 def tryread(self, path):
61 '''gracefully return an empty string for missing files'''
64 '''gracefully return an empty string for missing files'''
62 try:
65 try:
63 return self.read(path)
66 return self.read(path)
64 except IOError as inst:
67 except IOError as inst:
65 if inst.errno != errno.ENOENT:
68 if inst.errno != errno.ENOENT:
66 raise
69 raise
67 return b""
70 return b""
68
71
69 def tryreadlines(self, path, mode=b'rb'):
72 def tryreadlines(self, path, mode=b'rb'):
70 '''gracefully return an empty array for missing files'''
73 '''gracefully return an empty array for missing files'''
71 try:
74 try:
72 return self.readlines(path, mode=mode)
75 return self.readlines(path, mode=mode)
73 except IOError as inst:
76 except IOError as inst:
74 if inst.errno != errno.ENOENT:
77 if inst.errno != errno.ENOENT:
75 raise
78 raise
76 return []
79 return []
77
80
78 @util.propertycache
81 @util.propertycache
79 def open(self):
82 def open(self):
80 '''Open ``path`` file, which is relative to vfs root.
83 '''Open ``path`` file, which is relative to vfs root.
81
84
82 Newly created directories are marked as "not to be indexed by
85 Newly created directories are marked as "not to be indexed by
83 the content indexing service", if ``notindexed`` is specified
86 the content indexing service", if ``notindexed`` is specified
84 for "write" mode access.
87 for "write" mode access.
85 '''
88 '''
86 return self.__call__
89 return self.__call__
87
90
88 def read(self, path):
91 def read(self, path):
89 with self(path, b'rb') as fp:
92 with self(path, b'rb') as fp:
90 return fp.read()
93 return fp.read()
91
94
92 def readlines(self, path, mode=b'rb'):
95 def readlines(self, path, mode=b'rb'):
93 with self(path, mode=mode) as fp:
96 with self(path, mode=mode) as fp:
94 return fp.readlines()
97 return fp.readlines()
95
98
96 def write(self, path, data, backgroundclose=False, **kwargs):
99 def write(self, path, data, backgroundclose=False, **kwargs):
97 with self(path, b'wb', backgroundclose=backgroundclose, **kwargs) as fp:
100 with self(path, b'wb', backgroundclose=backgroundclose, **kwargs) as fp:
98 return fp.write(data)
101 return fp.write(data)
99
102
100 def writelines(self, path, data, mode=b'wb', notindexed=False):
103 def writelines(self, path, data, mode=b'wb', notindexed=False):
101 with self(path, mode=mode, notindexed=notindexed) as fp:
104 with self(path, mode=mode, notindexed=notindexed) as fp:
102 return fp.writelines(data)
105 return fp.writelines(data)
103
106
104 def append(self, path, data):
107 def append(self, path, data):
105 with self(path, b'ab') as fp:
108 with self(path, b'ab') as fp:
106 return fp.write(data)
109 return fp.write(data)
107
110
108 def basename(self, path):
111 def basename(self, path):
109 """return base element of a path (as os.path.basename would do)
112 """return base element of a path (as os.path.basename would do)
110
113
111 This exists to allow handling of strange encoding if needed."""
114 This exists to allow handling of strange encoding if needed."""
112 return os.path.basename(path)
115 return os.path.basename(path)
113
116
114 def chmod(self, path, mode):
117 def chmod(self, path, mode):
115 return os.chmod(self.join(path), mode)
118 return os.chmod(self.join(path), mode)
116
119
117 def dirname(self, path):
120 def dirname(self, path):
118 """return dirname element of a path (as os.path.dirname would do)
121 """return dirname element of a path (as os.path.dirname would do)
119
122
120 This exists to allow handling of strange encoding if needed."""
123 This exists to allow handling of strange encoding if needed."""
121 return os.path.dirname(path)
124 return os.path.dirname(path)
122
125
123 def exists(self, path=None):
126 def exists(self, path=None):
124 return os.path.exists(self.join(path))
127 return os.path.exists(self.join(path))
125
128
126 def fstat(self, fp):
129 def fstat(self, fp):
127 return util.fstat(fp)
130 return util.fstat(fp)
128
131
129 def isdir(self, path=None):
132 def isdir(self, path=None):
130 return os.path.isdir(self.join(path))
133 return os.path.isdir(self.join(path))
131
134
132 def isfile(self, path=None):
135 def isfile(self, path=None):
133 return os.path.isfile(self.join(path))
136 return os.path.isfile(self.join(path))
134
137
135 def islink(self, path=None):
138 def islink(self, path=None):
136 return os.path.islink(self.join(path))
139 return os.path.islink(self.join(path))
137
140
138 def isfileorlink(self, path=None):
141 def isfileorlink(self, path=None):
139 '''return whether path is a regular file or a symlink
142 '''return whether path is a regular file or a symlink
140
143
141 Unlike isfile, this doesn't follow symlinks.'''
144 Unlike isfile, this doesn't follow symlinks.'''
142 try:
145 try:
143 st = self.lstat(path)
146 st = self.lstat(path)
144 except OSError:
147 except OSError:
145 return False
148 return False
146 mode = st.st_mode
149 mode = st.st_mode
147 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
150 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
148
151
149 def reljoin(self, *paths):
152 def reljoin(self, *paths):
150 """join various elements of a path together (as os.path.join would do)
153 """join various elements of a path together (as os.path.join would do)
151
154
152 The vfs base is not injected so that path stay relative. This exists
155 The vfs base is not injected so that path stay relative. This exists
153 to allow handling of strange encoding if needed."""
156 to allow handling of strange encoding if needed."""
154 return os.path.join(*paths)
157 return os.path.join(*paths)
155
158
156 def split(self, path):
159 def split(self, path):
157 """split top-most element of a path (as os.path.split would do)
160 """split top-most element of a path (as os.path.split would do)
158
161
159 This exists to allow handling of strange encoding if needed."""
162 This exists to allow handling of strange encoding if needed."""
160 return os.path.split(path)
163 return os.path.split(path)
161
164
162 def lexists(self, path=None):
165 def lexists(self, path=None):
163 return os.path.lexists(self.join(path))
166 return os.path.lexists(self.join(path))
164
167
165 def lstat(self, path=None):
168 def lstat(self, path=None):
166 return os.lstat(self.join(path))
169 return os.lstat(self.join(path))
167
170
168 def listdir(self, path=None):
171 def listdir(self, path=None):
169 return os.listdir(self.join(path))
172 return os.listdir(self.join(path))
170
173
171 def makedir(self, path=None, notindexed=True):
174 def makedir(self, path=None, notindexed=True):
172 return util.makedir(self.join(path), notindexed)
175 return util.makedir(self.join(path), notindexed)
173
176
174 def makedirs(self, path=None, mode=None):
177 def makedirs(self, path=None, mode=None):
175 return util.makedirs(self.join(path), mode)
178 return util.makedirs(self.join(path), mode)
176
179
177 def makelock(self, info, path):
180 def makelock(self, info, path):
178 return util.makelock(info, self.join(path))
181 return util.makelock(info, self.join(path))
179
182
180 def mkdir(self, path=None):
183 def mkdir(self, path=None):
181 return os.mkdir(self.join(path))
184 return os.mkdir(self.join(path))
182
185
183 def mkstemp(self, suffix=b'', prefix=b'tmp', dir=None):
186 def mkstemp(self, suffix=b'', prefix=b'tmp', dir=None):
184 fd, name = pycompat.mkstemp(
187 fd, name = pycompat.mkstemp(
185 suffix=suffix, prefix=prefix, dir=self.join(dir)
188 suffix=suffix, prefix=prefix, dir=self.join(dir)
186 )
189 )
187 dname, fname = util.split(name)
190 dname, fname = util.split(name)
188 if dir:
191 if dir:
189 return fd, os.path.join(dir, fname)
192 return fd, os.path.join(dir, fname)
190 else:
193 else:
191 return fd, fname
194 return fd, fname
192
195
193 def readdir(self, path=None, stat=None, skip=None):
196 def readdir(self, path=None, stat=None, skip=None):
194 return util.listdir(self.join(path), stat, skip)
197 return util.listdir(self.join(path), stat, skip)
195
198
196 def readlock(self, path):
199 def readlock(self, path):
197 return util.readlock(self.join(path))
200 return util.readlock(self.join(path))
198
201
199 def rename(self, src, dst, checkambig=False):
202 def rename(self, src, dst, checkambig=False):
200 """Rename from src to dst
203 """Rename from src to dst
201
204
202 checkambig argument is used with util.filestat, and is useful
205 checkambig argument is used with util.filestat, and is useful
203 only if destination file is guarded by any lock
206 only if destination file is guarded by any lock
204 (e.g. repo.lock or repo.wlock).
207 (e.g. repo.lock or repo.wlock).
205
208
206 To avoid file stat ambiguity forcibly, checkambig=True involves
209 To avoid file stat ambiguity forcibly, checkambig=True involves
207 copying ``src`` file, if it is owned by another. Therefore, use
210 copying ``src`` file, if it is owned by another. Therefore, use
208 checkambig=True only in limited cases (see also issue5418 and
211 checkambig=True only in limited cases (see also issue5418 and
209 issue5584 for detail).
212 issue5584 for detail).
210 """
213 """
211 self._auditpath(dst, b'w')
214 self._auditpath(dst, b'w')
212 srcpath = self.join(src)
215 srcpath = self.join(src)
213 dstpath = self.join(dst)
216 dstpath = self.join(dst)
214 oldstat = checkambig and util.filestat.frompath(dstpath)
217 oldstat = checkambig and util.filestat.frompath(dstpath)
215 if oldstat and oldstat.stat:
218 if oldstat and oldstat.stat:
216 ret = util.rename(srcpath, dstpath)
219 ret = util.rename(srcpath, dstpath)
217 _avoidambig(dstpath, oldstat)
220 _avoidambig(dstpath, oldstat)
218 return ret
221 return ret
219 return util.rename(srcpath, dstpath)
222 return util.rename(srcpath, dstpath)
220
223
221 def readlink(self, path):
224 def readlink(self, path):
222 return util.readlink(self.join(path))
225 return util.readlink(self.join(path))
223
226
224 def removedirs(self, path=None):
227 def removedirs(self, path=None):
225 """Remove a leaf directory and all empty intermediate ones
228 """Remove a leaf directory and all empty intermediate ones
226 """
229 """
227 return util.removedirs(self.join(path))
230 return util.removedirs(self.join(path))
228
231
229 def rmdir(self, path=None):
232 def rmdir(self, path=None):
230 """Remove an empty directory."""
233 """Remove an empty directory."""
231 return os.rmdir(self.join(path))
234 return os.rmdir(self.join(path))
232
235
233 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
236 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
234 """Remove a directory tree recursively
237 """Remove a directory tree recursively
235
238
236 If ``forcibly``, this tries to remove READ-ONLY files, too.
239 If ``forcibly``, this tries to remove READ-ONLY files, too.
237 """
240 """
238 if forcibly:
241 if forcibly:
239
242
240 def onerror(function, path, excinfo):
243 def onerror(function, path, excinfo):
241 if function is not os.remove:
244 if function is not os.remove:
242 raise
245 raise
243 # read-only files cannot be unlinked under Windows
246 # read-only files cannot be unlinked under Windows
244 s = os.stat(path)
247 s = os.stat(path)
245 if (s.st_mode & stat.S_IWRITE) != 0:
248 if (s.st_mode & stat.S_IWRITE) != 0:
246 raise
249 raise
247 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
250 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
248 os.remove(path)
251 os.remove(path)
249
252
250 else:
253 else:
251 onerror = None
254 onerror = None
252 return shutil.rmtree(
255 return shutil.rmtree(
253 self.join(path), ignore_errors=ignore_errors, onerror=onerror
256 self.join(path), ignore_errors=ignore_errors, onerror=onerror
254 )
257 )
255
258
256 def setflags(self, path, l, x):
259 def setflags(self, path, l, x):
257 return util.setflags(self.join(path), l, x)
260 return util.setflags(self.join(path), l, x)
258
261
259 def stat(self, path=None):
262 def stat(self, path=None):
260 return os.stat(self.join(path))
263 return os.stat(self.join(path))
261
264
262 def unlink(self, path=None):
265 def unlink(self, path=None):
263 return util.unlink(self.join(path))
266 return util.unlink(self.join(path))
264
267
265 def tryunlink(self, path=None):
268 def tryunlink(self, path=None):
266 """Attempt to remove a file, ignoring missing file errors."""
269 """Attempt to remove a file, ignoring missing file errors."""
267 util.tryunlink(self.join(path))
270 util.tryunlink(self.join(path))
268
271
269 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
272 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
270 return util.unlinkpath(
273 return util.unlinkpath(
271 self.join(path), ignoremissing=ignoremissing, rmdir=rmdir
274 self.join(path), ignoremissing=ignoremissing, rmdir=rmdir
272 )
275 )
273
276
274 def utime(self, path=None, t=None):
277 def utime(self, path=None, t=None):
275 return os.utime(self.join(path), t)
278 return os.utime(self.join(path), t)
276
279
277 def walk(self, path=None, onerror=None):
280 def walk(self, path=None, onerror=None):
278 """Yield (dirpath, dirs, files) tuple for each directories under path
281 """Yield (dirpath, dirs, files) tuple for each directories under path
279
282
280 ``dirpath`` is relative one from the root of this vfs. This
283 ``dirpath`` is relative one from the root of this vfs. This
281 uses ``os.sep`` as path separator, even you specify POSIX
284 uses ``os.sep`` as path separator, even you specify POSIX
282 style ``path``.
285 style ``path``.
283
286
284 "The root of this vfs" is represented as empty ``dirpath``.
287 "The root of this vfs" is represented as empty ``dirpath``.
285 """
288 """
286 root = os.path.normpath(self.join(None))
289 root = os.path.normpath(self.join(None))
287 # when dirpath == root, dirpath[prefixlen:] becomes empty
290 # when dirpath == root, dirpath[prefixlen:] becomes empty
288 # because len(dirpath) < prefixlen.
291 # because len(dirpath) < prefixlen.
289 prefixlen = len(pathutil.normasprefix(root))
292 prefixlen = len(pathutil.normasprefix(root))
290 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
293 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
291 yield (dirpath[prefixlen:], dirs, files)
294 yield (dirpath[prefixlen:], dirs, files)
292
295
293 @contextlib.contextmanager
296 @contextlib.contextmanager
294 def backgroundclosing(self, ui, expectedcount=-1):
297 def backgroundclosing(self, ui, expectedcount=-1):
295 """Allow files to be closed asynchronously.
298 """Allow files to be closed asynchronously.
296
299
297 When this context manager is active, ``backgroundclose`` can be passed
300 When this context manager is active, ``backgroundclose`` can be passed
298 to ``__call__``/``open`` to result in the file possibly being closed
301 to ``__call__``/``open`` to result in the file possibly being closed
299 asynchronously, on a background thread.
302 asynchronously, on a background thread.
300 """
303 """
301 # Sharing backgroundfilecloser between threads is complex and using
304 # Sharing backgroundfilecloser between threads is complex and using
302 # multiple instances puts us at risk of running out of file descriptors
305 # multiple instances puts us at risk of running out of file descriptors
303 # only allow to use backgroundfilecloser when in main thread.
306 # only allow to use backgroundfilecloser when in main thread.
304 if not isinstance(threading.currentThread(), threading._MainThread):
307 if not isinstance(threading.currentThread(), threading._MainThread):
305 yield
308 yield
306 return
309 return
307 vfs = getattr(self, 'vfs', self)
310 vfs = getattr(self, 'vfs', self)
308 if getattr(vfs, '_backgroundfilecloser', None):
311 if getattr(vfs, '_backgroundfilecloser', None):
309 raise error.Abort(
312 raise error.Abort(
310 _(b'can only have 1 active background file closer')
313 _(b'can only have 1 active background file closer')
311 )
314 )
312
315
313 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
316 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
314 try:
317 try:
315 vfs._backgroundfilecloser = bfc
318 vfs._backgroundfilecloser = bfc
316 yield bfc
319 yield bfc
317 finally:
320 finally:
318 vfs._backgroundfilecloser = None
321 vfs._backgroundfilecloser = None
319
322
320
323
321 class vfs(abstractvfs):
324 class vfs(abstractvfs):
322 '''Operate files relative to a base directory
325 '''Operate files relative to a base directory
323
326
324 This class is used to hide the details of COW semantics and
327 This class is used to hide the details of COW semantics and
325 remote file access from higher level code.
328 remote file access from higher level code.
326
329
327 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
330 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
328 (b) the base directory is managed by hg and considered sort-of append-only.
331 (b) the base directory is managed by hg and considered sort-of append-only.
329 See pathutil.pathauditor() for details.
332 See pathutil.pathauditor() for details.
330 '''
333 '''
331
334
332 def __init__(
335 def __init__(
333 self,
336 self,
334 base,
337 base,
335 audit=True,
338 audit=True,
336 cacheaudited=False,
339 cacheaudited=False,
337 expandpath=False,
340 expandpath=False,
338 realpath=False,
341 realpath=False,
339 ):
342 ):
340 if expandpath:
343 if expandpath:
341 base = util.expandpath(base)
344 base = util.expandpath(base)
342 if realpath:
345 if realpath:
343 base = os.path.realpath(base)
346 base = os.path.realpath(base)
344 self.base = base
347 self.base = base
345 self._audit = audit
348 self._audit = audit
346 if audit:
349 if audit:
347 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
350 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
348 else:
351 else:
349 self.audit = lambda path, mode=None: True
352 self.audit = lambda path, mode=None: True
350 self.createmode = None
353 self.createmode = None
351 self._trustnlink = None
354 self._trustnlink = None
352 self.options = {}
355 self.options = {}
353
356
354 @util.propertycache
357 @util.propertycache
355 def _cansymlink(self):
358 def _cansymlink(self):
356 return util.checklink(self.base)
359 return util.checklink(self.base)
357
360
358 @util.propertycache
361 @util.propertycache
359 def _chmod(self):
362 def _chmod(self):
360 return util.checkexec(self.base)
363 return util.checkexec(self.base)
361
364
362 def _fixfilemode(self, name):
365 def _fixfilemode(self, name):
363 if self.createmode is None or not self._chmod:
366 if self.createmode is None or not self._chmod:
364 return
367 return
365 os.chmod(name, self.createmode & 0o666)
368 os.chmod(name, self.createmode & 0o666)
366
369
367 def _auditpath(self, path, mode):
370 def _auditpath(self, path, mode):
368 if self._audit:
371 if self._audit:
369 if os.path.isabs(path) and path.startswith(self.base):
372 if os.path.isabs(path) and path.startswith(self.base):
370 path = os.path.relpath(path, self.base)
373 path = os.path.relpath(path, self.base)
371 r = util.checkosfilename(path)
374 r = util.checkosfilename(path)
372 if r:
375 if r:
373 raise error.Abort(b"%s: %r" % (r, path))
376 raise error.Abort(b"%s: %r" % (r, path))
374 self.audit(path, mode=mode)
377 self.audit(path, mode=mode)
375
378
376 def __call__(
379 def __call__(
377 self,
380 self,
378 path,
381 path,
379 mode=b"r",
382 mode=b"r",
380 atomictemp=False,
383 atomictemp=False,
381 notindexed=False,
384 notindexed=False,
382 backgroundclose=False,
385 backgroundclose=False,
383 checkambig=False,
386 checkambig=False,
384 auditpath=True,
387 auditpath=True,
385 makeparentdirs=True,
388 makeparentdirs=True,
386 ):
389 ):
387 '''Open ``path`` file, which is relative to vfs root.
390 '''Open ``path`` file, which is relative to vfs root.
388
391
389 By default, parent directories are created as needed. Newly created
392 By default, parent directories are created as needed. Newly created
390 directories are marked as "not to be indexed by the content indexing
393 directories are marked as "not to be indexed by the content indexing
391 service", if ``notindexed`` is specified for "write" mode access.
394 service", if ``notindexed`` is specified for "write" mode access.
392 Set ``makeparentdirs=False`` to not create directories implicitly.
395 Set ``makeparentdirs=False`` to not create directories implicitly.
393
396
394 If ``backgroundclose`` is passed, the file may be closed asynchronously.
397 If ``backgroundclose`` is passed, the file may be closed asynchronously.
395 It can only be used if the ``self.backgroundclosing()`` context manager
398 It can only be used if the ``self.backgroundclosing()`` context manager
396 is active. This should only be specified if the following criteria hold:
399 is active. This should only be specified if the following criteria hold:
397
400
398 1. There is a potential for writing thousands of files. Unless you
401 1. There is a potential for writing thousands of files. Unless you
399 are writing thousands of files, the performance benefits of
402 are writing thousands of files, the performance benefits of
400 asynchronously closing files is not realized.
403 asynchronously closing files is not realized.
401 2. Files are opened exactly once for the ``backgroundclosing``
404 2. Files are opened exactly once for the ``backgroundclosing``
402 active duration and are therefore free of race conditions between
405 active duration and are therefore free of race conditions between
403 closing a file on a background thread and reopening it. (If the
406 closing a file on a background thread and reopening it. (If the
404 file were opened multiple times, there could be unflushed data
407 file were opened multiple times, there could be unflushed data
405 because the original file handle hasn't been flushed/closed yet.)
408 because the original file handle hasn't been flushed/closed yet.)
406
409
407 ``checkambig`` argument is passed to atomictemplfile (valid
410 ``checkambig`` argument is passed to atomictemplfile (valid
408 only for writing), and is useful only if target file is
411 only for writing), and is useful only if target file is
409 guarded by any lock (e.g. repo.lock or repo.wlock).
412 guarded by any lock (e.g. repo.lock or repo.wlock).
410
413
411 To avoid file stat ambiguity forcibly, checkambig=True involves
414 To avoid file stat ambiguity forcibly, checkambig=True involves
412 copying ``path`` file opened in "append" mode (e.g. for
415 copying ``path`` file opened in "append" mode (e.g. for
413 truncation), if it is owned by another. Therefore, use
416 truncation), if it is owned by another. Therefore, use
414 combination of append mode and checkambig=True only in limited
417 combination of append mode and checkambig=True only in limited
415 cases (see also issue5418 and issue5584 for detail).
418 cases (see also issue5418 and issue5584 for detail).
416 '''
419 '''
417 if auditpath:
420 if auditpath:
418 self._auditpath(path, mode)
421 self._auditpath(path, mode)
419 f = self.join(path)
422 f = self.join(path)
420
423
421 if b"b" not in mode:
424 if b"b" not in mode:
422 mode += b"b" # for that other OS
425 mode += b"b" # for that other OS
423
426
424 nlink = -1
427 nlink = -1
425 if mode not in (b'r', b'rb'):
428 if mode not in (b'r', b'rb'):
426 dirname, basename = util.split(f)
429 dirname, basename = util.split(f)
427 # If basename is empty, then the path is malformed because it points
430 # If basename is empty, then the path is malformed because it points
428 # to a directory. Let the posixfile() call below raise IOError.
431 # to a directory. Let the posixfile() call below raise IOError.
429 if basename:
432 if basename:
430 if atomictemp:
433 if atomictemp:
431 if makeparentdirs:
434 if makeparentdirs:
432 util.makedirs(dirname, self.createmode, notindexed)
435 util.makedirs(dirname, self.createmode, notindexed)
433 return util.atomictempfile(
436 return util.atomictempfile(
434 f, mode, self.createmode, checkambig=checkambig
437 f, mode, self.createmode, checkambig=checkambig
435 )
438 )
436 try:
439 try:
437 if b'w' in mode:
440 if b'w' in mode:
438 util.unlink(f)
441 util.unlink(f)
439 nlink = 0
442 nlink = 0
440 else:
443 else:
441 # nlinks() may behave differently for files on Windows
444 # nlinks() may behave differently for files on Windows
442 # shares if the file is open.
445 # shares if the file is open.
443 with util.posixfile(f):
446 with util.posixfile(f):
444 nlink = util.nlinks(f)
447 nlink = util.nlinks(f)
445 if nlink < 1:
448 if nlink < 1:
446 nlink = 2 # force mktempcopy (issue1922)
449 nlink = 2 # force mktempcopy (issue1922)
447 except (OSError, IOError) as e:
450 except (OSError, IOError) as e:
448 if e.errno != errno.ENOENT:
451 if e.errno != errno.ENOENT:
449 raise
452 raise
450 nlink = 0
453 nlink = 0
451 if makeparentdirs:
454 if makeparentdirs:
452 util.makedirs(dirname, self.createmode, notindexed)
455 util.makedirs(dirname, self.createmode, notindexed)
453 if nlink > 0:
456 if nlink > 0:
454 if self._trustnlink is None:
457 if self._trustnlink is None:
455 self._trustnlink = nlink > 1 or util.checknlink(f)
458 self._trustnlink = nlink > 1 or util.checknlink(f)
456 if nlink > 1 or not self._trustnlink:
459 if nlink > 1 or not self._trustnlink:
457 util.rename(util.mktempcopy(f), f)
460 util.rename(util.mktempcopy(f), f)
458 fp = util.posixfile(f, mode)
461 fp = util.posixfile(f, mode)
459 if nlink == 0:
462 if nlink == 0:
460 self._fixfilemode(f)
463 self._fixfilemode(f)
461
464
462 if checkambig:
465 if checkambig:
463 if mode in (b'r', b'rb'):
466 if mode in (b'r', b'rb'):
464 raise error.Abort(
467 raise error.Abort(
465 _(
468 _(
466 b'implementation error: mode %s is not'
469 b'implementation error: mode %s is not'
467 b' valid for checkambig=True'
470 b' valid for checkambig=True'
468 )
471 )
469 % mode
472 % mode
470 )
473 )
471 fp = checkambigatclosing(fp)
474 fp = checkambigatclosing(fp)
472
475
473 if backgroundclose and isinstance(
476 if backgroundclose and isinstance(
474 threading.currentThread(), threading._MainThread
477 threading.currentThread(), threading._MainThread
475 ):
478 ):
476 if not self._backgroundfilecloser:
479 if not self._backgroundfilecloser:
477 raise error.Abort(
480 raise error.Abort(
478 _(
481 _(
479 b'backgroundclose can only be used when a '
482 b'backgroundclose can only be used when a '
480 b'backgroundclosing context manager is active'
483 b'backgroundclosing context manager is active'
481 )
484 )
482 )
485 )
483
486
484 fp = delayclosedfile(fp, self._backgroundfilecloser)
487 fp = delayclosedfile(fp, self._backgroundfilecloser)
485
488
486 return fp
489 return fp
487
490
488 def symlink(self, src, dst):
491 def symlink(self, src, dst):
489 self.audit(dst)
492 self.audit(dst)
490 linkname = self.join(dst)
493 linkname = self.join(dst)
491 util.tryunlink(linkname)
494 util.tryunlink(linkname)
492
495
493 util.makedirs(os.path.dirname(linkname), self.createmode)
496 util.makedirs(os.path.dirname(linkname), self.createmode)
494
497
495 if self._cansymlink:
498 if self._cansymlink:
496 try:
499 try:
497 os.symlink(src, linkname)
500 os.symlink(src, linkname)
498 except OSError as err:
501 except OSError as err:
499 raise OSError(
502 raise OSError(
500 err.errno,
503 err.errno,
501 _(b'could not symlink to %r: %s')
504 _(b'could not symlink to %r: %s')
502 % (src, encoding.strtolocal(err.strerror)),
505 % (src, encoding.strtolocal(err.strerror)),
503 linkname,
506 linkname,
504 )
507 )
505 else:
508 else:
506 self.write(dst, src)
509 self.write(dst, src)
507
510
508 def join(self, path, *insidef):
511 def join(self, path, *insidef):
509 if path:
512 if path:
510 return os.path.join(self.base, path, *insidef)
513 return os.path.join(self.base, path, *insidef)
511 else:
514 else:
512 return self.base
515 return self.base
513
516
514
517
515 opener = vfs
518 opener = vfs
516
519
517
520
518 class proxyvfs(abstractvfs):
521 class proxyvfs(abstractvfs):
519 def __init__(self, vfs):
522 def __init__(self, vfs):
520 self.vfs = vfs
523 self.vfs = vfs
521
524
522 def _auditpath(self, path, mode):
525 def _auditpath(self, path, mode):
523 return self.vfs._auditpath(path, mode)
526 return self.vfs._auditpath(path, mode)
524
527
525 @property
528 @property
526 def options(self):
529 def options(self):
527 return self.vfs.options
530 return self.vfs.options
528
531
529 @options.setter
532 @options.setter
530 def options(self, value):
533 def options(self, value):
531 self.vfs.options = value
534 self.vfs.options = value
532
535
533
536
534 class filtervfs(proxyvfs, abstractvfs):
537 class filtervfs(proxyvfs, abstractvfs):
535 '''Wrapper vfs for filtering filenames with a function.'''
538 '''Wrapper vfs for filtering filenames with a function.'''
536
539
537 def __init__(self, vfs, filter):
540 def __init__(self, vfs, filter):
538 proxyvfs.__init__(self, vfs)
541 proxyvfs.__init__(self, vfs)
539 self._filter = filter
542 self._filter = filter
540
543
541 def __call__(self, path, *args, **kwargs):
544 def __call__(self, path, *args, **kwargs):
542 return self.vfs(self._filter(path), *args, **kwargs)
545 return self.vfs(self._filter(path), *args, **kwargs)
543
546
544 def join(self, path, *insidef):
547 def join(self, path, *insidef):
545 if path:
548 if path:
546 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
549 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
547 else:
550 else:
548 return self.vfs.join(path)
551 return self.vfs.join(path)
549
552
550
553
551 filteropener = filtervfs
554 filteropener = filtervfs
552
555
553
556
554 class readonlyvfs(proxyvfs):
557 class readonlyvfs(proxyvfs):
555 '''Wrapper vfs preventing any writing.'''
558 '''Wrapper vfs preventing any writing.'''
556
559
557 def __init__(self, vfs):
560 def __init__(self, vfs):
558 proxyvfs.__init__(self, vfs)
561 proxyvfs.__init__(self, vfs)
559
562
560 def __call__(self, path, mode=b'r', *args, **kw):
563 def __call__(self, path, mode=b'r', *args, **kw):
561 if mode not in (b'r', b'rb'):
564 if mode not in (b'r', b'rb'):
562 raise error.Abort(_(b'this vfs is read only'))
565 raise error.Abort(_(b'this vfs is read only'))
563 return self.vfs(path, mode, *args, **kw)
566 return self.vfs(path, mode, *args, **kw)
564
567
565 def join(self, path, *insidef):
568 def join(self, path, *insidef):
566 return self.vfs.join(path, *insidef)
569 return self.vfs.join(path, *insidef)
567
570
568
571
569 class closewrapbase(object):
572 class closewrapbase(object):
570 """Base class of wrapper, which hooks closing
573 """Base class of wrapper, which hooks closing
571
574
572 Do not instantiate outside of the vfs layer.
575 Do not instantiate outside of the vfs layer.
573 """
576 """
574
577
575 def __init__(self, fh):
578 def __init__(self, fh):
576 object.__setattr__(self, r'_origfh', fh)
579 object.__setattr__(self, r'_origfh', fh)
577
580
578 def __getattr__(self, attr):
581 def __getattr__(self, attr):
579 return getattr(self._origfh, attr)
582 return getattr(self._origfh, attr)
580
583
581 def __setattr__(self, attr, value):
584 def __setattr__(self, attr, value):
582 return setattr(self._origfh, attr, value)
585 return setattr(self._origfh, attr, value)
583
586
584 def __delattr__(self, attr):
587 def __delattr__(self, attr):
585 return delattr(self._origfh, attr)
588 return delattr(self._origfh, attr)
586
589
587 def __enter__(self):
590 def __enter__(self):
588 self._origfh.__enter__()
591 self._origfh.__enter__()
589 return self
592 return self
590
593
591 def __exit__(self, exc_type, exc_value, exc_tb):
594 def __exit__(self, exc_type, exc_value, exc_tb):
592 raise NotImplementedError('attempted instantiating ' + str(type(self)))
595 raise NotImplementedError('attempted instantiating ' + str(type(self)))
593
596
594 def close(self):
597 def close(self):
595 raise NotImplementedError('attempted instantiating ' + str(type(self)))
598 raise NotImplementedError('attempted instantiating ' + str(type(self)))
596
599
597
600
598 class delayclosedfile(closewrapbase):
601 class delayclosedfile(closewrapbase):
599 """Proxy for a file object whose close is delayed.
602 """Proxy for a file object whose close is delayed.
600
603
601 Do not instantiate outside of the vfs layer.
604 Do not instantiate outside of the vfs layer.
602 """
605 """
603
606
604 def __init__(self, fh, closer):
607 def __init__(self, fh, closer):
605 super(delayclosedfile, self).__init__(fh)
608 super(delayclosedfile, self).__init__(fh)
606 object.__setattr__(self, r'_closer', closer)
609 object.__setattr__(self, r'_closer', closer)
607
610
608 def __exit__(self, exc_type, exc_value, exc_tb):
611 def __exit__(self, exc_type, exc_value, exc_tb):
609 self._closer.close(self._origfh)
612 self._closer.close(self._origfh)
610
613
611 def close(self):
614 def close(self):
612 self._closer.close(self._origfh)
615 self._closer.close(self._origfh)
613
616
614
617
615 class backgroundfilecloser(object):
618 class backgroundfilecloser(object):
616 """Coordinates background closing of file handles on multiple threads."""
619 """Coordinates background closing of file handles on multiple threads."""
617
620
618 def __init__(self, ui, expectedcount=-1):
621 def __init__(self, ui, expectedcount=-1):
619 self._running = False
622 self._running = False
620 self._entered = False
623 self._entered = False
621 self._threads = []
624 self._threads = []
622 self._threadexception = None
625 self._threadexception = None
623
626
624 # Only Windows/NTFS has slow file closing. So only enable by default
627 # Only Windows/NTFS has slow file closing. So only enable by default
625 # on that platform. But allow to be enabled elsewhere for testing.
628 # on that platform. But allow to be enabled elsewhere for testing.
626 defaultenabled = pycompat.iswindows
629 defaultenabled = pycompat.iswindows
627 enabled = ui.configbool(b'worker', b'backgroundclose', defaultenabled)
630 enabled = ui.configbool(b'worker', b'backgroundclose', defaultenabled)
628
631
629 if not enabled:
632 if not enabled:
630 return
633 return
631
634
632 # There is overhead to starting and stopping the background threads.
635 # There is overhead to starting and stopping the background threads.
633 # Don't do background processing unless the file count is large enough
636 # Don't do background processing unless the file count is large enough
634 # to justify it.
637 # to justify it.
635 minfilecount = ui.configint(b'worker', b'backgroundcloseminfilecount')
638 minfilecount = ui.configint(b'worker', b'backgroundcloseminfilecount')
636 # FUTURE dynamically start background threads after minfilecount closes.
639 # FUTURE dynamically start background threads after minfilecount closes.
637 # (We don't currently have any callers that don't know their file count)
640 # (We don't currently have any callers that don't know their file count)
638 if expectedcount > 0 and expectedcount < minfilecount:
641 if expectedcount > 0 and expectedcount < minfilecount:
639 return
642 return
640
643
641 maxqueue = ui.configint(b'worker', b'backgroundclosemaxqueue')
644 maxqueue = ui.configint(b'worker', b'backgroundclosemaxqueue')
642 threadcount = ui.configint(b'worker', b'backgroundclosethreadcount')
645 threadcount = ui.configint(b'worker', b'backgroundclosethreadcount')
643
646
644 ui.debug(
647 ui.debug(
645 b'starting %d threads for background file closing\n' % threadcount
648 b'starting %d threads for background file closing\n' % threadcount
646 )
649 )
647
650
648 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
651 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
649 self._running = True
652 self._running = True
650
653
651 for i in range(threadcount):
654 for i in range(threadcount):
652 t = threading.Thread(target=self._worker, name='backgroundcloser')
655 t = threading.Thread(target=self._worker, name='backgroundcloser')
653 self._threads.append(t)
656 self._threads.append(t)
654 t.start()
657 t.start()
655
658
656 def __enter__(self):
659 def __enter__(self):
657 self._entered = True
660 self._entered = True
658 return self
661 return self
659
662
660 def __exit__(self, exc_type, exc_value, exc_tb):
663 def __exit__(self, exc_type, exc_value, exc_tb):
661 self._running = False
664 self._running = False
662
665
663 # Wait for threads to finish closing so open files don't linger for
666 # Wait for threads to finish closing so open files don't linger for
664 # longer than lifetime of context manager.
667 # longer than lifetime of context manager.
665 for t in self._threads:
668 for t in self._threads:
666 t.join()
669 t.join()
667
670
668 def _worker(self):
671 def _worker(self):
669 """Main routine for worker thread."""
672 """Main routine for worker thread."""
670 while True:
673 while True:
671 try:
674 try:
672 fh = self._queue.get(block=True, timeout=0.100)
675 fh = self._queue.get(block=True, timeout=0.100)
673 # Need to catch or the thread will terminate and
676 # Need to catch or the thread will terminate and
674 # we could orphan file descriptors.
677 # we could orphan file descriptors.
675 try:
678 try:
676 fh.close()
679 fh.close()
677 except Exception as e:
680 except Exception as e:
678 # Stash so can re-raise from main thread later.
681 # Stash so can re-raise from main thread later.
679 self._threadexception = e
682 self._threadexception = e
680 except pycompat.queue.Empty:
683 except pycompat.queue.Empty:
681 if not self._running:
684 if not self._running:
682 break
685 break
683
686
684 def close(self, fh):
687 def close(self, fh):
685 """Schedule a file for closing."""
688 """Schedule a file for closing."""
686 if not self._entered:
689 if not self._entered:
687 raise error.Abort(
690 raise error.Abort(
688 _(b'can only call close() when context manager active')
691 _(b'can only call close() when context manager active')
689 )
692 )
690
693
691 # If a background thread encountered an exception, raise now so we fail
694 # If a background thread encountered an exception, raise now so we fail
692 # fast. Otherwise we may potentially go on for minutes until the error
695 # fast. Otherwise we may potentially go on for minutes until the error
693 # is acted on.
696 # is acted on.
694 if self._threadexception:
697 if self._threadexception:
695 e = self._threadexception
698 e = self._threadexception
696 self._threadexception = None
699 self._threadexception = None
697 raise e
700 raise e
698
701
699 # If we're not actively running, close synchronously.
702 # If we're not actively running, close synchronously.
700 if not self._running:
703 if not self._running:
701 fh.close()
704 fh.close()
702 return
705 return
703
706
704 self._queue.put(fh, block=True, timeout=None)
707 self._queue.put(fh, block=True, timeout=None)
705
708
706
709
707 class checkambigatclosing(closewrapbase):
710 class checkambigatclosing(closewrapbase):
708 """Proxy for a file object, to avoid ambiguity of file stat
711 """Proxy for a file object, to avoid ambiguity of file stat
709
712
710 See also util.filestat for detail about "ambiguity of file stat".
713 See also util.filestat for detail about "ambiguity of file stat".
711
714
712 This proxy is useful only if the target file is guarded by any
715 This proxy is useful only if the target file is guarded by any
713 lock (e.g. repo.lock or repo.wlock)
716 lock (e.g. repo.lock or repo.wlock)
714
717
715 Do not instantiate outside of the vfs layer.
718 Do not instantiate outside of the vfs layer.
716 """
719 """
717
720
718 def __init__(self, fh):
721 def __init__(self, fh):
719 super(checkambigatclosing, self).__init__(fh)
722 super(checkambigatclosing, self).__init__(fh)
720 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
723 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
721
724
722 def _checkambig(self):
725 def _checkambig(self):
723 oldstat = self._oldstat
726 oldstat = self._oldstat
724 if oldstat.stat:
727 if oldstat.stat:
725 _avoidambig(self._origfh.name, oldstat)
728 _avoidambig(self._origfh.name, oldstat)
726
729
727 def __exit__(self, exc_type, exc_value, exc_tb):
730 def __exit__(self, exc_type, exc_value, exc_tb):
728 self._origfh.__exit__(exc_type, exc_value, exc_tb)
731 self._origfh.__exit__(exc_type, exc_value, exc_tb)
729 self._checkambig()
732 self._checkambig()
730
733
731 def close(self):
734 def close(self):
732 self._origfh.close()
735 self._origfh.close()
733 self._checkambig()
736 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now