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