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