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