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