##// END OF EJS Templates
dirstate: refresh _branch cache entry after writing it
Idan Kamara -
r18317:365fecd9 default
parent child Browse files
Show More
@@ -1,810 +1,816 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 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 f.close()
268
269 # make sure filecache has the correct stat info for _branch after
270 # replacing the underlying file
271 ce = self._filecache['_branch']
272 if ce:
273 ce.refresh()
268 274 except: # re-raises
269 275 f.discard()
270 276 raise
271 277
272 278 def _read(self):
273 279 self._map = {}
274 280 self._copymap = {}
275 281 try:
276 282 st = self._opener.read("dirstate")
277 283 except IOError, err:
278 284 if err.errno != errno.ENOENT:
279 285 raise
280 286 return
281 287 if not st:
282 288 return
283 289
284 290 p = parsers.parse_dirstate(self._map, self._copymap, st)
285 291 if not self._dirtypl:
286 292 self._pl = p
287 293
288 294 def invalidate(self):
289 295 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
290 296 "_ignore"):
291 297 if a in self.__dict__:
292 298 delattr(self, a)
293 299 self._lastnormaltime = 0
294 300 self._dirty = False
295 301
296 302 def copy(self, source, dest):
297 303 """Mark dest as a copy of source. Unmark dest if source is None."""
298 304 if source == dest:
299 305 return
300 306 self._dirty = True
301 307 if source is not None:
302 308 self._copymap[dest] = source
303 309 elif dest in self._copymap:
304 310 del self._copymap[dest]
305 311
306 312 def copied(self, file):
307 313 return self._copymap.get(file, None)
308 314
309 315 def copies(self):
310 316 return self._copymap
311 317
312 318 def _droppath(self, f):
313 319 if self[f] not in "?r" and "_dirs" in self.__dict__:
314 320 _decdirs(self._dirs, f)
315 321
316 322 def _addpath(self, f, state, mode, size, mtime):
317 323 oldstate = self[f]
318 324 if state == 'a' or oldstate == 'r':
319 325 scmutil.checkfilename(f)
320 326 if f in self._dirs:
321 327 raise util.Abort(_('directory %r already in dirstate') % f)
322 328 # shadows
323 329 for d in _finddirs(f):
324 330 if d in self._dirs:
325 331 break
326 332 if d in self._map and self[d] != 'r':
327 333 raise util.Abort(
328 334 _('file %r in dirstate clashes with %r') % (d, f))
329 335 if oldstate in "?r" and "_dirs" in self.__dict__:
330 336 _incdirs(self._dirs, f)
331 337 self._dirty = True
332 338 self._map[f] = (state, mode, size, mtime)
333 339
334 340 def normal(self, f):
335 341 '''Mark a file normal and clean.'''
336 342 s = os.lstat(self._join(f))
337 343 mtime = int(s.st_mtime)
338 344 self._addpath(f, 'n', s.st_mode,
339 345 s.st_size & _rangemask, mtime & _rangemask)
340 346 if f in self._copymap:
341 347 del self._copymap[f]
342 348 if mtime > self._lastnormaltime:
343 349 # Remember the most recent modification timeslot for status(),
344 350 # to make sure we won't miss future size-preserving file content
345 351 # modifications that happen within the same timeslot.
346 352 self._lastnormaltime = mtime
347 353
348 354 def normallookup(self, f):
349 355 '''Mark a file normal, but possibly dirty.'''
350 356 if self._pl[1] != nullid and f in self._map:
351 357 # if there is a merge going on and the file was either
352 358 # in state 'm' (-1) or coming from other parent (-2) before
353 359 # being removed, restore that state.
354 360 entry = self._map[f]
355 361 if entry[0] == 'r' and entry[2] in (-1, -2):
356 362 source = self._copymap.get(f)
357 363 if entry[2] == -1:
358 364 self.merge(f)
359 365 elif entry[2] == -2:
360 366 self.otherparent(f)
361 367 if source:
362 368 self.copy(source, f)
363 369 return
364 370 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
365 371 return
366 372 self._addpath(f, 'n', 0, -1, -1)
367 373 if f in self._copymap:
368 374 del self._copymap[f]
369 375
370 376 def otherparent(self, f):
371 377 '''Mark as coming from the other parent, always dirty.'''
372 378 if self._pl[1] == nullid:
373 379 raise util.Abort(_("setting %r to other parent "
374 380 "only allowed in merges") % f)
375 381 self._addpath(f, 'n', 0, -2, -1)
376 382 if f in self._copymap:
377 383 del self._copymap[f]
378 384
379 385 def add(self, f):
380 386 '''Mark a file added.'''
381 387 self._addpath(f, 'a', 0, -1, -1)
382 388 if f in self._copymap:
383 389 del self._copymap[f]
384 390
385 391 def remove(self, f):
386 392 '''Mark a file removed.'''
387 393 self._dirty = True
388 394 self._droppath(f)
389 395 size = 0
390 396 if self._pl[1] != nullid and f in self._map:
391 397 # backup the previous state
392 398 entry = self._map[f]
393 399 if entry[0] == 'm': # merge
394 400 size = -1
395 401 elif entry[0] == 'n' and entry[2] == -2: # other parent
396 402 size = -2
397 403 self._map[f] = ('r', 0, size, 0)
398 404 if size == 0 and f in self._copymap:
399 405 del self._copymap[f]
400 406
401 407 def merge(self, f):
402 408 '''Mark a file merged.'''
403 409 if self._pl[1] == nullid:
404 410 return self.normallookup(f)
405 411 s = os.lstat(self._join(f))
406 412 self._addpath(f, 'm', s.st_mode,
407 413 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
408 414 if f in self._copymap:
409 415 del self._copymap[f]
410 416
411 417 def drop(self, f):
412 418 '''Drop a file from the dirstate'''
413 419 if f in self._map:
414 420 self._dirty = True
415 421 self._droppath(f)
416 422 del self._map[f]
417 423
418 424 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
419 425 normed = util.normcase(path)
420 426 folded = self._foldmap.get(normed, None)
421 427 if folded is None:
422 428 if isknown:
423 429 folded = path
424 430 else:
425 431 if exists is None:
426 432 exists = os.path.lexists(os.path.join(self._root, path))
427 433 if not exists:
428 434 # Maybe a path component exists
429 435 if not ignoremissing and '/' in path:
430 436 d, f = path.rsplit('/', 1)
431 437 d = self._normalize(d, isknown, ignoremissing, None)
432 438 folded = d + "/" + f
433 439 else:
434 440 # No path components, preserve original case
435 441 folded = path
436 442 else:
437 443 # recursively normalize leading directory components
438 444 # against dirstate
439 445 if '/' in normed:
440 446 d, f = normed.rsplit('/', 1)
441 447 d = self._normalize(d, isknown, ignoremissing, True)
442 448 r = self._root + "/" + d
443 449 folded = d + "/" + util.fspath(f, r)
444 450 else:
445 451 folded = util.fspath(normed, self._root)
446 452 self._foldmap[normed] = folded
447 453
448 454 return folded
449 455
450 456 def normalize(self, path, isknown=False, ignoremissing=False):
451 457 '''
452 458 normalize the case of a pathname when on a casefolding filesystem
453 459
454 460 isknown specifies whether the filename came from walking the
455 461 disk, to avoid extra filesystem access.
456 462
457 463 If ignoremissing is True, missing path are returned
458 464 unchanged. Otherwise, we try harder to normalize possibly
459 465 existing path components.
460 466
461 467 The normalized case is determined based on the following precedence:
462 468
463 469 - version of name already stored in the dirstate
464 470 - version of name stored on disk
465 471 - version provided via command arguments
466 472 '''
467 473
468 474 if self._checkcase:
469 475 return self._normalize(path, isknown, ignoremissing)
470 476 return path
471 477
472 478 def clear(self):
473 479 self._map = {}
474 480 if "_dirs" in self.__dict__:
475 481 delattr(self, "_dirs")
476 482 self._copymap = {}
477 483 self._pl = [nullid, nullid]
478 484 self._lastnormaltime = 0
479 485 self._dirty = True
480 486
481 487 def rebuild(self, parent, files):
482 488 self.clear()
483 489 for f in files:
484 490 if 'x' in files.flags(f):
485 491 self._map[f] = ('n', 0777, -1, 0)
486 492 else:
487 493 self._map[f] = ('n', 0666, -1, 0)
488 494 self._pl = (parent, nullid)
489 495 self._dirty = True
490 496
491 497 def write(self):
492 498 if not self._dirty:
493 499 return
494 500 st = self._opener("dirstate", "w", atomictemp=True)
495 501
496 502 def finish(s):
497 503 st.write(s)
498 504 st.close()
499 505 self._lastnormaltime = 0
500 506 self._dirty = self._dirtypl = False
501 507
502 508 # use the modification time of the newly created temporary file as the
503 509 # filesystem's notion of 'now'
504 510 now = util.fstat(st).st_mtime
505 511 copymap = self._copymap
506 512 try:
507 513 finish(parsers.pack_dirstate(self._map, copymap, self._pl, now))
508 514 return
509 515 except AttributeError:
510 516 pass
511 517
512 518 now = int(now)
513 519 cs = cStringIO.StringIO()
514 520 pack = struct.pack
515 521 write = cs.write
516 522 write("".join(self._pl))
517 523 for f, e in self._map.iteritems():
518 524 if e[0] == 'n' and e[3] == now:
519 525 # The file was last modified "simultaneously" with the current
520 526 # write to dirstate (i.e. within the same second for file-
521 527 # systems with a granularity of 1 sec). This commonly happens
522 528 # for at least a couple of files on 'update'.
523 529 # The user could change the file without changing its size
524 530 # within the same second. Invalidate the file's stat data in
525 531 # dirstate, forcing future 'status' calls to compare the
526 532 # contents of the file. This prevents mistakenly treating such
527 533 # files as clean.
528 534 e = (e[0], 0, -1, -1) # mark entry as 'unset'
529 535 self._map[f] = e
530 536
531 537 if f in copymap:
532 538 f = "%s\0%s" % (f, copymap[f])
533 539 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
534 540 write(e)
535 541 write(f)
536 542 finish(cs.getvalue())
537 543
538 544 def _dirignore(self, f):
539 545 if f == '.':
540 546 return False
541 547 if self._ignore(f):
542 548 return True
543 549 for p in _finddirs(f):
544 550 if self._ignore(p):
545 551 return True
546 552 return False
547 553
548 554 def walk(self, match, subrepos, unknown, ignored):
549 555 '''
550 556 Walk recursively through the directory tree, finding all files
551 557 matched by match.
552 558
553 559 Return a dict mapping filename to stat-like object (either
554 560 mercurial.osutil.stat instance or return value of os.stat()).
555 561 '''
556 562
557 563 def fwarn(f, msg):
558 564 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
559 565 return False
560 566
561 567 def badtype(mode):
562 568 kind = _('unknown')
563 569 if stat.S_ISCHR(mode):
564 570 kind = _('character device')
565 571 elif stat.S_ISBLK(mode):
566 572 kind = _('block device')
567 573 elif stat.S_ISFIFO(mode):
568 574 kind = _('fifo')
569 575 elif stat.S_ISSOCK(mode):
570 576 kind = _('socket')
571 577 elif stat.S_ISDIR(mode):
572 578 kind = _('directory')
573 579 return _('unsupported file type (type is %s)') % kind
574 580
575 581 ignore = self._ignore
576 582 dirignore = self._dirignore
577 583 if ignored:
578 584 ignore = util.never
579 585 dirignore = util.never
580 586 elif not unknown:
581 587 # if unknown and ignored are False, skip step 2
582 588 ignore = util.always
583 589 dirignore = util.always
584 590
585 591 matchfn = match.matchfn
586 592 badfn = match.bad
587 593 dmap = self._map
588 594 normpath = util.normpath
589 595 listdir = osutil.listdir
590 596 lstat = os.lstat
591 597 getkind = stat.S_IFMT
592 598 dirkind = stat.S_IFDIR
593 599 regkind = stat.S_IFREG
594 600 lnkkind = stat.S_IFLNK
595 601 join = self._join
596 602 work = []
597 603 wadd = work.append
598 604
599 605 exact = skipstep3 = False
600 606 if matchfn == match.exact: # match.exact
601 607 exact = True
602 608 dirignore = util.always # skip step 2
603 609 elif match.files() and not match.anypats(): # match.match, no patterns
604 610 skipstep3 = True
605 611
606 612 if not exact and self._checkcase:
607 613 normalize = self._normalize
608 614 skipstep3 = False
609 615 else:
610 616 normalize = None
611 617
612 618 files = sorted(match.files())
613 619 subrepos.sort()
614 620 i, j = 0, 0
615 621 while i < len(files) and j < len(subrepos):
616 622 subpath = subrepos[j] + "/"
617 623 if files[i] < subpath:
618 624 i += 1
619 625 continue
620 626 while i < len(files) and files[i].startswith(subpath):
621 627 del files[i]
622 628 j += 1
623 629
624 630 if not files or '.' in files:
625 631 files = ['']
626 632 results = dict.fromkeys(subrepos)
627 633 results['.hg'] = None
628 634
629 635 # step 1: find all explicit files
630 636 for ff in files:
631 637 if normalize:
632 638 nf = normalize(normpath(ff), False, True)
633 639 else:
634 640 nf = normpath(ff)
635 641 if nf in results:
636 642 continue
637 643
638 644 try:
639 645 st = lstat(join(nf))
640 646 kind = getkind(st.st_mode)
641 647 if kind == dirkind:
642 648 skipstep3 = False
643 649 if nf in dmap:
644 650 #file deleted on disk but still in dirstate
645 651 results[nf] = None
646 652 match.dir(nf)
647 653 if not dirignore(nf):
648 654 wadd(nf)
649 655 elif kind == regkind or kind == lnkkind:
650 656 results[nf] = st
651 657 else:
652 658 badfn(ff, badtype(kind))
653 659 if nf in dmap:
654 660 results[nf] = None
655 661 except OSError, inst:
656 662 if nf in dmap: # does it exactly match a file?
657 663 results[nf] = None
658 664 else: # does it match a directory?
659 665 prefix = nf + "/"
660 666 for fn in dmap:
661 667 if fn.startswith(prefix):
662 668 match.dir(nf)
663 669 skipstep3 = False
664 670 break
665 671 else:
666 672 badfn(ff, inst.strerror)
667 673
668 674 # step 2: visit subdirectories
669 675 while work:
670 676 nd = work.pop()
671 677 skip = None
672 678 if nd == '.':
673 679 nd = ''
674 680 else:
675 681 skip = '.hg'
676 682 try:
677 683 entries = listdir(join(nd), stat=True, skip=skip)
678 684 except OSError, inst:
679 685 if inst.errno in (errno.EACCES, errno.ENOENT):
680 686 fwarn(nd, inst.strerror)
681 687 continue
682 688 raise
683 689 for f, kind, st in entries:
684 690 if normalize:
685 691 nf = normalize(nd and (nd + "/" + f) or f, True, True)
686 692 else:
687 693 nf = nd and (nd + "/" + f) or f
688 694 if nf not in results:
689 695 if kind == dirkind:
690 696 if not ignore(nf):
691 697 match.dir(nf)
692 698 wadd(nf)
693 699 if nf in dmap and matchfn(nf):
694 700 results[nf] = None
695 701 elif kind == regkind or kind == lnkkind:
696 702 if nf in dmap:
697 703 if matchfn(nf):
698 704 results[nf] = st
699 705 elif matchfn(nf) and not ignore(nf):
700 706 results[nf] = st
701 707 elif nf in dmap and matchfn(nf):
702 708 results[nf] = None
703 709
704 710 # step 3: report unseen items in the dmap hash
705 711 if not skipstep3 and not exact:
706 712 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
707 713 nf = iter(visit).next
708 714 for st in util.statfiles([join(i) for i in visit]):
709 715 results[nf()] = st
710 716 for s in subrepos:
711 717 del results[s]
712 718 del results['.hg']
713 719 return results
714 720
715 721 def status(self, match, subrepos, ignored, clean, unknown):
716 722 '''Determine the status of the working copy relative to the
717 723 dirstate and return a tuple of lists (unsure, modified, added,
718 724 removed, deleted, unknown, ignored, clean), where:
719 725
720 726 unsure:
721 727 files that might have been modified since the dirstate was
722 728 written, but need to be read to be sure (size is the same
723 729 but mtime differs)
724 730 modified:
725 731 files that have definitely been modified since the dirstate
726 732 was written (different size or mode)
727 733 added:
728 734 files that have been explicitly added with hg add
729 735 removed:
730 736 files that have been explicitly removed with hg remove
731 737 deleted:
732 738 files that have been deleted through other means ("missing")
733 739 unknown:
734 740 files not in the dirstate that are not ignored
735 741 ignored:
736 742 files not in the dirstate that are ignored
737 743 (by _dirignore())
738 744 clean:
739 745 files that have definitely not been modified since the
740 746 dirstate was written
741 747 '''
742 748 listignored, listclean, listunknown = ignored, clean, unknown
743 749 lookup, modified, added, unknown, ignored = [], [], [], [], []
744 750 removed, deleted, clean = [], [], []
745 751
746 752 dmap = self._map
747 753 ladd = lookup.append # aka "unsure"
748 754 madd = modified.append
749 755 aadd = added.append
750 756 uadd = unknown.append
751 757 iadd = ignored.append
752 758 radd = removed.append
753 759 dadd = deleted.append
754 760 cadd = clean.append
755 761 mexact = match.exact
756 762 dirignore = self._dirignore
757 763 checkexec = self._checkexec
758 764 checklink = self._checklink
759 765 copymap = self._copymap
760 766 lastnormaltime = self._lastnormaltime
761 767
762 768 lnkkind = stat.S_IFLNK
763 769
764 770 for fn, st in self.walk(match, subrepos, listunknown,
765 771 listignored).iteritems():
766 772 if fn not in dmap:
767 773 if (listignored or mexact(fn)) and dirignore(fn):
768 774 if listignored:
769 775 iadd(fn)
770 776 elif listunknown:
771 777 uadd(fn)
772 778 continue
773 779
774 780 state, mode, size, time = dmap[fn]
775 781
776 782 if not st and state in "nma":
777 783 dadd(fn)
778 784 elif state == 'n':
779 785 # The "mode & lnkkind != lnkkind or self._checklink"
780 786 # lines are an expansion of "islink => checklink"
781 787 # where islink means "is this a link?" and checklink
782 788 # means "can we check links?".
783 789 mtime = int(st.st_mtime)
784 790 if (size >= 0 and
785 791 ((size != st.st_size and size != st.st_size & _rangemask)
786 792 or ((mode ^ st.st_mode) & 0100 and checkexec))
787 793 and (mode & lnkkind != lnkkind or checklink)
788 794 or size == -2 # other parent
789 795 or fn in copymap):
790 796 madd(fn)
791 797 elif ((time != mtime and time != mtime & _rangemask)
792 798 and (mode & lnkkind != lnkkind or checklink)):
793 799 ladd(fn)
794 800 elif mtime == lastnormaltime:
795 801 # fn may have been changed in the same timeslot without
796 802 # changing its size. This can happen if we quickly do
797 803 # multiple commits in a single transaction.
798 804 # Force lookup, so we don't miss such a racy file change.
799 805 ladd(fn)
800 806 elif listclean:
801 807 cadd(fn)
802 808 elif state == 'm':
803 809 madd(fn)
804 810 elif state == 'a':
805 811 aadd(fn)
806 812 elif state == 'r':
807 813 radd(fn)
808 814
809 815 return (lookup, modified, added, removed, deleted, unknown, ignored,
810 816 clean)
General Comments 0
You need to be logged in to leave comments. Login now