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