diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -46,7 +46,7 @@ def _getcheckunknownconfig(repo, section return config -def _checkunknownfile(repo, wctx, mctx, f, f2=None): +def _checkunknownfile(repo, dircache, wctx, mctx, f, f2=None): if wctx.isinmemory(): # Nothing to do in IMM because nothing in the "working copy" can be an # unknown file. @@ -58,8 +58,7 @@ def _checkunknownfile(repo, wctx, mctx, if f2 is None: f2 = f return ( - repo.wvfs.audit.check(f) - and repo.wvfs.isfileorlink(f) + repo.wvfs.isfileorlink_checkdir(dircache, f) and repo.dirstate.normalize(f) not in repo.dirstate and mctx[f2].cmp(wctx[f]) ) @@ -136,6 +135,7 @@ def _checkunknownfiles(repo, wctx, mctx, pathconfig = repo.ui.configbool( b'experimental', b'merge.checkpathconflicts' ) + dircache = dict() if not force: def collectconflicts(conflicts, config): @@ -145,19 +145,18 @@ def _checkunknownfiles(repo, wctx, mctx, warnconflicts.update(conflicts) checkunknowndirs = _unknowndirschecker() - with repo.wvfs.audit.cached(): - for f in mresult.files( - ( - mergestatemod.ACTION_CREATED, - mergestatemod.ACTION_DELETED_CHANGED, - ) - ): - if _checkunknownfile(repo, wctx, mctx, f): - fileconflicts.add(f) - elif pathconfig and f not in wctx: - path = checkunknowndirs(repo, wctx, f) - if path is not None: - pathconflicts.add(path) + for f in mresult.files( + ( + mergestatemod.ACTION_CREATED, + mergestatemod.ACTION_DELETED_CHANGED, + ) + ): + if _checkunknownfile(repo, dircache, wctx, mctx, f): + fileconflicts.add(f) + elif pathconfig and f not in wctx: + path = checkunknowndirs(repo, wctx, f) + if path is not None: + pathconflicts.add(path) for f, args, msg in mresult.getactions( [mergestatemod.ACTION_LOCAL_DIR_RENAME_GET] ): diff --git a/mercurial/vfs.py b/mercurial/vfs.py --- a/mercurial/vfs.py +++ b/mercurial/vfs.py @@ -422,6 +422,25 @@ class vfs(abstractvfs): raise error.Abort(b"%s: %r" % (r, path)) self.audit(path, mode=mode) + def isfileorlink_checkdir( + self, dircache, path: Optional[bytes] = None + ) -> bool: + """return True if the path is a regular file or a symlink and + the directories along the path are "normal", that is + not symlinks or nested hg repositories.""" + try: + for prefix in pathutil.finddirs_rev_noroot(util.localpath(path)): + if prefix in dircache: + res = dircache[prefix] + else: + res = self.audit._checkfs_exists(prefix, path) + dircache[prefix] = res + if not res: + return False + except (OSError, error.Abort): + return False + return self.isfileorlink(path) + def __call__( self, path: bytes,