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