##// END OF EJS Templates
vfs: use tryunlink
Ryan McElroy -
r31549:18b9d9b9 default
parent child Browse files
Show More
@@ -1,640 +1,637 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):
226 def tryunlink(self, path=None):
227 """Attempt to remove a file, ignoring missing file errors."""
227 """Attempt to remove a file, ignoring missing file errors."""
228 util.tryunlink(self.join(path))
228 util.tryunlink(self.join(path))
229
229
230 def unlinkpath(self, path=None, ignoremissing=False):
230 def unlinkpath(self, path=None, ignoremissing=False):
231 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing)
231 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing)
232
232
233 def utime(self, path=None, t=None):
233 def utime(self, path=None, t=None):
234 return os.utime(self.join(path), t)
234 return os.utime(self.join(path), t)
235
235
236 def walk(self, path=None, onerror=None):
236 def walk(self, path=None, onerror=None):
237 """Yield (dirpath, dirs, files) tuple for each directories under path
237 """Yield (dirpath, dirs, files) tuple for each directories under path
238
238
239 ``dirpath`` is relative one from the root of this vfs. This
239 ``dirpath`` is relative one from the root of this vfs. This
240 uses ``os.sep`` as path separator, even you specify POSIX
240 uses ``os.sep`` as path separator, even you specify POSIX
241 style ``path``.
241 style ``path``.
242
242
243 "The root of this vfs" is represented as empty ``dirpath``.
243 "The root of this vfs" is represented as empty ``dirpath``.
244 """
244 """
245 root = os.path.normpath(self.join(None))
245 root = os.path.normpath(self.join(None))
246 # when dirpath == root, dirpath[prefixlen:] becomes empty
246 # when dirpath == root, dirpath[prefixlen:] becomes empty
247 # because len(dirpath) < prefixlen.
247 # because len(dirpath) < prefixlen.
248 prefixlen = len(pathutil.normasprefix(root))
248 prefixlen = len(pathutil.normasprefix(root))
249 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):
250 yield (dirpath[prefixlen:], dirs, files)
250 yield (dirpath[prefixlen:], dirs, files)
251
251
252 @contextlib.contextmanager
252 @contextlib.contextmanager
253 def backgroundclosing(self, ui, expectedcount=-1):
253 def backgroundclosing(self, ui, expectedcount=-1):
254 """Allow files to be closed asynchronously.
254 """Allow files to be closed asynchronously.
255
255
256 When this context manager is active, ``backgroundclose`` can be passed
256 When this context manager is active, ``backgroundclose`` can be passed
257 to ``__call__``/``open`` to result in the file possibly being closed
257 to ``__call__``/``open`` to result in the file possibly being closed
258 asynchronously, on a background thread.
258 asynchronously, on a background thread.
259 """
259 """
260 # 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
261 # have a use case.
261 # have a use case.
262 vfs = getattr(self, 'vfs', self)
262 vfs = getattr(self, 'vfs', self)
263 if getattr(vfs, '_backgroundfilecloser', None):
263 if getattr(vfs, '_backgroundfilecloser', None):
264 raise error.Abort(
264 raise error.Abort(
265 _('can only have 1 active background file closer'))
265 _('can only have 1 active background file closer'))
266
266
267 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
267 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
268 try:
268 try:
269 vfs._backgroundfilecloser = bfc
269 vfs._backgroundfilecloser = bfc
270 yield bfc
270 yield bfc
271 finally:
271 finally:
272 vfs._backgroundfilecloser = None
272 vfs._backgroundfilecloser = None
273
273
274 class vfs(abstractvfs):
274 class vfs(abstractvfs):
275 '''Operate files relative to a base directory
275 '''Operate files relative to a base directory
276
276
277 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
278 remote file access from higher level code.
278 remote file access from higher level code.
279 '''
279 '''
280 def __init__(self, base, audit=True, expandpath=False, realpath=False):
280 def __init__(self, base, audit=True, expandpath=False, realpath=False):
281 if expandpath:
281 if expandpath:
282 base = util.expandpath(base)
282 base = util.expandpath(base)
283 if realpath:
283 if realpath:
284 base = os.path.realpath(base)
284 base = os.path.realpath(base)
285 self.base = base
285 self.base = base
286 self.mustaudit = audit
286 self.mustaudit = audit
287 self.createmode = None
287 self.createmode = None
288 self._trustnlink = None
288 self._trustnlink = None
289
289
290 @property
290 @property
291 def mustaudit(self):
291 def mustaudit(self):
292 return self._audit
292 return self._audit
293
293
294 @mustaudit.setter
294 @mustaudit.setter
295 def mustaudit(self, onoff):
295 def mustaudit(self, onoff):
296 self._audit = onoff
296 self._audit = onoff
297 if onoff:
297 if onoff:
298 self.audit = pathutil.pathauditor(self.base)
298 self.audit = pathutil.pathauditor(self.base)
299 else:
299 else:
300 self.audit = util.always
300 self.audit = util.always
301
301
302 @util.propertycache
302 @util.propertycache
303 def _cansymlink(self):
303 def _cansymlink(self):
304 return util.checklink(self.base)
304 return util.checklink(self.base)
305
305
306 @util.propertycache
306 @util.propertycache
307 def _chmod(self):
307 def _chmod(self):
308 return util.checkexec(self.base)
308 return util.checkexec(self.base)
309
309
310 def _fixfilemode(self, name):
310 def _fixfilemode(self, name):
311 if self.createmode is None or not self._chmod:
311 if self.createmode is None or not self._chmod:
312 return
312 return
313 os.chmod(name, self.createmode & 0o666)
313 os.chmod(name, self.createmode & 0o666)
314
314
315 def __call__(self, path, mode="r", text=False, atomictemp=False,
315 def __call__(self, path, mode="r", text=False, atomictemp=False,
316 notindexed=False, backgroundclose=False, checkambig=False):
316 notindexed=False, backgroundclose=False, checkambig=False):
317 '''Open ``path`` file, which is relative to vfs root.
317 '''Open ``path`` file, which is relative to vfs root.
318
318
319 Newly created directories are marked as "not to be indexed by
319 Newly created directories are marked as "not to be indexed by
320 the content indexing service", if ``notindexed`` is specified
320 the content indexing service", if ``notindexed`` is specified
321 for "write" mode access.
321 for "write" mode access.
322
322
323 If ``backgroundclose`` is passed, the file may be closed asynchronously.
323 If ``backgroundclose`` is passed, the file may be closed asynchronously.
324 It can only be used if the ``self.backgroundclosing()`` context manager
324 It can only be used if the ``self.backgroundclosing()`` context manager
325 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:
326
326
327 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
328 are writing thousands of files, the performance benefits of
328 are writing thousands of files, the performance benefits of
329 asynchronously closing files is not realized.
329 asynchronously closing files is not realized.
330 2. Files are opened exactly once for the ``backgroundclosing``
330 2. Files are opened exactly once for the ``backgroundclosing``
331 active duration and are therefore free of race conditions between
331 active duration and are therefore free of race conditions between
332 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
333 file were opened multiple times, there could be unflushed data
333 file were opened multiple times, there could be unflushed data
334 because the original file handle hasn't been flushed/closed yet.)
334 because the original file handle hasn't been flushed/closed yet.)
335
335
336 ``checkambig`` argument is passed to atomictemplfile (valid
336 ``checkambig`` argument is passed to atomictemplfile (valid
337 only for writing), and is useful only if target file is
337 only for writing), and is useful only if target file is
338 guarded by any lock (e.g. repo.lock or repo.wlock).
338 guarded by any lock (e.g. repo.lock or repo.wlock).
339 '''
339 '''
340 if self._audit:
340 if self._audit:
341 r = util.checkosfilename(path)
341 r = util.checkosfilename(path)
342 if r:
342 if r:
343 raise error.Abort("%s: %r" % (r, path))
343 raise error.Abort("%s: %r" % (r, path))
344 self.audit(path)
344 self.audit(path)
345 f = self.join(path)
345 f = self.join(path)
346
346
347 if not text and "b" not in mode:
347 if not text and "b" not in mode:
348 mode += "b" # for that other OS
348 mode += "b" # for that other OS
349
349
350 nlink = -1
350 nlink = -1
351 if mode not in ('r', 'rb'):
351 if mode not in ('r', 'rb'):
352 dirname, basename = util.split(f)
352 dirname, basename = util.split(f)
353 # 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
354 # to a directory. Let the posixfile() call below raise IOError.
354 # to a directory. Let the posixfile() call below raise IOError.
355 if basename:
355 if basename:
356 if atomictemp:
356 if atomictemp:
357 util.makedirs(dirname, self.createmode, notindexed)
357 util.makedirs(dirname, self.createmode, notindexed)
358 return util.atomictempfile(f, mode, self.createmode,
358 return util.atomictempfile(f, mode, self.createmode,
359 checkambig=checkambig)
359 checkambig=checkambig)
360 try:
360 try:
361 if 'w' in mode:
361 if 'w' in mode:
362 util.unlink(f)
362 util.unlink(f)
363 nlink = 0
363 nlink = 0
364 else:
364 else:
365 # nlinks() may behave differently for files on Windows
365 # nlinks() may behave differently for files on Windows
366 # shares if the file is open.
366 # shares if the file is open.
367 with util.posixfile(f):
367 with util.posixfile(f):
368 nlink = util.nlinks(f)
368 nlink = util.nlinks(f)
369 if nlink < 1:
369 if nlink < 1:
370 nlink = 2 # force mktempcopy (issue1922)
370 nlink = 2 # force mktempcopy (issue1922)
371 except (OSError, IOError) as e:
371 except (OSError, IOError) as e:
372 if e.errno != errno.ENOENT:
372 if e.errno != errno.ENOENT:
373 raise
373 raise
374 nlink = 0
374 nlink = 0
375 util.makedirs(dirname, self.createmode, notindexed)
375 util.makedirs(dirname, self.createmode, notindexed)
376 if nlink > 0:
376 if nlink > 0:
377 if self._trustnlink is None:
377 if self._trustnlink is None:
378 self._trustnlink = nlink > 1 or util.checknlink(f)
378 self._trustnlink = nlink > 1 or util.checknlink(f)
379 if nlink > 1 or not self._trustnlink:
379 if nlink > 1 or not self._trustnlink:
380 util.rename(util.mktempcopy(f), f)
380 util.rename(util.mktempcopy(f), f)
381 fp = util.posixfile(f, mode)
381 fp = util.posixfile(f, mode)
382 if nlink == 0:
382 if nlink == 0:
383 self._fixfilemode(f)
383 self._fixfilemode(f)
384
384
385 if checkambig:
385 if checkambig:
386 if mode in ('r', 'rb'):
386 if mode in ('r', 'rb'):
387 raise error.Abort(_('implementation error: mode %s is not'
387 raise error.Abort(_('implementation error: mode %s is not'
388 ' valid for checkambig=True') % mode)
388 ' valid for checkambig=True') % mode)
389 fp = checkambigatclosing(fp)
389 fp = checkambigatclosing(fp)
390
390
391 if backgroundclose:
391 if backgroundclose:
392 if not self._backgroundfilecloser:
392 if not self._backgroundfilecloser:
393 raise error.Abort(_('backgroundclose can only be used when a '
393 raise error.Abort(_('backgroundclose can only be used when a '
394 'backgroundclosing context manager is active')
394 'backgroundclosing context manager is active')
395 )
395 )
396
396
397 fp = delayclosedfile(fp, self._backgroundfilecloser)
397 fp = delayclosedfile(fp, self._backgroundfilecloser)
398
398
399 return fp
399 return fp
400
400
401 def symlink(self, src, dst):
401 def symlink(self, src, dst):
402 self.audit(dst)
402 self.audit(dst)
403 linkname = self.join(dst)
403 linkname = self.join(dst)
404 try:
404 util.tryunlink(linkname)
405 os.unlink(linkname)
406 except OSError:
407 pass
408
405
409 util.makedirs(os.path.dirname(linkname), self.createmode)
406 util.makedirs(os.path.dirname(linkname), self.createmode)
410
407
411 if self._cansymlink:
408 if self._cansymlink:
412 try:
409 try:
413 os.symlink(src, linkname)
410 os.symlink(src, linkname)
414 except OSError as err:
411 except OSError as err:
415 raise OSError(err.errno, _('could not symlink to %r: %s') %
412 raise OSError(err.errno, _('could not symlink to %r: %s') %
416 (src, err.strerror), linkname)
413 (src, err.strerror), linkname)
417 else:
414 else:
418 self.write(dst, src)
415 self.write(dst, src)
419
416
420 def join(self, path, *insidef):
417 def join(self, path, *insidef):
421 if path:
418 if path:
422 return os.path.join(self.base, path, *insidef)
419 return os.path.join(self.base, path, *insidef)
423 else:
420 else:
424 return self.base
421 return self.base
425
422
426 opener = vfs
423 opener = vfs
427
424
428 class auditvfs(object):
425 class auditvfs(object):
429 def __init__(self, vfs):
426 def __init__(self, vfs):
430 self.vfs = vfs
427 self.vfs = vfs
431
428
432 @property
429 @property
433 def mustaudit(self):
430 def mustaudit(self):
434 return self.vfs.mustaudit
431 return self.vfs.mustaudit
435
432
436 @mustaudit.setter
433 @mustaudit.setter
437 def mustaudit(self, onoff):
434 def mustaudit(self, onoff):
438 self.vfs.mustaudit = onoff
435 self.vfs.mustaudit = onoff
439
436
440 @property
437 @property
441 def options(self):
438 def options(self):
442 return self.vfs.options
439 return self.vfs.options
443
440
444 @options.setter
441 @options.setter
445 def options(self, value):
442 def options(self, value):
446 self.vfs.options = value
443 self.vfs.options = value
447
444
448 class filtervfs(abstractvfs, auditvfs):
445 class filtervfs(abstractvfs, auditvfs):
449 '''Wrapper vfs for filtering filenames with a function.'''
446 '''Wrapper vfs for filtering filenames with a function.'''
450
447
451 def __init__(self, vfs, filter):
448 def __init__(self, vfs, filter):
452 auditvfs.__init__(self, vfs)
449 auditvfs.__init__(self, vfs)
453 self._filter = filter
450 self._filter = filter
454
451
455 def __call__(self, path, *args, **kwargs):
452 def __call__(self, path, *args, **kwargs):
456 return self.vfs(self._filter(path), *args, **kwargs)
453 return self.vfs(self._filter(path), *args, **kwargs)
457
454
458 def join(self, path, *insidef):
455 def join(self, path, *insidef):
459 if path:
456 if path:
460 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
457 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
461 else:
458 else:
462 return self.vfs.join(path)
459 return self.vfs.join(path)
463
460
464 filteropener = filtervfs
461 filteropener = filtervfs
465
462
466 class readonlyvfs(abstractvfs, auditvfs):
463 class readonlyvfs(abstractvfs, auditvfs):
467 '''Wrapper vfs preventing any writing.'''
464 '''Wrapper vfs preventing any writing.'''
468
465
469 def __init__(self, vfs):
466 def __init__(self, vfs):
470 auditvfs.__init__(self, vfs)
467 auditvfs.__init__(self, vfs)
471
468
472 def __call__(self, path, mode='r', *args, **kw):
469 def __call__(self, path, mode='r', *args, **kw):
473 if mode not in ('r', 'rb'):
470 if mode not in ('r', 'rb'):
474 raise error.Abort(_('this vfs is read only'))
471 raise error.Abort(_('this vfs is read only'))
475 return self.vfs(path, mode, *args, **kw)
472 return self.vfs(path, mode, *args, **kw)
476
473
477 def join(self, path, *insidef):
474 def join(self, path, *insidef):
478 return self.vfs.join(path, *insidef)
475 return self.vfs.join(path, *insidef)
479
476
480 class closewrapbase(object):
477 class closewrapbase(object):
481 """Base class of wrapper, which hooks closing
478 """Base class of wrapper, which hooks closing
482
479
483 Do not instantiate outside of the vfs layer.
480 Do not instantiate outside of the vfs layer.
484 """
481 """
485 def __init__(self, fh):
482 def __init__(self, fh):
486 object.__setattr__(self, '_origfh', fh)
483 object.__setattr__(self, '_origfh', fh)
487
484
488 def __getattr__(self, attr):
485 def __getattr__(self, attr):
489 return getattr(self._origfh, attr)
486 return getattr(self._origfh, attr)
490
487
491 def __setattr__(self, attr, value):
488 def __setattr__(self, attr, value):
492 return setattr(self._origfh, attr, value)
489 return setattr(self._origfh, attr, value)
493
490
494 def __delattr__(self, attr):
491 def __delattr__(self, attr):
495 return delattr(self._origfh, attr)
492 return delattr(self._origfh, attr)
496
493
497 def __enter__(self):
494 def __enter__(self):
498 return self._origfh.__enter__()
495 return self._origfh.__enter__()
499
496
500 def __exit__(self, exc_type, exc_value, exc_tb):
497 def __exit__(self, exc_type, exc_value, exc_tb):
501 raise NotImplementedError('attempted instantiating ' + str(type(self)))
498 raise NotImplementedError('attempted instantiating ' + str(type(self)))
502
499
503 def close(self):
500 def close(self):
504 raise NotImplementedError('attempted instantiating ' + str(type(self)))
501 raise NotImplementedError('attempted instantiating ' + str(type(self)))
505
502
506 class delayclosedfile(closewrapbase):
503 class delayclosedfile(closewrapbase):
507 """Proxy for a file object whose close is delayed.
504 """Proxy for a file object whose close is delayed.
508
505
509 Do not instantiate outside of the vfs layer.
506 Do not instantiate outside of the vfs layer.
510 """
507 """
511 def __init__(self, fh, closer):
508 def __init__(self, fh, closer):
512 super(delayclosedfile, self).__init__(fh)
509 super(delayclosedfile, self).__init__(fh)
513 object.__setattr__(self, '_closer', closer)
510 object.__setattr__(self, '_closer', closer)
514
511
515 def __exit__(self, exc_type, exc_value, exc_tb):
512 def __exit__(self, exc_type, exc_value, exc_tb):
516 self._closer.close(self._origfh)
513 self._closer.close(self._origfh)
517
514
518 def close(self):
515 def close(self):
519 self._closer.close(self._origfh)
516 self._closer.close(self._origfh)
520
517
521 class backgroundfilecloser(object):
518 class backgroundfilecloser(object):
522 """Coordinates background closing of file handles on multiple threads."""
519 """Coordinates background closing of file handles on multiple threads."""
523 def __init__(self, ui, expectedcount=-1):
520 def __init__(self, ui, expectedcount=-1):
524 self._running = False
521 self._running = False
525 self._entered = False
522 self._entered = False
526 self._threads = []
523 self._threads = []
527 self._threadexception = None
524 self._threadexception = None
528
525
529 # Only Windows/NTFS has slow file closing. So only enable by default
526 # Only Windows/NTFS has slow file closing. So only enable by default
530 # on that platform. But allow to be enabled elsewhere for testing.
527 # on that platform. But allow to be enabled elsewhere for testing.
531 defaultenabled = pycompat.osname == 'nt'
528 defaultenabled = pycompat.osname == 'nt'
532 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
529 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
533
530
534 if not enabled:
531 if not enabled:
535 return
532 return
536
533
537 # There is overhead to starting and stopping the background threads.
534 # There is overhead to starting and stopping the background threads.
538 # Don't do background processing unless the file count is large enough
535 # Don't do background processing unless the file count is large enough
539 # to justify it.
536 # to justify it.
540 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount',
537 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount',
541 2048)
538 2048)
542 # FUTURE dynamically start background threads after minfilecount closes.
539 # FUTURE dynamically start background threads after minfilecount closes.
543 # (We don't currently have any callers that don't know their file count)
540 # (We don't currently have any callers that don't know their file count)
544 if expectedcount > 0 and expectedcount < minfilecount:
541 if expectedcount > 0 and expectedcount < minfilecount:
545 return
542 return
546
543
547 # Windows defaults to a limit of 512 open files. A buffer of 128
544 # Windows defaults to a limit of 512 open files. A buffer of 128
548 # should give us enough headway.
545 # should give us enough headway.
549 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue', 384)
546 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue', 384)
550 threadcount = ui.configint('worker', 'backgroundclosethreadcount', 4)
547 threadcount = ui.configint('worker', 'backgroundclosethreadcount', 4)
551
548
552 ui.debug('starting %d threads for background file closing\n' %
549 ui.debug('starting %d threads for background file closing\n' %
553 threadcount)
550 threadcount)
554
551
555 self._queue = util.queue(maxsize=maxqueue)
552 self._queue = util.queue(maxsize=maxqueue)
556 self._running = True
553 self._running = True
557
554
558 for i in range(threadcount):
555 for i in range(threadcount):
559 t = threading.Thread(target=self._worker, name='backgroundcloser')
556 t = threading.Thread(target=self._worker, name='backgroundcloser')
560 self._threads.append(t)
557 self._threads.append(t)
561 t.start()
558 t.start()
562
559
563 def __enter__(self):
560 def __enter__(self):
564 self._entered = True
561 self._entered = True
565 return self
562 return self
566
563
567 def __exit__(self, exc_type, exc_value, exc_tb):
564 def __exit__(self, exc_type, exc_value, exc_tb):
568 self._running = False
565 self._running = False
569
566
570 # Wait for threads to finish closing so open files don't linger for
567 # Wait for threads to finish closing so open files don't linger for
571 # longer than lifetime of context manager.
568 # longer than lifetime of context manager.
572 for t in self._threads:
569 for t in self._threads:
573 t.join()
570 t.join()
574
571
575 def _worker(self):
572 def _worker(self):
576 """Main routine for worker thread."""
573 """Main routine for worker thread."""
577 while True:
574 while True:
578 try:
575 try:
579 fh = self._queue.get(block=True, timeout=0.100)
576 fh = self._queue.get(block=True, timeout=0.100)
580 # Need to catch or the thread will terminate and
577 # Need to catch or the thread will terminate and
581 # we could orphan file descriptors.
578 # we could orphan file descriptors.
582 try:
579 try:
583 fh.close()
580 fh.close()
584 except Exception as e:
581 except Exception as e:
585 # Stash so can re-raise from main thread later.
582 # Stash so can re-raise from main thread later.
586 self._threadexception = e
583 self._threadexception = e
587 except util.empty:
584 except util.empty:
588 if not self._running:
585 if not self._running:
589 break
586 break
590
587
591 def close(self, fh):
588 def close(self, fh):
592 """Schedule a file for closing."""
589 """Schedule a file for closing."""
593 if not self._entered:
590 if not self._entered:
594 raise error.Abort(_('can only call close() when context manager '
591 raise error.Abort(_('can only call close() when context manager '
595 'active'))
592 'active'))
596
593
597 # If a background thread encountered an exception, raise now so we fail
594 # If a background thread encountered an exception, raise now so we fail
598 # fast. Otherwise we may potentially go on for minutes until the error
595 # fast. Otherwise we may potentially go on for minutes until the error
599 # is acted on.
596 # is acted on.
600 if self._threadexception:
597 if self._threadexception:
601 e = self._threadexception
598 e = self._threadexception
602 self._threadexception = None
599 self._threadexception = None
603 raise e
600 raise e
604
601
605 # If we're not actively running, close synchronously.
602 # If we're not actively running, close synchronously.
606 if not self._running:
603 if not self._running:
607 fh.close()
604 fh.close()
608 return
605 return
609
606
610 self._queue.put(fh, block=True, timeout=None)
607 self._queue.put(fh, block=True, timeout=None)
611
608
612 class checkambigatclosing(closewrapbase):
609 class checkambigatclosing(closewrapbase):
613 """Proxy for a file object, to avoid ambiguity of file stat
610 """Proxy for a file object, to avoid ambiguity of file stat
614
611
615 See also util.filestat for detail about "ambiguity of file stat".
612 See also util.filestat for detail about "ambiguity of file stat".
616
613
617 This proxy is useful only if the target file is guarded by any
614 This proxy is useful only if the target file is guarded by any
618 lock (e.g. repo.lock or repo.wlock)
615 lock (e.g. repo.lock or repo.wlock)
619
616
620 Do not instantiate outside of the vfs layer.
617 Do not instantiate outside of the vfs layer.
621 """
618 """
622 def __init__(self, fh):
619 def __init__(self, fh):
623 super(checkambigatclosing, self).__init__(fh)
620 super(checkambigatclosing, self).__init__(fh)
624 object.__setattr__(self, '_oldstat', util.filestat(fh.name))
621 object.__setattr__(self, '_oldstat', util.filestat(fh.name))
625
622
626 def _checkambig(self):
623 def _checkambig(self):
627 oldstat = self._oldstat
624 oldstat = self._oldstat
628 if oldstat.stat:
625 if oldstat.stat:
629 newstat = util.filestat(self._origfh.name)
626 newstat = util.filestat(self._origfh.name)
630 if newstat.isambig(oldstat):
627 if newstat.isambig(oldstat):
631 # stat of changed file is ambiguous to original one
628 # stat of changed file is ambiguous to original one
632 newstat.avoidambig(self._origfh.name, oldstat)
629 newstat.avoidambig(self._origfh.name, oldstat)
633
630
634 def __exit__(self, exc_type, exc_value, exc_tb):
631 def __exit__(self, exc_type, exc_value, exc_tb):
635 self._origfh.__exit__(exc_type, exc_value, exc_tb)
632 self._origfh.__exit__(exc_type, exc_value, exc_tb)
636 self._checkambig()
633 self._checkambig()
637
634
638 def close(self):
635 def close(self):
639 self._origfh.close()
636 self._origfh.close()
640 self._checkambig()
637 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now