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