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