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