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