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