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