##// 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 return actions, diverge, renamedelete
515 return actions, diverge, renamedelete
516
516
517 @eh.wrapfunction(merge, 'recordupdates')
517 @eh.wrapfunction(merge, 'recordupdates')
518 def mergerecordupdates(orig, repo, actions, branchmerge):
518 def mergerecordupdates(orig, repo, actions, branchmerge, getfiledata):
519 if 'lfmr' in actions:
519 if 'lfmr' in actions:
520 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
520 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
521 for lfile, args, msg in actions['lfmr']:
521 for lfile, args, msg in actions['lfmr']:
@@ -526,7 +526,7 b' def mergerecordupdates(orig, repo, actio'
526 lfdirstate.add(lfile)
526 lfdirstate.add(lfile)
527 lfdirstate.write()
527 lfdirstate.write()
528
528
529 return orig(repo, actions, branchmerge)
529 return orig(repo, actions, branchmerge, getfiledata)
530
530
531 # Override filemerge to prompt the user about how they wish to merge
531 # Override filemerge to prompt the user about how they wish to merge
532 # largefiles. This will handle identical edits without prompting the user.
532 # largefiles. This will handle identical edits without prompting the user.
@@ -16,21 +16,21 b' def wrapdirstate(repo, dirstate):'
16 """Add narrow spec dirstate ignore, block changes outside narrow spec."""
16 """Add narrow spec dirstate ignore, block changes outside narrow spec."""
17
17
18 def _editfunc(fn):
18 def _editfunc(fn):
19 def _wrapper(self, *args):
19 def _wrapper(self, *args, **kwargs):
20 narrowmatch = repo.narrowmatch()
20 narrowmatch = repo.narrowmatch()
21 for f in args:
21 for f in args:
22 if f is not None and not narrowmatch(f) and f not in self:
22 if f is not None and not narrowmatch(f) and f not in self:
23 raise error.Abort(_("cannot track '%s' - it is outside " +
23 raise error.Abort(_("cannot track '%s' - it is outside " +
24 "the narrow clone") % f)
24 "the narrow clone") % f)
25 return fn(self, *args)
25 return fn(self, *args, **kwargs)
26 return _wrapper
26 return _wrapper
27
27
28 class narrowdirstate(dirstate.__class__):
28 class narrowdirstate(dirstate.__class__):
29 # Prevent adding/editing/copying/deleting files that are outside the
29 # Prevent adding/editing/copying/deleting files that are outside the
30 # sparse checkout
30 # sparse checkout
31 @_editfunc
31 @_editfunc
32 def normal(self, *args):
32 def normal(self, *args, **kwargs):
33 return super(narrowdirstate, self).normal(*args)
33 return super(narrowdirstate, self).normal(*args, **kwargs)
34
34
35 @_editfunc
35 @_editfunc
36 def add(self, *args):
36 def add(self, *args):
@@ -442,7 +442,8 b' def storewrapper(orig, requirements, pat'
442 return s
442 return s
443
443
444 # prefetch files before update
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 if isenabled(repo):
447 if isenabled(repo):
447 manifest = mctx.manifest()
448 manifest = mctx.manifest()
448 files = []
449 files = []
@@ -450,7 +451,8 b' def applyupdates(orig, repo, actions, wc'
450 files.append((f, hex(manifest[f])))
451 files.append((f, hex(manifest[f])))
451 # batch fetch the needed files from the server
452 # batch fetch the needed files from the server
452 repo.fileservice.prefetch(files)
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 # Prefetch merge checkunknownfiles
457 # Prefetch merge checkunknownfiles
456 def checkunknownfiles(orig, repo, wctx, mctx, force, actions,
458 def checkunknownfiles(orig, repo, wctx, mctx, force, actions,
@@ -228,7 +228,7 b' def _setupdirstate(ui):'
228 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
228 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
229 '`hg add -s <file>` to include file directory while adding')
229 '`hg add -s <file>` to include file directory while adding')
230 for func in editfuncs:
230 for func in editfuncs:
231 def _wrapper(orig, self, *args):
231 def _wrapper(orig, self, *args, **kwargs):
232 sparsematch = self._sparsematcher
232 sparsematch = self._sparsematcher
233 if not sparsematch.always():
233 if not sparsematch.always():
234 for f in args:
234 for f in args:
@@ -237,7 +237,7 b' def _setupdirstate(ui):'
237 raise error.Abort(_("cannot add '%s' - it is outside "
237 raise error.Abort(_("cannot add '%s' - it is outside "
238 "the sparse checkout") % f,
238 "the sparse checkout") % f,
239 hint=hint)
239 hint=hint)
240 return orig(self, *args)
240 return orig(self, *args, **kwargs)
241 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
241 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
242
242
243 @command('debugsparse', [
243 @command('debugsparse', [
@@ -1766,6 +1766,8 b' class workingfilectx(committablefilectx)'
1766
1766
1767 def size(self):
1767 def size(self):
1768 return self._repo.wvfs.lstat(self._path).st_size
1768 return self._repo.wvfs.lstat(self._path).st_size
1769 def lstat(self):
1770 return self._repo.wvfs.lstat(self._path)
1769 def date(self):
1771 def date(self):
1770 t, tz = self._changectx.date()
1772 t, tz = self._changectx.date()
1771 try:
1773 try:
@@ -1801,9 +1803,9 b' class workingfilectx(committablefilectx)'
1801
1803
1802 def write(self, data, flags, backgroundclose=False, **kwargs):
1804 def write(self, data, flags, backgroundclose=False, **kwargs):
1803 """wraps repo.wwrite"""
1805 """wraps repo.wwrite"""
1804 self._repo.wwrite(self._path, data, flags,
1806 return self._repo.wwrite(self._path, data, flags,
1805 backgroundclose=backgroundclose,
1807 backgroundclose=backgroundclose,
1806 **kwargs)
1808 **kwargs)
1807
1809
1808 def markcopied(self, src):
1810 def markcopied(self, src):
1809 """marks this file a copy of `src`"""
1811 """marks this file a copy of `src`"""
@@ -391,12 +391,24 b' class dirstate(object):'
391 self._updatedfiles.add(f)
391 self._updatedfiles.add(f)
392 self._map.addfile(f, oldstate, state, mode, size, mtime)
392 self._map.addfile(f, oldstate, state, mode, size, mtime)
393
393
394 def normal(self, f):
394 def normal(self, f, parentfiledata=None):
395 '''Mark a file normal and clean.'''
395 '''Mark a file normal and clean.
396 s = os.lstat(self._join(f))
396
397 mtime = s[stat.ST_MTIME]
397 parentfiledata: (mode, size, mtime) of the clean file
398 self._addpath(f, 'n', s.st_mode,
398
399 s.st_size & _rangemask, mtime & _rangemask)
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 self._map.copymap.pop(f, None)
412 self._map.copymap.pop(f, None)
401 if f in self._map.nonnormalset:
413 if f in self._map.nonnormalset:
402 self._map.nonnormalset.remove(f)
414 self._map.nonnormalset.remove(f)
@@ -10,6 +10,7 b' from __future__ import absolute_import'
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import shutil
12 import shutil
13 import stat
13 import struct
14 import struct
14
15
15 from .i18n import _
16 from .i18n import _
@@ -683,7 +684,7 b' class mergestate(object):'
683 def recordactions(self):
684 def recordactions(self):
684 """record remove/add/get actions in the dirstate"""
685 """record remove/add/get actions in the dirstate"""
685 branchmerge = self._repo.dirstate.p2() != nullid
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 def queueremove(self, f):
689 def queueremove(self, f):
689 """queues a file to be removed from the dirstate
690 """queues a file to be removed from the dirstate
@@ -1464,13 +1465,17 b' def batchremove(repo, wctx, actions):'
1464 repo.ui.warn(_("current directory was removed\n"
1465 repo.ui.warn(_("current directory was removed\n"
1465 "(consider changing to repo root: %s)\n") % repo.root)
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 """apply gets to the working directory
1469 """apply gets to the working directory
1469
1470
1470 mctx is the context to get from
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 verbose = repo.ui.verbose
1479 verbose = repo.ui.verbose
1475 fctx = mctx.filectx
1480 fctx = mctx.filectx
1476 ui = repo.ui
1481 ui = repo.ui
@@ -1494,16 +1499,24 b' def batchget(repo, mctx, wctx, actions):'
1494 if repo.wvfs.lexists(conflicting):
1499 if repo.wvfs.lexists(conflicting):
1495 orig = scmutil.backuppath(ui, repo, conflicting)
1500 orig = scmutil.backuppath(ui, repo, conflicting)
1496 util.rename(repo.wjoin(conflicting), orig)
1501 util.rename(repo.wjoin(conflicting), orig)
1497 wctx[f].clearunknown()
1502 wfctx = wctx[f]
1503 wfctx.clearunknown()
1498 atomictemp = ui.configbool("experimental", "update.atomic-file")
1504 atomictemp = ui.configbool("experimental", "update.atomic-file")
1499 wctx[f].write(fctx(f).data(), flags, backgroundclose=True,
1505 size = wfctx.write(fctx(f).data(), flags,
1500 atomictemp=atomictemp)
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 if i == 100:
1513 if i == 100:
1502 yield i, f
1514 yield False, (i, f)
1503 i = 0
1515 i = 0
1504 i += 1
1516 i += 1
1505 if i > 0:
1517 if i > 0:
1506 yield i, f
1518 yield False, (i, f)
1519 yield True, filedata
1507
1520
1508 def _prefetchfiles(repo, ctx, actions):
1521 def _prefetchfiles(repo, ctx, actions):
1509 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1522 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
@@ -1550,14 +1563,17 b' def emptyactions():'
1550 ACTION_PATH_CONFLICT,
1563 ACTION_PATH_CONFLICT,
1551 ACTION_PATH_CONFLICT_RESOLVE))
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 """apply the merge action list to the working directory
1568 """apply the merge action list to the working directory
1555
1569
1556 wctx is the working copy context
1570 wctx is the working copy context
1557 mctx is the context to be merged into the working copy
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
1573 Return a tuple of (counts, filedata), where counts is a tuple
1560 describes how many files were affected by the update.
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 _prefetchfiles(repo, mctx, actions)
1579 _prefetchfiles(repo, mctx, actions)
@@ -1649,11 +1665,18 b' def applyupdates(repo, actions, wctx, mc'
1649 # get in parallel.
1665 # get in parallel.
1650 threadsafe = repo.ui.configbool('experimental',
1666 threadsafe = repo.ui.configbool('experimental',
1651 'worker.wdir-get-thread-safe')
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 actions[ACTION_GET],
1670 actions[ACTION_GET],
1654 threadsafe=threadsafe)
1671 threadsafe=threadsafe,
1655 for i, item in prog:
1672 hasretval=True)
1656 progress.increment(step=i, item=item)
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 updated = len(actions[ACTION_GET])
1680 updated = len(actions[ACTION_GET])
1658
1681
1659 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
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 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1801 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1779 for k, acts in extraactions.iteritems():
1802 for k, acts in extraactions.iteritems():
1780 actions[k].extend(acts)
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 # Remove these files from actions[ACTION_MERGE] as well. This is
1807 # Remove these files from actions[ACTION_MERGE] as well. This is
1782 # important because in recordupdates, files in actions[ACTION_MERGE]
1808 # important because in recordupdates, files in actions[ACTION_MERGE]
1783 # are processed after files in other actions, and the merge driver
1809 # are processed after files in other actions, and the merge driver
@@ -1800,9 +1826,10 b' def applyupdates(repo, actions, wctx, mc'
1800 if a[0] in mfiles]
1826 if a[0] in mfiles]
1801
1827
1802 progress.complete()
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 "record merge actions to the dirstate"
1833 "record merge actions to the dirstate"
1807 # remove (must come first)
1834 # remove (must come first)
1808 for f, args, msg in actions.get(ACTION_REMOVE, []):
1835 for f, args, msg in actions.get(ACTION_REMOVE, []):
@@ -1846,11 +1873,12 b' def recordupdates(repo, actions, branchm'
1846 pass
1873 pass
1847
1874
1848 # get
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 if branchmerge:
1877 if branchmerge:
1851 repo.dirstate.otherparent(f)
1878 repo.dirstate.otherparent(f)
1852 else:
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 # merge
1883 # merge
1856 for f, args, msg in actions.get(ACTION_MERGE, []):
1884 for f, args, msg in actions.get(ACTION_MERGE, []):
@@ -2166,12 +2194,15 b' def update(repo, node, branchmerge, forc'
2166 'fsmonitor enabled; enable fsmonitor to improve performance; '
2194 'fsmonitor enabled; enable fsmonitor to improve performance; '
2167 'see "hg help -e fsmonitor")\n'))
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 with repo.dirstate.parentchange():
2203 with repo.dirstate.parentchange():
2173 repo.setparents(fp1, fp2)
2204 repo.setparents(fp1, fp2)
2174 recordupdates(repo, actions, branchmerge)
2205 recordupdates(repo, actions, branchmerge, getfiledata)
2175 # update completed, clear state
2206 # update completed, clear state
2176 util.unlink(repo.vfs.join('updatestate'))
2207 util.unlink(repo.vfs.join('updatestate'))
2177
2208
@@ -259,7 +259,7 b' def _writeaddedfiles(repo, pctx, files):'
259 if not repo.wvfs.exists(f):
259 if not repo.wvfs.exists(f):
260 addgaction((f, (mf.flags(f), False), "narrowspec updated"))
260 addgaction((f, (mf.flags(f), False), "narrowspec updated"))
261 merge.applyupdates(repo, actions, wctx=repo[None],
261 merge.applyupdates(repo, actions, wctx=repo[None],
262 mctx=repo['.'], overwrite=False)
262 mctx=repo['.'], overwrite=False, wantfiledata=False)
263
263
264 def checkworkingcopynarrowspec(repo):
264 def checkworkingcopynarrowspec(repo):
265 # Avoid infinite recursion when updating the working copy
265 # Avoid infinite recursion when updating the working copy
@@ -248,7 +248,8 b' def prunetemporaryincludes(repo):'
248
248
249 typeactions = mergemod.emptyactions()
249 typeactions = mergemod.emptyactions()
250 typeactions['r'] = actions
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 # Fix dirstate
254 # Fix dirstate
254 for file in dropped:
255 for file in dropped:
@@ -382,7 +383,7 b' def filterupdatesactions(repo, wctx, mct'
382 typeactions = mergemod.emptyactions()
383 typeactions = mergemod.emptyactions()
383 typeactions['g'] = actions
384 typeactions['g'] = actions
384 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'],
385 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'],
385 False)
386 False, wantfiledata=False)
386
387
387 dirstate = repo.dirstate
388 dirstate = repo.dirstate
388 for file, flags, msg in actions:
389 for file, flags, msg in actions:
@@ -486,7 +487,8 b' def refreshwdir(repo, origstatus, origsp'
486 for f, (m, args, msg) in actions.iteritems():
487 for f, (m, args, msg) in actions.iteritems():
487 typeactions[m].append((f, args, msg))
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 # Fix dirstate
493 # Fix dirstate
492 for file in added:
494 for file in added:
@@ -27,13 +27,11 b" outside of hg's control."
27 > EOF
27 > EOF
28
28
29 Do an update where file 'a' is changed between hg writing it to disk
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
30 and hg writing the dirstate. The dirstate is correct nonetheless, and
31 stores the wrong size, and thus hg status shows spuriously modified
31 so hg status correctly shows a as clean.
32 files.
33
32
34 $ hg up -r 0 --config extensions.race=$TESTTMP/dirstaterace.py
33 $ hg up -r 0 --config extensions.race=$TESTTMP/dirstaterace.py
35 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 $ hg debugdirstate --no-dates
35 $ hg debugdirstate --no-dates
37 n 644 0 (set |unset) a (re)
36 n 644 2 (set |unset) a (re)
38 $ echo a > a; hg status; hg diff
37 $ echo a > a; hg status; hg diff
39 M a
@@ -73,7 +73,7 b' coherent (issue4353)'
73 > merge,
73 > merge,
74 > )
74 > )
75 >
75 >
76 > def wraprecordupdates(orig, repo, actions, branchmerge):
76 > def wraprecordupdates(*args):
77 > raise error.Abort("simulated error while recording dirstateupdates")
77 > raise error.Abort("simulated error while recording dirstateupdates")
78 >
78 >
79 > def reposetup(ui, repo):
79 > def reposetup(ui, repo):
General Comments 0
You need to be logged in to leave comments. Login now