##// END OF EJS Templates
dirstate: fix invalid reference to self.ui
James Mills -
r26782:cbd04ce6 default
parent child Browse files
Show More
@@ -1,1167 +1,1167 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
8 8 from node import nullid
9 9 from i18n import _
10 10 import scmutil, util, osutil, parsers, encoding, pathutil, error
11 11 import os, stat, errno
12 12 import match as matchmod
13 13
14 14 propertycache = util.propertycache
15 15 filecache = scmutil.filecache
16 16 _rangemask = 0x7fffffff
17 17
18 18 dirstatetuple = parsers.dirstatetuple
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 _getfsnow(vfs):
31 31 '''Get "now" timestamp on filesystem'''
32 32 tmpfd, tmpname = vfs.mkstemp()
33 33 try:
34 34 return util.statmtimesec(os.fstat(tmpfd))
35 35 finally:
36 36 os.close(tmpfd)
37 37 vfs.unlink(tmpname)
38 38
39 39 def _trypending(root, vfs, filename):
40 40 '''Open file to be read according to HG_PENDING environment variable
41 41
42 42 This opens '.pending' of specified 'filename' only when HG_PENDING
43 43 is equal to 'root'.
44 44
45 45 This returns '(fp, is_pending_opened)' tuple.
46 46 '''
47 47 if root == os.environ.get('HG_PENDING'):
48 48 try:
49 49 return (vfs('%s.pending' % filename), True)
50 50 except IOError as inst:
51 51 if inst.errno != errno.ENOENT:
52 52 raise
53 53 return (vfs(filename), False)
54 54
55 55 class dirstate(object):
56 56
57 57 def __init__(self, opener, ui, root, validate):
58 58 '''Create a new dirstate object.
59 59
60 60 opener is an open()-like callable that can be used to open the
61 61 dirstate file; root is the root of the directory tracked by
62 62 the dirstate.
63 63 '''
64 64 self._opener = opener
65 65 self._validate = validate
66 66 self._root = root
67 67 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
68 68 # UNC path pointing to root share (issue4557)
69 69 self._rootdir = pathutil.normasprefix(root)
70 70 # internal config: ui.forcecwd
71 71 forcecwd = ui.config('ui', 'forcecwd')
72 72 if forcecwd:
73 73 self._cwd = forcecwd
74 74 self._dirty = False
75 75 self._dirtypl = False
76 76 self._lastnormaltime = 0
77 77 self._ui = ui
78 78 self._filecache = {}
79 79 self._parentwriters = 0
80 80 self._filename = 'dirstate'
81 81 self._pendingfilename = '%s.pending' % self._filename
82 82
83 83 # for consistent view between _pl() and _read() invocations
84 84 self._pendingmode = None
85 85
86 86 def beginparentchange(self):
87 87 '''Marks the beginning of a set of changes that involve changing
88 88 the dirstate parents. If there is an exception during this time,
89 89 the dirstate will not be written when the wlock is released. This
90 90 prevents writing an incoherent dirstate where the parent doesn't
91 91 match the contents.
92 92 '''
93 93 self._parentwriters += 1
94 94
95 95 def endparentchange(self):
96 96 '''Marks the end of a set of changes that involve changing the
97 97 dirstate parents. Once all parent changes have been marked done,
98 98 the wlock will be free to write the dirstate on release.
99 99 '''
100 100 if self._parentwriters > 0:
101 101 self._parentwriters -= 1
102 102
103 103 def pendingparentchange(self):
104 104 '''Returns true if the dirstate is in the middle of a set of changes
105 105 that modify the dirstate parent.
106 106 '''
107 107 return self._parentwriters > 0
108 108
109 109 @propertycache
110 110 def _map(self):
111 111 '''Return the dirstate contents as a map from filename to
112 112 (state, mode, size, time).'''
113 113 self._read()
114 114 return self._map
115 115
116 116 @propertycache
117 117 def _copymap(self):
118 118 self._read()
119 119 return self._copymap
120 120
121 121 @propertycache
122 122 def _filefoldmap(self):
123 123 try:
124 124 makefilefoldmap = parsers.make_file_foldmap
125 125 except AttributeError:
126 126 pass
127 127 else:
128 128 return makefilefoldmap(self._map, util.normcasespec,
129 129 util.normcasefallback)
130 130
131 131 f = {}
132 132 normcase = util.normcase
133 133 for name, s in self._map.iteritems():
134 134 if s[0] != 'r':
135 135 f[normcase(name)] = name
136 136 f['.'] = '.' # prevents useless util.fspath() invocation
137 137 return f
138 138
139 139 @propertycache
140 140 def _dirfoldmap(self):
141 141 f = {}
142 142 normcase = util.normcase
143 143 for name in self._dirs:
144 144 f[normcase(name)] = name
145 145 return f
146 146
147 147 @repocache('branch')
148 148 def _branch(self):
149 149 try:
150 150 return self._opener.read("branch").strip() or "default"
151 151 except IOError as inst:
152 152 if inst.errno != errno.ENOENT:
153 153 raise
154 154 return "default"
155 155
156 156 @propertycache
157 157 def _pl(self):
158 158 try:
159 159 fp = self._opendirstatefile()
160 160 st = fp.read(40)
161 161 fp.close()
162 162 l = len(st)
163 163 if l == 40:
164 164 return st[:20], st[20:40]
165 165 elif l > 0 and l < 40:
166 166 raise error.Abort(_('working directory state appears damaged!'))
167 167 except IOError as err:
168 168 if err.errno != errno.ENOENT:
169 169 raise
170 170 return [nullid, nullid]
171 171
172 172 @propertycache
173 173 def _dirs(self):
174 174 return util.dirs(self._map, 'r')
175 175
176 176 def dirs(self):
177 177 return self._dirs
178 178
179 179 @rootcache('.hgignore')
180 180 def _ignore(self):
181 181 files = []
182 182 if os.path.exists(self._join('.hgignore')):
183 183 files.append(self._join('.hgignore'))
184 184 for name, path in self._ui.configitems("ui"):
185 185 if name == 'ignore' or name.startswith('ignore.'):
186 186 # we need to use os.path.join here rather than self._join
187 187 # because path is arbitrary and user-specified
188 188 files.append(os.path.join(self._rootdir, util.expandpath(path)))
189 189
190 190 if not files:
191 191 return util.never
192 192
193 193 pats = ['include:%s' % f for f in files]
194 194 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
195 195
196 196 @propertycache
197 197 def _slash(self):
198 198 return self._ui.configbool('ui', 'slash') and os.sep != '/'
199 199
200 200 @propertycache
201 201 def _checklink(self):
202 202 return util.checklink(self._root)
203 203
204 204 @propertycache
205 205 def _checkexec(self):
206 206 return util.checkexec(self._root)
207 207
208 208 @propertycache
209 209 def _checkcase(self):
210 210 return not util.checkcase(self._join('.hg'))
211 211
212 212 def _join(self, f):
213 213 # much faster than os.path.join()
214 214 # it's safe because f is always a relative path
215 215 return self._rootdir + f
216 216
217 217 def flagfunc(self, buildfallback):
218 218 if self._checklink and self._checkexec:
219 219 def f(x):
220 220 try:
221 221 st = os.lstat(self._join(x))
222 222 if util.statislink(st):
223 223 return 'l'
224 224 if util.statisexec(st):
225 225 return 'x'
226 226 except OSError:
227 227 pass
228 228 return ''
229 229 return f
230 230
231 231 fallback = buildfallback()
232 232 if self._checklink:
233 233 def f(x):
234 234 if os.path.islink(self._join(x)):
235 235 return 'l'
236 236 if 'x' in fallback(x):
237 237 return 'x'
238 238 return ''
239 239 return f
240 240 if self._checkexec:
241 241 def f(x):
242 242 if 'l' in fallback(x):
243 243 return 'l'
244 244 if util.isexec(self._join(x)):
245 245 return 'x'
246 246 return ''
247 247 return f
248 248 else:
249 249 return fallback
250 250
251 251 @propertycache
252 252 def _cwd(self):
253 253 return os.getcwd()
254 254
255 255 def getcwd(self):
256 256 '''Return the path from which a canonical path is calculated.
257 257
258 258 This path should be used to resolve file patterns or to convert
259 259 canonical paths back to file paths for display. It shouldn't be
260 260 used to get real file paths. Use vfs functions instead.
261 261 '''
262 262 cwd = self._cwd
263 263 if cwd == self._root:
264 264 return ''
265 265 # self._root ends with a path separator if self._root is '/' or 'C:\'
266 266 rootsep = self._root
267 267 if not util.endswithsep(rootsep):
268 268 rootsep += os.sep
269 269 if cwd.startswith(rootsep):
270 270 return cwd[len(rootsep):]
271 271 else:
272 272 # we're outside the repo. return an absolute path.
273 273 return cwd
274 274
275 275 def pathto(self, f, cwd=None):
276 276 if cwd is None:
277 277 cwd = self.getcwd()
278 278 path = util.pathto(self._root, cwd, f)
279 279 if self._slash:
280 280 return util.pconvert(path)
281 281 return path
282 282
283 283 def __getitem__(self, key):
284 284 '''Return the current state of key (a filename) in the dirstate.
285 285
286 286 States are:
287 287 n normal
288 288 m needs merging
289 289 r marked for removal
290 290 a marked for addition
291 291 ? not tracked
292 292 '''
293 293 return self._map.get(key, ("?",))[0]
294 294
295 295 def __contains__(self, key):
296 296 return key in self._map
297 297
298 298 def __iter__(self):
299 299 for x in sorted(self._map):
300 300 yield x
301 301
302 302 def iteritems(self):
303 303 return self._map.iteritems()
304 304
305 305 def parents(self):
306 306 return [self._validate(p) for p in self._pl]
307 307
308 308 def p1(self):
309 309 return self._validate(self._pl[0])
310 310
311 311 def p2(self):
312 312 return self._validate(self._pl[1])
313 313
314 314 def branch(self):
315 315 return encoding.tolocal(self._branch)
316 316
317 317 def setparents(self, p1, p2=nullid):
318 318 """Set dirstate parents to p1 and p2.
319 319
320 320 When moving from two parents to one, 'm' merged entries a
321 321 adjusted to normal and previous copy records discarded and
322 322 returned by the call.
323 323
324 324 See localrepo.setparents()
325 325 """
326 326 if self._parentwriters == 0:
327 327 raise ValueError("cannot set dirstate parent without "
328 328 "calling dirstate.beginparentchange")
329 329
330 330 self._dirty = self._dirtypl = True
331 331 oldp2 = self._pl[1]
332 332 self._pl = p1, p2
333 333 copies = {}
334 334 if oldp2 != nullid and p2 == nullid:
335 335 for f, s in self._map.iteritems():
336 336 # Discard 'm' markers when moving away from a merge state
337 337 if s[0] == 'm':
338 338 if f in self._copymap:
339 339 copies[f] = self._copymap[f]
340 340 self.normallookup(f)
341 341 # Also fix up otherparent markers
342 342 elif s[0] == 'n' and s[2] == -2:
343 343 if f in self._copymap:
344 344 copies[f] = self._copymap[f]
345 345 self.add(f)
346 346 return copies
347 347
348 348 def setbranch(self, branch):
349 349 self._branch = encoding.fromlocal(branch)
350 350 f = self._opener('branch', 'w', atomictemp=True)
351 351 try:
352 352 f.write(self._branch + '\n')
353 353 f.close()
354 354
355 355 # make sure filecache has the correct stat info for _branch after
356 356 # replacing the underlying file
357 357 ce = self._filecache['_branch']
358 358 if ce:
359 359 ce.refresh()
360 360 except: # re-raises
361 361 f.discard()
362 362 raise
363 363
364 364 def _opendirstatefile(self):
365 365 fp, mode = _trypending(self._root, self._opener, self._filename)
366 366 if self._pendingmode is not None and self._pendingmode != mode:
367 367 fp.close()
368 368 raise error.Abort(_('working directory state may be '
369 369 'changed parallelly'))
370 370 self._pendingmode = mode
371 371 return fp
372 372
373 373 def _read(self):
374 374 self._map = {}
375 375 self._copymap = {}
376 376 try:
377 377 fp = self._opendirstatefile()
378 378 try:
379 379 st = fp.read()
380 380 finally:
381 381 fp.close()
382 382 except IOError as err:
383 383 if err.errno != errno.ENOENT:
384 384 raise
385 385 return
386 386 if not st:
387 387 return
388 388
389 389 if util.safehasattr(parsers, 'dict_new_presized'):
390 390 # Make an estimate of the number of files in the dirstate based on
391 391 # its size. From a linear regression on a set of real-world repos,
392 392 # all over 10,000 files, the size of a dirstate entry is 85
393 393 # bytes. The cost of resizing is significantly higher than the cost
394 394 # of filling in a larger presized dict, so subtract 20% from the
395 395 # size.
396 396 #
397 397 # This heuristic is imperfect in many ways, so in a future dirstate
398 398 # format update it makes sense to just record the number of entries
399 399 # on write.
400 400 self._map = parsers.dict_new_presized(len(st) / 71)
401 401
402 402 # Python's garbage collector triggers a GC each time a certain number
403 403 # of container objects (the number being defined by
404 404 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
405 405 # for each file in the dirstate. The C version then immediately marks
406 406 # them as not to be tracked by the collector. However, this has no
407 407 # effect on when GCs are triggered, only on what objects the GC looks
408 408 # into. This means that O(number of files) GCs are unavoidable.
409 409 # Depending on when in the process's lifetime the dirstate is parsed,
410 410 # this can get very expensive. As a workaround, disable GC while
411 411 # parsing the dirstate.
412 412 #
413 413 # (we cannot decorate the function directly since it is in a C module)
414 414 parse_dirstate = util.nogc(parsers.parse_dirstate)
415 415 p = parse_dirstate(self._map, self._copymap, st)
416 416 if not self._dirtypl:
417 417 self._pl = p
418 418
419 419 def invalidate(self):
420 420 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
421 421 "_pl", "_dirs", "_ignore"):
422 422 if a in self.__dict__:
423 423 delattr(self, a)
424 424 self._lastnormaltime = 0
425 425 self._dirty = False
426 426 self._parentwriters = 0
427 427
428 428 def copy(self, source, dest):
429 429 """Mark dest as a copy of source. Unmark dest if source is None."""
430 430 if source == dest:
431 431 return
432 432 self._dirty = True
433 433 if source is not None:
434 434 self._copymap[dest] = source
435 435 elif dest in self._copymap:
436 436 del self._copymap[dest]
437 437
438 438 def copied(self, file):
439 439 return self._copymap.get(file, None)
440 440
441 441 def copies(self):
442 442 return self._copymap
443 443
444 444 def _droppath(self, f):
445 445 if self[f] not in "?r" and "_dirs" in self.__dict__:
446 446 self._dirs.delpath(f)
447 447
448 448 def _addpath(self, f, state, mode, size, mtime):
449 449 oldstate = self[f]
450 450 if state == 'a' or oldstate == 'r':
451 451 scmutil.checkfilename(f)
452 452 if f in self._dirs:
453 453 raise error.Abort(_('directory %r already in dirstate') % f)
454 454 # shadows
455 455 for d in util.finddirs(f):
456 456 if d in self._dirs:
457 457 break
458 458 if d in self._map and self[d] != 'r':
459 459 raise error.Abort(
460 460 _('file %r in dirstate clashes with %r') % (d, f))
461 461 if oldstate in "?r" and "_dirs" in self.__dict__:
462 462 self._dirs.addpath(f)
463 463 self._dirty = True
464 464 self._map[f] = dirstatetuple(state, mode, size, mtime)
465 465
466 466 def normal(self, f):
467 467 '''Mark a file normal and clean.'''
468 468 s = os.lstat(self._join(f))
469 469 mtime = util.statmtimesec(s)
470 470 self._addpath(f, 'n', s.st_mode,
471 471 s.st_size & _rangemask, mtime & _rangemask)
472 472 if f in self._copymap:
473 473 del self._copymap[f]
474 474 if mtime > self._lastnormaltime:
475 475 # Remember the most recent modification timeslot for status(),
476 476 # to make sure we won't miss future size-preserving file content
477 477 # modifications that happen within the same timeslot.
478 478 self._lastnormaltime = mtime
479 479
480 480 def normallookup(self, f):
481 481 '''Mark a file normal, but possibly dirty.'''
482 482 if self._pl[1] != nullid and f in self._map:
483 483 # if there is a merge going on and the file was either
484 484 # in state 'm' (-1) or coming from other parent (-2) before
485 485 # being removed, restore that state.
486 486 entry = self._map[f]
487 487 if entry[0] == 'r' and entry[2] in (-1, -2):
488 488 source = self._copymap.get(f)
489 489 if entry[2] == -1:
490 490 self.merge(f)
491 491 elif entry[2] == -2:
492 492 self.otherparent(f)
493 493 if source:
494 494 self.copy(source, f)
495 495 return
496 496 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
497 497 return
498 498 self._addpath(f, 'n', 0, -1, -1)
499 499 if f in self._copymap:
500 500 del self._copymap[f]
501 501
502 502 def otherparent(self, f):
503 503 '''Mark as coming from the other parent, always dirty.'''
504 504 if self._pl[1] == nullid:
505 505 raise error.Abort(_("setting %r to other parent "
506 506 "only allowed in merges") % f)
507 507 if f in self and self[f] == 'n':
508 508 # merge-like
509 509 self._addpath(f, 'm', 0, -2, -1)
510 510 else:
511 511 # add-like
512 512 self._addpath(f, 'n', 0, -2, -1)
513 513
514 514 if f in self._copymap:
515 515 del self._copymap[f]
516 516
517 517 def add(self, f):
518 518 '''Mark a file added.'''
519 519 self._addpath(f, 'a', 0, -1, -1)
520 520 if f in self._copymap:
521 521 del self._copymap[f]
522 522
523 523 def remove(self, f):
524 524 '''Mark a file removed.'''
525 525 self._dirty = True
526 526 self._droppath(f)
527 527 size = 0
528 528 if self._pl[1] != nullid and f in self._map:
529 529 # backup the previous state
530 530 entry = self._map[f]
531 531 if entry[0] == 'm': # merge
532 532 size = -1
533 533 elif entry[0] == 'n' and entry[2] == -2: # other parent
534 534 size = -2
535 535 self._map[f] = dirstatetuple('r', 0, size, 0)
536 536 if size == 0 and f in self._copymap:
537 537 del self._copymap[f]
538 538
539 539 def merge(self, f):
540 540 '''Mark a file merged.'''
541 541 if self._pl[1] == nullid:
542 542 return self.normallookup(f)
543 543 return self.otherparent(f)
544 544
545 545 def drop(self, f):
546 546 '''Drop a file from the dirstate'''
547 547 if f in self._map:
548 548 self._dirty = True
549 549 self._droppath(f)
550 550 del self._map[f]
551 551
552 552 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
553 553 if exists is None:
554 554 exists = os.path.lexists(os.path.join(self._root, path))
555 555 if not exists:
556 556 # Maybe a path component exists
557 557 if not ignoremissing and '/' in path:
558 558 d, f = path.rsplit('/', 1)
559 559 d = self._normalize(d, False, ignoremissing, None)
560 560 folded = d + "/" + f
561 561 else:
562 562 # No path components, preserve original case
563 563 folded = path
564 564 else:
565 565 # recursively normalize leading directory components
566 566 # against dirstate
567 567 if '/' in normed:
568 568 d, f = normed.rsplit('/', 1)
569 569 d = self._normalize(d, False, ignoremissing, True)
570 570 r = self._root + "/" + d
571 571 folded = d + "/" + util.fspath(f, r)
572 572 else:
573 573 folded = util.fspath(normed, self._root)
574 574 storemap[normed] = folded
575 575
576 576 return folded
577 577
578 578 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
579 579 normed = util.normcase(path)
580 580 folded = self._filefoldmap.get(normed, None)
581 581 if folded is None:
582 582 if isknown:
583 583 folded = path
584 584 else:
585 585 folded = self._discoverpath(path, normed, ignoremissing, exists,
586 586 self._filefoldmap)
587 587 return folded
588 588
589 589 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
590 590 normed = util.normcase(path)
591 591 folded = self._filefoldmap.get(normed, None)
592 592 if folded is None:
593 593 folded = self._dirfoldmap.get(normed, None)
594 594 if folded is None:
595 595 if isknown:
596 596 folded = path
597 597 else:
598 598 # store discovered result in dirfoldmap so that future
599 599 # normalizefile calls don't start matching directories
600 600 folded = self._discoverpath(path, normed, ignoremissing, exists,
601 601 self._dirfoldmap)
602 602 return folded
603 603
604 604 def normalize(self, path, isknown=False, ignoremissing=False):
605 605 '''
606 606 normalize the case of a pathname when on a casefolding filesystem
607 607
608 608 isknown specifies whether the filename came from walking the
609 609 disk, to avoid extra filesystem access.
610 610
611 611 If ignoremissing is True, missing path are returned
612 612 unchanged. Otherwise, we try harder to normalize possibly
613 613 existing path components.
614 614
615 615 The normalized case is determined based on the following precedence:
616 616
617 617 - version of name already stored in the dirstate
618 618 - version of name stored on disk
619 619 - version provided via command arguments
620 620 '''
621 621
622 622 if self._checkcase:
623 623 return self._normalize(path, isknown, ignoremissing)
624 624 return path
625 625
626 626 def clear(self):
627 627 self._map = {}
628 628 if "_dirs" in self.__dict__:
629 629 delattr(self, "_dirs")
630 630 self._copymap = {}
631 631 self._pl = [nullid, nullid]
632 632 self._lastnormaltime = 0
633 633 self._dirty = True
634 634
635 635 def rebuild(self, parent, allfiles, changedfiles=None):
636 636 if changedfiles is None:
637 637 changedfiles = allfiles
638 638 oldmap = self._map
639 639 self.clear()
640 640 for f in allfiles:
641 641 if f not in changedfiles:
642 642 self._map[f] = oldmap[f]
643 643 else:
644 644 if 'x' in allfiles.flags(f):
645 645 self._map[f] = dirstatetuple('n', 0o777, -1, 0)
646 646 else:
647 647 self._map[f] = dirstatetuple('n', 0o666, -1, 0)
648 648 self._pl = (parent, nullid)
649 649 self._dirty = True
650 650
651 651 def write(self, tr=False):
652 652 if not self._dirty:
653 653 return
654 654
655 655 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
656 656 # timestamp of each entries in dirstate, because of 'now > mtime'
657 657 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
658 658 if delaywrite > 0:
659 659 import time # to avoid useless import
660 660 time.sleep(delaywrite)
661 661
662 662 filename = self._filename
663 663 if tr is False: # not explicitly specified
664 664 if (self._ui.configbool('devel', 'all-warnings')
665 or self.ui.configbool('devel', 'check-dirstate-write')):
665 or self._ui.configbool('devel', 'check-dirstate-write')):
666 666 self._ui.develwarn('use dirstate.write with '
667 667 'repo.currenttransaction()')
668 668
669 669 if self._opener.lexists(self._pendingfilename):
670 670 # if pending file already exists, in-memory changes
671 671 # should be written into it, because it has priority
672 672 # to '.hg/dirstate' at reading under HG_PENDING mode
673 673 filename = self._pendingfilename
674 674 elif tr:
675 675 # 'dirstate.write()' is not only for writing in-memory
676 676 # changes out, but also for dropping ambiguous timestamp.
677 677 # delayed writing re-raise "ambiguous timestamp issue".
678 678 # See also the wiki page below for detail:
679 679 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
680 680
681 681 # emulate dropping timestamp in 'parsers.pack_dirstate'
682 682 now = _getfsnow(self._opener)
683 683 dmap = self._map
684 684 for f, e in dmap.iteritems():
685 685 if e[0] == 'n' and e[3] == now:
686 686 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
687 687
688 688 # emulate that all 'dirstate.normal' results are written out
689 689 self._lastnormaltime = 0
690 690
691 691 # delay writing in-memory changes out
692 692 tr.addfilegenerator('dirstate', (self._filename,),
693 693 self._writedirstate, location='plain')
694 694 return
695 695
696 696 st = self._opener(filename, "w", atomictemp=True)
697 697 self._writedirstate(st)
698 698
699 699 def _writedirstate(self, st):
700 700 # use the modification time of the newly created temporary file as the
701 701 # filesystem's notion of 'now'
702 702 now = util.statmtimesec(util.fstat(st)) & _rangemask
703 703 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
704 704 st.close()
705 705 self._lastnormaltime = 0
706 706 self._dirty = self._dirtypl = False
707 707
708 708 def _dirignore(self, f):
709 709 if f == '.':
710 710 return False
711 711 if self._ignore(f):
712 712 return True
713 713 for p in util.finddirs(f):
714 714 if self._ignore(p):
715 715 return True
716 716 return False
717 717
718 718 def _walkexplicit(self, match, subrepos):
719 719 '''Get stat data about the files explicitly specified by match.
720 720
721 721 Return a triple (results, dirsfound, dirsnotfound).
722 722 - results is a mapping from filename to stat result. It also contains
723 723 listings mapping subrepos and .hg to None.
724 724 - dirsfound is a list of files found to be directories.
725 725 - dirsnotfound is a list of files that the dirstate thinks are
726 726 directories and that were not found.'''
727 727
728 728 def badtype(mode):
729 729 kind = _('unknown')
730 730 if stat.S_ISCHR(mode):
731 731 kind = _('character device')
732 732 elif stat.S_ISBLK(mode):
733 733 kind = _('block device')
734 734 elif stat.S_ISFIFO(mode):
735 735 kind = _('fifo')
736 736 elif stat.S_ISSOCK(mode):
737 737 kind = _('socket')
738 738 elif stat.S_ISDIR(mode):
739 739 kind = _('directory')
740 740 return _('unsupported file type (type is %s)') % kind
741 741
742 742 matchedir = match.explicitdir
743 743 badfn = match.bad
744 744 dmap = self._map
745 745 lstat = os.lstat
746 746 getkind = stat.S_IFMT
747 747 dirkind = stat.S_IFDIR
748 748 regkind = stat.S_IFREG
749 749 lnkkind = stat.S_IFLNK
750 750 join = self._join
751 751 dirsfound = []
752 752 foundadd = dirsfound.append
753 753 dirsnotfound = []
754 754 notfoundadd = dirsnotfound.append
755 755
756 756 if not match.isexact() and self._checkcase:
757 757 normalize = self._normalize
758 758 else:
759 759 normalize = None
760 760
761 761 files = sorted(match.files())
762 762 subrepos.sort()
763 763 i, j = 0, 0
764 764 while i < len(files) and j < len(subrepos):
765 765 subpath = subrepos[j] + "/"
766 766 if files[i] < subpath:
767 767 i += 1
768 768 continue
769 769 while i < len(files) and files[i].startswith(subpath):
770 770 del files[i]
771 771 j += 1
772 772
773 773 if not files or '.' in files:
774 774 files = ['.']
775 775 results = dict.fromkeys(subrepos)
776 776 results['.hg'] = None
777 777
778 778 alldirs = None
779 779 for ff in files:
780 780 # constructing the foldmap is expensive, so don't do it for the
781 781 # common case where files is ['.']
782 782 if normalize and ff != '.':
783 783 nf = normalize(ff, False, True)
784 784 else:
785 785 nf = ff
786 786 if nf in results:
787 787 continue
788 788
789 789 try:
790 790 st = lstat(join(nf))
791 791 kind = getkind(st.st_mode)
792 792 if kind == dirkind:
793 793 if nf in dmap:
794 794 # file replaced by dir on disk but still in dirstate
795 795 results[nf] = None
796 796 if matchedir:
797 797 matchedir(nf)
798 798 foundadd((nf, ff))
799 799 elif kind == regkind or kind == lnkkind:
800 800 results[nf] = st
801 801 else:
802 802 badfn(ff, badtype(kind))
803 803 if nf in dmap:
804 804 results[nf] = None
805 805 except OSError as inst: # nf not found on disk - it is dirstate only
806 806 if nf in dmap: # does it exactly match a missing file?
807 807 results[nf] = None
808 808 else: # does it match a missing directory?
809 809 if alldirs is None:
810 810 alldirs = util.dirs(dmap)
811 811 if nf in alldirs:
812 812 if matchedir:
813 813 matchedir(nf)
814 814 notfoundadd(nf)
815 815 else:
816 816 badfn(ff, inst.strerror)
817 817
818 818 # Case insensitive filesystems cannot rely on lstat() failing to detect
819 819 # a case-only rename. Prune the stat object for any file that does not
820 820 # match the case in the filesystem, if there are multiple files that
821 821 # normalize to the same path.
822 822 if match.isexact() and self._checkcase:
823 823 normed = {}
824 824
825 825 for f, st in results.iteritems():
826 826 if st is None:
827 827 continue
828 828
829 829 nc = util.normcase(f)
830 830 paths = normed.get(nc)
831 831
832 832 if paths is None:
833 833 paths = set()
834 834 normed[nc] = paths
835 835
836 836 paths.add(f)
837 837
838 838 for norm, paths in normed.iteritems():
839 839 if len(paths) > 1:
840 840 for path in paths:
841 841 folded = self._discoverpath(path, norm, True, None,
842 842 self._dirfoldmap)
843 843 if path != folded:
844 844 results[path] = None
845 845
846 846 return results, dirsfound, dirsnotfound
847 847
848 848 def walk(self, match, subrepos, unknown, ignored, full=True):
849 849 '''
850 850 Walk recursively through the directory tree, finding all files
851 851 matched by match.
852 852
853 853 If full is False, maybe skip some known-clean files.
854 854
855 855 Return a dict mapping filename to stat-like object (either
856 856 mercurial.osutil.stat instance or return value of os.stat()).
857 857
858 858 '''
859 859 # full is a flag that extensions that hook into walk can use -- this
860 860 # implementation doesn't use it at all. This satisfies the contract
861 861 # because we only guarantee a "maybe".
862 862
863 863 if ignored:
864 864 ignore = util.never
865 865 dirignore = util.never
866 866 elif unknown:
867 867 ignore = self._ignore
868 868 dirignore = self._dirignore
869 869 else:
870 870 # if not unknown and not ignored, drop dir recursion and step 2
871 871 ignore = util.always
872 872 dirignore = util.always
873 873
874 874 matchfn = match.matchfn
875 875 matchalways = match.always()
876 876 matchtdir = match.traversedir
877 877 dmap = self._map
878 878 listdir = osutil.listdir
879 879 lstat = os.lstat
880 880 dirkind = stat.S_IFDIR
881 881 regkind = stat.S_IFREG
882 882 lnkkind = stat.S_IFLNK
883 883 join = self._join
884 884
885 885 exact = skipstep3 = False
886 886 if match.isexact(): # match.exact
887 887 exact = True
888 888 dirignore = util.always # skip step 2
889 889 elif match.prefix(): # match.match, no patterns
890 890 skipstep3 = True
891 891
892 892 if not exact and self._checkcase:
893 893 normalize = self._normalize
894 894 normalizefile = self._normalizefile
895 895 skipstep3 = False
896 896 else:
897 897 normalize = self._normalize
898 898 normalizefile = None
899 899
900 900 # step 1: find all explicit files
901 901 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
902 902
903 903 skipstep3 = skipstep3 and not (work or dirsnotfound)
904 904 work = [d for d in work if not dirignore(d[0])]
905 905
906 906 # step 2: visit subdirectories
907 907 def traverse(work, alreadynormed):
908 908 wadd = work.append
909 909 while work:
910 910 nd = work.pop()
911 911 skip = None
912 912 if nd == '.':
913 913 nd = ''
914 914 else:
915 915 skip = '.hg'
916 916 try:
917 917 entries = listdir(join(nd), stat=True, skip=skip)
918 918 except OSError as inst:
919 919 if inst.errno in (errno.EACCES, errno.ENOENT):
920 920 match.bad(self.pathto(nd), inst.strerror)
921 921 continue
922 922 raise
923 923 for f, kind, st in entries:
924 924 if normalizefile:
925 925 # even though f might be a directory, we're only
926 926 # interested in comparing it to files currently in the
927 927 # dmap -- therefore normalizefile is enough
928 928 nf = normalizefile(nd and (nd + "/" + f) or f, True,
929 929 True)
930 930 else:
931 931 nf = nd and (nd + "/" + f) or f
932 932 if nf not in results:
933 933 if kind == dirkind:
934 934 if not ignore(nf):
935 935 if matchtdir:
936 936 matchtdir(nf)
937 937 wadd(nf)
938 938 if nf in dmap and (matchalways or matchfn(nf)):
939 939 results[nf] = None
940 940 elif kind == regkind or kind == lnkkind:
941 941 if nf in dmap:
942 942 if matchalways or matchfn(nf):
943 943 results[nf] = st
944 944 elif ((matchalways or matchfn(nf))
945 945 and not ignore(nf)):
946 946 # unknown file -- normalize if necessary
947 947 if not alreadynormed:
948 948 nf = normalize(nf, False, True)
949 949 results[nf] = st
950 950 elif nf in dmap and (matchalways or matchfn(nf)):
951 951 results[nf] = None
952 952
953 953 for nd, d in work:
954 954 # alreadynormed means that processwork doesn't have to do any
955 955 # expensive directory normalization
956 956 alreadynormed = not normalize or nd == d
957 957 traverse([d], alreadynormed)
958 958
959 959 for s in subrepos:
960 960 del results[s]
961 961 del results['.hg']
962 962
963 963 # step 3: visit remaining files from dmap
964 964 if not skipstep3 and not exact:
965 965 # If a dmap file is not in results yet, it was either
966 966 # a) not matching matchfn b) ignored, c) missing, or d) under a
967 967 # symlink directory.
968 968 if not results and matchalways:
969 969 visit = dmap.keys()
970 970 else:
971 971 visit = [f for f in dmap if f not in results and matchfn(f)]
972 972 visit.sort()
973 973
974 974 if unknown:
975 975 # unknown == True means we walked all dirs under the roots
976 976 # that wasn't ignored, and everything that matched was stat'ed
977 977 # and is already in results.
978 978 # The rest must thus be ignored or under a symlink.
979 979 audit_path = pathutil.pathauditor(self._root)
980 980
981 981 for nf in iter(visit):
982 982 # If a stat for the same file was already added with a
983 983 # different case, don't add one for this, since that would
984 984 # make it appear as if the file exists under both names
985 985 # on disk.
986 986 if (normalizefile and
987 987 normalizefile(nf, True, True) in results):
988 988 results[nf] = None
989 989 # Report ignored items in the dmap as long as they are not
990 990 # under a symlink directory.
991 991 elif audit_path.check(nf):
992 992 try:
993 993 results[nf] = lstat(join(nf))
994 994 # file was just ignored, no links, and exists
995 995 except OSError:
996 996 # file doesn't exist
997 997 results[nf] = None
998 998 else:
999 999 # It's either missing or under a symlink directory
1000 1000 # which we in this case report as missing
1001 1001 results[nf] = None
1002 1002 else:
1003 1003 # We may not have walked the full directory tree above,
1004 1004 # so stat and check everything we missed.
1005 1005 nf = iter(visit).next
1006 1006 pos = 0
1007 1007 while pos < len(visit):
1008 1008 # visit in mid-sized batches so that we don't
1009 1009 # block signals indefinitely
1010 1010 xr = xrange(pos, min(len(visit), pos + 1000))
1011 1011 for st in util.statfiles([join(visit[n]) for n in xr]):
1012 1012 results[nf()] = st
1013 1013 pos += 1000
1014 1014 return results
1015 1015
1016 1016 def status(self, match, subrepos, ignored, clean, unknown):
1017 1017 '''Determine the status of the working copy relative to the
1018 1018 dirstate and return a pair of (unsure, status), where status is of type
1019 1019 scmutil.status and:
1020 1020
1021 1021 unsure:
1022 1022 files that might have been modified since the dirstate was
1023 1023 written, but need to be read to be sure (size is the same
1024 1024 but mtime differs)
1025 1025 status.modified:
1026 1026 files that have definitely been modified since the dirstate
1027 1027 was written (different size or mode)
1028 1028 status.clean:
1029 1029 files that have definitely not been modified since the
1030 1030 dirstate was written
1031 1031 '''
1032 1032 listignored, listclean, listunknown = ignored, clean, unknown
1033 1033 lookup, modified, added, unknown, ignored = [], [], [], [], []
1034 1034 removed, deleted, clean = [], [], []
1035 1035
1036 1036 dmap = self._map
1037 1037 ladd = lookup.append # aka "unsure"
1038 1038 madd = modified.append
1039 1039 aadd = added.append
1040 1040 uadd = unknown.append
1041 1041 iadd = ignored.append
1042 1042 radd = removed.append
1043 1043 dadd = deleted.append
1044 1044 cadd = clean.append
1045 1045 mexact = match.exact
1046 1046 dirignore = self._dirignore
1047 1047 checkexec = self._checkexec
1048 1048 copymap = self._copymap
1049 1049 lastnormaltime = self._lastnormaltime
1050 1050
1051 1051 # We need to do full walks when either
1052 1052 # - we're listing all clean files, or
1053 1053 # - match.traversedir does something, because match.traversedir should
1054 1054 # be called for every dir in the working dir
1055 1055 full = listclean or match.traversedir is not None
1056 1056 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1057 1057 full=full).iteritems():
1058 1058 if fn not in dmap:
1059 1059 if (listignored or mexact(fn)) and dirignore(fn):
1060 1060 if listignored:
1061 1061 iadd(fn)
1062 1062 else:
1063 1063 uadd(fn)
1064 1064 continue
1065 1065
1066 1066 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1067 1067 # written like that for performance reasons. dmap[fn] is not a
1068 1068 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1069 1069 # opcode has fast paths when the value to be unpacked is a tuple or
1070 1070 # a list, but falls back to creating a full-fledged iterator in
1071 1071 # general. That is much slower than simply accessing and storing the
1072 1072 # tuple members one by one.
1073 1073 t = dmap[fn]
1074 1074 state = t[0]
1075 1075 mode = t[1]
1076 1076 size = t[2]
1077 1077 time = t[3]
1078 1078
1079 1079 if not st and state in "nma":
1080 1080 dadd(fn)
1081 1081 elif state == 'n':
1082 1082 mtime = util.statmtimesec(st)
1083 1083 if (size >= 0 and
1084 1084 ((size != st.st_size and size != st.st_size & _rangemask)
1085 1085 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1086 1086 or size == -2 # other parent
1087 1087 or fn in copymap):
1088 1088 madd(fn)
1089 1089 elif time != mtime and time != mtime & _rangemask:
1090 1090 ladd(fn)
1091 1091 elif mtime == lastnormaltime:
1092 1092 # fn may have just been marked as normal and it may have
1093 1093 # changed in the same second without changing its size.
1094 1094 # This can happen if we quickly do multiple commits.
1095 1095 # Force lookup, so we don't miss such a racy file change.
1096 1096 ladd(fn)
1097 1097 elif listclean:
1098 1098 cadd(fn)
1099 1099 elif state == 'm':
1100 1100 madd(fn)
1101 1101 elif state == 'a':
1102 1102 aadd(fn)
1103 1103 elif state == 'r':
1104 1104 radd(fn)
1105 1105
1106 1106 return (lookup, scmutil.status(modified, added, removed, deleted,
1107 1107 unknown, ignored, clean))
1108 1108
1109 1109 def matches(self, match):
1110 1110 '''
1111 1111 return files in the dirstate (in whatever state) filtered by match
1112 1112 '''
1113 1113 dmap = self._map
1114 1114 if match.always():
1115 1115 return dmap.keys()
1116 1116 files = match.files()
1117 1117 if match.isexact():
1118 1118 # fast path -- filter the other way around, since typically files is
1119 1119 # much smaller than dmap
1120 1120 return [f for f in files if f in dmap]
1121 1121 if match.prefix() and all(fn in dmap for fn in files):
1122 1122 # fast path -- all the values are known to be files, so just return
1123 1123 # that
1124 1124 return list(files)
1125 1125 return [f for f in dmap if match(f)]
1126 1126
1127 1127 def _actualfilename(self, tr):
1128 1128 if tr:
1129 1129 return self._pendingfilename
1130 1130 else:
1131 1131 return self._filename
1132 1132
1133 1133 def _savebackup(self, tr, suffix):
1134 1134 '''Save current dirstate into backup file with suffix'''
1135 1135 filename = self._actualfilename(tr)
1136 1136
1137 1137 # use '_writedirstate' instead of 'write' to write changes certainly,
1138 1138 # because the latter omits writing out if transaction is running.
1139 1139 # output file will be used to create backup of dirstate at this point.
1140 1140 self._writedirstate(self._opener(filename, "w", atomictemp=True))
1141 1141
1142 1142 if tr:
1143 1143 # ensure that subsequent tr.writepending returns True for
1144 1144 # changes written out above, even if dirstate is never
1145 1145 # changed after this
1146 1146 tr.addfilegenerator('dirstate', (self._filename,),
1147 1147 self._writedirstate, location='plain')
1148 1148
1149 1149 # ensure that pending file written above is unlinked at
1150 1150 # failure, even if tr.writepending isn't invoked until the
1151 1151 # end of this transaction
1152 1152 tr.registertmp(filename, location='plain')
1153 1153
1154 1154 self._opener.write(filename + suffix, self._opener.tryread(filename))
1155 1155
1156 1156 def _restorebackup(self, tr, suffix):
1157 1157 '''Restore dirstate by backup file with suffix'''
1158 1158 # this "invalidate()" prevents "wlock.release()" from writing
1159 1159 # changes of dirstate out after restoring from backup file
1160 1160 self.invalidate()
1161 1161 filename = self._actualfilename(tr)
1162 1162 self._opener.rename(filename + suffix, filename)
1163 1163
1164 1164 def _clearbackup(self, tr, suffix):
1165 1165 '''Clear backup file with suffix'''
1166 1166 filename = self._actualfilename(tr)
1167 1167 self._opener.unlink(filename + suffix)
General Comments 0
You need to be logged in to leave comments. Login now