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