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