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