##// END OF EJS Templates
dirstate: use open/read of vfs(opener) explicitly instead of read...
FUJIWARA Katsunori -
r25227:fd0f9191 default
parent child Browse files
Show More
@@ -1,988 +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 st = self._opener.read(self._filename)
329 fp = self._opener.open(self._filename)
330 try:
331 st = fp.read()
332 finally:
333 fp.close()
330 334 except IOError, err:
331 335 if err.errno != errno.ENOENT:
332 336 raise
333 337 return
334 338 if not st:
335 339 return
336 340
337 341 # Python's garbage collector triggers a GC each time a certain number
338 342 # of container objects (the number being defined by
339 343 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
340 344 # for each file in the dirstate. The C version then immediately marks
341 345 # them as not to be tracked by the collector. However, this has no
342 346 # effect on when GCs are triggered, only on what objects the GC looks
343 347 # into. This means that O(number of files) GCs are unavoidable.
344 348 # Depending on when in the process's lifetime the dirstate is parsed,
345 349 # this can get very expensive. As a workaround, disable GC while
346 350 # parsing the dirstate.
347 351 #
348 352 # (we cannot decorate the function directly since it is in a C module)
349 353 parse_dirstate = util.nogc(parsers.parse_dirstate)
350 354 p = parse_dirstate(self._map, self._copymap, st)
351 355 if not self._dirtypl:
352 356 self._pl = p
353 357
354 358 def invalidate(self):
355 359 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
356 360 "_pl", "_dirs", "_ignore"):
357 361 if a in self.__dict__:
358 362 delattr(self, a)
359 363 self._lastnormaltime = 0
360 364 self._dirty = False
361 365 self._parentwriters = 0
362 366
363 367 def copy(self, source, dest):
364 368 """Mark dest as a copy of source. Unmark dest if source is None."""
365 369 if source == dest:
366 370 return
367 371 self._dirty = True
368 372 if source is not None:
369 373 self._copymap[dest] = source
370 374 elif dest in self._copymap:
371 375 del self._copymap[dest]
372 376
373 377 def copied(self, file):
374 378 return self._copymap.get(file, None)
375 379
376 380 def copies(self):
377 381 return self._copymap
378 382
379 383 def _droppath(self, f):
380 384 if self[f] not in "?r" and "_dirs" in self.__dict__:
381 385 self._dirs.delpath(f)
382 386
383 387 def _addpath(self, f, state, mode, size, mtime):
384 388 oldstate = self[f]
385 389 if state == 'a' or oldstate == 'r':
386 390 scmutil.checkfilename(f)
387 391 if f in self._dirs:
388 392 raise util.Abort(_('directory %r already in dirstate') % f)
389 393 # shadows
390 394 for d in util.finddirs(f):
391 395 if d in self._dirs:
392 396 break
393 397 if d in self._map and self[d] != 'r':
394 398 raise util.Abort(
395 399 _('file %r in dirstate clashes with %r') % (d, f))
396 400 if oldstate in "?r" and "_dirs" in self.__dict__:
397 401 self._dirs.addpath(f)
398 402 self._dirty = True
399 403 self._map[f] = dirstatetuple(state, mode, size, mtime)
400 404
401 405 def normal(self, f):
402 406 '''Mark a file normal and clean.'''
403 407 s = os.lstat(self._join(f))
404 408 mtime = int(s.st_mtime)
405 409 self._addpath(f, 'n', s.st_mode,
406 410 s.st_size & _rangemask, mtime & _rangemask)
407 411 if f in self._copymap:
408 412 del self._copymap[f]
409 413 if mtime > self._lastnormaltime:
410 414 # Remember the most recent modification timeslot for status(),
411 415 # to make sure we won't miss future size-preserving file content
412 416 # modifications that happen within the same timeslot.
413 417 self._lastnormaltime = mtime
414 418
415 419 def normallookup(self, f):
416 420 '''Mark a file normal, but possibly dirty.'''
417 421 if self._pl[1] != nullid and f in self._map:
418 422 # if there is a merge going on and the file was either
419 423 # in state 'm' (-1) or coming from other parent (-2) before
420 424 # being removed, restore that state.
421 425 entry = self._map[f]
422 426 if entry[0] == 'r' and entry[2] in (-1, -2):
423 427 source = self._copymap.get(f)
424 428 if entry[2] == -1:
425 429 self.merge(f)
426 430 elif entry[2] == -2:
427 431 self.otherparent(f)
428 432 if source:
429 433 self.copy(source, f)
430 434 return
431 435 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
432 436 return
433 437 self._addpath(f, 'n', 0, -1, -1)
434 438 if f in self._copymap:
435 439 del self._copymap[f]
436 440
437 441 def otherparent(self, f):
438 442 '''Mark as coming from the other parent, always dirty.'''
439 443 if self._pl[1] == nullid:
440 444 raise util.Abort(_("setting %r to other parent "
441 445 "only allowed in merges") % f)
442 446 if f in self and self[f] == 'n':
443 447 # merge-like
444 448 self._addpath(f, 'm', 0, -2, -1)
445 449 else:
446 450 # add-like
447 451 self._addpath(f, 'n', 0, -2, -1)
448 452
449 453 if f in self._copymap:
450 454 del self._copymap[f]
451 455
452 456 def add(self, f):
453 457 '''Mark a file added.'''
454 458 self._addpath(f, 'a', 0, -1, -1)
455 459 if f in self._copymap:
456 460 del self._copymap[f]
457 461
458 462 def remove(self, f):
459 463 '''Mark a file removed.'''
460 464 self._dirty = True
461 465 self._droppath(f)
462 466 size = 0
463 467 if self._pl[1] != nullid and f in self._map:
464 468 # backup the previous state
465 469 entry = self._map[f]
466 470 if entry[0] == 'm': # merge
467 471 size = -1
468 472 elif entry[0] == 'n' and entry[2] == -2: # other parent
469 473 size = -2
470 474 self._map[f] = dirstatetuple('r', 0, size, 0)
471 475 if size == 0 and f in self._copymap:
472 476 del self._copymap[f]
473 477
474 478 def merge(self, f):
475 479 '''Mark a file merged.'''
476 480 if self._pl[1] == nullid:
477 481 return self.normallookup(f)
478 482 return self.otherparent(f)
479 483
480 484 def drop(self, f):
481 485 '''Drop a file from the dirstate'''
482 486 if f in self._map:
483 487 self._dirty = True
484 488 self._droppath(f)
485 489 del self._map[f]
486 490
487 491 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
488 492 if exists is None:
489 493 exists = os.path.lexists(os.path.join(self._root, path))
490 494 if not exists:
491 495 # Maybe a path component exists
492 496 if not ignoremissing and '/' in path:
493 497 d, f = path.rsplit('/', 1)
494 498 d = self._normalize(d, False, ignoremissing, None)
495 499 folded = d + "/" + f
496 500 else:
497 501 # No path components, preserve original case
498 502 folded = path
499 503 else:
500 504 # recursively normalize leading directory components
501 505 # against dirstate
502 506 if '/' in normed:
503 507 d, f = normed.rsplit('/', 1)
504 508 d = self._normalize(d, False, ignoremissing, True)
505 509 r = self._root + "/" + d
506 510 folded = d + "/" + util.fspath(f, r)
507 511 else:
508 512 folded = util.fspath(normed, self._root)
509 513 storemap[normed] = folded
510 514
511 515 return folded
512 516
513 517 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
514 518 normed = util.normcase(path)
515 519 folded = self._filefoldmap.get(normed, None)
516 520 if folded is None:
517 521 if isknown:
518 522 folded = path
519 523 else:
520 524 folded = self._discoverpath(path, normed, ignoremissing, exists,
521 525 self._filefoldmap)
522 526 return folded
523 527
524 528 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
525 529 normed = util.normcase(path)
526 530 folded = self._filefoldmap.get(normed, None)
527 531 if folded is None:
528 532 folded = self._dirfoldmap.get(normed, None)
529 533 if folded is None:
530 534 if isknown:
531 535 folded = path
532 536 else:
533 537 # store discovered result in dirfoldmap so that future
534 538 # normalizefile calls don't start matching directories
535 539 folded = self._discoverpath(path, normed, ignoremissing, exists,
536 540 self._dirfoldmap)
537 541 return folded
538 542
539 543 def normalize(self, path, isknown=False, ignoremissing=False):
540 544 '''
541 545 normalize the case of a pathname when on a casefolding filesystem
542 546
543 547 isknown specifies whether the filename came from walking the
544 548 disk, to avoid extra filesystem access.
545 549
546 550 If ignoremissing is True, missing path are returned
547 551 unchanged. Otherwise, we try harder to normalize possibly
548 552 existing path components.
549 553
550 554 The normalized case is determined based on the following precedence:
551 555
552 556 - version of name already stored in the dirstate
553 557 - version of name stored on disk
554 558 - version provided via command arguments
555 559 '''
556 560
557 561 if self._checkcase:
558 562 return self._normalize(path, isknown, ignoremissing)
559 563 return path
560 564
561 565 def clear(self):
562 566 self._map = {}
563 567 if "_dirs" in self.__dict__:
564 568 delattr(self, "_dirs")
565 569 self._copymap = {}
566 570 self._pl = [nullid, nullid]
567 571 self._lastnormaltime = 0
568 572 self._dirty = True
569 573
570 574 def rebuild(self, parent, allfiles, changedfiles=None):
571 575 changedfiles = changedfiles or allfiles
572 576 oldmap = self._map
573 577 self.clear()
574 578 for f in allfiles:
575 579 if f not in changedfiles:
576 580 self._map[f] = oldmap[f]
577 581 else:
578 582 if 'x' in allfiles.flags(f):
579 583 self._map[f] = dirstatetuple('n', 0777, -1, 0)
580 584 else:
581 585 self._map[f] = dirstatetuple('n', 0666, -1, 0)
582 586 self._pl = (parent, nullid)
583 587 self._dirty = True
584 588
585 589 def write(self):
586 590 if not self._dirty:
587 591 return
588 592
589 593 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
590 594 # timestamp of each entries in dirstate, because of 'now > mtime'
591 595 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
592 596 if delaywrite > 0:
593 597 import time # to avoid useless import
594 598 time.sleep(delaywrite)
595 599
596 600 st = self._opener(self._filename, "w", atomictemp=True)
597 601 # use the modification time of the newly created temporary file as the
598 602 # filesystem's notion of 'now'
599 603 now = util.fstat(st).st_mtime
600 604 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
601 605 st.close()
602 606 self._lastnormaltime = 0
603 607 self._dirty = self._dirtypl = False
604 608
605 609 def _dirignore(self, f):
606 610 if f == '.':
607 611 return False
608 612 if self._ignore(f):
609 613 return True
610 614 for p in util.finddirs(f):
611 615 if self._ignore(p):
612 616 return True
613 617 return False
614 618
615 619 def _walkexplicit(self, match, subrepos):
616 620 '''Get stat data about the files explicitly specified by match.
617 621
618 622 Return a triple (results, dirsfound, dirsnotfound).
619 623 - results is a mapping from filename to stat result. It also contains
620 624 listings mapping subrepos and .hg to None.
621 625 - dirsfound is a list of files found to be directories.
622 626 - dirsnotfound is a list of files that the dirstate thinks are
623 627 directories and that were not found.'''
624 628
625 629 def badtype(mode):
626 630 kind = _('unknown')
627 631 if stat.S_ISCHR(mode):
628 632 kind = _('character device')
629 633 elif stat.S_ISBLK(mode):
630 634 kind = _('block device')
631 635 elif stat.S_ISFIFO(mode):
632 636 kind = _('fifo')
633 637 elif stat.S_ISSOCK(mode):
634 638 kind = _('socket')
635 639 elif stat.S_ISDIR(mode):
636 640 kind = _('directory')
637 641 return _('unsupported file type (type is %s)') % kind
638 642
639 643 matchedir = match.explicitdir
640 644 badfn = match.bad
641 645 dmap = self._map
642 646 lstat = os.lstat
643 647 getkind = stat.S_IFMT
644 648 dirkind = stat.S_IFDIR
645 649 regkind = stat.S_IFREG
646 650 lnkkind = stat.S_IFLNK
647 651 join = self._join
648 652 dirsfound = []
649 653 foundadd = dirsfound.append
650 654 dirsnotfound = []
651 655 notfoundadd = dirsnotfound.append
652 656
653 657 if not match.isexact() and self._checkcase:
654 658 normalize = self._normalize
655 659 else:
656 660 normalize = None
657 661
658 662 files = sorted(match.files())
659 663 subrepos.sort()
660 664 i, j = 0, 0
661 665 while i < len(files) and j < len(subrepos):
662 666 subpath = subrepos[j] + "/"
663 667 if files[i] < subpath:
664 668 i += 1
665 669 continue
666 670 while i < len(files) and files[i].startswith(subpath):
667 671 del files[i]
668 672 j += 1
669 673
670 674 if not files or '.' in files:
671 675 files = ['.']
672 676 results = dict.fromkeys(subrepos)
673 677 results['.hg'] = None
674 678
675 679 alldirs = None
676 680 for ff in files:
677 681 # constructing the foldmap is expensive, so don't do it for the
678 682 # common case where files is ['.']
679 683 if normalize and ff != '.':
680 684 nf = normalize(ff, False, True)
681 685 else:
682 686 nf = ff
683 687 if nf in results:
684 688 continue
685 689
686 690 try:
687 691 st = lstat(join(nf))
688 692 kind = getkind(st.st_mode)
689 693 if kind == dirkind:
690 694 if nf in dmap:
691 695 # file replaced by dir on disk but still in dirstate
692 696 results[nf] = None
693 697 if matchedir:
694 698 matchedir(nf)
695 699 foundadd((nf, ff))
696 700 elif kind == regkind or kind == lnkkind:
697 701 results[nf] = st
698 702 else:
699 703 badfn(ff, badtype(kind))
700 704 if nf in dmap:
701 705 results[nf] = None
702 706 except OSError, inst: # nf not found on disk - it is dirstate only
703 707 if nf in dmap: # does it exactly match a missing file?
704 708 results[nf] = None
705 709 else: # does it match a missing directory?
706 710 if alldirs is None:
707 711 alldirs = util.dirs(dmap)
708 712 if nf in alldirs:
709 713 if matchedir:
710 714 matchedir(nf)
711 715 notfoundadd(nf)
712 716 else:
713 717 badfn(ff, inst.strerror)
714 718
715 719 return results, dirsfound, dirsnotfound
716 720
717 721 def walk(self, match, subrepos, unknown, ignored, full=True):
718 722 '''
719 723 Walk recursively through the directory tree, finding all files
720 724 matched by match.
721 725
722 726 If full is False, maybe skip some known-clean files.
723 727
724 728 Return a dict mapping filename to stat-like object (either
725 729 mercurial.osutil.stat instance or return value of os.stat()).
726 730
727 731 '''
728 732 # full is a flag that extensions that hook into walk can use -- this
729 733 # implementation doesn't use it at all. This satisfies the contract
730 734 # because we only guarantee a "maybe".
731 735
732 736 if ignored:
733 737 ignore = util.never
734 738 dirignore = util.never
735 739 elif unknown:
736 740 ignore = self._ignore
737 741 dirignore = self._dirignore
738 742 else:
739 743 # if not unknown and not ignored, drop dir recursion and step 2
740 744 ignore = util.always
741 745 dirignore = util.always
742 746
743 747 matchfn = match.matchfn
744 748 matchalways = match.always()
745 749 matchtdir = match.traversedir
746 750 dmap = self._map
747 751 listdir = osutil.listdir
748 752 lstat = os.lstat
749 753 dirkind = stat.S_IFDIR
750 754 regkind = stat.S_IFREG
751 755 lnkkind = stat.S_IFLNK
752 756 join = self._join
753 757
754 758 exact = skipstep3 = False
755 759 if match.isexact(): # match.exact
756 760 exact = True
757 761 dirignore = util.always # skip step 2
758 762 elif match.files() and not match.anypats(): # match.match, no patterns
759 763 skipstep3 = True
760 764
761 765 if not exact and self._checkcase:
762 766 normalize = self._normalize
763 767 normalizefile = self._normalizefile
764 768 skipstep3 = False
765 769 else:
766 770 normalize = self._normalize
767 771 normalizefile = None
768 772
769 773 # step 1: find all explicit files
770 774 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
771 775
772 776 skipstep3 = skipstep3 and not (work or dirsnotfound)
773 777 work = [d for d in work if not dirignore(d[0])]
774 778
775 779 # step 2: visit subdirectories
776 780 def traverse(work, alreadynormed):
777 781 wadd = work.append
778 782 while work:
779 783 nd = work.pop()
780 784 skip = None
781 785 if nd == '.':
782 786 nd = ''
783 787 else:
784 788 skip = '.hg'
785 789 try:
786 790 entries = listdir(join(nd), stat=True, skip=skip)
787 791 except OSError, inst:
788 792 if inst.errno in (errno.EACCES, errno.ENOENT):
789 793 match.bad(self.pathto(nd), inst.strerror)
790 794 continue
791 795 raise
792 796 for f, kind, st in entries:
793 797 if normalizefile:
794 798 # even though f might be a directory, we're only
795 799 # interested in comparing it to files currently in the
796 800 # dmap -- therefore normalizefile is enough
797 801 nf = normalizefile(nd and (nd + "/" + f) or f, True,
798 802 True)
799 803 else:
800 804 nf = nd and (nd + "/" + f) or f
801 805 if nf not in results:
802 806 if kind == dirkind:
803 807 if not ignore(nf):
804 808 if matchtdir:
805 809 matchtdir(nf)
806 810 wadd(nf)
807 811 if nf in dmap and (matchalways or matchfn(nf)):
808 812 results[nf] = None
809 813 elif kind == regkind or kind == lnkkind:
810 814 if nf in dmap:
811 815 if matchalways or matchfn(nf):
812 816 results[nf] = st
813 817 elif ((matchalways or matchfn(nf))
814 818 and not ignore(nf)):
815 819 # unknown file -- normalize if necessary
816 820 if not alreadynormed:
817 821 nf = normalize(nf, False, True)
818 822 results[nf] = st
819 823 elif nf in dmap and (matchalways or matchfn(nf)):
820 824 results[nf] = None
821 825
822 826 for nd, d in work:
823 827 # alreadynormed means that processwork doesn't have to do any
824 828 # expensive directory normalization
825 829 alreadynormed = not normalize or nd == d
826 830 traverse([d], alreadynormed)
827 831
828 832 for s in subrepos:
829 833 del results[s]
830 834 del results['.hg']
831 835
832 836 # step 3: visit remaining files from dmap
833 837 if not skipstep3 and not exact:
834 838 # If a dmap file is not in results yet, it was either
835 839 # a) not matching matchfn b) ignored, c) missing, or d) under a
836 840 # symlink directory.
837 841 if not results and matchalways:
838 842 visit = dmap.keys()
839 843 else:
840 844 visit = [f for f in dmap if f not in results and matchfn(f)]
841 845 visit.sort()
842 846
843 847 if unknown:
844 848 # unknown == True means we walked all dirs under the roots
845 849 # that wasn't ignored, and everything that matched was stat'ed
846 850 # and is already in results.
847 851 # The rest must thus be ignored or under a symlink.
848 852 audit_path = pathutil.pathauditor(self._root)
849 853
850 854 for nf in iter(visit):
851 855 # If a stat for the same file was already added with a
852 856 # different case, don't add one for this, since that would
853 857 # make it appear as if the file exists under both names
854 858 # on disk.
855 859 if (normalizefile and
856 860 normalizefile(nf, True, True) in results):
857 861 results[nf] = None
858 862 # Report ignored items in the dmap as long as they are not
859 863 # under a symlink directory.
860 864 elif audit_path.check(nf):
861 865 try:
862 866 results[nf] = lstat(join(nf))
863 867 # file was just ignored, no links, and exists
864 868 except OSError:
865 869 # file doesn't exist
866 870 results[nf] = None
867 871 else:
868 872 # It's either missing or under a symlink directory
869 873 # which we in this case report as missing
870 874 results[nf] = None
871 875 else:
872 876 # We may not have walked the full directory tree above,
873 877 # so stat and check everything we missed.
874 878 nf = iter(visit).next
875 879 for st in util.statfiles([join(i) for i in visit]):
876 880 results[nf()] = st
877 881 return results
878 882
879 883 def status(self, match, subrepos, ignored, clean, unknown):
880 884 '''Determine the status of the working copy relative to the
881 885 dirstate and return a pair of (unsure, status), where status is of type
882 886 scmutil.status and:
883 887
884 888 unsure:
885 889 files that might have been modified since the dirstate was
886 890 written, but need to be read to be sure (size is the same
887 891 but mtime differs)
888 892 status.modified:
889 893 files that have definitely been modified since the dirstate
890 894 was written (different size or mode)
891 895 status.clean:
892 896 files that have definitely not been modified since the
893 897 dirstate was written
894 898 '''
895 899 listignored, listclean, listunknown = ignored, clean, unknown
896 900 lookup, modified, added, unknown, ignored = [], [], [], [], []
897 901 removed, deleted, clean = [], [], []
898 902
899 903 dmap = self._map
900 904 ladd = lookup.append # aka "unsure"
901 905 madd = modified.append
902 906 aadd = added.append
903 907 uadd = unknown.append
904 908 iadd = ignored.append
905 909 radd = removed.append
906 910 dadd = deleted.append
907 911 cadd = clean.append
908 912 mexact = match.exact
909 913 dirignore = self._dirignore
910 914 checkexec = self._checkexec
911 915 copymap = self._copymap
912 916 lastnormaltime = self._lastnormaltime
913 917
914 918 # We need to do full walks when either
915 919 # - we're listing all clean files, or
916 920 # - match.traversedir does something, because match.traversedir should
917 921 # be called for every dir in the working dir
918 922 full = listclean or match.traversedir is not None
919 923 for fn, st in self.walk(match, subrepos, listunknown, listignored,
920 924 full=full).iteritems():
921 925 if fn not in dmap:
922 926 if (listignored or mexact(fn)) and dirignore(fn):
923 927 if listignored:
924 928 iadd(fn)
925 929 else:
926 930 uadd(fn)
927 931 continue
928 932
929 933 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
930 934 # written like that for performance reasons. dmap[fn] is not a
931 935 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
932 936 # opcode has fast paths when the value to be unpacked is a tuple or
933 937 # a list, but falls back to creating a full-fledged iterator in
934 938 # general. That is much slower than simply accessing and storing the
935 939 # tuple members one by one.
936 940 t = dmap[fn]
937 941 state = t[0]
938 942 mode = t[1]
939 943 size = t[2]
940 944 time = t[3]
941 945
942 946 if not st and state in "nma":
943 947 dadd(fn)
944 948 elif state == 'n':
945 949 mtime = int(st.st_mtime)
946 950 if (size >= 0 and
947 951 ((size != st.st_size and size != st.st_size & _rangemask)
948 952 or ((mode ^ st.st_mode) & 0100 and checkexec))
949 953 or size == -2 # other parent
950 954 or fn in copymap):
951 955 madd(fn)
952 956 elif time != mtime and time != mtime & _rangemask:
953 957 ladd(fn)
954 958 elif mtime == lastnormaltime:
955 959 # fn may have just been marked as normal and it may have
956 960 # changed in the same second without changing its size.
957 961 # This can happen if we quickly do multiple commits.
958 962 # Force lookup, so we don't miss such a racy file change.
959 963 ladd(fn)
960 964 elif listclean:
961 965 cadd(fn)
962 966 elif state == 'm':
963 967 madd(fn)
964 968 elif state == 'a':
965 969 aadd(fn)
966 970 elif state == 'r':
967 971 radd(fn)
968 972
969 973 return (lookup, scmutil.status(modified, added, removed, deleted,
970 974 unknown, ignored, clean))
971 975
972 976 def matches(self, match):
973 977 '''
974 978 return files in the dirstate (in whatever state) filtered by match
975 979 '''
976 980 dmap = self._map
977 981 if match.always():
978 982 return dmap.keys()
979 983 files = match.files()
980 984 if match.isexact():
981 985 # fast path -- filter the other way around, since typically files is
982 986 # much smaller than dmap
983 987 return [f for f in files if f in dmap]
984 988 if not match.anypats() and all(fn in dmap for fn in files):
985 989 # fast path -- all the values are known to be files, so just return
986 990 # that
987 991 return list(files)
988 992 return [f for f in dmap if match(f)]
General Comments 0
You need to be logged in to leave comments. Login now