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