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