##// END OF EJS Templates
dirstate: cache util.normcase while constructing the foldmap...
Siddharth Agarwal -
r22782:a1eb21f5 default
parent child Browse files
Show More
@@ -1,922 +1,923 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
11 import os, stat, errno, gc
11 import os, stat, errno, gc
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14 filecache = scmutil.filecache
14 filecache = scmutil.filecache
15 _rangemask = 0x7fffffff
15 _rangemask = 0x7fffffff
16
16
17 dirstatetuple = parsers.dirstatetuple
17 dirstatetuple = parsers.dirstatetuple
18
18
19 class repocache(filecache):
19 class repocache(filecache):
20 """filecache for files in .hg/"""
20 """filecache for files in .hg/"""
21 def join(self, obj, fname):
21 def join(self, obj, fname):
22 return obj._opener.join(fname)
22 return obj._opener.join(fname)
23
23
24 class rootcache(filecache):
24 class rootcache(filecache):
25 """filecache for files in the repository root"""
25 """filecache for files in the repository root"""
26 def join(self, obj, fname):
26 def join(self, obj, fname):
27 return obj._join(fname)
27 return obj._join(fname)
28
28
29 class dirstate(object):
29 class dirstate(object):
30
30
31 def __init__(self, opener, ui, root, validate):
31 def __init__(self, opener, ui, root, validate):
32 '''Create a new dirstate object.
32 '''Create a new dirstate object.
33
33
34 opener is an open()-like callable that can be used to open the
34 opener is an open()-like callable that can be used to open the
35 dirstate file; root is the root of the directory tracked by
35 dirstate file; root is the root of the directory tracked by
36 the dirstate.
36 the dirstate.
37 '''
37 '''
38 self._opener = opener
38 self._opener = opener
39 self._validate = validate
39 self._validate = validate
40 self._root = root
40 self._root = root
41 self._rootdir = os.path.join(root, '')
41 self._rootdir = os.path.join(root, '')
42 self._dirty = False
42 self._dirty = False
43 self._dirtypl = False
43 self._dirtypl = False
44 self._lastnormaltime = 0
44 self._lastnormaltime = 0
45 self._ui = ui
45 self._ui = ui
46 self._filecache = {}
46 self._filecache = {}
47 self._parentwriters = 0
47 self._parentwriters = 0
48
48
49 def beginparentchange(self):
49 def beginparentchange(self):
50 '''Marks the beginning of a set of changes that involve changing
50 '''Marks the beginning of a set of changes that involve changing
51 the dirstate parents. If there is an exception during this time,
51 the dirstate parents. If there is an exception during this time,
52 the dirstate will not be written when the wlock is released. This
52 the dirstate will not be written when the wlock is released. This
53 prevents writing an incoherent dirstate where the parent doesn't
53 prevents writing an incoherent dirstate where the parent doesn't
54 match the contents.
54 match the contents.
55 '''
55 '''
56 self._parentwriters += 1
56 self._parentwriters += 1
57
57
58 def endparentchange(self):
58 def endparentchange(self):
59 '''Marks the end of a set of changes that involve changing the
59 '''Marks the end of a set of changes that involve changing the
60 dirstate parents. Once all parent changes have been marked done,
60 dirstate parents. Once all parent changes have been marked done,
61 the wlock will be free to write the dirstate on release.
61 the wlock will be free to write the dirstate on release.
62 '''
62 '''
63 if self._parentwriters > 0:
63 if self._parentwriters > 0:
64 self._parentwriters -= 1
64 self._parentwriters -= 1
65
65
66 def pendingparentchange(self):
66 def pendingparentchange(self):
67 '''Returns true if the dirstate is in the middle of a set of changes
67 '''Returns true if the dirstate is in the middle of a set of changes
68 that modify the dirstate parent.
68 that modify the dirstate parent.
69 '''
69 '''
70 return self._parentwriters > 0
70 return self._parentwriters > 0
71
71
72 @propertycache
72 @propertycache
73 def _map(self):
73 def _map(self):
74 '''Return the dirstate contents as a map from filename to
74 '''Return the dirstate contents as a map from filename to
75 (state, mode, size, time).'''
75 (state, mode, size, time).'''
76 self._read()
76 self._read()
77 return self._map
77 return self._map
78
78
79 @propertycache
79 @propertycache
80 def _copymap(self):
80 def _copymap(self):
81 self._read()
81 self._read()
82 return self._copymap
82 return self._copymap
83
83
84 @propertycache
84 @propertycache
85 def _foldmap(self):
85 def _foldmap(self):
86 f = {}
86 f = {}
87 normcase = util.normcase
87 for name, s in self._map.iteritems():
88 for name, s in self._map.iteritems():
88 if s[0] != 'r':
89 if s[0] != 'r':
89 f[util.normcase(name)] = name
90 f[normcase(name)] = name
90 for name in self._dirs:
91 for name in self._dirs:
91 f[util.normcase(name)] = name
92 f[normcase(name)] = name
92 f['.'] = '.' # prevents useless util.fspath() invocation
93 f['.'] = '.' # prevents useless util.fspath() invocation
93 return f
94 return f
94
95
95 @repocache('branch')
96 @repocache('branch')
96 def _branch(self):
97 def _branch(self):
97 try:
98 try:
98 return self._opener.read("branch").strip() or "default"
99 return self._opener.read("branch").strip() or "default"
99 except IOError, inst:
100 except IOError, inst:
100 if inst.errno != errno.ENOENT:
101 if inst.errno != errno.ENOENT:
101 raise
102 raise
102 return "default"
103 return "default"
103
104
104 @propertycache
105 @propertycache
105 def _pl(self):
106 def _pl(self):
106 try:
107 try:
107 fp = self._opener("dirstate")
108 fp = self._opener("dirstate")
108 st = fp.read(40)
109 st = fp.read(40)
109 fp.close()
110 fp.close()
110 l = len(st)
111 l = len(st)
111 if l == 40:
112 if l == 40:
112 return st[:20], st[20:40]
113 return st[:20], st[20:40]
113 elif l > 0 and l < 40:
114 elif l > 0 and l < 40:
114 raise util.Abort(_('working directory state appears damaged!'))
115 raise util.Abort(_('working directory state appears damaged!'))
115 except IOError, err:
116 except IOError, err:
116 if err.errno != errno.ENOENT:
117 if err.errno != errno.ENOENT:
117 raise
118 raise
118 return [nullid, nullid]
119 return [nullid, nullid]
119
120
120 @propertycache
121 @propertycache
121 def _dirs(self):
122 def _dirs(self):
122 return scmutil.dirs(self._map, 'r')
123 return scmutil.dirs(self._map, 'r')
123
124
124 def dirs(self):
125 def dirs(self):
125 return self._dirs
126 return self._dirs
126
127
127 @rootcache('.hgignore')
128 @rootcache('.hgignore')
128 def _ignore(self):
129 def _ignore(self):
129 files = [self._join('.hgignore')]
130 files = [self._join('.hgignore')]
130 for name, path in self._ui.configitems("ui"):
131 for name, path in self._ui.configitems("ui"):
131 if name == 'ignore' or name.startswith('ignore.'):
132 if name == 'ignore' or name.startswith('ignore.'):
132 files.append(util.expandpath(path))
133 files.append(util.expandpath(path))
133 return ignore.ignore(self._root, files, self._ui.warn)
134 return ignore.ignore(self._root, files, self._ui.warn)
134
135
135 @propertycache
136 @propertycache
136 def _slash(self):
137 def _slash(self):
137 return self._ui.configbool('ui', 'slash') and os.sep != '/'
138 return self._ui.configbool('ui', 'slash') and os.sep != '/'
138
139
139 @propertycache
140 @propertycache
140 def _checklink(self):
141 def _checklink(self):
141 return util.checklink(self._root)
142 return util.checklink(self._root)
142
143
143 @propertycache
144 @propertycache
144 def _checkexec(self):
145 def _checkexec(self):
145 return util.checkexec(self._root)
146 return util.checkexec(self._root)
146
147
147 @propertycache
148 @propertycache
148 def _checkcase(self):
149 def _checkcase(self):
149 return not util.checkcase(self._join('.hg'))
150 return not util.checkcase(self._join('.hg'))
150
151
151 def _join(self, f):
152 def _join(self, f):
152 # much faster than os.path.join()
153 # much faster than os.path.join()
153 # it's safe because f is always a relative path
154 # it's safe because f is always a relative path
154 return self._rootdir + f
155 return self._rootdir + f
155
156
156 def flagfunc(self, buildfallback):
157 def flagfunc(self, buildfallback):
157 if self._checklink and self._checkexec:
158 if self._checklink and self._checkexec:
158 def f(x):
159 def f(x):
159 try:
160 try:
160 st = os.lstat(self._join(x))
161 st = os.lstat(self._join(x))
161 if util.statislink(st):
162 if util.statislink(st):
162 return 'l'
163 return 'l'
163 if util.statisexec(st):
164 if util.statisexec(st):
164 return 'x'
165 return 'x'
165 except OSError:
166 except OSError:
166 pass
167 pass
167 return ''
168 return ''
168 return f
169 return f
169
170
170 fallback = buildfallback()
171 fallback = buildfallback()
171 if self._checklink:
172 if self._checklink:
172 def f(x):
173 def f(x):
173 if os.path.islink(self._join(x)):
174 if os.path.islink(self._join(x)):
174 return 'l'
175 return 'l'
175 if 'x' in fallback(x):
176 if 'x' in fallback(x):
176 return 'x'
177 return 'x'
177 return ''
178 return ''
178 return f
179 return f
179 if self._checkexec:
180 if self._checkexec:
180 def f(x):
181 def f(x):
181 if 'l' in fallback(x):
182 if 'l' in fallback(x):
182 return 'l'
183 return 'l'
183 if util.isexec(self._join(x)):
184 if util.isexec(self._join(x)):
184 return 'x'
185 return 'x'
185 return ''
186 return ''
186 return f
187 return f
187 else:
188 else:
188 return fallback
189 return fallback
189
190
190 @propertycache
191 @propertycache
191 def _cwd(self):
192 def _cwd(self):
192 return os.getcwd()
193 return os.getcwd()
193
194
194 def getcwd(self):
195 def getcwd(self):
195 cwd = self._cwd
196 cwd = self._cwd
196 if cwd == self._root:
197 if cwd == self._root:
197 return ''
198 return ''
198 # self._root ends with a path separator if self._root is '/' or 'C:\'
199 # self._root ends with a path separator if self._root is '/' or 'C:\'
199 rootsep = self._root
200 rootsep = self._root
200 if not util.endswithsep(rootsep):
201 if not util.endswithsep(rootsep):
201 rootsep += os.sep
202 rootsep += os.sep
202 if cwd.startswith(rootsep):
203 if cwd.startswith(rootsep):
203 return cwd[len(rootsep):]
204 return cwd[len(rootsep):]
204 else:
205 else:
205 # we're outside the repo. return an absolute path.
206 # we're outside the repo. return an absolute path.
206 return cwd
207 return cwd
207
208
208 def pathto(self, f, cwd=None):
209 def pathto(self, f, cwd=None):
209 if cwd is None:
210 if cwd is None:
210 cwd = self.getcwd()
211 cwd = self.getcwd()
211 path = util.pathto(self._root, cwd, f)
212 path = util.pathto(self._root, cwd, f)
212 if self._slash:
213 if self._slash:
213 return util.pconvert(path)
214 return util.pconvert(path)
214 return path
215 return path
215
216
216 def __getitem__(self, key):
217 def __getitem__(self, key):
217 '''Return the current state of key (a filename) in the dirstate.
218 '''Return the current state of key (a filename) in the dirstate.
218
219
219 States are:
220 States are:
220 n normal
221 n normal
221 m needs merging
222 m needs merging
222 r marked for removal
223 r marked for removal
223 a marked for addition
224 a marked for addition
224 ? not tracked
225 ? not tracked
225 '''
226 '''
226 return self._map.get(key, ("?",))[0]
227 return self._map.get(key, ("?",))[0]
227
228
228 def __contains__(self, key):
229 def __contains__(self, key):
229 return key in self._map
230 return key in self._map
230
231
231 def __iter__(self):
232 def __iter__(self):
232 for x in sorted(self._map):
233 for x in sorted(self._map):
233 yield x
234 yield x
234
235
235 def iteritems(self):
236 def iteritems(self):
236 return self._map.iteritems()
237 return self._map.iteritems()
237
238
238 def parents(self):
239 def parents(self):
239 return [self._validate(p) for p in self._pl]
240 return [self._validate(p) for p in self._pl]
240
241
241 def p1(self):
242 def p1(self):
242 return self._validate(self._pl[0])
243 return self._validate(self._pl[0])
243
244
244 def p2(self):
245 def p2(self):
245 return self._validate(self._pl[1])
246 return self._validate(self._pl[1])
246
247
247 def branch(self):
248 def branch(self):
248 return encoding.tolocal(self._branch)
249 return encoding.tolocal(self._branch)
249
250
250 def setparents(self, p1, p2=nullid):
251 def setparents(self, p1, p2=nullid):
251 """Set dirstate parents to p1 and p2.
252 """Set dirstate parents to p1 and p2.
252
253
253 When moving from two parents to one, 'm' merged entries a
254 When moving from two parents to one, 'm' merged entries a
254 adjusted to normal and previous copy records discarded and
255 adjusted to normal and previous copy records discarded and
255 returned by the call.
256 returned by the call.
256
257
257 See localrepo.setparents()
258 See localrepo.setparents()
258 """
259 """
259 if self._parentwriters == 0:
260 if self._parentwriters == 0:
260 raise ValueError("cannot set dirstate parent without "
261 raise ValueError("cannot set dirstate parent without "
261 "calling dirstate.beginparentchange")
262 "calling dirstate.beginparentchange")
262
263
263 self._dirty = self._dirtypl = True
264 self._dirty = self._dirtypl = True
264 oldp2 = self._pl[1]
265 oldp2 = self._pl[1]
265 self._pl = p1, p2
266 self._pl = p1, p2
266 copies = {}
267 copies = {}
267 if oldp2 != nullid and p2 == nullid:
268 if oldp2 != nullid and p2 == nullid:
268 # Discard 'm' markers when moving away from a merge state
269 # Discard 'm' markers when moving away from a merge state
269 for f, s in self._map.iteritems():
270 for f, s in self._map.iteritems():
270 if s[0] == 'm':
271 if s[0] == 'm':
271 if f in self._copymap:
272 if f in self._copymap:
272 copies[f] = self._copymap[f]
273 copies[f] = self._copymap[f]
273 self.normallookup(f)
274 self.normallookup(f)
274 return copies
275 return copies
275
276
276 def setbranch(self, branch):
277 def setbranch(self, branch):
277 self._branch = encoding.fromlocal(branch)
278 self._branch = encoding.fromlocal(branch)
278 f = self._opener('branch', 'w', atomictemp=True)
279 f = self._opener('branch', 'w', atomictemp=True)
279 try:
280 try:
280 f.write(self._branch + '\n')
281 f.write(self._branch + '\n')
281 f.close()
282 f.close()
282
283
283 # make sure filecache has the correct stat info for _branch after
284 # make sure filecache has the correct stat info for _branch after
284 # replacing the underlying file
285 # replacing the underlying file
285 ce = self._filecache['_branch']
286 ce = self._filecache['_branch']
286 if ce:
287 if ce:
287 ce.refresh()
288 ce.refresh()
288 except: # re-raises
289 except: # re-raises
289 f.discard()
290 f.discard()
290 raise
291 raise
291
292
292 def _read(self):
293 def _read(self):
293 self._map = {}
294 self._map = {}
294 self._copymap = {}
295 self._copymap = {}
295 try:
296 try:
296 st = self._opener.read("dirstate")
297 st = self._opener.read("dirstate")
297 except IOError, err:
298 except IOError, err:
298 if err.errno != errno.ENOENT:
299 if err.errno != errno.ENOENT:
299 raise
300 raise
300 return
301 return
301 if not st:
302 if not st:
302 return
303 return
303
304
304 # Python's garbage collector triggers a GC each time a certain number
305 # Python's garbage collector triggers a GC each time a certain number
305 # of container objects (the number being defined by
306 # of container objects (the number being defined by
306 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
307 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
307 # for each file in the dirstate. The C version then immediately marks
308 # for each file in the dirstate. The C version then immediately marks
308 # them as not to be tracked by the collector. However, this has no
309 # them as not to be tracked by the collector. However, this has no
309 # effect on when GCs are triggered, only on what objects the GC looks
310 # effect on when GCs are triggered, only on what objects the GC looks
310 # into. This means that O(number of files) GCs are unavoidable.
311 # into. This means that O(number of files) GCs are unavoidable.
311 # Depending on when in the process's lifetime the dirstate is parsed,
312 # Depending on when in the process's lifetime the dirstate is parsed,
312 # this can get very expensive. As a workaround, disable GC while
313 # this can get very expensive. As a workaround, disable GC while
313 # parsing the dirstate.
314 # parsing the dirstate.
314 gcenabled = gc.isenabled()
315 gcenabled = gc.isenabled()
315 gc.disable()
316 gc.disable()
316 try:
317 try:
317 p = parsers.parse_dirstate(self._map, self._copymap, st)
318 p = parsers.parse_dirstate(self._map, self._copymap, st)
318 finally:
319 finally:
319 if gcenabled:
320 if gcenabled:
320 gc.enable()
321 gc.enable()
321 if not self._dirtypl:
322 if not self._dirtypl:
322 self._pl = p
323 self._pl = p
323
324
324 def invalidate(self):
325 def invalidate(self):
325 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
326 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
326 "_ignore"):
327 "_ignore"):
327 if a in self.__dict__:
328 if a in self.__dict__:
328 delattr(self, a)
329 delattr(self, a)
329 self._lastnormaltime = 0
330 self._lastnormaltime = 0
330 self._dirty = False
331 self._dirty = False
331 self._parentwriters = 0
332 self._parentwriters = 0
332
333
333 def copy(self, source, dest):
334 def copy(self, source, dest):
334 """Mark dest as a copy of source. Unmark dest if source is None."""
335 """Mark dest as a copy of source. Unmark dest if source is None."""
335 if source == dest:
336 if source == dest:
336 return
337 return
337 self._dirty = True
338 self._dirty = True
338 if source is not None:
339 if source is not None:
339 self._copymap[dest] = source
340 self._copymap[dest] = source
340 elif dest in self._copymap:
341 elif dest in self._copymap:
341 del self._copymap[dest]
342 del self._copymap[dest]
342
343
343 def copied(self, file):
344 def copied(self, file):
344 return self._copymap.get(file, None)
345 return self._copymap.get(file, None)
345
346
346 def copies(self):
347 def copies(self):
347 return self._copymap
348 return self._copymap
348
349
349 def _droppath(self, f):
350 def _droppath(self, f):
350 if self[f] not in "?r" and "_dirs" in self.__dict__:
351 if self[f] not in "?r" and "_dirs" in self.__dict__:
351 self._dirs.delpath(f)
352 self._dirs.delpath(f)
352
353
353 def _addpath(self, f, state, mode, size, mtime):
354 def _addpath(self, f, state, mode, size, mtime):
354 oldstate = self[f]
355 oldstate = self[f]
355 if state == 'a' or oldstate == 'r':
356 if state == 'a' or oldstate == 'r':
356 scmutil.checkfilename(f)
357 scmutil.checkfilename(f)
357 if f in self._dirs:
358 if f in self._dirs:
358 raise util.Abort(_('directory %r already in dirstate') % f)
359 raise util.Abort(_('directory %r already in dirstate') % f)
359 # shadows
360 # shadows
360 for d in scmutil.finddirs(f):
361 for d in scmutil.finddirs(f):
361 if d in self._dirs:
362 if d in self._dirs:
362 break
363 break
363 if d in self._map and self[d] != 'r':
364 if d in self._map and self[d] != 'r':
364 raise util.Abort(
365 raise util.Abort(
365 _('file %r in dirstate clashes with %r') % (d, f))
366 _('file %r in dirstate clashes with %r') % (d, f))
366 if oldstate in "?r" and "_dirs" in self.__dict__:
367 if oldstate in "?r" and "_dirs" in self.__dict__:
367 self._dirs.addpath(f)
368 self._dirs.addpath(f)
368 self._dirty = True
369 self._dirty = True
369 self._map[f] = dirstatetuple(state, mode, size, mtime)
370 self._map[f] = dirstatetuple(state, mode, size, mtime)
370
371
371 def normal(self, f):
372 def normal(self, f):
372 '''Mark a file normal and clean.'''
373 '''Mark a file normal and clean.'''
373 s = os.lstat(self._join(f))
374 s = os.lstat(self._join(f))
374 mtime = int(s.st_mtime)
375 mtime = int(s.st_mtime)
375 self._addpath(f, 'n', s.st_mode,
376 self._addpath(f, 'n', s.st_mode,
376 s.st_size & _rangemask, mtime & _rangemask)
377 s.st_size & _rangemask, mtime & _rangemask)
377 if f in self._copymap:
378 if f in self._copymap:
378 del self._copymap[f]
379 del self._copymap[f]
379 if mtime > self._lastnormaltime:
380 if mtime > self._lastnormaltime:
380 # Remember the most recent modification timeslot for status(),
381 # Remember the most recent modification timeslot for status(),
381 # to make sure we won't miss future size-preserving file content
382 # to make sure we won't miss future size-preserving file content
382 # modifications that happen within the same timeslot.
383 # modifications that happen within the same timeslot.
383 self._lastnormaltime = mtime
384 self._lastnormaltime = mtime
384
385
385 def normallookup(self, f):
386 def normallookup(self, f):
386 '''Mark a file normal, but possibly dirty.'''
387 '''Mark a file normal, but possibly dirty.'''
387 if self._pl[1] != nullid and f in self._map:
388 if self._pl[1] != nullid and f in self._map:
388 # if there is a merge going on and the file was either
389 # if there is a merge going on and the file was either
389 # in state 'm' (-1) or coming from other parent (-2) before
390 # in state 'm' (-1) or coming from other parent (-2) before
390 # being removed, restore that state.
391 # being removed, restore that state.
391 entry = self._map[f]
392 entry = self._map[f]
392 if entry[0] == 'r' and entry[2] in (-1, -2):
393 if entry[0] == 'r' and entry[2] in (-1, -2):
393 source = self._copymap.get(f)
394 source = self._copymap.get(f)
394 if entry[2] == -1:
395 if entry[2] == -1:
395 self.merge(f)
396 self.merge(f)
396 elif entry[2] == -2:
397 elif entry[2] == -2:
397 self.otherparent(f)
398 self.otherparent(f)
398 if source:
399 if source:
399 self.copy(source, f)
400 self.copy(source, f)
400 return
401 return
401 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
402 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
402 return
403 return
403 self._addpath(f, 'n', 0, -1, -1)
404 self._addpath(f, 'n', 0, -1, -1)
404 if f in self._copymap:
405 if f in self._copymap:
405 del self._copymap[f]
406 del self._copymap[f]
406
407
407 def otherparent(self, f):
408 def otherparent(self, f):
408 '''Mark as coming from the other parent, always dirty.'''
409 '''Mark as coming from the other parent, always dirty.'''
409 if self._pl[1] == nullid:
410 if self._pl[1] == nullid:
410 raise util.Abort(_("setting %r to other parent "
411 raise util.Abort(_("setting %r to other parent "
411 "only allowed in merges") % f)
412 "only allowed in merges") % f)
412 self._addpath(f, 'n', 0, -2, -1)
413 self._addpath(f, 'n', 0, -2, -1)
413 if f in self._copymap:
414 if f in self._copymap:
414 del self._copymap[f]
415 del self._copymap[f]
415
416
416 def add(self, f):
417 def add(self, f):
417 '''Mark a file added.'''
418 '''Mark a file added.'''
418 self._addpath(f, 'a', 0, -1, -1)
419 self._addpath(f, 'a', 0, -1, -1)
419 if f in self._copymap:
420 if f in self._copymap:
420 del self._copymap[f]
421 del self._copymap[f]
421
422
422 def remove(self, f):
423 def remove(self, f):
423 '''Mark a file removed.'''
424 '''Mark a file removed.'''
424 self._dirty = True
425 self._dirty = True
425 self._droppath(f)
426 self._droppath(f)
426 size = 0
427 size = 0
427 if self._pl[1] != nullid and f in self._map:
428 if self._pl[1] != nullid and f in self._map:
428 # backup the previous state
429 # backup the previous state
429 entry = self._map[f]
430 entry = self._map[f]
430 if entry[0] == 'm': # merge
431 if entry[0] == 'm': # merge
431 size = -1
432 size = -1
432 elif entry[0] == 'n' and entry[2] == -2: # other parent
433 elif entry[0] == 'n' and entry[2] == -2: # other parent
433 size = -2
434 size = -2
434 self._map[f] = dirstatetuple('r', 0, size, 0)
435 self._map[f] = dirstatetuple('r', 0, size, 0)
435 if size == 0 and f in self._copymap:
436 if size == 0 and f in self._copymap:
436 del self._copymap[f]
437 del self._copymap[f]
437
438
438 def merge(self, f):
439 def merge(self, f):
439 '''Mark a file merged.'''
440 '''Mark a file merged.'''
440 if self._pl[1] == nullid:
441 if self._pl[1] == nullid:
441 return self.normallookup(f)
442 return self.normallookup(f)
442 s = os.lstat(self._join(f))
443 s = os.lstat(self._join(f))
443 self._addpath(f, 'm', s.st_mode,
444 self._addpath(f, 'm', s.st_mode,
444 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
445 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
445 if f in self._copymap:
446 if f in self._copymap:
446 del self._copymap[f]
447 del self._copymap[f]
447
448
448 def drop(self, f):
449 def drop(self, f):
449 '''Drop a file from the dirstate'''
450 '''Drop a file from the dirstate'''
450 if f in self._map:
451 if f in self._map:
451 self._dirty = True
452 self._dirty = True
452 self._droppath(f)
453 self._droppath(f)
453 del self._map[f]
454 del self._map[f]
454
455
455 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
456 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
456 normed = util.normcase(path)
457 normed = util.normcase(path)
457 folded = self._foldmap.get(normed, None)
458 folded = self._foldmap.get(normed, None)
458 if folded is None:
459 if folded is None:
459 if isknown:
460 if isknown:
460 folded = path
461 folded = path
461 else:
462 else:
462 if exists is None:
463 if exists is None:
463 exists = os.path.lexists(os.path.join(self._root, path))
464 exists = os.path.lexists(os.path.join(self._root, path))
464 if not exists:
465 if not exists:
465 # Maybe a path component exists
466 # Maybe a path component exists
466 if not ignoremissing and '/' in path:
467 if not ignoremissing and '/' in path:
467 d, f = path.rsplit('/', 1)
468 d, f = path.rsplit('/', 1)
468 d = self._normalize(d, isknown, ignoremissing, None)
469 d = self._normalize(d, isknown, ignoremissing, None)
469 folded = d + "/" + f
470 folded = d + "/" + f
470 else:
471 else:
471 # No path components, preserve original case
472 # No path components, preserve original case
472 folded = path
473 folded = path
473 else:
474 else:
474 # recursively normalize leading directory components
475 # recursively normalize leading directory components
475 # against dirstate
476 # against dirstate
476 if '/' in normed:
477 if '/' in normed:
477 d, f = normed.rsplit('/', 1)
478 d, f = normed.rsplit('/', 1)
478 d = self._normalize(d, isknown, ignoremissing, True)
479 d = self._normalize(d, isknown, ignoremissing, True)
479 r = self._root + "/" + d
480 r = self._root + "/" + d
480 folded = d + "/" + util.fspath(f, r)
481 folded = d + "/" + util.fspath(f, r)
481 else:
482 else:
482 folded = util.fspath(normed, self._root)
483 folded = util.fspath(normed, self._root)
483 self._foldmap[normed] = folded
484 self._foldmap[normed] = folded
484
485
485 return folded
486 return folded
486
487
487 def normalize(self, path, isknown=False, ignoremissing=False):
488 def normalize(self, path, isknown=False, ignoremissing=False):
488 '''
489 '''
489 normalize the case of a pathname when on a casefolding filesystem
490 normalize the case of a pathname when on a casefolding filesystem
490
491
491 isknown specifies whether the filename came from walking the
492 isknown specifies whether the filename came from walking the
492 disk, to avoid extra filesystem access.
493 disk, to avoid extra filesystem access.
493
494
494 If ignoremissing is True, missing path are returned
495 If ignoremissing is True, missing path are returned
495 unchanged. Otherwise, we try harder to normalize possibly
496 unchanged. Otherwise, we try harder to normalize possibly
496 existing path components.
497 existing path components.
497
498
498 The normalized case is determined based on the following precedence:
499 The normalized case is determined based on the following precedence:
499
500
500 - version of name already stored in the dirstate
501 - version of name already stored in the dirstate
501 - version of name stored on disk
502 - version of name stored on disk
502 - version provided via command arguments
503 - version provided via command arguments
503 '''
504 '''
504
505
505 if self._checkcase:
506 if self._checkcase:
506 return self._normalize(path, isknown, ignoremissing)
507 return self._normalize(path, isknown, ignoremissing)
507 return path
508 return path
508
509
509 def clear(self):
510 def clear(self):
510 self._map = {}
511 self._map = {}
511 if "_dirs" in self.__dict__:
512 if "_dirs" in self.__dict__:
512 delattr(self, "_dirs")
513 delattr(self, "_dirs")
513 self._copymap = {}
514 self._copymap = {}
514 self._pl = [nullid, nullid]
515 self._pl = [nullid, nullid]
515 self._lastnormaltime = 0
516 self._lastnormaltime = 0
516 self._dirty = True
517 self._dirty = True
517
518
518 def rebuild(self, parent, allfiles, changedfiles=None):
519 def rebuild(self, parent, allfiles, changedfiles=None):
519 changedfiles = changedfiles or allfiles
520 changedfiles = changedfiles or allfiles
520 oldmap = self._map
521 oldmap = self._map
521 self.clear()
522 self.clear()
522 for f in allfiles:
523 for f in allfiles:
523 if f not in changedfiles:
524 if f not in changedfiles:
524 self._map[f] = oldmap[f]
525 self._map[f] = oldmap[f]
525 else:
526 else:
526 if 'x' in allfiles.flags(f):
527 if 'x' in allfiles.flags(f):
527 self._map[f] = dirstatetuple('n', 0777, -1, 0)
528 self._map[f] = dirstatetuple('n', 0777, -1, 0)
528 else:
529 else:
529 self._map[f] = dirstatetuple('n', 0666, -1, 0)
530 self._map[f] = dirstatetuple('n', 0666, -1, 0)
530 self._pl = (parent, nullid)
531 self._pl = (parent, nullid)
531 self._dirty = True
532 self._dirty = True
532
533
533 def write(self):
534 def write(self):
534 if not self._dirty:
535 if not self._dirty:
535 return
536 return
536
537
537 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
538 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
538 # timestamp of each entries in dirstate, because of 'now > mtime'
539 # timestamp of each entries in dirstate, because of 'now > mtime'
539 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
540 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
540 if delaywrite:
541 if delaywrite:
541 import time # to avoid useless import
542 import time # to avoid useless import
542 time.sleep(delaywrite)
543 time.sleep(delaywrite)
543
544
544 st = self._opener("dirstate", "w", atomictemp=True)
545 st = self._opener("dirstate", "w", atomictemp=True)
545 # use the modification time of the newly created temporary file as the
546 # use the modification time of the newly created temporary file as the
546 # filesystem's notion of 'now'
547 # filesystem's notion of 'now'
547 now = util.fstat(st).st_mtime
548 now = util.fstat(st).st_mtime
548 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
549 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
549 st.close()
550 st.close()
550 self._lastnormaltime = 0
551 self._lastnormaltime = 0
551 self._dirty = self._dirtypl = False
552 self._dirty = self._dirtypl = False
552
553
553 def _dirignore(self, f):
554 def _dirignore(self, f):
554 if f == '.':
555 if f == '.':
555 return False
556 return False
556 if self._ignore(f):
557 if self._ignore(f):
557 return True
558 return True
558 for p in scmutil.finddirs(f):
559 for p in scmutil.finddirs(f):
559 if self._ignore(p):
560 if self._ignore(p):
560 return True
561 return True
561 return False
562 return False
562
563
563 def _walkexplicit(self, match, subrepos):
564 def _walkexplicit(self, match, subrepos):
564 '''Get stat data about the files explicitly specified by match.
565 '''Get stat data about the files explicitly specified by match.
565
566
566 Return a triple (results, dirsfound, dirsnotfound).
567 Return a triple (results, dirsfound, dirsnotfound).
567 - results is a mapping from filename to stat result. It also contains
568 - results is a mapping from filename to stat result. It also contains
568 listings mapping subrepos and .hg to None.
569 listings mapping subrepos and .hg to None.
569 - dirsfound is a list of files found to be directories.
570 - dirsfound is a list of files found to be directories.
570 - dirsnotfound is a list of files that the dirstate thinks are
571 - dirsnotfound is a list of files that the dirstate thinks are
571 directories and that were not found.'''
572 directories and that were not found.'''
572
573
573 def badtype(mode):
574 def badtype(mode):
574 kind = _('unknown')
575 kind = _('unknown')
575 if stat.S_ISCHR(mode):
576 if stat.S_ISCHR(mode):
576 kind = _('character device')
577 kind = _('character device')
577 elif stat.S_ISBLK(mode):
578 elif stat.S_ISBLK(mode):
578 kind = _('block device')
579 kind = _('block device')
579 elif stat.S_ISFIFO(mode):
580 elif stat.S_ISFIFO(mode):
580 kind = _('fifo')
581 kind = _('fifo')
581 elif stat.S_ISSOCK(mode):
582 elif stat.S_ISSOCK(mode):
582 kind = _('socket')
583 kind = _('socket')
583 elif stat.S_ISDIR(mode):
584 elif stat.S_ISDIR(mode):
584 kind = _('directory')
585 kind = _('directory')
585 return _('unsupported file type (type is %s)') % kind
586 return _('unsupported file type (type is %s)') % kind
586
587
587 matchedir = match.explicitdir
588 matchedir = match.explicitdir
588 badfn = match.bad
589 badfn = match.bad
589 dmap = self._map
590 dmap = self._map
590 normpath = util.normpath
591 normpath = util.normpath
591 lstat = os.lstat
592 lstat = os.lstat
592 getkind = stat.S_IFMT
593 getkind = stat.S_IFMT
593 dirkind = stat.S_IFDIR
594 dirkind = stat.S_IFDIR
594 regkind = stat.S_IFREG
595 regkind = stat.S_IFREG
595 lnkkind = stat.S_IFLNK
596 lnkkind = stat.S_IFLNK
596 join = self._join
597 join = self._join
597 dirsfound = []
598 dirsfound = []
598 foundadd = dirsfound.append
599 foundadd = dirsfound.append
599 dirsnotfound = []
600 dirsnotfound = []
600 notfoundadd = dirsnotfound.append
601 notfoundadd = dirsnotfound.append
601
602
602 if match.matchfn != match.exact and self._checkcase:
603 if match.matchfn != match.exact and self._checkcase:
603 normalize = self._normalize
604 normalize = self._normalize
604 else:
605 else:
605 normalize = None
606 normalize = None
606
607
607 files = sorted(match.files())
608 files = sorted(match.files())
608 subrepos.sort()
609 subrepos.sort()
609 i, j = 0, 0
610 i, j = 0, 0
610 while i < len(files) and j < len(subrepos):
611 while i < len(files) and j < len(subrepos):
611 subpath = subrepos[j] + "/"
612 subpath = subrepos[j] + "/"
612 if files[i] < subpath:
613 if files[i] < subpath:
613 i += 1
614 i += 1
614 continue
615 continue
615 while i < len(files) and files[i].startswith(subpath):
616 while i < len(files) and files[i].startswith(subpath):
616 del files[i]
617 del files[i]
617 j += 1
618 j += 1
618
619
619 if not files or '.' in files:
620 if not files or '.' in files:
620 files = ['']
621 files = ['']
621 results = dict.fromkeys(subrepos)
622 results = dict.fromkeys(subrepos)
622 results['.hg'] = None
623 results['.hg'] = None
623
624
624 for ff in files:
625 for ff in files:
625 if normalize:
626 if normalize:
626 nf = normalize(normpath(ff), False, True)
627 nf = normalize(normpath(ff), False, True)
627 else:
628 else:
628 nf = normpath(ff)
629 nf = normpath(ff)
629 if nf in results:
630 if nf in results:
630 continue
631 continue
631
632
632 try:
633 try:
633 st = lstat(join(nf))
634 st = lstat(join(nf))
634 kind = getkind(st.st_mode)
635 kind = getkind(st.st_mode)
635 if kind == dirkind:
636 if kind == dirkind:
636 if nf in dmap:
637 if nf in dmap:
637 # file replaced by dir on disk but still in dirstate
638 # file replaced by dir on disk but still in dirstate
638 results[nf] = None
639 results[nf] = None
639 if matchedir:
640 if matchedir:
640 matchedir(nf)
641 matchedir(nf)
641 foundadd(nf)
642 foundadd(nf)
642 elif kind == regkind or kind == lnkkind:
643 elif kind == regkind or kind == lnkkind:
643 results[nf] = st
644 results[nf] = st
644 else:
645 else:
645 badfn(ff, badtype(kind))
646 badfn(ff, badtype(kind))
646 if nf in dmap:
647 if nf in dmap:
647 results[nf] = None
648 results[nf] = None
648 except OSError, inst: # nf not found on disk - it is dirstate only
649 except OSError, inst: # nf not found on disk - it is dirstate only
649 if nf in dmap: # does it exactly match a missing file?
650 if nf in dmap: # does it exactly match a missing file?
650 results[nf] = None
651 results[nf] = None
651 else: # does it match a missing directory?
652 else: # does it match a missing directory?
652 prefix = nf + "/"
653 prefix = nf + "/"
653 for fn in dmap:
654 for fn in dmap:
654 if fn.startswith(prefix):
655 if fn.startswith(prefix):
655 if matchedir:
656 if matchedir:
656 matchedir(nf)
657 matchedir(nf)
657 notfoundadd(nf)
658 notfoundadd(nf)
658 break
659 break
659 else:
660 else:
660 badfn(ff, inst.strerror)
661 badfn(ff, inst.strerror)
661
662
662 return results, dirsfound, dirsnotfound
663 return results, dirsfound, dirsnotfound
663
664
664 def walk(self, match, subrepos, unknown, ignored, full=True):
665 def walk(self, match, subrepos, unknown, ignored, full=True):
665 '''
666 '''
666 Walk recursively through the directory tree, finding all files
667 Walk recursively through the directory tree, finding all files
667 matched by match.
668 matched by match.
668
669
669 If full is False, maybe skip some known-clean files.
670 If full is False, maybe skip some known-clean files.
670
671
671 Return a dict mapping filename to stat-like object (either
672 Return a dict mapping filename to stat-like object (either
672 mercurial.osutil.stat instance or return value of os.stat()).
673 mercurial.osutil.stat instance or return value of os.stat()).
673
674
674 '''
675 '''
675 # full is a flag that extensions that hook into walk can use -- this
676 # full is a flag that extensions that hook into walk can use -- this
676 # implementation doesn't use it at all. This satisfies the contract
677 # implementation doesn't use it at all. This satisfies the contract
677 # because we only guarantee a "maybe".
678 # because we only guarantee a "maybe".
678
679
679 if ignored:
680 if ignored:
680 ignore = util.never
681 ignore = util.never
681 dirignore = util.never
682 dirignore = util.never
682 elif unknown:
683 elif unknown:
683 ignore = self._ignore
684 ignore = self._ignore
684 dirignore = self._dirignore
685 dirignore = self._dirignore
685 else:
686 else:
686 # if not unknown and not ignored, drop dir recursion and step 2
687 # if not unknown and not ignored, drop dir recursion and step 2
687 ignore = util.always
688 ignore = util.always
688 dirignore = util.always
689 dirignore = util.always
689
690
690 matchfn = match.matchfn
691 matchfn = match.matchfn
691 matchalways = match.always()
692 matchalways = match.always()
692 matchtdir = match.traversedir
693 matchtdir = match.traversedir
693 dmap = self._map
694 dmap = self._map
694 listdir = osutil.listdir
695 listdir = osutil.listdir
695 lstat = os.lstat
696 lstat = os.lstat
696 dirkind = stat.S_IFDIR
697 dirkind = stat.S_IFDIR
697 regkind = stat.S_IFREG
698 regkind = stat.S_IFREG
698 lnkkind = stat.S_IFLNK
699 lnkkind = stat.S_IFLNK
699 join = self._join
700 join = self._join
700
701
701 exact = skipstep3 = False
702 exact = skipstep3 = False
702 if matchfn == match.exact: # match.exact
703 if matchfn == match.exact: # match.exact
703 exact = True
704 exact = True
704 dirignore = util.always # skip step 2
705 dirignore = util.always # skip step 2
705 elif match.files() and not match.anypats(): # match.match, no patterns
706 elif match.files() and not match.anypats(): # match.match, no patterns
706 skipstep3 = True
707 skipstep3 = True
707
708
708 if not exact and self._checkcase:
709 if not exact and self._checkcase:
709 normalize = self._normalize
710 normalize = self._normalize
710 skipstep3 = False
711 skipstep3 = False
711 else:
712 else:
712 normalize = None
713 normalize = None
713
714
714 # step 1: find all explicit files
715 # step 1: find all explicit files
715 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
716 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
716
717
717 skipstep3 = skipstep3 and not (work or dirsnotfound)
718 skipstep3 = skipstep3 and not (work or dirsnotfound)
718 work = [d for d in work if not dirignore(d)]
719 work = [d for d in work if not dirignore(d)]
719 wadd = work.append
720 wadd = work.append
720
721
721 # step 2: visit subdirectories
722 # step 2: visit subdirectories
722 while work:
723 while work:
723 nd = work.pop()
724 nd = work.pop()
724 skip = None
725 skip = None
725 if nd == '.':
726 if nd == '.':
726 nd = ''
727 nd = ''
727 else:
728 else:
728 skip = '.hg'
729 skip = '.hg'
729 try:
730 try:
730 entries = listdir(join(nd), stat=True, skip=skip)
731 entries = listdir(join(nd), stat=True, skip=skip)
731 except OSError, inst:
732 except OSError, inst:
732 if inst.errno in (errno.EACCES, errno.ENOENT):
733 if inst.errno in (errno.EACCES, errno.ENOENT):
733 match.bad(self.pathto(nd), inst.strerror)
734 match.bad(self.pathto(nd), inst.strerror)
734 continue
735 continue
735 raise
736 raise
736 for f, kind, st in entries:
737 for f, kind, st in entries:
737 if normalize:
738 if normalize:
738 nf = normalize(nd and (nd + "/" + f) or f, True, True)
739 nf = normalize(nd and (nd + "/" + f) or f, True, True)
739 else:
740 else:
740 nf = nd and (nd + "/" + f) or f
741 nf = nd and (nd + "/" + f) or f
741 if nf not in results:
742 if nf not in results:
742 if kind == dirkind:
743 if kind == dirkind:
743 if not ignore(nf):
744 if not ignore(nf):
744 if matchtdir:
745 if matchtdir:
745 matchtdir(nf)
746 matchtdir(nf)
746 wadd(nf)
747 wadd(nf)
747 if nf in dmap and (matchalways or matchfn(nf)):
748 if nf in dmap and (matchalways or matchfn(nf)):
748 results[nf] = None
749 results[nf] = None
749 elif kind == regkind or kind == lnkkind:
750 elif kind == regkind or kind == lnkkind:
750 if nf in dmap:
751 if nf in dmap:
751 if matchalways or matchfn(nf):
752 if matchalways or matchfn(nf):
752 results[nf] = st
753 results[nf] = st
753 elif (matchalways or matchfn(nf)) and not ignore(nf):
754 elif (matchalways or matchfn(nf)) and not ignore(nf):
754 results[nf] = st
755 results[nf] = st
755 elif nf in dmap and (matchalways or matchfn(nf)):
756 elif nf in dmap and (matchalways or matchfn(nf)):
756 results[nf] = None
757 results[nf] = None
757
758
758 for s in subrepos:
759 for s in subrepos:
759 del results[s]
760 del results[s]
760 del results['.hg']
761 del results['.hg']
761
762
762 # step 3: visit remaining files from dmap
763 # step 3: visit remaining files from dmap
763 if not skipstep3 and not exact:
764 if not skipstep3 and not exact:
764 # If a dmap file is not in results yet, it was either
765 # If a dmap file is not in results yet, it was either
765 # a) not matching matchfn b) ignored, c) missing, or d) under a
766 # a) not matching matchfn b) ignored, c) missing, or d) under a
766 # symlink directory.
767 # symlink directory.
767 if not results and matchalways:
768 if not results and matchalways:
768 visit = dmap.keys()
769 visit = dmap.keys()
769 else:
770 else:
770 visit = [f for f in dmap if f not in results and matchfn(f)]
771 visit = [f for f in dmap if f not in results and matchfn(f)]
771 visit.sort()
772 visit.sort()
772
773
773 if unknown:
774 if unknown:
774 # unknown == True means we walked all dirs under the roots
775 # unknown == True means we walked all dirs under the roots
775 # that wasn't ignored, and everything that matched was stat'ed
776 # that wasn't ignored, and everything that matched was stat'ed
776 # and is already in results.
777 # and is already in results.
777 # The rest must thus be ignored or under a symlink.
778 # The rest must thus be ignored or under a symlink.
778 audit_path = pathutil.pathauditor(self._root)
779 audit_path = pathutil.pathauditor(self._root)
779
780
780 for nf in iter(visit):
781 for nf in iter(visit):
781 # Report ignored items in the dmap as long as they are not
782 # Report ignored items in the dmap as long as they are not
782 # under a symlink directory.
783 # under a symlink directory.
783 if audit_path.check(nf):
784 if audit_path.check(nf):
784 try:
785 try:
785 results[nf] = lstat(join(nf))
786 results[nf] = lstat(join(nf))
786 # file was just ignored, no links, and exists
787 # file was just ignored, no links, and exists
787 except OSError:
788 except OSError:
788 # file doesn't exist
789 # file doesn't exist
789 results[nf] = None
790 results[nf] = None
790 else:
791 else:
791 # It's either missing or under a symlink directory
792 # It's either missing or under a symlink directory
792 # which we in this case report as missing
793 # which we in this case report as missing
793 results[nf] = None
794 results[nf] = None
794 else:
795 else:
795 # We may not have walked the full directory tree above,
796 # We may not have walked the full directory tree above,
796 # so stat and check everything we missed.
797 # so stat and check everything we missed.
797 nf = iter(visit).next
798 nf = iter(visit).next
798 for st in util.statfiles([join(i) for i in visit]):
799 for st in util.statfiles([join(i) for i in visit]):
799 results[nf()] = st
800 results[nf()] = st
800 return results
801 return results
801
802
802 def status(self, match, subrepos, ignored, clean, unknown):
803 def status(self, match, subrepos, ignored, clean, unknown):
803 '''Determine the status of the working copy relative to the
804 '''Determine the status of the working copy relative to the
804 dirstate and return a tuple of lists (unsure, modified, added,
805 dirstate and return a tuple of lists (unsure, modified, added,
805 removed, deleted, unknown, ignored, clean), where:
806 removed, deleted, unknown, ignored, clean), where:
806
807
807 unsure:
808 unsure:
808 files that might have been modified since the dirstate was
809 files that might have been modified since the dirstate was
809 written, but need to be read to be sure (size is the same
810 written, but need to be read to be sure (size is the same
810 but mtime differs)
811 but mtime differs)
811 modified:
812 modified:
812 files that have definitely been modified since the dirstate
813 files that have definitely been modified since the dirstate
813 was written (different size or mode)
814 was written (different size or mode)
814 added:
815 added:
815 files that have been explicitly added with hg add
816 files that have been explicitly added with hg add
816 removed:
817 removed:
817 files that have been explicitly removed with hg remove
818 files that have been explicitly removed with hg remove
818 deleted:
819 deleted:
819 files that have been deleted through other means ("missing")
820 files that have been deleted through other means ("missing")
820 unknown:
821 unknown:
821 files not in the dirstate that are not ignored
822 files not in the dirstate that are not ignored
822 ignored:
823 ignored:
823 files not in the dirstate that are ignored
824 files not in the dirstate that are ignored
824 (by _dirignore())
825 (by _dirignore())
825 clean:
826 clean:
826 files that have definitely not been modified since the
827 files that have definitely not been modified since the
827 dirstate was written
828 dirstate was written
828 '''
829 '''
829 listignored, listclean, listunknown = ignored, clean, unknown
830 listignored, listclean, listunknown = ignored, clean, unknown
830 lookup, modified, added, unknown, ignored = [], [], [], [], []
831 lookup, modified, added, unknown, ignored = [], [], [], [], []
831 removed, deleted, clean = [], [], []
832 removed, deleted, clean = [], [], []
832
833
833 dmap = self._map
834 dmap = self._map
834 ladd = lookup.append # aka "unsure"
835 ladd = lookup.append # aka "unsure"
835 madd = modified.append
836 madd = modified.append
836 aadd = added.append
837 aadd = added.append
837 uadd = unknown.append
838 uadd = unknown.append
838 iadd = ignored.append
839 iadd = ignored.append
839 radd = removed.append
840 radd = removed.append
840 dadd = deleted.append
841 dadd = deleted.append
841 cadd = clean.append
842 cadd = clean.append
842 mexact = match.exact
843 mexact = match.exact
843 dirignore = self._dirignore
844 dirignore = self._dirignore
844 checkexec = self._checkexec
845 checkexec = self._checkexec
845 copymap = self._copymap
846 copymap = self._copymap
846 lastnormaltime = self._lastnormaltime
847 lastnormaltime = self._lastnormaltime
847
848
848 # We need to do full walks when either
849 # We need to do full walks when either
849 # - we're listing all clean files, or
850 # - we're listing all clean files, or
850 # - match.traversedir does something, because match.traversedir should
851 # - match.traversedir does something, because match.traversedir should
851 # be called for every dir in the working dir
852 # be called for every dir in the working dir
852 full = listclean or match.traversedir is not None
853 full = listclean or match.traversedir is not None
853 for fn, st in self.walk(match, subrepos, listunknown, listignored,
854 for fn, st in self.walk(match, subrepos, listunknown, listignored,
854 full=full).iteritems():
855 full=full).iteritems():
855 if fn not in dmap:
856 if fn not in dmap:
856 if (listignored or mexact(fn)) and dirignore(fn):
857 if (listignored or mexact(fn)) and dirignore(fn):
857 if listignored:
858 if listignored:
858 iadd(fn)
859 iadd(fn)
859 else:
860 else:
860 uadd(fn)
861 uadd(fn)
861 continue
862 continue
862
863
863 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
864 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
864 # written like that for performance reasons. dmap[fn] is not a
865 # written like that for performance reasons. dmap[fn] is not a
865 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
866 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
866 # opcode has fast paths when the value to be unpacked is a tuple or
867 # opcode has fast paths when the value to be unpacked is a tuple or
867 # a list, but falls back to creating a full-fledged iterator in
868 # a list, but falls back to creating a full-fledged iterator in
868 # general. That is much slower than simply accessing and storing the
869 # general. That is much slower than simply accessing and storing the
869 # tuple members one by one.
870 # tuple members one by one.
870 t = dmap[fn]
871 t = dmap[fn]
871 state = t[0]
872 state = t[0]
872 mode = t[1]
873 mode = t[1]
873 size = t[2]
874 size = t[2]
874 time = t[3]
875 time = t[3]
875
876
876 if not st and state in "nma":
877 if not st and state in "nma":
877 dadd(fn)
878 dadd(fn)
878 elif state == 'n':
879 elif state == 'n':
879 mtime = int(st.st_mtime)
880 mtime = int(st.st_mtime)
880 if (size >= 0 and
881 if (size >= 0 and
881 ((size != st.st_size and size != st.st_size & _rangemask)
882 ((size != st.st_size and size != st.st_size & _rangemask)
882 or ((mode ^ st.st_mode) & 0100 and checkexec))
883 or ((mode ^ st.st_mode) & 0100 and checkexec))
883 or size == -2 # other parent
884 or size == -2 # other parent
884 or fn in copymap):
885 or fn in copymap):
885 madd(fn)
886 madd(fn)
886 elif time != mtime and time != mtime & _rangemask:
887 elif time != mtime and time != mtime & _rangemask:
887 ladd(fn)
888 ladd(fn)
888 elif mtime == lastnormaltime:
889 elif mtime == lastnormaltime:
889 # fn may have been changed in the same timeslot without
890 # fn may have been changed in the same timeslot without
890 # changing its size. This can happen if we quickly do
891 # changing its size. This can happen if we quickly do
891 # multiple commits in a single transaction.
892 # multiple commits in a single transaction.
892 # Force lookup, so we don't miss such a racy file change.
893 # Force lookup, so we don't miss such a racy file change.
893 ladd(fn)
894 ladd(fn)
894 elif listclean:
895 elif listclean:
895 cadd(fn)
896 cadd(fn)
896 elif state == 'm':
897 elif state == 'm':
897 madd(fn)
898 madd(fn)
898 elif state == 'a':
899 elif state == 'a':
899 aadd(fn)
900 aadd(fn)
900 elif state == 'r':
901 elif state == 'r':
901 radd(fn)
902 radd(fn)
902
903
903 return (lookup, modified, added, removed, deleted, unknown, ignored,
904 return (lookup, modified, added, removed, deleted, unknown, ignored,
904 clean)
905 clean)
905
906
906 def matches(self, match):
907 def matches(self, match):
907 '''
908 '''
908 return files in the dirstate (in whatever state) filtered by match
909 return files in the dirstate (in whatever state) filtered by match
909 '''
910 '''
910 dmap = self._map
911 dmap = self._map
911 if match.always():
912 if match.always():
912 return dmap.keys()
913 return dmap.keys()
913 files = match.files()
914 files = match.files()
914 if match.matchfn == match.exact:
915 if match.matchfn == match.exact:
915 # fast path -- filter the other way around, since typically files is
916 # fast path -- filter the other way around, since typically files is
916 # much smaller than dmap
917 # much smaller than dmap
917 return [f for f in files if f in dmap]
918 return [f for f in files if f in dmap]
918 if not match.anypats() and util.all(fn in dmap for fn in files):
919 if not match.anypats() and util.all(fn in dmap for fn in files):
919 # fast path -- all the values are known to be files, so just return
920 # fast path -- all the values are known to be files, so just return
920 # that
921 # that
921 return list(files)
922 return list(files)
922 return [f for f in dmap if match(f)]
923 return [f for f in dmap if match(f)]
General Comments 0
You need to be logged in to leave comments. Login now