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