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