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