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