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