##// END OF EJS Templates
merge: fix race that could cause wrong size in dirstate...
Valentin Gatien-Baron -
r42649:773b0222 default draft
parent child Browse files
Show More
@@ -515,7 +515,7 b' def overridecalculateupdates(origfn, rep'
515 515 return actions, diverge, renamedelete
516 516
517 517 @eh.wrapfunction(merge, 'recordupdates')
518 def mergerecordupdates(orig, repo, actions, branchmerge):
518 def mergerecordupdates(orig, repo, actions, branchmerge, getfiledata):
519 519 if 'lfmr' in actions:
520 520 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
521 521 for lfile, args, msg in actions['lfmr']:
@@ -526,7 +526,7 b' def mergerecordupdates(orig, repo, actio'
526 526 lfdirstate.add(lfile)
527 527 lfdirstate.write()
528 528
529 return orig(repo, actions, branchmerge)
529 return orig(repo, actions, branchmerge, getfiledata)
530 530
531 531 # Override filemerge to prompt the user about how they wish to merge
532 532 # largefiles. This will handle identical edits without prompting the user.
@@ -16,21 +16,21 b' def wrapdirstate(repo, dirstate):'
16 16 """Add narrow spec dirstate ignore, block changes outside narrow spec."""
17 17
18 18 def _editfunc(fn):
19 def _wrapper(self, *args):
19 def _wrapper(self, *args, **kwargs):
20 20 narrowmatch = repo.narrowmatch()
21 21 for f in args:
22 22 if f is not None and not narrowmatch(f) and f not in self:
23 23 raise error.Abort(_("cannot track '%s' - it is outside " +
24 24 "the narrow clone") % f)
25 return fn(self, *args)
25 return fn(self, *args, **kwargs)
26 26 return _wrapper
27 27
28 28 class narrowdirstate(dirstate.__class__):
29 29 # Prevent adding/editing/copying/deleting files that are outside the
30 30 # sparse checkout
31 31 @_editfunc
32 def normal(self, *args):
33 return super(narrowdirstate, self).normal(*args)
32 def normal(self, *args, **kwargs):
33 return super(narrowdirstate, self).normal(*args, **kwargs)
34 34
35 35 @_editfunc
36 36 def add(self, *args):
@@ -442,7 +442,8 b' def storewrapper(orig, requirements, pat'
442 442 return s
443 443
444 444 # prefetch files before update
445 def applyupdates(orig, repo, actions, wctx, mctx, overwrite, labels=None):
445 def applyupdates(orig, repo, actions, wctx, mctx, overwrite, wantfiledata,
446 labels=None):
446 447 if isenabled(repo):
447 448 manifest = mctx.manifest()
448 449 files = []
@@ -450,7 +451,8 b' def applyupdates(orig, repo, actions, wc'
450 451 files.append((f, hex(manifest[f])))
451 452 # batch fetch the needed files from the server
452 453 repo.fileservice.prefetch(files)
453 return orig(repo, actions, wctx, mctx, overwrite, labels=labels)
454 return orig(repo, actions, wctx, mctx, overwrite, wantfiledata,
455 labels=labels)
454 456
455 457 # Prefetch merge checkunknownfiles
456 458 def checkunknownfiles(orig, repo, wctx, mctx, force, actions,
@@ -228,7 +228,7 b' def _setupdirstate(ui):'
228 228 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
229 229 '`hg add -s <file>` to include file directory while adding')
230 230 for func in editfuncs:
231 def _wrapper(orig, self, *args):
231 def _wrapper(orig, self, *args, **kwargs):
232 232 sparsematch = self._sparsematcher
233 233 if not sparsematch.always():
234 234 for f in args:
@@ -237,7 +237,7 b' def _setupdirstate(ui):'
237 237 raise error.Abort(_("cannot add '%s' - it is outside "
238 238 "the sparse checkout") % f,
239 239 hint=hint)
240 return orig(self, *args)
240 return orig(self, *args, **kwargs)
241 241 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
242 242
243 243 @command('debugsparse', [
@@ -1766,6 +1766,8 b' class workingfilectx(committablefilectx)'
1766 1766
1767 1767 def size(self):
1768 1768 return self._repo.wvfs.lstat(self._path).st_size
1769 def lstat(self):
1770 return self._repo.wvfs.lstat(self._path)
1769 1771 def date(self):
1770 1772 t, tz = self._changectx.date()
1771 1773 try:
@@ -1801,9 +1803,9 b' class workingfilectx(committablefilectx)'
1801 1803
1802 1804 def write(self, data, flags, backgroundclose=False, **kwargs):
1803 1805 """wraps repo.wwrite"""
1804 self._repo.wwrite(self._path, data, flags,
1805 backgroundclose=backgroundclose,
1806 **kwargs)
1806 return self._repo.wwrite(self._path, data, flags,
1807 backgroundclose=backgroundclose,
1808 **kwargs)
1807 1809
1808 1810 def markcopied(self, src):
1809 1811 """marks this file a copy of `src`"""
@@ -391,12 +391,24 b' class dirstate(object):'
391 391 self._updatedfiles.add(f)
392 392 self._map.addfile(f, oldstate, state, mode, size, mtime)
393 393
394 def normal(self, f):
395 '''Mark a file normal and clean.'''
396 s = os.lstat(self._join(f))
397 mtime = s[stat.ST_MTIME]
398 self._addpath(f, 'n', s.st_mode,
399 s.st_size & _rangemask, mtime & _rangemask)
394 def normal(self, f, parentfiledata=None):
395 '''Mark a file normal and clean.
396
397 parentfiledata: (mode, size, mtime) of the clean file
398
399 parentfiledata should be computed from memory (for mode,
400 size), as or close as possible from the point where we
401 determined the file was clean, to limit the risk of the
402 file having been changed by an external process between the
403 moment where the file was determined to be clean and now.'''
404 if parentfiledata:
405 (mode, size, mtime) = parentfiledata
406 else:
407 s = os.lstat(self._join(f))
408 mode = s.st_mode
409 size = s.st_size
410 mtime = s[stat.ST_MTIME]
411 self._addpath(f, 'n', mode, size & _rangemask, mtime & _rangemask)
400 412 self._map.copymap.pop(f, None)
401 413 if f in self._map.nonnormalset:
402 414 self._map.nonnormalset.remove(f)
@@ -10,6 +10,7 b' from __future__ import absolute_import'
10 10 import errno
11 11 import hashlib
12 12 import shutil
13 import stat
13 14 import struct
14 15
15 16 from .i18n import _
@@ -683,7 +684,7 b' class mergestate(object):'
683 684 def recordactions(self):
684 685 """record remove/add/get actions in the dirstate"""
685 686 branchmerge = self._repo.dirstate.p2() != nullid
686 recordupdates(self._repo, self.actions(), branchmerge)
687 recordupdates(self._repo, self.actions(), branchmerge, None)
687 688
688 689 def queueremove(self, f):
689 690 """queues a file to be removed from the dirstate
@@ -1464,13 +1465,17 b' def batchremove(repo, wctx, actions):'
1464 1465 repo.ui.warn(_("current directory was removed\n"
1465 1466 "(consider changing to repo root: %s)\n") % repo.root)
1466 1467
1467 def batchget(repo, mctx, wctx, actions):
1468 def batchget(repo, mctx, wctx, wantfiledata, actions):
1468 1469 """apply gets to the working directory
1469 1470
1470 1471 mctx is the context to get from
1471 1472
1472 yields tuples for progress updates
1473 Yields arbitrarily many (False, tuple) for progress updates, followed by
1474 exactly one (True, filedata). When wantfiledata is false, filedata is an
1475 empty list. When wantfiledata is true, filedata[i] is a triple (mode, size,
1476 mtime) of the file written for action[i].
1473 1477 """
1478 filedata = []
1474 1479 verbose = repo.ui.verbose
1475 1480 fctx = mctx.filectx
1476 1481 ui = repo.ui
@@ -1494,16 +1499,24 b' def batchget(repo, mctx, wctx, actions):'
1494 1499 if repo.wvfs.lexists(conflicting):
1495 1500 orig = scmutil.backuppath(ui, repo, conflicting)
1496 1501 util.rename(repo.wjoin(conflicting), orig)
1497 wctx[f].clearunknown()
1502 wfctx = wctx[f]
1503 wfctx.clearunknown()
1498 1504 atomictemp = ui.configbool("experimental", "update.atomic-file")
1499 wctx[f].write(fctx(f).data(), flags, backgroundclose=True,
1500 atomictemp=atomictemp)
1505 size = wfctx.write(fctx(f).data(), flags,
1506 backgroundclose=True,
1507 atomictemp=atomictemp)
1508 if wantfiledata:
1509 s = wfctx.lstat()
1510 mode = s.st_mode
1511 mtime = s[stat.ST_MTIME]
1512 filedata.append((mode, size, mtime)) # for dirstate.normal
1501 1513 if i == 100:
1502 yield i, f
1514 yield False, (i, f)
1503 1515 i = 0
1504 1516 i += 1
1505 1517 if i > 0:
1506 yield i, f
1518 yield False, (i, f)
1519 yield True, filedata
1507 1520
1508 1521 def _prefetchfiles(repo, ctx, actions):
1509 1522 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
@@ -1550,14 +1563,17 b' def emptyactions():'
1550 1563 ACTION_PATH_CONFLICT,
1551 1564 ACTION_PATH_CONFLICT_RESOLVE))
1552 1565
1553 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1566 def applyupdates(repo, actions, wctx, mctx, overwrite, wantfiledata,
1567 labels=None):
1554 1568 """apply the merge action list to the working directory
1555 1569
1556 1570 wctx is the working copy context
1557 1571 mctx is the context to be merged into the working copy
1558 1572
1559 Return a tuple of counts (updated, merged, removed, unresolved) that
1560 describes how many files were affected by the update.
1573 Return a tuple of (counts, filedata), where counts is a tuple
1574 (updated, merged, removed, unresolved) that describes how many
1575 files were affected by the update, and filedata is as described in
1576 batchget.
1561 1577 """
1562 1578
1563 1579 _prefetchfiles(repo, mctx, actions)
@@ -1649,11 +1665,18 b' def applyupdates(repo, actions, wctx, mc'
1649 1665 # get in parallel.
1650 1666 threadsafe = repo.ui.configbool('experimental',
1651 1667 'worker.wdir-get-thread-safe')
1652 prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
1668 prog = worker.worker(repo.ui, cost, batchget,
1669 (repo, mctx, wctx, wantfiledata),
1653 1670 actions[ACTION_GET],
1654 threadsafe=threadsafe)
1655 for i, item in prog:
1656 progress.increment(step=i, item=item)
1671 threadsafe=threadsafe,
1672 hasretval=True)
1673 getfiledata = []
1674 for final, res in prog:
1675 if final:
1676 getfiledata = res
1677 else:
1678 i, item = res
1679 progress.increment(step=i, item=item)
1657 1680 updated = len(actions[ACTION_GET])
1658 1681
1659 1682 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
@@ -1778,6 +1801,9 b' def applyupdates(repo, actions, wctx, mc'
1778 1801 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1779 1802 for k, acts in extraactions.iteritems():
1780 1803 actions[k].extend(acts)
1804 if k == ACTION_GET and wantfiledata:
1805 # no filedata until mergestate is updated to provide it
1806 getfiledata.extend([None] * len(acts))
1781 1807 # Remove these files from actions[ACTION_MERGE] as well. This is
1782 1808 # important because in recordupdates, files in actions[ACTION_MERGE]
1783 1809 # are processed after files in other actions, and the merge driver
@@ -1800,9 +1826,10 b' def applyupdates(repo, actions, wctx, mc'
1800 1826 if a[0] in mfiles]
1801 1827
1802 1828 progress.complete()
1803 return updateresult(updated, merged, removed, unresolved)
1829 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
1830 return updateresult(updated, merged, removed, unresolved), getfiledata
1804 1831
1805 def recordupdates(repo, actions, branchmerge):
1832 def recordupdates(repo, actions, branchmerge, getfiledata):
1806 1833 "record merge actions to the dirstate"
1807 1834 # remove (must come first)
1808 1835 for f, args, msg in actions.get(ACTION_REMOVE, []):
@@ -1846,11 +1873,12 b' def recordupdates(repo, actions, branchm'
1846 1873 pass
1847 1874
1848 1875 # get
1849 for f, args, msg in actions.get(ACTION_GET, []):
1876 for i, (f, args, msg) in enumerate(actions.get(ACTION_GET, [])):
1850 1877 if branchmerge:
1851 1878 repo.dirstate.otherparent(f)
1852 1879 else:
1853 repo.dirstate.normal(f)
1880 parentfiledata = getfiledata[i] if getfiledata else None
1881 repo.dirstate.normal(f, parentfiledata=parentfiledata)
1854 1882
1855 1883 # merge
1856 1884 for f, args, msg in actions.get(ACTION_MERGE, []):
@@ -2166,12 +2194,15 b' def update(repo, node, branchmerge, forc'
2166 2194 'fsmonitor enabled; enable fsmonitor to improve performance; '
2167 2195 'see "hg help -e fsmonitor")\n'))
2168 2196
2169 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
2197 updatedirstate = not partial and not wc.isinmemory()
2198 wantfiledata = updatedirstate and not branchmerge
2199 stats, getfiledata = applyupdates(repo, actions, wc, p2, overwrite,
2200 wantfiledata, labels=labels)
2170 2201
2171 if not partial and not wc.isinmemory():
2202 if updatedirstate:
2172 2203 with repo.dirstate.parentchange():
2173 2204 repo.setparents(fp1, fp2)
2174 recordupdates(repo, actions, branchmerge)
2205 recordupdates(repo, actions, branchmerge, getfiledata)
2175 2206 # update completed, clear state
2176 2207 util.unlink(repo.vfs.join('updatestate'))
2177 2208
@@ -259,7 +259,7 b' def _writeaddedfiles(repo, pctx, files):'
259 259 if not repo.wvfs.exists(f):
260 260 addgaction((f, (mf.flags(f), False), "narrowspec updated"))
261 261 merge.applyupdates(repo, actions, wctx=repo[None],
262 mctx=repo['.'], overwrite=False)
262 mctx=repo['.'], overwrite=False, wantfiledata=False)
263 263
264 264 def checkworkingcopynarrowspec(repo):
265 265 # Avoid infinite recursion when updating the working copy
@@ -248,7 +248,8 b' def prunetemporaryincludes(repo):'
248 248
249 249 typeactions = mergemod.emptyactions()
250 250 typeactions['r'] = actions
251 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
251 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False,
252 wantfiledata=False)
252 253
253 254 # Fix dirstate
254 255 for file in dropped:
@@ -382,7 +383,7 b' def filterupdatesactions(repo, wctx, mct'
382 383 typeactions = mergemod.emptyactions()
383 384 typeactions['g'] = actions
384 385 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'],
385 False)
386 False, wantfiledata=False)
386 387
387 388 dirstate = repo.dirstate
388 389 for file, flags, msg in actions:
@@ -486,7 +487,8 b' def refreshwdir(repo, origstatus, origsp'
486 487 for f, (m, args, msg) in actions.iteritems():
487 488 typeactions[m].append((f, args, msg))
488 489
489 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
490 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False,
491 wantfiledata=False)
490 492
491 493 # Fix dirstate
492 494 for file in added:
@@ -27,13 +27,11 b" outside of hg's control."
27 27 > EOF
28 28
29 29 Do an update where file 'a' is changed between hg writing it to disk
30 and hg writing the dirstate. It results in a corrupted dirstate, which
31 stores the wrong size, and thus hg status shows spuriously modified
32 files.
30 and hg writing the dirstate. The dirstate is correct nonetheless, and
31 so hg status correctly shows a as clean.
33 32
34 33 $ hg up -r 0 --config extensions.race=$TESTTMP/dirstaterace.py
35 34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 35 $ hg debugdirstate --no-dates
37 n 644 0 (set |unset) a (re)
36 n 644 2 (set |unset) a (re)
38 37 $ echo a > a; hg status; hg diff
39 M a
@@ -73,7 +73,7 b' coherent (issue4353)'
73 73 > merge,
74 74 > )
75 75 >
76 > def wraprecordupdates(orig, repo, actions, branchmerge):
76 > def wraprecordupdates(*args):
77 77 > raise error.Abort("simulated error while recording dirstateupdates")
78 78 >
79 79 > def reposetup(ui, repo):
General Comments 0
You need to be logged in to leave comments. Login now