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