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