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