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