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