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