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