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