##// END OF EJS Templates
vfs: suppress some pytype errors around us using a private attribute...
Augie Fackler -
r43783:fc19f8ab default
parent child Browse files
Show More
@@ -1,739 +1,747 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):
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(threading.currentThread(), threading._MainThread):
310 if not isinstance(
311 threading.currentThread(),
312 threading._MainThread, # pytype: disable=module-attr
313 ):
311 yield
314 yield
312 return
315 return
313 vfs = getattr(self, 'vfs', self)
316 vfs = getattr(self, 'vfs', self)
314 if getattr(vfs, '_backgroundfilecloser', None):
317 if getattr(vfs, '_backgroundfilecloser', None):
315 raise error.Abort(
318 raise error.Abort(
316 _(b'can only have 1 active background file closer')
319 _(b'can only have 1 active background file closer')
317 )
320 )
318
321
319 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
322 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
320 try:
323 try:
321 vfs._backgroundfilecloser = bfc
324 vfs._backgroundfilecloser = (
325 bfc # pytype: disable=attribute-error
326 )
322 yield bfc
327 yield bfc
323 finally:
328 finally:
324 vfs._backgroundfilecloser = None
329 vfs._backgroundfilecloser = (
330 None # pytype: disable=attribute-error
331 )
325
332
326
333
327 class vfs(abstractvfs):
334 class vfs(abstractvfs):
328 '''Operate files relative to a base directory
335 '''Operate files relative to a base directory
329
336
330 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
331 remote file access from higher level code.
338 remote file access from higher level code.
332
339
333 '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
334 (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.
335 See pathutil.pathauditor() for details.
342 See pathutil.pathauditor() for details.
336 '''
343 '''
337
344
338 def __init__(
345 def __init__(
339 self,
346 self,
340 base,
347 base,
341 audit=True,
348 audit=True,
342 cacheaudited=False,
349 cacheaudited=False,
343 expandpath=False,
350 expandpath=False,
344 realpath=False,
351 realpath=False,
345 ):
352 ):
346 if expandpath:
353 if expandpath:
347 base = util.expandpath(base)
354 base = util.expandpath(base)
348 if realpath:
355 if realpath:
349 base = os.path.realpath(base)
356 base = os.path.realpath(base)
350 self.base = base
357 self.base = base
351 self._audit = audit
358 self._audit = audit
352 if audit:
359 if audit:
353 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
360 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
354 else:
361 else:
355 self.audit = lambda path, mode=None: True
362 self.audit = lambda path, mode=None: True
356 self.createmode = None
363 self.createmode = None
357 self._trustnlink = None
364 self._trustnlink = None
358 self.options = {}
365 self.options = {}
359
366
360 @util.propertycache
367 @util.propertycache
361 def _cansymlink(self):
368 def _cansymlink(self):
362 return util.checklink(self.base)
369 return util.checklink(self.base)
363
370
364 @util.propertycache
371 @util.propertycache
365 def _chmod(self):
372 def _chmod(self):
366 return util.checkexec(self.base)
373 return util.checkexec(self.base)
367
374
368 def _fixfilemode(self, name):
375 def _fixfilemode(self, name):
369 if self.createmode is None or not self._chmod:
376 if self.createmode is None or not self._chmod:
370 return
377 return
371 os.chmod(name, self.createmode & 0o666)
378 os.chmod(name, self.createmode & 0o666)
372
379
373 def _auditpath(self, path, mode):
380 def _auditpath(self, path, mode):
374 if self._audit:
381 if self._audit:
375 if os.path.isabs(path) and path.startswith(self.base):
382 if os.path.isabs(path) and path.startswith(self.base):
376 path = os.path.relpath(path, self.base)
383 path = os.path.relpath(path, self.base)
377 r = util.checkosfilename(path)
384 r = util.checkosfilename(path)
378 if r:
385 if r:
379 raise error.Abort(b"%s: %r" % (r, path))
386 raise error.Abort(b"%s: %r" % (r, path))
380 self.audit(path, mode=mode)
387 self.audit(path, mode=mode)
381
388
382 def __call__(
389 def __call__(
383 self,
390 self,
384 path,
391 path,
385 mode=b"r",
392 mode=b"r",
386 atomictemp=False,
393 atomictemp=False,
387 notindexed=False,
394 notindexed=False,
388 backgroundclose=False,
395 backgroundclose=False,
389 checkambig=False,
396 checkambig=False,
390 auditpath=True,
397 auditpath=True,
391 makeparentdirs=True,
398 makeparentdirs=True,
392 ):
399 ):
393 '''Open ``path`` file, which is relative to vfs root.
400 '''Open ``path`` file, which is relative to vfs root.
394
401
395 By default, parent directories are created as needed. Newly created
402 By default, parent directories are created as needed. Newly created
396 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
397 service", if ``notindexed`` is specified for "write" mode access.
404 service", if ``notindexed`` is specified for "write" mode access.
398 Set ``makeparentdirs=False`` to not create directories implicitly.
405 Set ``makeparentdirs=False`` to not create directories implicitly.
399
406
400 If ``backgroundclose`` is passed, the file may be closed asynchronously.
407 If ``backgroundclose`` is passed, the file may be closed asynchronously.
401 It can only be used if the ``self.backgroundclosing()`` context manager
408 It can only be used if the ``self.backgroundclosing()`` context manager
402 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:
403
410
404 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
405 are writing thousands of files, the performance benefits of
412 are writing thousands of files, the performance benefits of
406 asynchronously closing files is not realized.
413 asynchronously closing files is not realized.
407 2. Files are opened exactly once for the ``backgroundclosing``
414 2. Files are opened exactly once for the ``backgroundclosing``
408 active duration and are therefore free of race conditions between
415 active duration and are therefore free of race conditions between
409 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
410 file were opened multiple times, there could be unflushed data
417 file were opened multiple times, there could be unflushed data
411 because the original file handle hasn't been flushed/closed yet.)
418 because the original file handle hasn't been flushed/closed yet.)
412
419
413 ``checkambig`` argument is passed to atomictemplfile (valid
420 ``checkambig`` argument is passed to atomictemplfile (valid
414 only for writing), and is useful only if target file is
421 only for writing), and is useful only if target file is
415 guarded by any lock (e.g. repo.lock or repo.wlock).
422 guarded by any lock (e.g. repo.lock or repo.wlock).
416
423
417 To avoid file stat ambiguity forcibly, checkambig=True involves
424 To avoid file stat ambiguity forcibly, checkambig=True involves
418 copying ``path`` file opened in "append" mode (e.g. for
425 copying ``path`` file opened in "append" mode (e.g. for
419 truncation), if it is owned by another. Therefore, use
426 truncation), if it is owned by another. Therefore, use
420 combination of append mode and checkambig=True only in limited
427 combination of append mode and checkambig=True only in limited
421 cases (see also issue5418 and issue5584 for detail).
428 cases (see also issue5418 and issue5584 for detail).
422 '''
429 '''
423 if auditpath:
430 if auditpath:
424 self._auditpath(path, mode)
431 self._auditpath(path, mode)
425 f = self.join(path)
432 f = self.join(path)
426
433
427 if b"b" not in mode:
434 if b"b" not in mode:
428 mode += b"b" # for that other OS
435 mode += b"b" # for that other OS
429
436
430 nlink = -1
437 nlink = -1
431 if mode not in (b'r', b'rb'):
438 if mode not in (b'r', b'rb'):
432 dirname, basename = util.split(f)
439 dirname, basename = util.split(f)
433 # 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
434 # to a directory. Let the posixfile() call below raise IOError.
441 # to a directory. Let the posixfile() call below raise IOError.
435 if basename:
442 if basename:
436 if atomictemp:
443 if atomictemp:
437 if makeparentdirs:
444 if makeparentdirs:
438 util.makedirs(dirname, self.createmode, notindexed)
445 util.makedirs(dirname, self.createmode, notindexed)
439 return util.atomictempfile(
446 return util.atomictempfile(
440 f, mode, self.createmode, checkambig=checkambig
447 f, mode, self.createmode, checkambig=checkambig
441 )
448 )
442 try:
449 try:
443 if b'w' in mode:
450 if b'w' in mode:
444 util.unlink(f)
451 util.unlink(f)
445 nlink = 0
452 nlink = 0
446 else:
453 else:
447 # nlinks() may behave differently for files on Windows
454 # nlinks() may behave differently for files on Windows
448 # shares if the file is open.
455 # shares if the file is open.
449 with util.posixfile(f):
456 with util.posixfile(f):
450 nlink = util.nlinks(f)
457 nlink = util.nlinks(f)
451 if nlink < 1:
458 if nlink < 1:
452 nlink = 2 # force mktempcopy (issue1922)
459 nlink = 2 # force mktempcopy (issue1922)
453 except (OSError, IOError) as e:
460 except (OSError, IOError) as e:
454 if e.errno != errno.ENOENT:
461 if e.errno != errno.ENOENT:
455 raise
462 raise
456 nlink = 0
463 nlink = 0
457 if makeparentdirs:
464 if makeparentdirs:
458 util.makedirs(dirname, self.createmode, notindexed)
465 util.makedirs(dirname, self.createmode, notindexed)
459 if nlink > 0:
466 if nlink > 0:
460 if self._trustnlink is None:
467 if self._trustnlink is None:
461 self._trustnlink = nlink > 1 or util.checknlink(f)
468 self._trustnlink = nlink > 1 or util.checknlink(f)
462 if nlink > 1 or not self._trustnlink:
469 if nlink > 1 or not self._trustnlink:
463 util.rename(util.mktempcopy(f), f)
470 util.rename(util.mktempcopy(f), f)
464 fp = util.posixfile(f, mode)
471 fp = util.posixfile(f, mode)
465 if nlink == 0:
472 if nlink == 0:
466 self._fixfilemode(f)
473 self._fixfilemode(f)
467
474
468 if checkambig:
475 if checkambig:
469 if mode in (b'r', b'rb'):
476 if mode in (b'r', b'rb'):
470 raise error.Abort(
477 raise error.Abort(
471 _(
478 _(
472 b'implementation error: mode %s is not'
479 b'implementation error: mode %s is not'
473 b' valid for checkambig=True'
480 b' valid for checkambig=True'
474 )
481 )
475 % mode
482 % mode
476 )
483 )
477 fp = checkambigatclosing(fp)
484 fp = checkambigatclosing(fp)
478
485
479 if backgroundclose and isinstance(
486 if backgroundclose and isinstance(
480 threading.currentThread(), threading._MainThread
487 threading.currentThread(),
488 threading._MainThread, # pytype: disable=module-attr
481 ):
489 ):
482 if not self._backgroundfilecloser:
490 if not self._backgroundfilecloser:
483 raise error.Abort(
491 raise error.Abort(
484 _(
492 _(
485 b'backgroundclose can only be used when a '
493 b'backgroundclose can only be used when a '
486 b'backgroundclosing context manager is active'
494 b'backgroundclosing context manager is active'
487 )
495 )
488 )
496 )
489
497
490 fp = delayclosedfile(fp, self._backgroundfilecloser)
498 fp = delayclosedfile(fp, self._backgroundfilecloser)
491
499
492 return fp
500 return fp
493
501
494 def symlink(self, src, dst):
502 def symlink(self, src, dst):
495 self.audit(dst)
503 self.audit(dst)
496 linkname = self.join(dst)
504 linkname = self.join(dst)
497 util.tryunlink(linkname)
505 util.tryunlink(linkname)
498
506
499 util.makedirs(os.path.dirname(linkname), self.createmode)
507 util.makedirs(os.path.dirname(linkname), self.createmode)
500
508
501 if self._cansymlink:
509 if self._cansymlink:
502 try:
510 try:
503 os.symlink(src, linkname)
511 os.symlink(src, linkname)
504 except OSError as err:
512 except OSError as err:
505 raise OSError(
513 raise OSError(
506 err.errno,
514 err.errno,
507 _(b'could not symlink to %r: %s')
515 _(b'could not symlink to %r: %s')
508 % (src, encoding.strtolocal(err.strerror)),
516 % (src, encoding.strtolocal(err.strerror)),
509 linkname,
517 linkname,
510 )
518 )
511 else:
519 else:
512 self.write(dst, src)
520 self.write(dst, src)
513
521
514 def join(self, path, *insidef):
522 def join(self, path, *insidef):
515 if path:
523 if path:
516 return os.path.join(self.base, path, *insidef)
524 return os.path.join(self.base, path, *insidef)
517 else:
525 else:
518 return self.base
526 return self.base
519
527
520
528
521 opener = vfs
529 opener = vfs
522
530
523
531
524 class proxyvfs(abstractvfs):
532 class proxyvfs(abstractvfs):
525 def __init__(self, vfs):
533 def __init__(self, vfs):
526 self.vfs = vfs
534 self.vfs = vfs
527
535
528 def _auditpath(self, path, mode):
536 def _auditpath(self, path, mode):
529 return self.vfs._auditpath(path, mode)
537 return self.vfs._auditpath(path, mode)
530
538
531 @property
539 @property
532 def options(self):
540 def options(self):
533 return self.vfs.options
541 return self.vfs.options
534
542
535 @options.setter
543 @options.setter
536 def options(self, value):
544 def options(self, value):
537 self.vfs.options = value
545 self.vfs.options = value
538
546
539
547
540 class filtervfs(proxyvfs, abstractvfs):
548 class filtervfs(proxyvfs, abstractvfs):
541 '''Wrapper vfs for filtering filenames with a function.'''
549 '''Wrapper vfs for filtering filenames with a function.'''
542
550
543 def __init__(self, vfs, filter):
551 def __init__(self, vfs, filter):
544 proxyvfs.__init__(self, vfs)
552 proxyvfs.__init__(self, vfs)
545 self._filter = filter
553 self._filter = filter
546
554
547 def __call__(self, path, *args, **kwargs):
555 def __call__(self, path, *args, **kwargs):
548 return self.vfs(self._filter(path), *args, **kwargs)
556 return self.vfs(self._filter(path), *args, **kwargs)
549
557
550 def join(self, path, *insidef):
558 def join(self, path, *insidef):
551 if path:
559 if path:
552 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
560 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
553 else:
561 else:
554 return self.vfs.join(path)
562 return self.vfs.join(path)
555
563
556
564
557 filteropener = filtervfs
565 filteropener = filtervfs
558
566
559
567
560 class readonlyvfs(proxyvfs):
568 class readonlyvfs(proxyvfs):
561 '''Wrapper vfs preventing any writing.'''
569 '''Wrapper vfs preventing any writing.'''
562
570
563 def __init__(self, vfs):
571 def __init__(self, vfs):
564 proxyvfs.__init__(self, vfs)
572 proxyvfs.__init__(self, vfs)
565
573
566 def __call__(self, path, mode=b'r', *args, **kw):
574 def __call__(self, path, mode=b'r', *args, **kw):
567 if mode not in (b'r', b'rb'):
575 if mode not in (b'r', b'rb'):
568 raise error.Abort(_(b'this vfs is read only'))
576 raise error.Abort(_(b'this vfs is read only'))
569 return self.vfs(path, mode, *args, **kw)
577 return self.vfs(path, mode, *args, **kw)
570
578
571 def join(self, path, *insidef):
579 def join(self, path, *insidef):
572 return self.vfs.join(path, *insidef)
580 return self.vfs.join(path, *insidef)
573
581
574
582
575 class closewrapbase(object):
583 class closewrapbase(object):
576 """Base class of wrapper, which hooks closing
584 """Base class of wrapper, which hooks closing
577
585
578 Do not instantiate outside of the vfs layer.
586 Do not instantiate outside of the vfs layer.
579 """
587 """
580
588
581 def __init__(self, fh):
589 def __init__(self, fh):
582 object.__setattr__(self, r'_origfh', fh)
590 object.__setattr__(self, r'_origfh', fh)
583
591
584 def __getattr__(self, attr):
592 def __getattr__(self, attr):
585 return getattr(self._origfh, attr)
593 return getattr(self._origfh, attr)
586
594
587 def __setattr__(self, attr, value):
595 def __setattr__(self, attr, value):
588 return setattr(self._origfh, attr, value)
596 return setattr(self._origfh, attr, value)
589
597
590 def __delattr__(self, attr):
598 def __delattr__(self, attr):
591 return delattr(self._origfh, attr)
599 return delattr(self._origfh, attr)
592
600
593 def __enter__(self):
601 def __enter__(self):
594 self._origfh.__enter__()
602 self._origfh.__enter__()
595 return self
603 return self
596
604
597 def __exit__(self, exc_type, exc_value, exc_tb):
605 def __exit__(self, exc_type, exc_value, exc_tb):
598 raise NotImplementedError('attempted instantiating ' + str(type(self)))
606 raise NotImplementedError('attempted instantiating ' + str(type(self)))
599
607
600 def close(self):
608 def close(self):
601 raise NotImplementedError('attempted instantiating ' + str(type(self)))
609 raise NotImplementedError('attempted instantiating ' + str(type(self)))
602
610
603
611
604 class delayclosedfile(closewrapbase):
612 class delayclosedfile(closewrapbase):
605 """Proxy for a file object whose close is delayed.
613 """Proxy for a file object whose close is delayed.
606
614
607 Do not instantiate outside of the vfs layer.
615 Do not instantiate outside of the vfs layer.
608 """
616 """
609
617
610 def __init__(self, fh, closer):
618 def __init__(self, fh, closer):
611 super(delayclosedfile, self).__init__(fh)
619 super(delayclosedfile, self).__init__(fh)
612 object.__setattr__(self, r'_closer', closer)
620 object.__setattr__(self, r'_closer', closer)
613
621
614 def __exit__(self, exc_type, exc_value, exc_tb):
622 def __exit__(self, exc_type, exc_value, exc_tb):
615 self._closer.close(self._origfh)
623 self._closer.close(self._origfh)
616
624
617 def close(self):
625 def close(self):
618 self._closer.close(self._origfh)
626 self._closer.close(self._origfh)
619
627
620
628
621 class backgroundfilecloser(object):
629 class backgroundfilecloser(object):
622 """Coordinates background closing of file handles on multiple threads."""
630 """Coordinates background closing of file handles on multiple threads."""
623
631
624 def __init__(self, ui, expectedcount=-1):
632 def __init__(self, ui, expectedcount=-1):
625 self._running = False
633 self._running = False
626 self._entered = False
634 self._entered = False
627 self._threads = []
635 self._threads = []
628 self._threadexception = None
636 self._threadexception = None
629
637
630 # Only Windows/NTFS has slow file closing. So only enable by default
638 # Only Windows/NTFS has slow file closing. So only enable by default
631 # on that platform. But allow to be enabled elsewhere for testing.
639 # on that platform. But allow to be enabled elsewhere for testing.
632 defaultenabled = pycompat.iswindows
640 defaultenabled = pycompat.iswindows
633 enabled = ui.configbool(b'worker', b'backgroundclose', defaultenabled)
641 enabled = ui.configbool(b'worker', b'backgroundclose', defaultenabled)
634
642
635 if not enabled:
643 if not enabled:
636 return
644 return
637
645
638 # There is overhead to starting and stopping the background threads.
646 # There is overhead to starting and stopping the background threads.
639 # Don't do background processing unless the file count is large enough
647 # Don't do background processing unless the file count is large enough
640 # to justify it.
648 # to justify it.
641 minfilecount = ui.configint(b'worker', b'backgroundcloseminfilecount')
649 minfilecount = ui.configint(b'worker', b'backgroundcloseminfilecount')
642 # FUTURE dynamically start background threads after minfilecount closes.
650 # FUTURE dynamically start background threads after minfilecount closes.
643 # (We don't currently have any callers that don't know their file count)
651 # (We don't currently have any callers that don't know their file count)
644 if expectedcount > 0 and expectedcount < minfilecount:
652 if expectedcount > 0 and expectedcount < minfilecount:
645 return
653 return
646
654
647 maxqueue = ui.configint(b'worker', b'backgroundclosemaxqueue')
655 maxqueue = ui.configint(b'worker', b'backgroundclosemaxqueue')
648 threadcount = ui.configint(b'worker', b'backgroundclosethreadcount')
656 threadcount = ui.configint(b'worker', b'backgroundclosethreadcount')
649
657
650 ui.debug(
658 ui.debug(
651 b'starting %d threads for background file closing\n' % threadcount
659 b'starting %d threads for background file closing\n' % threadcount
652 )
660 )
653
661
654 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
662 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
655 self._running = True
663 self._running = True
656
664
657 for i in range(threadcount):
665 for i in range(threadcount):
658 t = threading.Thread(target=self._worker, name='backgroundcloser')
666 t = threading.Thread(target=self._worker, name='backgroundcloser')
659 self._threads.append(t)
667 self._threads.append(t)
660 t.start()
668 t.start()
661
669
662 def __enter__(self):
670 def __enter__(self):
663 self._entered = True
671 self._entered = True
664 return self
672 return self
665
673
666 def __exit__(self, exc_type, exc_value, exc_tb):
674 def __exit__(self, exc_type, exc_value, exc_tb):
667 self._running = False
675 self._running = False
668
676
669 # Wait for threads to finish closing so open files don't linger for
677 # Wait for threads to finish closing so open files don't linger for
670 # longer than lifetime of context manager.
678 # longer than lifetime of context manager.
671 for t in self._threads:
679 for t in self._threads:
672 t.join()
680 t.join()
673
681
674 def _worker(self):
682 def _worker(self):
675 """Main routine for worker thread."""
683 """Main routine for worker thread."""
676 while True:
684 while True:
677 try:
685 try:
678 fh = self._queue.get(block=True, timeout=0.100)
686 fh = self._queue.get(block=True, timeout=0.100)
679 # Need to catch or the thread will terminate and
687 # Need to catch or the thread will terminate and
680 # we could orphan file descriptors.
688 # we could orphan file descriptors.
681 try:
689 try:
682 fh.close()
690 fh.close()
683 except Exception as e:
691 except Exception as e:
684 # Stash so can re-raise from main thread later.
692 # Stash so can re-raise from main thread later.
685 self._threadexception = e
693 self._threadexception = e
686 except pycompat.queue.Empty:
694 except pycompat.queue.Empty:
687 if not self._running:
695 if not self._running:
688 break
696 break
689
697
690 def close(self, fh):
698 def close(self, fh):
691 """Schedule a file for closing."""
699 """Schedule a file for closing."""
692 if not self._entered:
700 if not self._entered:
693 raise error.Abort(
701 raise error.Abort(
694 _(b'can only call close() when context manager active')
702 _(b'can only call close() when context manager active')
695 )
703 )
696
704
697 # If a background thread encountered an exception, raise now so we fail
705 # If a background thread encountered an exception, raise now so we fail
698 # fast. Otherwise we may potentially go on for minutes until the error
706 # fast. Otherwise we may potentially go on for minutes until the error
699 # is acted on.
707 # is acted on.
700 if self._threadexception:
708 if self._threadexception:
701 e = self._threadexception
709 e = self._threadexception
702 self._threadexception = None
710 self._threadexception = None
703 raise e
711 raise e
704
712
705 # If we're not actively running, close synchronously.
713 # If we're not actively running, close synchronously.
706 if not self._running:
714 if not self._running:
707 fh.close()
715 fh.close()
708 return
716 return
709
717
710 self._queue.put(fh, block=True, timeout=None)
718 self._queue.put(fh, block=True, timeout=None)
711
719
712
720
713 class checkambigatclosing(closewrapbase):
721 class checkambigatclosing(closewrapbase):
714 """Proxy for a file object, to avoid ambiguity of file stat
722 """Proxy for a file object, to avoid ambiguity of file stat
715
723
716 See also util.filestat for detail about "ambiguity of file stat".
724 See also util.filestat for detail about "ambiguity of file stat".
717
725
718 This proxy is useful only if the target file is guarded by any
726 This proxy is useful only if the target file is guarded by any
719 lock (e.g. repo.lock or repo.wlock)
727 lock (e.g. repo.lock or repo.wlock)
720
728
721 Do not instantiate outside of the vfs layer.
729 Do not instantiate outside of the vfs layer.
722 """
730 """
723
731
724 def __init__(self, fh):
732 def __init__(self, fh):
725 super(checkambigatclosing, self).__init__(fh)
733 super(checkambigatclosing, self).__init__(fh)
726 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
734 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
727
735
728 def _checkambig(self):
736 def _checkambig(self):
729 oldstat = self._oldstat
737 oldstat = self._oldstat
730 if oldstat.stat:
738 if oldstat.stat:
731 _avoidambig(self._origfh.name, oldstat)
739 _avoidambig(self._origfh.name, oldstat)
732
740
733 def __exit__(self, exc_type, exc_value, exc_tb):
741 def __exit__(self, exc_type, exc_value, exc_tb):
734 self._origfh.__exit__(exc_type, exc_value, exc_tb)
742 self._origfh.__exit__(exc_type, exc_value, exc_tb)
735 self._checkambig()
743 self._checkambig()
736
744
737 def close(self):
745 def close(self):
738 self._origfh.close()
746 self._origfh.close()
739 self._checkambig()
747 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now