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