##// END OF EJS Templates
dirstate: move delaywrite logic from write to _write...
Matt Mackall -
r27397:6c6b48ac default
parent child Browse files
Show More
@@ -1,1170 +1,1171 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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
7
8 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import scmutil, util, osutil, parsers, encoding, pathutil, error
10 import scmutil, util, osutil, parsers, encoding, pathutil, error
11 import os, stat, errno
11 import os, stat, errno
12 import match as matchmod
12 import match as matchmod
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15 filecache = scmutil.filecache
15 filecache = scmutil.filecache
16 _rangemask = 0x7fffffff
16 _rangemask = 0x7fffffff
17
17
18 dirstatetuple = parsers.dirstatetuple
18 dirstatetuple = parsers.dirstatetuple
19
19
20 class repocache(filecache):
20 class repocache(filecache):
21 """filecache for files in .hg/"""
21 """filecache for files in .hg/"""
22 def join(self, obj, fname):
22 def join(self, obj, fname):
23 return obj._opener.join(fname)
23 return obj._opener.join(fname)
24
24
25 class rootcache(filecache):
25 class rootcache(filecache):
26 """filecache for files in the repository root"""
26 """filecache for files in the repository root"""
27 def join(self, obj, fname):
27 def join(self, obj, fname):
28 return obj._join(fname)
28 return obj._join(fname)
29
29
30 def _getfsnow(vfs):
30 def _getfsnow(vfs):
31 '''Get "now" timestamp on filesystem'''
31 '''Get "now" timestamp on filesystem'''
32 tmpfd, tmpname = vfs.mkstemp()
32 tmpfd, tmpname = vfs.mkstemp()
33 try:
33 try:
34 return os.fstat(tmpfd).st_mtime
34 return os.fstat(tmpfd).st_mtime
35 finally:
35 finally:
36 os.close(tmpfd)
36 os.close(tmpfd)
37 vfs.unlink(tmpname)
37 vfs.unlink(tmpname)
38
38
39 def _trypending(root, vfs, filename):
39 def _trypending(root, vfs, filename):
40 '''Open file to be read according to HG_PENDING environment variable
40 '''Open file to be read according to HG_PENDING environment variable
41
41
42 This opens '.pending' of specified 'filename' only when HG_PENDING
42 This opens '.pending' of specified 'filename' only when HG_PENDING
43 is equal to 'root'.
43 is equal to 'root'.
44
44
45 This returns '(fp, is_pending_opened)' tuple.
45 This returns '(fp, is_pending_opened)' tuple.
46 '''
46 '''
47 if root == os.environ.get('HG_PENDING'):
47 if root == os.environ.get('HG_PENDING'):
48 try:
48 try:
49 return (vfs('%s.pending' % filename), True)
49 return (vfs('%s.pending' % filename), True)
50 except IOError as inst:
50 except IOError as inst:
51 if inst.errno != errno.ENOENT:
51 if inst.errno != errno.ENOENT:
52 raise
52 raise
53 return (vfs(filename), False)
53 return (vfs(filename), False)
54
54
55 class dirstate(object):
55 class dirstate(object):
56
56
57 def __init__(self, opener, ui, root, validate):
57 def __init__(self, opener, ui, root, validate):
58 '''Create a new dirstate object.
58 '''Create a new dirstate object.
59
59
60 opener is an open()-like callable that can be used to open the
60 opener is an open()-like callable that can be used to open the
61 dirstate file; root is the root of the directory tracked by
61 dirstate file; root is the root of the directory tracked by
62 the dirstate.
62 the dirstate.
63 '''
63 '''
64 self._opener = opener
64 self._opener = opener
65 self._validate = validate
65 self._validate = validate
66 self._root = root
66 self._root = root
67 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
67 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
68 # UNC path pointing to root share (issue4557)
68 # UNC path pointing to root share (issue4557)
69 self._rootdir = pathutil.normasprefix(root)
69 self._rootdir = pathutil.normasprefix(root)
70 # internal config: ui.forcecwd
70 # internal config: ui.forcecwd
71 forcecwd = ui.config('ui', 'forcecwd')
71 forcecwd = ui.config('ui', 'forcecwd')
72 if forcecwd:
72 if forcecwd:
73 self._cwd = forcecwd
73 self._cwd = forcecwd
74 self._dirty = False
74 self._dirty = False
75 self._dirtypl = False
75 self._dirtypl = False
76 self._lastnormaltime = 0
76 self._lastnormaltime = 0
77 self._ui = ui
77 self._ui = ui
78 self._filecache = {}
78 self._filecache = {}
79 self._parentwriters = 0
79 self._parentwriters = 0
80 self._filename = 'dirstate'
80 self._filename = 'dirstate'
81 self._pendingfilename = '%s.pending' % self._filename
81 self._pendingfilename = '%s.pending' % self._filename
82
82
83 # for consistent view between _pl() and _read() invocations
83 # for consistent view between _pl() and _read() invocations
84 self._pendingmode = None
84 self._pendingmode = None
85
85
86 def beginparentchange(self):
86 def beginparentchange(self):
87 '''Marks the beginning of a set of changes that involve changing
87 '''Marks the beginning of a set of changes that involve changing
88 the dirstate parents. If there is an exception during this time,
88 the dirstate parents. If there is an exception during this time,
89 the dirstate will not be written when the wlock is released. This
89 the dirstate will not be written when the wlock is released. This
90 prevents writing an incoherent dirstate where the parent doesn't
90 prevents writing an incoherent dirstate where the parent doesn't
91 match the contents.
91 match the contents.
92 '''
92 '''
93 self._parentwriters += 1
93 self._parentwriters += 1
94
94
95 def endparentchange(self):
95 def endparentchange(self):
96 '''Marks the end of a set of changes that involve changing the
96 '''Marks the end of a set of changes that involve changing the
97 dirstate parents. Once all parent changes have been marked done,
97 dirstate parents. Once all parent changes have been marked done,
98 the wlock will be free to write the dirstate on release.
98 the wlock will be free to write the dirstate on release.
99 '''
99 '''
100 if self._parentwriters > 0:
100 if self._parentwriters > 0:
101 self._parentwriters -= 1
101 self._parentwriters -= 1
102
102
103 def pendingparentchange(self):
103 def pendingparentchange(self):
104 '''Returns true if the dirstate is in the middle of a set of changes
104 '''Returns true if the dirstate is in the middle of a set of changes
105 that modify the dirstate parent.
105 that modify the dirstate parent.
106 '''
106 '''
107 return self._parentwriters > 0
107 return self._parentwriters > 0
108
108
109 @propertycache
109 @propertycache
110 def _map(self):
110 def _map(self):
111 '''Return the dirstate contents as a map from filename to
111 '''Return the dirstate contents as a map from filename to
112 (state, mode, size, time).'''
112 (state, mode, size, time).'''
113 self._read()
113 self._read()
114 return self._map
114 return self._map
115
115
116 @propertycache
116 @propertycache
117 def _copymap(self):
117 def _copymap(self):
118 self._read()
118 self._read()
119 return self._copymap
119 return self._copymap
120
120
121 @propertycache
121 @propertycache
122 def _filefoldmap(self):
122 def _filefoldmap(self):
123 try:
123 try:
124 makefilefoldmap = parsers.make_file_foldmap
124 makefilefoldmap = parsers.make_file_foldmap
125 except AttributeError:
125 except AttributeError:
126 pass
126 pass
127 else:
127 else:
128 return makefilefoldmap(self._map, util.normcasespec,
128 return makefilefoldmap(self._map, util.normcasespec,
129 util.normcasefallback)
129 util.normcasefallback)
130
130
131 f = {}
131 f = {}
132 normcase = util.normcase
132 normcase = util.normcase
133 for name, s in self._map.iteritems():
133 for name, s in self._map.iteritems():
134 if s[0] != 'r':
134 if s[0] != 'r':
135 f[normcase(name)] = name
135 f[normcase(name)] = name
136 f['.'] = '.' # prevents useless util.fspath() invocation
136 f['.'] = '.' # prevents useless util.fspath() invocation
137 return f
137 return f
138
138
139 @propertycache
139 @propertycache
140 def _dirfoldmap(self):
140 def _dirfoldmap(self):
141 f = {}
141 f = {}
142 normcase = util.normcase
142 normcase = util.normcase
143 for name in self._dirs:
143 for name in self._dirs:
144 f[normcase(name)] = name
144 f[normcase(name)] = name
145 return f
145 return f
146
146
147 @repocache('branch')
147 @repocache('branch')
148 def _branch(self):
148 def _branch(self):
149 try:
149 try:
150 return self._opener.read("branch").strip() or "default"
150 return self._opener.read("branch").strip() or "default"
151 except IOError as inst:
151 except IOError as inst:
152 if inst.errno != errno.ENOENT:
152 if inst.errno != errno.ENOENT:
153 raise
153 raise
154 return "default"
154 return "default"
155
155
156 @propertycache
156 @propertycache
157 def _pl(self):
157 def _pl(self):
158 try:
158 try:
159 fp = self._opendirstatefile()
159 fp = self._opendirstatefile()
160 st = fp.read(40)
160 st = fp.read(40)
161 fp.close()
161 fp.close()
162 l = len(st)
162 l = len(st)
163 if l == 40:
163 if l == 40:
164 return st[:20], st[20:40]
164 return st[:20], st[20:40]
165 elif l > 0 and l < 40:
165 elif l > 0 and l < 40:
166 raise error.Abort(_('working directory state appears damaged!'))
166 raise error.Abort(_('working directory state appears damaged!'))
167 except IOError as err:
167 except IOError as err:
168 if err.errno != errno.ENOENT:
168 if err.errno != errno.ENOENT:
169 raise
169 raise
170 return [nullid, nullid]
170 return [nullid, nullid]
171
171
172 @propertycache
172 @propertycache
173 def _dirs(self):
173 def _dirs(self):
174 return util.dirs(self._map, 'r')
174 return util.dirs(self._map, 'r')
175
175
176 def dirs(self):
176 def dirs(self):
177 return self._dirs
177 return self._dirs
178
178
179 @rootcache('.hgignore')
179 @rootcache('.hgignore')
180 def _ignore(self):
180 def _ignore(self):
181 files = []
181 files = []
182 if os.path.exists(self._join('.hgignore')):
182 if os.path.exists(self._join('.hgignore')):
183 files.append(self._join('.hgignore'))
183 files.append(self._join('.hgignore'))
184 for name, path in self._ui.configitems("ui"):
184 for name, path in self._ui.configitems("ui"):
185 if name == 'ignore' or name.startswith('ignore.'):
185 if name == 'ignore' or name.startswith('ignore.'):
186 # we need to use os.path.join here rather than self._join
186 # we need to use os.path.join here rather than self._join
187 # because path is arbitrary and user-specified
187 # because path is arbitrary and user-specified
188 files.append(os.path.join(self._rootdir, util.expandpath(path)))
188 files.append(os.path.join(self._rootdir, util.expandpath(path)))
189
189
190 if not files:
190 if not files:
191 return util.never
191 return util.never
192
192
193 pats = ['include:%s' % f for f in files]
193 pats = ['include:%s' % f for f in files]
194 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
194 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
195
195
196 @propertycache
196 @propertycache
197 def _slash(self):
197 def _slash(self):
198 return self._ui.configbool('ui', 'slash') and os.sep != '/'
198 return self._ui.configbool('ui', 'slash') and os.sep != '/'
199
199
200 @propertycache
200 @propertycache
201 def _checklink(self):
201 def _checklink(self):
202 return util.checklink(self._root)
202 return util.checklink(self._root)
203
203
204 @propertycache
204 @propertycache
205 def _checkexec(self):
205 def _checkexec(self):
206 return util.checkexec(self._root)
206 return util.checkexec(self._root)
207
207
208 @propertycache
208 @propertycache
209 def _checkcase(self):
209 def _checkcase(self):
210 return not util.checkcase(self._join('.hg'))
210 return not util.checkcase(self._join('.hg'))
211
211
212 def _join(self, f):
212 def _join(self, f):
213 # much faster than os.path.join()
213 # much faster than os.path.join()
214 # it's safe because f is always a relative path
214 # it's safe because f is always a relative path
215 return self._rootdir + f
215 return self._rootdir + f
216
216
217 def flagfunc(self, buildfallback):
217 def flagfunc(self, buildfallback):
218 if self._checklink and self._checkexec:
218 if self._checklink and self._checkexec:
219 def f(x):
219 def f(x):
220 try:
220 try:
221 st = os.lstat(self._join(x))
221 st = os.lstat(self._join(x))
222 if util.statislink(st):
222 if util.statislink(st):
223 return 'l'
223 return 'l'
224 if util.statisexec(st):
224 if util.statisexec(st):
225 return 'x'
225 return 'x'
226 except OSError:
226 except OSError:
227 pass
227 pass
228 return ''
228 return ''
229 return f
229 return f
230
230
231 fallback = buildfallback()
231 fallback = buildfallback()
232 if self._checklink:
232 if self._checklink:
233 def f(x):
233 def f(x):
234 if os.path.islink(self._join(x)):
234 if os.path.islink(self._join(x)):
235 return 'l'
235 return 'l'
236 if 'x' in fallback(x):
236 if 'x' in fallback(x):
237 return 'x'
237 return 'x'
238 return ''
238 return ''
239 return f
239 return f
240 if self._checkexec:
240 if self._checkexec:
241 def f(x):
241 def f(x):
242 if 'l' in fallback(x):
242 if 'l' in fallback(x):
243 return 'l'
243 return 'l'
244 if util.isexec(self._join(x)):
244 if util.isexec(self._join(x)):
245 return 'x'
245 return 'x'
246 return ''
246 return ''
247 return f
247 return f
248 else:
248 else:
249 return fallback
249 return fallback
250
250
251 @propertycache
251 @propertycache
252 def _cwd(self):
252 def _cwd(self):
253 return os.getcwd()
253 return os.getcwd()
254
254
255 def getcwd(self):
255 def getcwd(self):
256 '''Return the path from which a canonical path is calculated.
256 '''Return the path from which a canonical path is calculated.
257
257
258 This path should be used to resolve file patterns or to convert
258 This path should be used to resolve file patterns or to convert
259 canonical paths back to file paths for display. It shouldn't be
259 canonical paths back to file paths for display. It shouldn't be
260 used to get real file paths. Use vfs functions instead.
260 used to get real file paths. Use vfs functions instead.
261 '''
261 '''
262 cwd = self._cwd
262 cwd = self._cwd
263 if cwd == self._root:
263 if cwd == self._root:
264 return ''
264 return ''
265 # self._root ends with a path separator if self._root is '/' or 'C:\'
265 # self._root ends with a path separator if self._root is '/' or 'C:\'
266 rootsep = self._root
266 rootsep = self._root
267 if not util.endswithsep(rootsep):
267 if not util.endswithsep(rootsep):
268 rootsep += os.sep
268 rootsep += os.sep
269 if cwd.startswith(rootsep):
269 if cwd.startswith(rootsep):
270 return cwd[len(rootsep):]
270 return cwd[len(rootsep):]
271 else:
271 else:
272 # we're outside the repo. return an absolute path.
272 # we're outside the repo. return an absolute path.
273 return cwd
273 return cwd
274
274
275 def pathto(self, f, cwd=None):
275 def pathto(self, f, cwd=None):
276 if cwd is None:
276 if cwd is None:
277 cwd = self.getcwd()
277 cwd = self.getcwd()
278 path = util.pathto(self._root, cwd, f)
278 path = util.pathto(self._root, cwd, f)
279 if self._slash:
279 if self._slash:
280 return util.pconvert(path)
280 return util.pconvert(path)
281 return path
281 return path
282
282
283 def __getitem__(self, key):
283 def __getitem__(self, key):
284 '''Return the current state of key (a filename) in the dirstate.
284 '''Return the current state of key (a filename) in the dirstate.
285
285
286 States are:
286 States are:
287 n normal
287 n normal
288 m needs merging
288 m needs merging
289 r marked for removal
289 r marked for removal
290 a marked for addition
290 a marked for addition
291 ? not tracked
291 ? not tracked
292 '''
292 '''
293 return self._map.get(key, ("?",))[0]
293 return self._map.get(key, ("?",))[0]
294
294
295 def __contains__(self, key):
295 def __contains__(self, key):
296 return key in self._map
296 return key in self._map
297
297
298 def __iter__(self):
298 def __iter__(self):
299 for x in sorted(self._map):
299 for x in sorted(self._map):
300 yield x
300 yield x
301
301
302 def iteritems(self):
302 def iteritems(self):
303 return self._map.iteritems()
303 return self._map.iteritems()
304
304
305 def parents(self):
305 def parents(self):
306 return [self._validate(p) for p in self._pl]
306 return [self._validate(p) for p in self._pl]
307
307
308 def p1(self):
308 def p1(self):
309 return self._validate(self._pl[0])
309 return self._validate(self._pl[0])
310
310
311 def p2(self):
311 def p2(self):
312 return self._validate(self._pl[1])
312 return self._validate(self._pl[1])
313
313
314 def branch(self):
314 def branch(self):
315 return encoding.tolocal(self._branch)
315 return encoding.tolocal(self._branch)
316
316
317 def setparents(self, p1, p2=nullid):
317 def setparents(self, p1, p2=nullid):
318 """Set dirstate parents to p1 and p2.
318 """Set dirstate parents to p1 and p2.
319
319
320 When moving from two parents to one, 'm' merged entries a
320 When moving from two parents to one, 'm' merged entries a
321 adjusted to normal and previous copy records discarded and
321 adjusted to normal and previous copy records discarded and
322 returned by the call.
322 returned by the call.
323
323
324 See localrepo.setparents()
324 See localrepo.setparents()
325 """
325 """
326 if self._parentwriters == 0:
326 if self._parentwriters == 0:
327 raise ValueError("cannot set dirstate parent without "
327 raise ValueError("cannot set dirstate parent without "
328 "calling dirstate.beginparentchange")
328 "calling dirstate.beginparentchange")
329
329
330 self._dirty = self._dirtypl = True
330 self._dirty = self._dirtypl = True
331 oldp2 = self._pl[1]
331 oldp2 = self._pl[1]
332 self._pl = p1, p2
332 self._pl = p1, p2
333 copies = {}
333 copies = {}
334 if oldp2 != nullid and p2 == nullid:
334 if oldp2 != nullid and p2 == nullid:
335 for f, s in self._map.iteritems():
335 for f, s in self._map.iteritems():
336 # Discard 'm' markers when moving away from a merge state
336 # Discard 'm' markers when moving away from a merge state
337 if s[0] == 'm':
337 if s[0] == 'm':
338 if f in self._copymap:
338 if f in self._copymap:
339 copies[f] = self._copymap[f]
339 copies[f] = self._copymap[f]
340 self.normallookup(f)
340 self.normallookup(f)
341 # Also fix up otherparent markers
341 # Also fix up otherparent markers
342 elif s[0] == 'n' and s[2] == -2:
342 elif s[0] == 'n' and s[2] == -2:
343 if f in self._copymap:
343 if f in self._copymap:
344 copies[f] = self._copymap[f]
344 copies[f] = self._copymap[f]
345 self.add(f)
345 self.add(f)
346 return copies
346 return copies
347
347
348 def setbranch(self, branch):
348 def setbranch(self, branch):
349 self._branch = encoding.fromlocal(branch)
349 self._branch = encoding.fromlocal(branch)
350 f = self._opener('branch', 'w', atomictemp=True)
350 f = self._opener('branch', 'w', atomictemp=True)
351 try:
351 try:
352 f.write(self._branch + '\n')
352 f.write(self._branch + '\n')
353 f.close()
353 f.close()
354
354
355 # make sure filecache has the correct stat info for _branch after
355 # make sure filecache has the correct stat info for _branch after
356 # replacing the underlying file
356 # replacing the underlying file
357 ce = self._filecache['_branch']
357 ce = self._filecache['_branch']
358 if ce:
358 if ce:
359 ce.refresh()
359 ce.refresh()
360 except: # re-raises
360 except: # re-raises
361 f.discard()
361 f.discard()
362 raise
362 raise
363
363
364 def _opendirstatefile(self):
364 def _opendirstatefile(self):
365 fp, mode = _trypending(self._root, self._opener, self._filename)
365 fp, mode = _trypending(self._root, self._opener, self._filename)
366 if self._pendingmode is not None and self._pendingmode != mode:
366 if self._pendingmode is not None and self._pendingmode != mode:
367 fp.close()
367 fp.close()
368 raise error.Abort(_('working directory state may be '
368 raise error.Abort(_('working directory state may be '
369 'changed parallelly'))
369 'changed parallelly'))
370 self._pendingmode = mode
370 self._pendingmode = mode
371 return fp
371 return fp
372
372
373 def _read(self):
373 def _read(self):
374 self._map = {}
374 self._map = {}
375 self._copymap = {}
375 self._copymap = {}
376 try:
376 try:
377 fp = self._opendirstatefile()
377 fp = self._opendirstatefile()
378 try:
378 try:
379 st = fp.read()
379 st = fp.read()
380 finally:
380 finally:
381 fp.close()
381 fp.close()
382 except IOError as err:
382 except IOError as err:
383 if err.errno != errno.ENOENT:
383 if err.errno != errno.ENOENT:
384 raise
384 raise
385 return
385 return
386 if not st:
386 if not st:
387 return
387 return
388
388
389 if util.safehasattr(parsers, 'dict_new_presized'):
389 if util.safehasattr(parsers, 'dict_new_presized'):
390 # Make an estimate of the number of files in the dirstate based on
390 # Make an estimate of the number of files in the dirstate based on
391 # its size. From a linear regression on a set of real-world repos,
391 # its size. From a linear regression on a set of real-world repos,
392 # all over 10,000 files, the size of a dirstate entry is 85
392 # all over 10,000 files, the size of a dirstate entry is 85
393 # bytes. The cost of resizing is significantly higher than the cost
393 # bytes. The cost of resizing is significantly higher than the cost
394 # of filling in a larger presized dict, so subtract 20% from the
394 # of filling in a larger presized dict, so subtract 20% from the
395 # size.
395 # size.
396 #
396 #
397 # This heuristic is imperfect in many ways, so in a future dirstate
397 # This heuristic is imperfect in many ways, so in a future dirstate
398 # format update it makes sense to just record the number of entries
398 # format update it makes sense to just record the number of entries
399 # on write.
399 # on write.
400 self._map = parsers.dict_new_presized(len(st) / 71)
400 self._map = parsers.dict_new_presized(len(st) / 71)
401
401
402 # Python's garbage collector triggers a GC each time a certain number
402 # Python's garbage collector triggers a GC each time a certain number
403 # of container objects (the number being defined by
403 # of container objects (the number being defined by
404 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
404 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
405 # for each file in the dirstate. The C version then immediately marks
405 # for each file in the dirstate. The C version then immediately marks
406 # them as not to be tracked by the collector. However, this has no
406 # them as not to be tracked by the collector. However, this has no
407 # effect on when GCs are triggered, only on what objects the GC looks
407 # effect on when GCs are triggered, only on what objects the GC looks
408 # into. This means that O(number of files) GCs are unavoidable.
408 # into. This means that O(number of files) GCs are unavoidable.
409 # Depending on when in the process's lifetime the dirstate is parsed,
409 # Depending on when in the process's lifetime the dirstate is parsed,
410 # this can get very expensive. As a workaround, disable GC while
410 # this can get very expensive. As a workaround, disable GC while
411 # parsing the dirstate.
411 # parsing the dirstate.
412 #
412 #
413 # (we cannot decorate the function directly since it is in a C module)
413 # (we cannot decorate the function directly since it is in a C module)
414 parse_dirstate = util.nogc(parsers.parse_dirstate)
414 parse_dirstate = util.nogc(parsers.parse_dirstate)
415 p = parse_dirstate(self._map, self._copymap, st)
415 p = parse_dirstate(self._map, self._copymap, st)
416 if not self._dirtypl:
416 if not self._dirtypl:
417 self._pl = p
417 self._pl = p
418
418
419 def invalidate(self):
419 def invalidate(self):
420 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
420 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
421 "_pl", "_dirs", "_ignore"):
421 "_pl", "_dirs", "_ignore"):
422 if a in self.__dict__:
422 if a in self.__dict__:
423 delattr(self, a)
423 delattr(self, a)
424 self._lastnormaltime = 0
424 self._lastnormaltime = 0
425 self._dirty = False
425 self._dirty = False
426 self._parentwriters = 0
426 self._parentwriters = 0
427
427
428 def copy(self, source, dest):
428 def copy(self, source, dest):
429 """Mark dest as a copy of source. Unmark dest if source is None."""
429 """Mark dest as a copy of source. Unmark dest if source is None."""
430 if source == dest:
430 if source == dest:
431 return
431 return
432 self._dirty = True
432 self._dirty = True
433 if source is not None:
433 if source is not None:
434 self._copymap[dest] = source
434 self._copymap[dest] = source
435 elif dest in self._copymap:
435 elif dest in self._copymap:
436 del self._copymap[dest]
436 del self._copymap[dest]
437
437
438 def copied(self, file):
438 def copied(self, file):
439 return self._copymap.get(file, None)
439 return self._copymap.get(file, None)
440
440
441 def copies(self):
441 def copies(self):
442 return self._copymap
442 return self._copymap
443
443
444 def _droppath(self, f):
444 def _droppath(self, f):
445 if self[f] not in "?r" and "_dirs" in self.__dict__:
445 if self[f] not in "?r" and "_dirs" in self.__dict__:
446 self._dirs.delpath(f)
446 self._dirs.delpath(f)
447
447
448 if "_filefoldmap" in self.__dict__:
448 if "_filefoldmap" in self.__dict__:
449 normed = util.normcase(f)
449 normed = util.normcase(f)
450 if normed in self._filefoldmap:
450 if normed in self._filefoldmap:
451 del self._filefoldmap[normed]
451 del self._filefoldmap[normed]
452
452
453 def _addpath(self, f, state, mode, size, mtime):
453 def _addpath(self, f, state, mode, size, mtime):
454 oldstate = self[f]
454 oldstate = self[f]
455 if state == 'a' or oldstate == 'r':
455 if state == 'a' or oldstate == 'r':
456 scmutil.checkfilename(f)
456 scmutil.checkfilename(f)
457 if f in self._dirs:
457 if f in self._dirs:
458 raise error.Abort(_('directory %r already in dirstate') % f)
458 raise error.Abort(_('directory %r already in dirstate') % f)
459 # shadows
459 # shadows
460 for d in util.finddirs(f):
460 for d in util.finddirs(f):
461 if d in self._dirs:
461 if d in self._dirs:
462 break
462 break
463 if d in self._map and self[d] != 'r':
463 if d in self._map and self[d] != 'r':
464 raise error.Abort(
464 raise error.Abort(
465 _('file %r in dirstate clashes with %r') % (d, f))
465 _('file %r in dirstate clashes with %r') % (d, f))
466 if oldstate in "?r" and "_dirs" in self.__dict__:
466 if oldstate in "?r" and "_dirs" in self.__dict__:
467 self._dirs.addpath(f)
467 self._dirs.addpath(f)
468 self._dirty = True
468 self._dirty = True
469 self._map[f] = dirstatetuple(state, mode, size, mtime)
469 self._map[f] = dirstatetuple(state, mode, size, mtime)
470
470
471 def normal(self, f):
471 def normal(self, f):
472 '''Mark a file normal and clean.'''
472 '''Mark a file normal and clean.'''
473 s = os.lstat(self._join(f))
473 s = os.lstat(self._join(f))
474 mtime = s.st_mtime
474 mtime = s.st_mtime
475 self._addpath(f, 'n', s.st_mode,
475 self._addpath(f, 'n', s.st_mode,
476 s.st_size & _rangemask, mtime & _rangemask)
476 s.st_size & _rangemask, mtime & _rangemask)
477 if f in self._copymap:
477 if f in self._copymap:
478 del self._copymap[f]
478 del self._copymap[f]
479 if mtime > self._lastnormaltime:
479 if mtime > self._lastnormaltime:
480 # Remember the most recent modification timeslot for status(),
480 # Remember the most recent modification timeslot for status(),
481 # to make sure we won't miss future size-preserving file content
481 # to make sure we won't miss future size-preserving file content
482 # modifications that happen within the same timeslot.
482 # modifications that happen within the same timeslot.
483 self._lastnormaltime = mtime
483 self._lastnormaltime = mtime
484
484
485 def normallookup(self, f):
485 def normallookup(self, f):
486 '''Mark a file normal, but possibly dirty.'''
486 '''Mark a file normal, but possibly dirty.'''
487 if self._pl[1] != nullid and f in self._map:
487 if self._pl[1] != nullid and f in self._map:
488 # if there is a merge going on and the file was either
488 # if there is a merge going on and the file was either
489 # in state 'm' (-1) or coming from other parent (-2) before
489 # in state 'm' (-1) or coming from other parent (-2) before
490 # being removed, restore that state.
490 # being removed, restore that state.
491 entry = self._map[f]
491 entry = self._map[f]
492 if entry[0] == 'r' and entry[2] in (-1, -2):
492 if entry[0] == 'r' and entry[2] in (-1, -2):
493 source = self._copymap.get(f)
493 source = self._copymap.get(f)
494 if entry[2] == -1:
494 if entry[2] == -1:
495 self.merge(f)
495 self.merge(f)
496 elif entry[2] == -2:
496 elif entry[2] == -2:
497 self.otherparent(f)
497 self.otherparent(f)
498 if source:
498 if source:
499 self.copy(source, f)
499 self.copy(source, f)
500 return
500 return
501 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
501 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
502 return
502 return
503 self._addpath(f, 'n', 0, -1, -1)
503 self._addpath(f, 'n', 0, -1, -1)
504 if f in self._copymap:
504 if f in self._copymap:
505 del self._copymap[f]
505 del self._copymap[f]
506
506
507 def otherparent(self, f):
507 def otherparent(self, f):
508 '''Mark as coming from the other parent, always dirty.'''
508 '''Mark as coming from the other parent, always dirty.'''
509 if self._pl[1] == nullid:
509 if self._pl[1] == nullid:
510 raise error.Abort(_("setting %r to other parent "
510 raise error.Abort(_("setting %r to other parent "
511 "only allowed in merges") % f)
511 "only allowed in merges") % f)
512 if f in self and self[f] == 'n':
512 if f in self and self[f] == 'n':
513 # merge-like
513 # merge-like
514 self._addpath(f, 'm', 0, -2, -1)
514 self._addpath(f, 'm', 0, -2, -1)
515 else:
515 else:
516 # add-like
516 # add-like
517 self._addpath(f, 'n', 0, -2, -1)
517 self._addpath(f, 'n', 0, -2, -1)
518
518
519 if f in self._copymap:
519 if f in self._copymap:
520 del self._copymap[f]
520 del self._copymap[f]
521
521
522 def add(self, f):
522 def add(self, f):
523 '''Mark a file added.'''
523 '''Mark a file added.'''
524 self._addpath(f, 'a', 0, -1, -1)
524 self._addpath(f, 'a', 0, -1, -1)
525 if f in self._copymap:
525 if f in self._copymap:
526 del self._copymap[f]
526 del self._copymap[f]
527
527
528 def remove(self, f):
528 def remove(self, f):
529 '''Mark a file removed.'''
529 '''Mark a file removed.'''
530 self._dirty = True
530 self._dirty = True
531 self._droppath(f)
531 self._droppath(f)
532 size = 0
532 size = 0
533 if self._pl[1] != nullid and f in self._map:
533 if self._pl[1] != nullid and f in self._map:
534 # backup the previous state
534 # backup the previous state
535 entry = self._map[f]
535 entry = self._map[f]
536 if entry[0] == 'm': # merge
536 if entry[0] == 'm': # merge
537 size = -1
537 size = -1
538 elif entry[0] == 'n' and entry[2] == -2: # other parent
538 elif entry[0] == 'n' and entry[2] == -2: # other parent
539 size = -2
539 size = -2
540 self._map[f] = dirstatetuple('r', 0, size, 0)
540 self._map[f] = dirstatetuple('r', 0, size, 0)
541 if size == 0 and f in self._copymap:
541 if size == 0 and f in self._copymap:
542 del self._copymap[f]
542 del self._copymap[f]
543
543
544 def merge(self, f):
544 def merge(self, f):
545 '''Mark a file merged.'''
545 '''Mark a file merged.'''
546 if self._pl[1] == nullid:
546 if self._pl[1] == nullid:
547 return self.normallookup(f)
547 return self.normallookup(f)
548 return self.otherparent(f)
548 return self.otherparent(f)
549
549
550 def drop(self, f):
550 def drop(self, f):
551 '''Drop a file from the dirstate'''
551 '''Drop a file from the dirstate'''
552 if f in self._map:
552 if f in self._map:
553 self._dirty = True
553 self._dirty = True
554 self._droppath(f)
554 self._droppath(f)
555 del self._map[f]
555 del self._map[f]
556
556
557 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
557 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
558 if exists is None:
558 if exists is None:
559 exists = os.path.lexists(os.path.join(self._root, path))
559 exists = os.path.lexists(os.path.join(self._root, path))
560 if not exists:
560 if not exists:
561 # Maybe a path component exists
561 # Maybe a path component exists
562 if not ignoremissing and '/' in path:
562 if not ignoremissing and '/' in path:
563 d, f = path.rsplit('/', 1)
563 d, f = path.rsplit('/', 1)
564 d = self._normalize(d, False, ignoremissing, None)
564 d = self._normalize(d, False, ignoremissing, None)
565 folded = d + "/" + f
565 folded = d + "/" + f
566 else:
566 else:
567 # No path components, preserve original case
567 # No path components, preserve original case
568 folded = path
568 folded = path
569 else:
569 else:
570 # recursively normalize leading directory components
570 # recursively normalize leading directory components
571 # against dirstate
571 # against dirstate
572 if '/' in normed:
572 if '/' in normed:
573 d, f = normed.rsplit('/', 1)
573 d, f = normed.rsplit('/', 1)
574 d = self._normalize(d, False, ignoremissing, True)
574 d = self._normalize(d, False, ignoremissing, True)
575 r = self._root + "/" + d
575 r = self._root + "/" + d
576 folded = d + "/" + util.fspath(f, r)
576 folded = d + "/" + util.fspath(f, r)
577 else:
577 else:
578 folded = util.fspath(normed, self._root)
578 folded = util.fspath(normed, self._root)
579 storemap[normed] = folded
579 storemap[normed] = folded
580
580
581 return folded
581 return folded
582
582
583 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
583 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
584 normed = util.normcase(path)
584 normed = util.normcase(path)
585 folded = self._filefoldmap.get(normed, None)
585 folded = self._filefoldmap.get(normed, None)
586 if folded is None:
586 if folded is None:
587 if isknown:
587 if isknown:
588 folded = path
588 folded = path
589 else:
589 else:
590 folded = self._discoverpath(path, normed, ignoremissing, exists,
590 folded = self._discoverpath(path, normed, ignoremissing, exists,
591 self._filefoldmap)
591 self._filefoldmap)
592 return folded
592 return folded
593
593
594 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
594 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
595 normed = util.normcase(path)
595 normed = util.normcase(path)
596 folded = self._filefoldmap.get(normed, None)
596 folded = self._filefoldmap.get(normed, None)
597 if folded is None:
597 if folded is None:
598 folded = self._dirfoldmap.get(normed, None)
598 folded = self._dirfoldmap.get(normed, None)
599 if folded is None:
599 if folded is None:
600 if isknown:
600 if isknown:
601 folded = path
601 folded = path
602 else:
602 else:
603 # store discovered result in dirfoldmap so that future
603 # store discovered result in dirfoldmap so that future
604 # normalizefile calls don't start matching directories
604 # normalizefile calls don't start matching directories
605 folded = self._discoverpath(path, normed, ignoremissing, exists,
605 folded = self._discoverpath(path, normed, ignoremissing, exists,
606 self._dirfoldmap)
606 self._dirfoldmap)
607 return folded
607 return folded
608
608
609 def normalize(self, path, isknown=False, ignoremissing=False):
609 def normalize(self, path, isknown=False, ignoremissing=False):
610 '''
610 '''
611 normalize the case of a pathname when on a casefolding filesystem
611 normalize the case of a pathname when on a casefolding filesystem
612
612
613 isknown specifies whether the filename came from walking the
613 isknown specifies whether the filename came from walking the
614 disk, to avoid extra filesystem access.
614 disk, to avoid extra filesystem access.
615
615
616 If ignoremissing is True, missing path are returned
616 If ignoremissing is True, missing path are returned
617 unchanged. Otherwise, we try harder to normalize possibly
617 unchanged. Otherwise, we try harder to normalize possibly
618 existing path components.
618 existing path components.
619
619
620 The normalized case is determined based on the following precedence:
620 The normalized case is determined based on the following precedence:
621
621
622 - version of name already stored in the dirstate
622 - version of name already stored in the dirstate
623 - version of name stored on disk
623 - version of name stored on disk
624 - version provided via command arguments
624 - version provided via command arguments
625 '''
625 '''
626
626
627 if self._checkcase:
627 if self._checkcase:
628 return self._normalize(path, isknown, ignoremissing)
628 return self._normalize(path, isknown, ignoremissing)
629 return path
629 return path
630
630
631 def clear(self):
631 def clear(self):
632 self._map = {}
632 self._map = {}
633 if "_dirs" in self.__dict__:
633 if "_dirs" in self.__dict__:
634 delattr(self, "_dirs")
634 delattr(self, "_dirs")
635 self._copymap = {}
635 self._copymap = {}
636 self._pl = [nullid, nullid]
636 self._pl = [nullid, nullid]
637 self._lastnormaltime = 0
637 self._lastnormaltime = 0
638 self._dirty = True
638 self._dirty = True
639
639
640 def rebuild(self, parent, allfiles, changedfiles=None):
640 def rebuild(self, parent, allfiles, changedfiles=None):
641 if changedfiles is None:
641 if changedfiles is None:
642 # Rebuild entire dirstate
642 # Rebuild entire dirstate
643 changedfiles = allfiles
643 changedfiles = allfiles
644 lastnormaltime = self._lastnormaltime
644 lastnormaltime = self._lastnormaltime
645 self.clear()
645 self.clear()
646 self._lastnormaltime = lastnormaltime
646 self._lastnormaltime = lastnormaltime
647
647
648 for f in changedfiles:
648 for f in changedfiles:
649 mode = 0o666
649 mode = 0o666
650 if f in allfiles and 'x' in allfiles.flags(f):
650 if f in allfiles and 'x' in allfiles.flags(f):
651 mode = 0o777
651 mode = 0o777
652
652
653 if f in allfiles:
653 if f in allfiles:
654 self._map[f] = dirstatetuple('n', mode, -1, 0)
654 self._map[f] = dirstatetuple('n', mode, -1, 0)
655 else:
655 else:
656 self._map.pop(f, None)
656 self._map.pop(f, None)
657
657
658 self._pl = (parent, nullid)
658 self._pl = (parent, nullid)
659 self._dirty = True
659 self._dirty = True
660
660
661 def write(self, tr=False):
661 def write(self, tr=False):
662 if not self._dirty:
662 if not self._dirty:
663 return
663 return
664
664
665 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
666 # timestamp of each entries in dirstate, because of 'now > mtime'
667 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
668 if delaywrite > 0:
669 import time # to avoid useless import
670 time.sleep(delaywrite)
671
672 filename = self._filename
665 filename = self._filename
673 if tr is False: # not explicitly specified
666 if tr is False: # not explicitly specified
674 if (self._ui.configbool('devel', 'all-warnings')
667 if (self._ui.configbool('devel', 'all-warnings')
675 or self._ui.configbool('devel', 'check-dirstate-write')):
668 or self._ui.configbool('devel', 'check-dirstate-write')):
676 self._ui.develwarn('use dirstate.write with '
669 self._ui.develwarn('use dirstate.write with '
677 'repo.currenttransaction()')
670 'repo.currenttransaction()')
678
671
679 if self._opener.lexists(self._pendingfilename):
672 if self._opener.lexists(self._pendingfilename):
680 # if pending file already exists, in-memory changes
673 # if pending file already exists, in-memory changes
681 # should be written into it, because it has priority
674 # should be written into it, because it has priority
682 # to '.hg/dirstate' at reading under HG_PENDING mode
675 # to '.hg/dirstate' at reading under HG_PENDING mode
683 filename = self._pendingfilename
676 filename = self._pendingfilename
684 elif tr:
677 elif tr:
685 # 'dirstate.write()' is not only for writing in-memory
678 # 'dirstate.write()' is not only for writing in-memory
686 # changes out, but also for dropping ambiguous timestamp.
679 # changes out, but also for dropping ambiguous timestamp.
687 # delayed writing re-raise "ambiguous timestamp issue".
680 # delayed writing re-raise "ambiguous timestamp issue".
688 # See also the wiki page below for detail:
681 # See also the wiki page below for detail:
689 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
682 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
690
683
691 # emulate dropping timestamp in 'parsers.pack_dirstate'
684 # emulate dropping timestamp in 'parsers.pack_dirstate'
692 now = _getfsnow(self._opener)
685 now = _getfsnow(self._opener)
693 dmap = self._map
686 dmap = self._map
694 for f, e in dmap.iteritems():
687 for f, e in dmap.iteritems():
695 if e[0] == 'n' and e[3] == now:
688 if e[0] == 'n' and e[3] == now:
696 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
689 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
697
690
698 # emulate that all 'dirstate.normal' results are written out
691 # emulate that all 'dirstate.normal' results are written out
699 self._lastnormaltime = 0
692 self._lastnormaltime = 0
700
693
701 # delay writing in-memory changes out
694 # delay writing in-memory changes out
702 tr.addfilegenerator('dirstate', (self._filename,),
695 tr.addfilegenerator('dirstate', (self._filename,),
703 self._writedirstate, location='plain')
696 self._writedirstate, location='plain')
704 return
697 return
705
698
706 st = self._opener(filename, "w", atomictemp=True)
699 st = self._opener(filename, "w", atomictemp=True)
707 self._writedirstate(st)
700 self._writedirstate(st)
708
701
709 def _writedirstate(self, st):
702 def _writedirstate(self, st):
710 # use the modification time of the newly created temporary file as the
703 # use the modification time of the newly created temporary file as the
711 # filesystem's notion of 'now'
704 # filesystem's notion of 'now'
712 now = util.fstat(st).st_mtime & _rangemask
705 now = util.fstat(st).st_mtime & _rangemask
706
707 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
708 # timestamp of each entries in dirstate, because of 'now > mtime'
709 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
710 if delaywrite > 0:
711 import time # to avoid useless import
712 time.sleep(delaywrite)
713
713 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
714 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
714 st.close()
715 st.close()
715 self._lastnormaltime = 0
716 self._lastnormaltime = 0
716 self._dirty = self._dirtypl = False
717 self._dirty = self._dirtypl = False
717
718
718 def _dirignore(self, f):
719 def _dirignore(self, f):
719 if f == '.':
720 if f == '.':
720 return False
721 return False
721 if self._ignore(f):
722 if self._ignore(f):
722 return True
723 return True
723 for p in util.finddirs(f):
724 for p in util.finddirs(f):
724 if self._ignore(p):
725 if self._ignore(p):
725 return True
726 return True
726 return False
727 return False
727
728
728 def _walkexplicit(self, match, subrepos):
729 def _walkexplicit(self, match, subrepos):
729 '''Get stat data about the files explicitly specified by match.
730 '''Get stat data about the files explicitly specified by match.
730
731
731 Return a triple (results, dirsfound, dirsnotfound).
732 Return a triple (results, dirsfound, dirsnotfound).
732 - results is a mapping from filename to stat result. It also contains
733 - results is a mapping from filename to stat result. It also contains
733 listings mapping subrepos and .hg to None.
734 listings mapping subrepos and .hg to None.
734 - dirsfound is a list of files found to be directories.
735 - dirsfound is a list of files found to be directories.
735 - dirsnotfound is a list of files that the dirstate thinks are
736 - dirsnotfound is a list of files that the dirstate thinks are
736 directories and that were not found.'''
737 directories and that were not found.'''
737
738
738 def badtype(mode):
739 def badtype(mode):
739 kind = _('unknown')
740 kind = _('unknown')
740 if stat.S_ISCHR(mode):
741 if stat.S_ISCHR(mode):
741 kind = _('character device')
742 kind = _('character device')
742 elif stat.S_ISBLK(mode):
743 elif stat.S_ISBLK(mode):
743 kind = _('block device')
744 kind = _('block device')
744 elif stat.S_ISFIFO(mode):
745 elif stat.S_ISFIFO(mode):
745 kind = _('fifo')
746 kind = _('fifo')
746 elif stat.S_ISSOCK(mode):
747 elif stat.S_ISSOCK(mode):
747 kind = _('socket')
748 kind = _('socket')
748 elif stat.S_ISDIR(mode):
749 elif stat.S_ISDIR(mode):
749 kind = _('directory')
750 kind = _('directory')
750 return _('unsupported file type (type is %s)') % kind
751 return _('unsupported file type (type is %s)') % kind
751
752
752 matchedir = match.explicitdir
753 matchedir = match.explicitdir
753 badfn = match.bad
754 badfn = match.bad
754 dmap = self._map
755 dmap = self._map
755 lstat = os.lstat
756 lstat = os.lstat
756 getkind = stat.S_IFMT
757 getkind = stat.S_IFMT
757 dirkind = stat.S_IFDIR
758 dirkind = stat.S_IFDIR
758 regkind = stat.S_IFREG
759 regkind = stat.S_IFREG
759 lnkkind = stat.S_IFLNK
760 lnkkind = stat.S_IFLNK
760 join = self._join
761 join = self._join
761 dirsfound = []
762 dirsfound = []
762 foundadd = dirsfound.append
763 foundadd = dirsfound.append
763 dirsnotfound = []
764 dirsnotfound = []
764 notfoundadd = dirsnotfound.append
765 notfoundadd = dirsnotfound.append
765
766
766 if not match.isexact() and self._checkcase:
767 if not match.isexact() and self._checkcase:
767 normalize = self._normalize
768 normalize = self._normalize
768 else:
769 else:
769 normalize = None
770 normalize = None
770
771
771 files = sorted(match.files())
772 files = sorted(match.files())
772 subrepos.sort()
773 subrepos.sort()
773 i, j = 0, 0
774 i, j = 0, 0
774 while i < len(files) and j < len(subrepos):
775 while i < len(files) and j < len(subrepos):
775 subpath = subrepos[j] + "/"
776 subpath = subrepos[j] + "/"
776 if files[i] < subpath:
777 if files[i] < subpath:
777 i += 1
778 i += 1
778 continue
779 continue
779 while i < len(files) and files[i].startswith(subpath):
780 while i < len(files) and files[i].startswith(subpath):
780 del files[i]
781 del files[i]
781 j += 1
782 j += 1
782
783
783 if not files or '.' in files:
784 if not files or '.' in files:
784 files = ['.']
785 files = ['.']
785 results = dict.fromkeys(subrepos)
786 results = dict.fromkeys(subrepos)
786 results['.hg'] = None
787 results['.hg'] = None
787
788
788 alldirs = None
789 alldirs = None
789 for ff in files:
790 for ff in files:
790 # constructing the foldmap is expensive, so don't do it for the
791 # constructing the foldmap is expensive, so don't do it for the
791 # common case where files is ['.']
792 # common case where files is ['.']
792 if normalize and ff != '.':
793 if normalize and ff != '.':
793 nf = normalize(ff, False, True)
794 nf = normalize(ff, False, True)
794 else:
795 else:
795 nf = ff
796 nf = ff
796 if nf in results:
797 if nf in results:
797 continue
798 continue
798
799
799 try:
800 try:
800 st = lstat(join(nf))
801 st = lstat(join(nf))
801 kind = getkind(st.st_mode)
802 kind = getkind(st.st_mode)
802 if kind == dirkind:
803 if kind == dirkind:
803 if nf in dmap:
804 if nf in dmap:
804 # file replaced by dir on disk but still in dirstate
805 # file replaced by dir on disk but still in dirstate
805 results[nf] = None
806 results[nf] = None
806 if matchedir:
807 if matchedir:
807 matchedir(nf)
808 matchedir(nf)
808 foundadd((nf, ff))
809 foundadd((nf, ff))
809 elif kind == regkind or kind == lnkkind:
810 elif kind == regkind or kind == lnkkind:
810 results[nf] = st
811 results[nf] = st
811 else:
812 else:
812 badfn(ff, badtype(kind))
813 badfn(ff, badtype(kind))
813 if nf in dmap:
814 if nf in dmap:
814 results[nf] = None
815 results[nf] = None
815 except OSError as inst: # nf not found on disk - it is dirstate only
816 except OSError as inst: # nf not found on disk - it is dirstate only
816 if nf in dmap: # does it exactly match a missing file?
817 if nf in dmap: # does it exactly match a missing file?
817 results[nf] = None
818 results[nf] = None
818 else: # does it match a missing directory?
819 else: # does it match a missing directory?
819 if alldirs is None:
820 if alldirs is None:
820 alldirs = util.dirs(dmap)
821 alldirs = util.dirs(dmap)
821 if nf in alldirs:
822 if nf in alldirs:
822 if matchedir:
823 if matchedir:
823 matchedir(nf)
824 matchedir(nf)
824 notfoundadd(nf)
825 notfoundadd(nf)
825 else:
826 else:
826 badfn(ff, inst.strerror)
827 badfn(ff, inst.strerror)
827
828
828 # Case insensitive filesystems cannot rely on lstat() failing to detect
829 # Case insensitive filesystems cannot rely on lstat() failing to detect
829 # a case-only rename. Prune the stat object for any file that does not
830 # a case-only rename. Prune the stat object for any file that does not
830 # match the case in the filesystem, if there are multiple files that
831 # match the case in the filesystem, if there are multiple files that
831 # normalize to the same path.
832 # normalize to the same path.
832 if match.isexact() and self._checkcase:
833 if match.isexact() and self._checkcase:
833 normed = {}
834 normed = {}
834
835
835 for f, st in results.iteritems():
836 for f, st in results.iteritems():
836 if st is None:
837 if st is None:
837 continue
838 continue
838
839
839 nc = util.normcase(f)
840 nc = util.normcase(f)
840 paths = normed.get(nc)
841 paths = normed.get(nc)
841
842
842 if paths is None:
843 if paths is None:
843 paths = set()
844 paths = set()
844 normed[nc] = paths
845 normed[nc] = paths
845
846
846 paths.add(f)
847 paths.add(f)
847
848
848 for norm, paths in normed.iteritems():
849 for norm, paths in normed.iteritems():
849 if len(paths) > 1:
850 if len(paths) > 1:
850 for path in paths:
851 for path in paths:
851 folded = self._discoverpath(path, norm, True, None,
852 folded = self._discoverpath(path, norm, True, None,
852 self._dirfoldmap)
853 self._dirfoldmap)
853 if path != folded:
854 if path != folded:
854 results[path] = None
855 results[path] = None
855
856
856 return results, dirsfound, dirsnotfound
857 return results, dirsfound, dirsnotfound
857
858
858 def walk(self, match, subrepos, unknown, ignored, full=True):
859 def walk(self, match, subrepos, unknown, ignored, full=True):
859 '''
860 '''
860 Walk recursively through the directory tree, finding all files
861 Walk recursively through the directory tree, finding all files
861 matched by match.
862 matched by match.
862
863
863 If full is False, maybe skip some known-clean files.
864 If full is False, maybe skip some known-clean files.
864
865
865 Return a dict mapping filename to stat-like object (either
866 Return a dict mapping filename to stat-like object (either
866 mercurial.osutil.stat instance or return value of os.stat()).
867 mercurial.osutil.stat instance or return value of os.stat()).
867
868
868 '''
869 '''
869 # full is a flag that extensions that hook into walk can use -- this
870 # full is a flag that extensions that hook into walk can use -- this
870 # implementation doesn't use it at all. This satisfies the contract
871 # implementation doesn't use it at all. This satisfies the contract
871 # because we only guarantee a "maybe".
872 # because we only guarantee a "maybe".
872
873
873 if ignored:
874 if ignored:
874 ignore = util.never
875 ignore = util.never
875 dirignore = util.never
876 dirignore = util.never
876 elif unknown:
877 elif unknown:
877 ignore = self._ignore
878 ignore = self._ignore
878 dirignore = self._dirignore
879 dirignore = self._dirignore
879 else:
880 else:
880 # if not unknown and not ignored, drop dir recursion and step 2
881 # if not unknown and not ignored, drop dir recursion and step 2
881 ignore = util.always
882 ignore = util.always
882 dirignore = util.always
883 dirignore = util.always
883
884
884 matchfn = match.matchfn
885 matchfn = match.matchfn
885 matchalways = match.always()
886 matchalways = match.always()
886 matchtdir = match.traversedir
887 matchtdir = match.traversedir
887 dmap = self._map
888 dmap = self._map
888 listdir = osutil.listdir
889 listdir = osutil.listdir
889 lstat = os.lstat
890 lstat = os.lstat
890 dirkind = stat.S_IFDIR
891 dirkind = stat.S_IFDIR
891 regkind = stat.S_IFREG
892 regkind = stat.S_IFREG
892 lnkkind = stat.S_IFLNK
893 lnkkind = stat.S_IFLNK
893 join = self._join
894 join = self._join
894
895
895 exact = skipstep3 = False
896 exact = skipstep3 = False
896 if match.isexact(): # match.exact
897 if match.isexact(): # match.exact
897 exact = True
898 exact = True
898 dirignore = util.always # skip step 2
899 dirignore = util.always # skip step 2
899 elif match.prefix(): # match.match, no patterns
900 elif match.prefix(): # match.match, no patterns
900 skipstep3 = True
901 skipstep3 = True
901
902
902 if not exact and self._checkcase:
903 if not exact and self._checkcase:
903 normalize = self._normalize
904 normalize = self._normalize
904 normalizefile = self._normalizefile
905 normalizefile = self._normalizefile
905 skipstep3 = False
906 skipstep3 = False
906 else:
907 else:
907 normalize = self._normalize
908 normalize = self._normalize
908 normalizefile = None
909 normalizefile = None
909
910
910 # step 1: find all explicit files
911 # step 1: find all explicit files
911 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
912 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
912
913
913 skipstep3 = skipstep3 and not (work or dirsnotfound)
914 skipstep3 = skipstep3 and not (work or dirsnotfound)
914 work = [d for d in work if not dirignore(d[0])]
915 work = [d for d in work if not dirignore(d[0])]
915
916
916 # step 2: visit subdirectories
917 # step 2: visit subdirectories
917 def traverse(work, alreadynormed):
918 def traverse(work, alreadynormed):
918 wadd = work.append
919 wadd = work.append
919 while work:
920 while work:
920 nd = work.pop()
921 nd = work.pop()
921 skip = None
922 skip = None
922 if nd == '.':
923 if nd == '.':
923 nd = ''
924 nd = ''
924 else:
925 else:
925 skip = '.hg'
926 skip = '.hg'
926 try:
927 try:
927 entries = listdir(join(nd), stat=True, skip=skip)
928 entries = listdir(join(nd), stat=True, skip=skip)
928 except OSError as inst:
929 except OSError as inst:
929 if inst.errno in (errno.EACCES, errno.ENOENT):
930 if inst.errno in (errno.EACCES, errno.ENOENT):
930 match.bad(self.pathto(nd), inst.strerror)
931 match.bad(self.pathto(nd), inst.strerror)
931 continue
932 continue
932 raise
933 raise
933 for f, kind, st in entries:
934 for f, kind, st in entries:
934 if normalizefile:
935 if normalizefile:
935 # even though f might be a directory, we're only
936 # even though f might be a directory, we're only
936 # interested in comparing it to files currently in the
937 # interested in comparing it to files currently in the
937 # dmap -- therefore normalizefile is enough
938 # dmap -- therefore normalizefile is enough
938 nf = normalizefile(nd and (nd + "/" + f) or f, True,
939 nf = normalizefile(nd and (nd + "/" + f) or f, True,
939 True)
940 True)
940 else:
941 else:
941 nf = nd and (nd + "/" + f) or f
942 nf = nd and (nd + "/" + f) or f
942 if nf not in results:
943 if nf not in results:
943 if kind == dirkind:
944 if kind == dirkind:
944 if not ignore(nf):
945 if not ignore(nf):
945 if matchtdir:
946 if matchtdir:
946 matchtdir(nf)
947 matchtdir(nf)
947 wadd(nf)
948 wadd(nf)
948 if nf in dmap and (matchalways or matchfn(nf)):
949 if nf in dmap and (matchalways or matchfn(nf)):
949 results[nf] = None
950 results[nf] = None
950 elif kind == regkind or kind == lnkkind:
951 elif kind == regkind or kind == lnkkind:
951 if nf in dmap:
952 if nf in dmap:
952 if matchalways or matchfn(nf):
953 if matchalways or matchfn(nf):
953 results[nf] = st
954 results[nf] = st
954 elif ((matchalways or matchfn(nf))
955 elif ((matchalways or matchfn(nf))
955 and not ignore(nf)):
956 and not ignore(nf)):
956 # unknown file -- normalize if necessary
957 # unknown file -- normalize if necessary
957 if not alreadynormed:
958 if not alreadynormed:
958 nf = normalize(nf, False, True)
959 nf = normalize(nf, False, True)
959 results[nf] = st
960 results[nf] = st
960 elif nf in dmap and (matchalways or matchfn(nf)):
961 elif nf in dmap and (matchalways or matchfn(nf)):
961 results[nf] = None
962 results[nf] = None
962
963
963 for nd, d in work:
964 for nd, d in work:
964 # alreadynormed means that processwork doesn't have to do any
965 # alreadynormed means that processwork doesn't have to do any
965 # expensive directory normalization
966 # expensive directory normalization
966 alreadynormed = not normalize or nd == d
967 alreadynormed = not normalize or nd == d
967 traverse([d], alreadynormed)
968 traverse([d], alreadynormed)
968
969
969 for s in subrepos:
970 for s in subrepos:
970 del results[s]
971 del results[s]
971 del results['.hg']
972 del results['.hg']
972
973
973 # step 3: visit remaining files from dmap
974 # step 3: visit remaining files from dmap
974 if not skipstep3 and not exact:
975 if not skipstep3 and not exact:
975 # If a dmap file is not in results yet, it was either
976 # If a dmap file is not in results yet, it was either
976 # a) not matching matchfn b) ignored, c) missing, or d) under a
977 # a) not matching matchfn b) ignored, c) missing, or d) under a
977 # symlink directory.
978 # symlink directory.
978 if not results and matchalways:
979 if not results and matchalways:
979 visit = dmap.keys()
980 visit = dmap.keys()
980 else:
981 else:
981 visit = [f for f in dmap if f not in results and matchfn(f)]
982 visit = [f for f in dmap if f not in results and matchfn(f)]
982 visit.sort()
983 visit.sort()
983
984
984 if unknown:
985 if unknown:
985 # unknown == True means we walked all dirs under the roots
986 # unknown == True means we walked all dirs under the roots
986 # that wasn't ignored, and everything that matched was stat'ed
987 # that wasn't ignored, and everything that matched was stat'ed
987 # and is already in results.
988 # and is already in results.
988 # The rest must thus be ignored or under a symlink.
989 # The rest must thus be ignored or under a symlink.
989 audit_path = pathutil.pathauditor(self._root)
990 audit_path = pathutil.pathauditor(self._root)
990
991
991 for nf in iter(visit):
992 for nf in iter(visit):
992 # If a stat for the same file was already added with a
993 # If a stat for the same file was already added with a
993 # different case, don't add one for this, since that would
994 # different case, don't add one for this, since that would
994 # make it appear as if the file exists under both names
995 # make it appear as if the file exists under both names
995 # on disk.
996 # on disk.
996 if (normalizefile and
997 if (normalizefile and
997 normalizefile(nf, True, True) in results):
998 normalizefile(nf, True, True) in results):
998 results[nf] = None
999 results[nf] = None
999 # Report ignored items in the dmap as long as they are not
1000 # Report ignored items in the dmap as long as they are not
1000 # under a symlink directory.
1001 # under a symlink directory.
1001 elif audit_path.check(nf):
1002 elif audit_path.check(nf):
1002 try:
1003 try:
1003 results[nf] = lstat(join(nf))
1004 results[nf] = lstat(join(nf))
1004 # file was just ignored, no links, and exists
1005 # file was just ignored, no links, and exists
1005 except OSError:
1006 except OSError:
1006 # file doesn't exist
1007 # file doesn't exist
1007 results[nf] = None
1008 results[nf] = None
1008 else:
1009 else:
1009 # It's either missing or under a symlink directory
1010 # It's either missing or under a symlink directory
1010 # which we in this case report as missing
1011 # which we in this case report as missing
1011 results[nf] = None
1012 results[nf] = None
1012 else:
1013 else:
1013 # We may not have walked the full directory tree above,
1014 # We may not have walked the full directory tree above,
1014 # so stat and check everything we missed.
1015 # so stat and check everything we missed.
1015 nf = iter(visit).next
1016 nf = iter(visit).next
1016 for st in util.statfiles([join(i) for i in visit]):
1017 for st in util.statfiles([join(i) for i in visit]):
1017 results[nf()] = st
1018 results[nf()] = st
1018 return results
1019 return results
1019
1020
1020 def status(self, match, subrepos, ignored, clean, unknown):
1021 def status(self, match, subrepos, ignored, clean, unknown):
1021 '''Determine the status of the working copy relative to the
1022 '''Determine the status of the working copy relative to the
1022 dirstate and return a pair of (unsure, status), where status is of type
1023 dirstate and return a pair of (unsure, status), where status is of type
1023 scmutil.status and:
1024 scmutil.status and:
1024
1025
1025 unsure:
1026 unsure:
1026 files that might have been modified since the dirstate was
1027 files that might have been modified since the dirstate was
1027 written, but need to be read to be sure (size is the same
1028 written, but need to be read to be sure (size is the same
1028 but mtime differs)
1029 but mtime differs)
1029 status.modified:
1030 status.modified:
1030 files that have definitely been modified since the dirstate
1031 files that have definitely been modified since the dirstate
1031 was written (different size or mode)
1032 was written (different size or mode)
1032 status.clean:
1033 status.clean:
1033 files that have definitely not been modified since the
1034 files that have definitely not been modified since the
1034 dirstate was written
1035 dirstate was written
1035 '''
1036 '''
1036 listignored, listclean, listunknown = ignored, clean, unknown
1037 listignored, listclean, listunknown = ignored, clean, unknown
1037 lookup, modified, added, unknown, ignored = [], [], [], [], []
1038 lookup, modified, added, unknown, ignored = [], [], [], [], []
1038 removed, deleted, clean = [], [], []
1039 removed, deleted, clean = [], [], []
1039
1040
1040 dmap = self._map
1041 dmap = self._map
1041 ladd = lookup.append # aka "unsure"
1042 ladd = lookup.append # aka "unsure"
1042 madd = modified.append
1043 madd = modified.append
1043 aadd = added.append
1044 aadd = added.append
1044 uadd = unknown.append
1045 uadd = unknown.append
1045 iadd = ignored.append
1046 iadd = ignored.append
1046 radd = removed.append
1047 radd = removed.append
1047 dadd = deleted.append
1048 dadd = deleted.append
1048 cadd = clean.append
1049 cadd = clean.append
1049 mexact = match.exact
1050 mexact = match.exact
1050 dirignore = self._dirignore
1051 dirignore = self._dirignore
1051 checkexec = self._checkexec
1052 checkexec = self._checkexec
1052 copymap = self._copymap
1053 copymap = self._copymap
1053 lastnormaltime = self._lastnormaltime
1054 lastnormaltime = self._lastnormaltime
1054
1055
1055 # We need to do full walks when either
1056 # We need to do full walks when either
1056 # - we're listing all clean files, or
1057 # - we're listing all clean files, or
1057 # - match.traversedir does something, because match.traversedir should
1058 # - match.traversedir does something, because match.traversedir should
1058 # be called for every dir in the working dir
1059 # be called for every dir in the working dir
1059 full = listclean or match.traversedir is not None
1060 full = listclean or match.traversedir is not None
1060 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1061 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1061 full=full).iteritems():
1062 full=full).iteritems():
1062 if fn not in dmap:
1063 if fn not in dmap:
1063 if (listignored or mexact(fn)) and dirignore(fn):
1064 if (listignored or mexact(fn)) and dirignore(fn):
1064 if listignored:
1065 if listignored:
1065 iadd(fn)
1066 iadd(fn)
1066 else:
1067 else:
1067 uadd(fn)
1068 uadd(fn)
1068 continue
1069 continue
1069
1070
1070 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1071 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1071 # written like that for performance reasons. dmap[fn] is not a
1072 # written like that for performance reasons. dmap[fn] is not a
1072 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1073 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1073 # opcode has fast paths when the value to be unpacked is a tuple or
1074 # opcode has fast paths when the value to be unpacked is a tuple or
1074 # a list, but falls back to creating a full-fledged iterator in
1075 # a list, but falls back to creating a full-fledged iterator in
1075 # general. That is much slower than simply accessing and storing the
1076 # general. That is much slower than simply accessing and storing the
1076 # tuple members one by one.
1077 # tuple members one by one.
1077 t = dmap[fn]
1078 t = dmap[fn]
1078 state = t[0]
1079 state = t[0]
1079 mode = t[1]
1080 mode = t[1]
1080 size = t[2]
1081 size = t[2]
1081 time = t[3]
1082 time = t[3]
1082
1083
1083 if not st and state in "nma":
1084 if not st and state in "nma":
1084 dadd(fn)
1085 dadd(fn)
1085 elif state == 'n':
1086 elif state == 'n':
1086 if (size >= 0 and
1087 if (size >= 0 and
1087 ((size != st.st_size and size != st.st_size & _rangemask)
1088 ((size != st.st_size and size != st.st_size & _rangemask)
1088 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1089 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1089 or size == -2 # other parent
1090 or size == -2 # other parent
1090 or fn in copymap):
1091 or fn in copymap):
1091 madd(fn)
1092 madd(fn)
1092 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1093 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1093 ladd(fn)
1094 ladd(fn)
1094 elif st.st_mtime == lastnormaltime:
1095 elif st.st_mtime == lastnormaltime:
1095 # fn may have just been marked as normal and it may have
1096 # fn may have just been marked as normal and it may have
1096 # changed in the same second without changing its size.
1097 # changed in the same second without changing its size.
1097 # This can happen if we quickly do multiple commits.
1098 # This can happen if we quickly do multiple commits.
1098 # Force lookup, so we don't miss such a racy file change.
1099 # Force lookup, so we don't miss such a racy file change.
1099 ladd(fn)
1100 ladd(fn)
1100 elif listclean:
1101 elif listclean:
1101 cadd(fn)
1102 cadd(fn)
1102 elif state == 'm':
1103 elif state == 'm':
1103 madd(fn)
1104 madd(fn)
1104 elif state == 'a':
1105 elif state == 'a':
1105 aadd(fn)
1106 aadd(fn)
1106 elif state == 'r':
1107 elif state == 'r':
1107 radd(fn)
1108 radd(fn)
1108
1109
1109 return (lookup, scmutil.status(modified, added, removed, deleted,
1110 return (lookup, scmutil.status(modified, added, removed, deleted,
1110 unknown, ignored, clean))
1111 unknown, ignored, clean))
1111
1112
1112 def matches(self, match):
1113 def matches(self, match):
1113 '''
1114 '''
1114 return files in the dirstate (in whatever state) filtered by match
1115 return files in the dirstate (in whatever state) filtered by match
1115 '''
1116 '''
1116 dmap = self._map
1117 dmap = self._map
1117 if match.always():
1118 if match.always():
1118 return dmap.keys()
1119 return dmap.keys()
1119 files = match.files()
1120 files = match.files()
1120 if match.isexact():
1121 if match.isexact():
1121 # fast path -- filter the other way around, since typically files is
1122 # fast path -- filter the other way around, since typically files is
1122 # much smaller than dmap
1123 # much smaller than dmap
1123 return [f for f in files if f in dmap]
1124 return [f for f in files if f in dmap]
1124 if match.prefix() and all(fn in dmap for fn in files):
1125 if match.prefix() and all(fn in dmap for fn in files):
1125 # fast path -- all the values are known to be files, so just return
1126 # fast path -- all the values are known to be files, so just return
1126 # that
1127 # that
1127 return list(files)
1128 return list(files)
1128 return [f for f in dmap if match(f)]
1129 return [f for f in dmap if match(f)]
1129
1130
1130 def _actualfilename(self, tr):
1131 def _actualfilename(self, tr):
1131 if tr:
1132 if tr:
1132 return self._pendingfilename
1133 return self._pendingfilename
1133 else:
1134 else:
1134 return self._filename
1135 return self._filename
1135
1136
1136 def _savebackup(self, tr, suffix):
1137 def _savebackup(self, tr, suffix):
1137 '''Save current dirstate into backup file with suffix'''
1138 '''Save current dirstate into backup file with suffix'''
1138 filename = self._actualfilename(tr)
1139 filename = self._actualfilename(tr)
1139
1140
1140 # use '_writedirstate' instead of 'write' to write changes certainly,
1141 # use '_writedirstate' instead of 'write' to write changes certainly,
1141 # because the latter omits writing out if transaction is running.
1142 # because the latter omits writing out if transaction is running.
1142 # output file will be used to create backup of dirstate at this point.
1143 # output file will be used to create backup of dirstate at this point.
1143 self._writedirstate(self._opener(filename, "w", atomictemp=True))
1144 self._writedirstate(self._opener(filename, "w", atomictemp=True))
1144
1145
1145 if tr:
1146 if tr:
1146 # ensure that subsequent tr.writepending returns True for
1147 # ensure that subsequent tr.writepending returns True for
1147 # changes written out above, even if dirstate is never
1148 # changes written out above, even if dirstate is never
1148 # changed after this
1149 # changed after this
1149 tr.addfilegenerator('dirstate', (self._filename,),
1150 tr.addfilegenerator('dirstate', (self._filename,),
1150 self._writedirstate, location='plain')
1151 self._writedirstate, location='plain')
1151
1152
1152 # ensure that pending file written above is unlinked at
1153 # ensure that pending file written above is unlinked at
1153 # failure, even if tr.writepending isn't invoked until the
1154 # failure, even if tr.writepending isn't invoked until the
1154 # end of this transaction
1155 # end of this transaction
1155 tr.registertmp(filename, location='plain')
1156 tr.registertmp(filename, location='plain')
1156
1157
1157 self._opener.write(filename + suffix, self._opener.tryread(filename))
1158 self._opener.write(filename + suffix, self._opener.tryread(filename))
1158
1159
1159 def _restorebackup(self, tr, suffix):
1160 def _restorebackup(self, tr, suffix):
1160 '''Restore dirstate by backup file with suffix'''
1161 '''Restore dirstate by backup file with suffix'''
1161 # this "invalidate()" prevents "wlock.release()" from writing
1162 # this "invalidate()" prevents "wlock.release()" from writing
1162 # changes of dirstate out after restoring from backup file
1163 # changes of dirstate out after restoring from backup file
1163 self.invalidate()
1164 self.invalidate()
1164 filename = self._actualfilename(tr)
1165 filename = self._actualfilename(tr)
1165 self._opener.rename(filename + suffix, filename)
1166 self._opener.rename(filename + suffix, filename)
1166
1167
1167 def _clearbackup(self, tr, suffix):
1168 def _clearbackup(self, tr, suffix):
1168 '''Clear backup file with suffix'''
1169 '''Clear backup file with suffix'''
1169 filename = self._actualfilename(tr)
1170 filename = self._actualfilename(tr)
1170 self._opener.unlink(filename + suffix)
1171 self._opener.unlink(filename + suffix)
General Comments 0
You need to be logged in to leave comments. Login now