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