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