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