comparison mercurial/scmutil.py @ 51802:95cdc01f313d

sparse: reliably avoid writing to store without a lock With the code as written before this patch we can still end up writing to store in `debugsparse`. Obviously we'll write to it if by accident a store requirement is modified, but more importantly we write to it if another concurrent transaction modifies the requirements file on disk. We can't rule this out since we're not holding the store lock, so it's better to explicitly pass a permission to write instead of inferring it based on file contents.
author Arseniy Alekseyev <aalekseyev@janestreet.com>
date Fri, 16 Aug 2024 11:12:19 +0100
parents e69e3d585f07
children f4733654f144
comparison
equal deleted inserted replaced
51801:0d7ccb163b4f 51802:95cdc01f313d
1666 def istreemanifest(repo) -> bool: 1666 def istreemanifest(repo) -> bool:
1667 """returns whether the repository is using treemanifest or not""" 1667 """returns whether the repository is using treemanifest or not"""
1668 return requirementsmod.TREEMANIFEST_REQUIREMENT in repo.requirements 1668 return requirementsmod.TREEMANIFEST_REQUIREMENT in repo.requirements
1669 1669
1670 1670
1671 def writereporequirements(repo, requirements=None) -> None: 1671 def writereporequirements(repo, requirements=None, maywritestore=True) -> None:
1672 """writes requirements for the repo 1672 """writes requirements for the repo
1673 1673
1674 Requirements are written to .hg/requires and .hg/store/requires based 1674 Requirements are written to .hg/requires and .hg/store/requires based
1675 on whether share-safe mode is enabled and which requirements are wdir 1675 on whether share-safe mode is enabled and which requirements are wdir
1676 requirements and which are store requirements 1676 requirements and which are store requirements
1679 repo.requirements = requirements 1679 repo.requirements = requirements
1680 wcreq, storereq = filterrequirements(repo.requirements) 1680 wcreq, storereq = filterrequirements(repo.requirements)
1681 if wcreq is not None: 1681 if wcreq is not None:
1682 writerequires(repo.vfs, wcreq) 1682 writerequires(repo.vfs, wcreq)
1683 if storereq is not None: 1683 if storereq is not None:
1684 writerequires(repo.svfs, storereq) 1684 writerequires(repo.svfs, storereq, maywrite=maywritestore)
1685 elif repo.ui.configbool(b'format', b'usestore'): 1685 elif repo.ui.configbool(b'format', b'usestore'):
1686 # only remove store requires if we are using store 1686 # only remove store requires if we are using store
1687 repo.svfs.tryunlink(b'requires') 1687 if maywritestore:
1688 repo.svfs.tryunlink(b'requires')
1688 1689
1689 1690
1690 def readrequires(vfs, allowmissing): 1691 def readrequires(vfs, allowmissing):
1691 """reads the require file present at root of this vfs 1692 """reads the require file present at root of this vfs
1692 and return a set of requirements 1693 and return a set of requirements
1699 # a missing file translates to no requirements. 1700 # a missing file translates to no requirements.
1700 read = vfs.tryread if allowmissing else vfs.read 1701 read = vfs.tryread if allowmissing else vfs.read
1701 return set(read(b'requires').splitlines()) 1702 return set(read(b'requires').splitlines())
1702 1703
1703 1704
1704 def writerequires(opener, requirements) -> None: 1705 def writerequires(opener, requirements, maywrite=True) -> None:
1705 on_disk = readrequires(opener, True) 1706 on_disk = readrequires(opener, True)
1706 if not (on_disk == set(requirements)): 1707 if not (on_disk == set(requirements)):
1708 if not maywrite:
1709 raise error.Abort(_(b"store requirements are not as expected"))
1707 with opener(b'requires', b'w', atomictemp=True) as fp: 1710 with opener(b'requires', b'w', atomictemp=True) as fp:
1708 for r in sorted(requirements): 1711 for r in sorted(requirements):
1709 fp.write(b"%s\n" % r) 1712 fp.write(b"%s\n" % r)
1710 1713
1711 1714