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