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