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