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