changeset 50928:d718eddf01d9

safehasattr: drop usage in favor of hasattr The two functions should now be equivalent at least in their usage in core.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 31 Aug 2023 23:56:15 +0200
parents 7a8ea1397816
children 18c8c18993f0
files contrib/benchmarks/__init__.py hgext/absorb.py hgext/bugzilla.py hgext/clonebundles.py hgext/commitextras.py hgext/convert/cvsps.py hgext/convert/transport.py hgext/fastannotate/commands.py hgext/fastannotate/context.py hgext/fsmonitor/__init__.py hgext/fsmonitor/watchmanclient.py hgext/journal.py hgext/largefiles/lfutil.py hgext/largefiles/overrides.py hgext/largefiles/storefactory.py hgext/lfs/blobstore.py hgext/lfs/wireprotolfsserver.py hgext/lfs/wrapper.py hgext/mq.py hgext/narrow/narrowbundle2.py hgext/relink.py hgext/remotefilelog/__init__.py hgext/remotefilelog/basestore.py hgext/remotefilelog/connectionpool.py hgext/remotefilelog/fileserverclient.py hgext/remotefilelog/remotefilelogserver.py hgext/remotefilelog/repack.py hgext/remotefilelog/shallowrepo.py mercurial/bundle2.py mercurial/bundlerepo.py mercurial/changegroup.py mercurial/chgserver.py mercurial/cmdutil.py mercurial/commandserver.py mercurial/crecord.py mercurial/debugcommands.py mercurial/dirstatemap.py mercurial/dispatch.py mercurial/extensions.py mercurial/help.py mercurial/hg.py mercurial/hgweb/hgweb_mod.py mercurial/hgweb/server.py mercurial/hgweb/webutil.py mercurial/httppeer.py mercurial/localrepo.py mercurial/manifest.py mercurial/mdiff.py mercurial/patch.py mercurial/pathutil.py mercurial/pvec.py mercurial/registrar.py mercurial/repoview.py mercurial/revlog.py mercurial/revlogutils/debug.py mercurial/revlogutils/deltas.py mercurial/revlogutils/nodemap.py mercurial/rewriteutil.py mercurial/scmutil.py mercurial/shelve.py mercurial/smartset.py mercurial/sslutil.py mercurial/streamclone.py mercurial/strip.py mercurial/subrepo.py mercurial/subrepoutil.py mercurial/templatefilters.py mercurial/templateutil.py mercurial/ui.py mercurial/upgrade_utils/actions.py mercurial/url.py mercurial/util.py mercurial/utils/compression.py mercurial/utils/resourceutil.py mercurial/wireprotov1peer.py mercurial/wireprotov1server.py tests/test-ancestor.py tests/test-demandimport.py tests/test-remotefilelog-bundle2-legacy.t
diffstat 79 files changed, 187 insertions(+), 228 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/benchmarks/__init__.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/contrib/benchmarks/__init__.py	Thu Aug 31 23:56:15 2023 +0200
@@ -40,7 +40,6 @@
     extensions,
     hg,
     ui as uimod,
-    util,
 )
 
 basedir = os.path.abspath(
@@ -66,7 +65,7 @@
     os.environ["HGRCPATH"] = os.environ.get("ASVHGRCPATH", "")
     # for "historical portability"
     # ui.load() has been available since d83ca85
-    if util.safehasattr(uimod.ui, "load"):
+    if hasattr(uimod.ui, "load"):
         ui = uimod.ui.load()
     else:
         ui = uimod.ui()
--- a/hgext/absorb.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/absorb.py	Thu Aug 31 23:56:15 2023 +0200
@@ -873,7 +873,7 @@
         # be slow. in absorb's case, no need to invalidate fsmonitorstate.
         noop = lambda: 0
         restore = noop
-        if util.safehasattr(dirstate, '_fsmonitorstate'):
+        if hasattr(dirstate, '_fsmonitorstate'):
             bak = dirstate._fsmonitorstate.invalidate
 
             def restore():
--- a/hgext/bugzilla.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/bugzilla.py	Thu Aug 31 23:56:15 2023 +0200
@@ -766,13 +766,13 @@
 # inheritance with a new-style class.
 class cookietransport(cookietransportrequest, xmlrpclib.Transport):
     def __init__(self, use_datetime=0):
-        if util.safehasattr(xmlrpclib.Transport, "__init__"):
+        if hasattr(xmlrpclib.Transport, "__init__"):
             xmlrpclib.Transport.__init__(self, use_datetime)
 
 
 class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport):
     def __init__(self, use_datetime=0):
-        if util.safehasattr(xmlrpclib.Transport, "__init__"):
+        if hasattr(xmlrpclib.Transport, "__init__"):
             xmlrpclib.SafeTransport.__init__(self, use_datetime)
 
 
--- a/hgext/clonebundles.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/clonebundles.py	Thu Aug 31 23:56:15 2023 +0200
@@ -987,7 +987,7 @@
         @localrepo.unfilteredmethod
         def clonebundles_lock(self, wait=True):
             '''Lock the repository file related to clone bundles'''
-            if not util.safehasattr(self, '_cb_lock_ref'):
+            if not hasattr(self, '_cb_lock_ref'):
                 self._cb_lock_ref = None
             l = self._currentlock(self._cb_lock_ref)
             if l is not None:
--- a/hgext/commitextras.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/commitextras.py	Thu Aug 31 23:56:15 2023 +0200
@@ -16,7 +16,6 @@
     error,
     extensions,
     registrar,
-    util,
 )
 
 cmdtable = {}
@@ -52,7 +51,7 @@
 
 
 def _commit(orig, ui, repo, *pats, **opts):
-    if util.safehasattr(repo, 'unfiltered'):
+    if hasattr(repo, 'unfiltered'):
         repo = repo.unfiltered()
 
     class repoextra(repo.__class__):
--- a/hgext/convert/cvsps.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/convert/cvsps.py	Thu Aug 31 23:56:15 2023 +0200
@@ -198,9 +198,9 @@
             oldlog = pickle.load(open(cachefile, b'rb'))
             for e in oldlog:
                 if not (
-                    util.safehasattr(e, b'branchpoints')
-                    and util.safehasattr(e, b'commitid')
-                    and util.safehasattr(e, b'mergepoint')
+                    hasattr(e, b'branchpoints')
+                    and hasattr(e, b'commitid')
+                    and hasattr(e, b'mergepoint')
                 ):
                     ui.status(_(b'ignoring old cache\n'))
                     oldlog = []
--- a/hgext/convert/transport.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/convert/transport.py	Thu Aug 31 23:56:15 2023 +0200
@@ -28,7 +28,6 @@
 SubversionException = svn.core.SubversionException
 
 from mercurial.pycompat import getattr
-from mercurial import util
 
 # Some older versions of the Python bindings need to be
 # explicitly initialized. But what we want to do probably
@@ -63,7 +62,7 @@
                 if p:
                     providers.append(p)
     else:
-        if util.safehasattr(svn.client, 'get_windows_simple_provider'):
+        if hasattr(svn.client, 'get_windows_simple_provider'):
             providers.append(svn.client.get_windows_simple_provider(pool))
 
     return svn.core.svn_auth_open(providers, pool)
@@ -85,7 +84,7 @@
         self.password = b''
 
         # Only Subversion 1.4 has reparent()
-        if ra is None or not util.safehasattr(svn.ra, 'reparent'):
+        if ra is None or not hasattr(svn.ra, 'reparent'):
             self.client = svn.client.create_context(self.pool)
             ab = _create_auth_baton(self.pool)
             self.client.auth_baton = ab
--- a/hgext/fastannotate/commands.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/fastannotate/commands.py	Thu Aug 31 23:56:15 2023 +0200
@@ -19,7 +19,6 @@
     pycompat,
     registrar,
     scmutil,
-    util,
 )
 
 from . import (
@@ -218,7 +217,7 @@
     paths = list(_matchpaths(repo, rev, pats, opts, aopts))
 
     # for client, prefetch from the server
-    if util.safehasattr(repo, 'prefetchfastannotate'):
+    if hasattr(repo, 'prefetchfastannotate'):
         repo.prefetchfastannotate(paths)
 
     for path in paths:
@@ -273,7 +272,7 @@
 
     # check if we need to do prefetch (client-side)
     rev = opts.get('rev')
-    if util.safehasattr(repo, 'prefetchfastannotate') and rev is not None:
+    if hasattr(repo, 'prefetchfastannotate') and rev is not None:
         paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts)))
         repo.prefetchfastannotate(paths)
 
@@ -320,7 +319,7 @@
     ctx = logcmdutil.revsingle(repo, rev)
     m = scmutil.match(ctx, pats, opts)
     paths = list(ctx.walk(m))
-    if util.safehasattr(repo, 'prefetchfastannotate'):
+    if hasattr(repo, 'prefetchfastannotate'):
         # client
         if opts.get(b'REV'):
             raise error.Abort(_(b'--rev cannot be used for client'))
--- a/hgext/fastannotate/context.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/fastannotate/context.py	Thu Aug 31 23:56:15 2023 +0200
@@ -324,7 +324,7 @@
                     b'(resolved fctx: %s)\n'
                     % (
                         self.path,
-                        stringutil.pprint(util.safehasattr(revfctx, 'node')),
+                        stringutil.pprint(hasattr(revfctx, 'node')),
                     )
                 )
             return self.annotatedirectly(revfctx, showpath, showlines)
--- a/hgext/fsmonitor/__init__.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/fsmonitor/__init__.py	Thu Aug 31 23:56:15 2023 +0200
@@ -332,7 +332,7 @@
     matchfn = match.matchfn
     matchalways = match.always()
     dmap = self._map
-    if util.safehasattr(dmap, b'_map'):
+    if hasattr(dmap, b'_map'):
         # for better performance, directly access the inner dirstate map if the
         # standard dirstate implementation is in use.
         dmap = dmap._map
@@ -744,7 +744,7 @@
 def wrapdirstate(orig, self):
     ds = orig(self)
     # only override the dirstate when Watchman is available for the repo
-    if util.safehasattr(self, b'_fsmonitorstate'):
+    if hasattr(self, b'_fsmonitorstate'):
         makedirstate(self, ds)
     return ds
 
@@ -811,7 +811,7 @@
             self.oldnode = self.repo[b'.'].node()
 
         if self.repo.currentwlock() is None:
-            if util.safehasattr(self.repo, b'wlocknostateupdate'):
+            if hasattr(self.repo, b'wlocknostateupdate'):
                 self._lock = self.repo.wlocknostateupdate()
             else:
                 self._lock = self.repo.wlock()
@@ -839,7 +839,7 @@
                 self._lock.release()
 
     def _state(self, cmd, commithash, status=b'ok'):
-        if not util.safehasattr(self.repo, b'_watchmanclient'):
+        if not hasattr(self.repo, b'_watchmanclient'):
             return False
         try:
             self.repo._watchmanclient.command(
--- a/hgext/fsmonitor/watchmanclient.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/fsmonitor/watchmanclient.py	Thu Aug 31 23:56:15 2023 +0200
@@ -69,7 +69,7 @@
 
     def getcurrentclock(self):
         result = self.command(b'clock')
-        if not util.safehasattr(result, 'clock'):
+        if not hasattr(result, 'clock'):
             raise Unavailable(
                 b'clock result is missing clock value', invalidate=True
             )
--- a/hgext/journal.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/journal.py	Thu Aug 31 23:56:15 2023 +0200
@@ -103,7 +103,7 @@
 def wrapdirstate(orig, repo):
     """Make journal storage available to the dirstate object"""
     dirstate = orig(repo)
-    if util.safehasattr(repo, 'journal'):
+    if hasattr(repo, 'journal'):
         _setupdirstate(repo, dirstate)
     return dirstate
 
@@ -112,7 +112,7 @@
     """Records all dirstate parent changes in the journal."""
     old = list(old)
     new = list(new)
-    if util.safehasattr(dirstate, 'journalstorage'):
+    if hasattr(dirstate, 'journalstorage'):
         # only record two hashes if there was a merge
         oldhashes = old[:1] if old[1] == dirstate._nodeconstants.nullid else old
         newhashes = new[:1] if new[1] == dirstate._nodeconstants.nullid else new
@@ -125,7 +125,7 @@
 def recordbookmarks(orig, store, fp):
     """Records all bookmark changes in the journal."""
     repo = store._repo
-    if util.safehasattr(repo, 'journal'):
+    if hasattr(repo, 'journal'):
         oldmarks = bookmarks.bmstore(repo)
         all_marks = set(b for b, n in oldmarks.items())
         all_marks.update(b for b, n in store.items())
@@ -185,11 +185,7 @@
 
 def unsharejournal(orig, ui, repo, repopath):
     """Copy shared journal entries into this repo when unsharing"""
-    if (
-        repo.path == repopath
-        and repo.shared()
-        and util.safehasattr(repo, 'journal')
-    ):
+    if repo.path == repopath and repo.shared() and hasattr(repo, 'journal'):
         sharedrepo = hg.sharedreposource(repo)
         sharedfeatures = _readsharedfeatures(repo)
         if sharedrepo and sharedfeatures > {b'journal'}:
--- a/hgext/largefiles/lfutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/largefiles/lfutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -814,7 +814,7 @@
     Otherwise, this returns the function to always write out (or
     ignore if ``not forcibly``) status.
     """
-    if forcibly is None and util.safehasattr(repo, '_largefilesenabled'):
+    if forcibly is None and hasattr(repo, '_largefilesenabled'):
         return repo._lfstatuswriters[-1]
     else:
         if forcibly:
--- a/hgext/largefiles/overrides.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/largefiles/overrides.py	Thu Aug 31 23:56:15 2023 +0200
@@ -1167,7 +1167,7 @@
 
 @eh.wrapcommand(b'rebase', extension=b'rebase')
 def overriderebasecmd(orig, ui, repo, **opts):
-    if not util.safehasattr(repo, '_largefilesenabled'):
+    if not hasattr(repo, '_largefilesenabled'):
         return orig(ui, repo, **opts)
 
     resuming = opts.get('continue')
@@ -1298,7 +1298,7 @@
             # allow only hgsubrepos to set this, instead of the current scheme
             # where the parent sets this for the child.
             with (
-                util.safehasattr(sub, '_repo')
+                hasattr(sub, '_repo')
                 and lfstatus(sub._repo)
                 or util.nullcontextmanager()
             ):
@@ -1309,7 +1309,7 @@
 
 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
-    lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
+    lfenabled = hasattr(repo._repo, '_largefilesenabled')
     if not lfenabled or not repo._repo.lfstatus:
         return orig(repo, archiver, prefix, match, decode)
 
@@ -1364,7 +1364,7 @@
         # would allow only hgsubrepos to set this, instead of the current scheme
         # where the parent sets this for the child.
         with (
-            util.safehasattr(sub, '_repo')
+            hasattr(sub, '_repo')
             and lfstatus(sub._repo)
             or util.nullcontextmanager()
         ):
--- a/hgext/largefiles/storefactory.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/largefiles/storefactory.py	Thu Aug 31 23:56:15 2023 +0200
@@ -57,7 +57,7 @@
 
     # The path could be a scheme so use Mercurial's normal functionality
     # to resolve the scheme to a repository and use its path
-    path = util.safehasattr(remote, 'url') and remote.url() or remote.path
+    path = hasattr(remote, 'url') and remote.url() or remote.path
 
     match = _scheme_re.match(path)
     if not match:  # regular filesystem path
--- a/hgext/lfs/blobstore.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/lfs/blobstore.py	Thu Aug 31 23:56:15 2023 +0200
@@ -271,7 +271,7 @@
     if isinstance(urlerror.reason, Exception):
         inst = urlerror.reason
 
-    if util.safehasattr(inst, 'reason'):
+    if hasattr(inst, 'reason'):
         try:  # usually it is in the form (errno, strerror)
             reason = inst.reason.args[1]
         except (AttributeError, IndexError):
@@ -751,7 +751,7 @@
     if lfsurl is None:
         if remote:
             path = remote
-        elif util.safehasattr(repo, '_subtoppath'):
+        elif hasattr(repo, '_subtoppath'):
             # The pull command sets this during the optional update phase, which
             # tells exactly where the pull originated, whether 'paths.default'
             # or explicit.
--- a/hgext/lfs/wireprotolfsserver.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/lfs/wireprotolfsserver.py	Thu Aug 31 23:56:15 2023 +0200
@@ -16,7 +16,6 @@
 from mercurial import (
     exthelper,
     pycompat,
-    util,
     wireprotoserver,
 )
 
@@ -44,7 +43,7 @@
     if not rctx.repo.ui.configbool(b'experimental', b'lfs.serve'):
         return False
 
-    if not util.safehasattr(rctx.repo.svfs, 'lfslocalblobstore'):
+    if not hasattr(rctx.repo.svfs, 'lfslocalblobstore'):
         return False
 
     if not req.dispatchpath:
--- a/hgext/lfs/wrapper.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/lfs/wrapper.py	Thu Aug 31 23:56:15 2023 +0200
@@ -26,7 +26,6 @@
     localrepo,
     revlog,
     scmutil,
-    util,
     vfs as vfsmod,
     wireprotov1server,
 )
@@ -72,7 +71,7 @@
 def _capabilities(orig, repo, proto):
     '''Wrap server command to announce lfs server capability'''
     caps = orig(repo, proto)
-    if util.safehasattr(repo.svfs, 'lfslocalblobstore'):
+    if hasattr(repo.svfs, 'lfslocalblobstore'):
         # Advertise a slightly different capability when lfs is *required*, so
         # that the client knows it MUST load the extension.  If lfs is not
         # required on the server, there's no reason to autoload the extension
@@ -335,14 +334,14 @@
     # also copy lfs blobstores. note: this can run before reposetup, so lfs
     # blobstore attributes are not always ready at this time.
     for name in ['lfslocalblobstore', 'lfsremoteblobstore']:
-        if util.safehasattr(othervfs, name):
+        if hasattr(othervfs, name):
             setattr(self, name, getattr(othervfs, name))
 
 
 def _prefetchfiles(repo, revmatches):
     """Ensure that required LFS blobs are present, fetching them as a group if
     needed."""
-    if not util.safehasattr(repo.svfs, 'lfslocalblobstore'):
+    if not hasattr(repo.svfs, 'lfslocalblobstore'):
         return
 
     pointers = []
@@ -366,7 +365,7 @@
 
 def _canskipupload(repo):
     # Skip if this hasn't been passed to reposetup()
-    if not util.safehasattr(repo.svfs, 'lfsremoteblobstore'):
+    if not hasattr(repo.svfs, 'lfsremoteblobstore'):
         return True
 
     # if remotestore is a null store, upload is a no-op and can be skipped
@@ -375,7 +374,7 @@
 
 def candownload(repo):
     # Skip if this hasn't been passed to reposetup()
-    if not util.safehasattr(repo.svfs, 'lfsremoteblobstore'):
+    if not hasattr(repo.svfs, 'lfsremoteblobstore'):
         return False
 
     # if remotestore is a null store, downloads will lead to nothing
@@ -524,7 +523,7 @@
     orig(ui, srcrepo, dstrepo, requirements)
 
     # Skip if this hasn't been passed to reposetup()
-    if util.safehasattr(srcrepo.svfs, 'lfslocalblobstore') and util.safehasattr(
+    if hasattr(srcrepo.svfs, 'lfslocalblobstore') and hasattr(
         dstrepo.svfs, 'lfslocalblobstore'
     ):
         srclfsvfs = srcrepo.svfs.lfslocalblobstore.vfs
--- a/hgext/mq.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/mq.py	Thu Aug 31 23:56:15 2023 +0200
@@ -4186,7 +4186,7 @@
 
 
 def mqimport(orig, ui, repo, *args, **kwargs):
-    if util.safehasattr(repo, 'abortifwdirpatched') and not kwargs.get(
+    if hasattr(repo, 'abortifwdirpatched') and not kwargs.get(
         'no_commit', False
     ):
         repo.abortifwdirpatched(
--- a/hgext/narrow/narrowbundle2.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/narrow/narrowbundle2.py	Thu Aug 31 23:56:15 2023 +0200
@@ -259,7 +259,7 @@
     # will currently always be there when using the core+narrowhg server, but
     # other servers may include a changespec part even when not widening (e.g.
     # because we're deepening a shallow repo).
-    if util.safehasattr(repo, 'setnewnarrowpats'):
+    if hasattr(repo, 'setnewnarrowpats'):
         op.gettransaction()
         repo.setnewnarrowpats()
 
@@ -333,9 +333,9 @@
 
     def wrappedcghandler(op, inpart):
         origcghandler(op, inpart)
-        if util.safehasattr(op, '_widen_bundle'):
+        if hasattr(op, '_widen_bundle'):
             handlechangegroup_widen(op, inpart)
-        if util.safehasattr(op, '_bookmarksbackup'):
+        if hasattr(op, '_bookmarksbackup'):
             localrepo.localrepository._bookmarks.set(
                 op.repo, op._bookmarksbackup
             )
--- a/hgext/relink.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/relink.py	Thu Aug 31 23:56:15 2023 +0200
@@ -60,9 +60,7 @@
     command is running. (Both repositories will be locked against
     writes.)
     """
-    if not util.safehasattr(util, 'samefile') or not util.safehasattr(
-        util, 'samedevice'
-    ):
+    if not hasattr(util, 'samefile') or not hasattr(util, 'samedevice'):
         raise error.Abort(_(b'hardlinks are not supported on this system'))
 
     if origin is None and b'default-relink' in ui.paths:
--- a/hgext/remotefilelog/__init__.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/remotefilelog/__init__.py	Thu Aug 31 23:56:15 2023 +0200
@@ -425,7 +425,7 @@
     finally:
         if opts.get('shallow'):
             for r in repos:
-                if util.safehasattr(r, 'fileservice'):
+                if hasattr(r, 'fileservice'):
                     r.fileservice.close()
 
 
@@ -904,7 +904,7 @@
         if not isenabled(repo):
             continue
 
-        if not util.safehasattr(repo, 'name'):
+        if not hasattr(repo, 'name'):
             ui.warn(
                 _(b"repo %s is a misconfigured remotefilelog repo\n") % path
             )
@@ -1034,7 +1034,7 @@
     bgprefetchrevs = revdatelimit(ui, bgprefetchrevs)
 
     def anon(unused_success):
-        if util.safehasattr(repo, 'ranprefetch') and repo.ranprefetch:
+        if hasattr(repo, 'ranprefetch') and repo.ranprefetch:
             return
         repo.ranprefetch = True
         repo.backgroundprefetch(bgprefetchrevs, repack=bgrepack)
@@ -1080,9 +1080,9 @@
             source, heads=heads, common=common, bundlecaps=bundlecaps, **kwargs
         )
 
-    if util.safehasattr(remote, '_callstream'):
+    if hasattr(remote, '_callstream'):
         remote._localrepo = repo
-    elif util.safehasattr(remote, 'getbundle'):
+    elif hasattr(remote, 'getbundle'):
         extensions.wrapfunction(remote, 'getbundle', localgetbundle)
 
     return orig(repo, remote, *args, **kwargs)
--- a/hgext/remotefilelog/basestore.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/remotefilelog/basestore.py	Thu Aug 31 23:56:15 2023 +0200
@@ -415,7 +415,7 @@
 
     def markforrefresh(self):
         for store in self.stores:
-            if util.safehasattr(store, b'markforrefresh'):
+            if hasattr(store, b'markforrefresh'):
                 store.markforrefresh()
 
     @staticmethod
--- a/hgext/remotefilelog/connectionpool.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/remotefilelog/connectionpool.py	Thu Aug 31 23:56:15 2023 +0200
@@ -9,7 +9,6 @@
 from mercurial import (
     hg,
     sshpeer,
-    util,
 )
 
 _sshv1peer = sshpeer.sshv1peer
@@ -41,14 +40,14 @@
         if conn is None:
 
             peer = hg.peer(self._repo.ui, {}, path)
-            if util.safehasattr(peer, '_cleanup'):
+            if hasattr(peer, '_cleanup'):
 
                 class mypeer(peer.__class__):
                     def _cleanup(self, warn=None):
                         # close pipee first so peer.cleanup reading it won't
                         # deadlock, if there are other processes with pipeo
                         # open (i.e. us).
-                        if util.safehasattr(self, 'pipee'):
+                        if hasattr(self, 'pipee'):
                             self.pipee.close()
                         return super(mypeer, self)._cleanup()
 
@@ -83,5 +82,5 @@
             self.close()
 
     def close(self):
-        if util.safehasattr(self.peer, 'cleanup'):
+        if hasattr(self.peer, 'cleanup'):
             self.peer.cleanup()
--- a/hgext/remotefilelog/fileserverclient.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/remotefilelog/fileserverclient.py	Thu Aug 31 23:56:15 2023 +0200
@@ -92,7 +92,7 @@
                 not in self.capabilities()
             ):
                 return
-            if not util.safehasattr(self, '_localrepo'):
+            if not hasattr(self, '_localrepo'):
                 return
             if (
                 constants.SHALLOWREPO_REQUIREMENT
@@ -132,7 +132,7 @@
 
         def _callstream(self, command, **opts):
             supertype = super(remotefilepeer, self)
-            if not util.safehasattr(supertype, '_sendrequest'):
+            if not hasattr(supertype, '_sendrequest'):
                 self._updatecallstreamopts(command, pycompat.byteskwargs(opts))
             return super(remotefilepeer, self)._callstream(command, **opts)
 
@@ -641,9 +641,7 @@
             self._lfsprefetch(fileids)
 
     def _lfsprefetch(self, fileids):
-        if not _lfsmod or not util.safehasattr(
-            self.repo.svfs, b'lfslocalblobstore'
-        ):
+        if not _lfsmod or not hasattr(self.repo.svfs, b'lfslocalblobstore'):
             return
         if not _lfsmod.wrapper.candownload(self.repo):
             return
--- a/hgext/remotefilelog/remotefilelogserver.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/remotefilelog/remotefilelogserver.py	Thu Aug 31 23:56:15 2023 +0200
@@ -228,7 +228,7 @@
         # When generating file blobs, taking the real path is too slow on large
         # repos, so force it to just return the linkrev directly.
         repo = self._repo
-        if util.safehasattr(repo, 'forcelinkrev') and repo.forcelinkrev:
+        if hasattr(repo, 'forcelinkrev') and repo.forcelinkrev:
             return self._filelog.linkrev(self._filelog.rev(self._filenode))
         return orig(self, *args, **kwargs)
 
--- a/hgext/remotefilelog/repack.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/remotefilelog/repack.py	Thu Aug 31 23:56:15 2023 +0200
@@ -49,7 +49,7 @@
 
 def fullrepack(repo, options=None):
     """If ``packsonly`` is True, stores creating only loose objects are skipped."""
-    if util.safehasattr(repo, 'shareddatastores'):
+    if hasattr(repo, 'shareddatastores'):
         datasource = contentstore.unioncontentstore(*repo.shareddatastores)
         historysource = metadatastore.unionmetadatastore(
             *repo.sharedhistorystores, allowincomplete=True
@@ -67,7 +67,7 @@
             options=options,
         )
 
-    if util.safehasattr(repo.manifestlog, 'datastore'):
+    if hasattr(repo.manifestlog, 'datastore'):
         localdata, shareddata = _getmanifeststores(repo)
         lpackpath, ldstores, lhstores = localdata
         spackpath, sdstores, shstores = shareddata
@@ -107,7 +107,7 @@
     """This repacks the repo by looking at the distribution of pack files in the
     repo and performing the most minimal repack to keep the repo in good shape.
     """
-    if util.safehasattr(repo, 'shareddatastores'):
+    if hasattr(repo, 'shareddatastores'):
         packpath = shallowutil.getcachepackpath(
             repo, constants.FILEPACK_CATEGORY
         )
@@ -120,7 +120,7 @@
             options=options,
         )
 
-    if util.safehasattr(repo.manifestlog, 'datastore'):
+    if hasattr(repo.manifestlog, 'datastore'):
         localdata, shareddata = _getmanifeststores(repo)
         lpackpath, ldstores, lhstores = localdata
         spackpath, sdstores, shstores = shareddata
@@ -895,7 +895,7 @@
 
 
 def repacklockvfs(repo):
-    if util.safehasattr(repo, 'name'):
+    if hasattr(repo, 'name'):
         # Lock in the shared cache so repacks across multiple copies of the same
         # repo are coordinated.
         sharedcachepath = shallowutil.getcachepackpath(
--- a/hgext/remotefilelog/shallowrepo.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/hgext/remotefilelog/shallowrepo.py	Thu Aug 31 23:56:15 2023 +0200
@@ -340,7 +340,7 @@
     repo.excludepattern = repo.ui.configlist(
         b"remotefilelog", b"excludepattern", None
     )
-    if not util.safehasattr(repo, 'connectionpool'):
+    if not hasattr(repo, 'connectionpool'):
         repo.connectionpool = connectionpool.connectionpool(repo)
 
     if repo.includepattern or repo.excludepattern:
--- a/mercurial/bundle2.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/bundle2.py	Thu Aug 31 23:56:15 2023 +0200
@@ -980,7 +980,7 @@
 
     def close(self):
         """close underlying file"""
-        if util.safehasattr(self._fp, 'close'):
+        if hasattr(self._fp, 'close'):
             return self._fp.close()
 
 
@@ -1068,7 +1068,7 @@
 
         The new part have the very same content but no partid assigned yet.
         Parts with generated data cannot be copied."""
-        assert not util.safehasattr(self.data, 'next')
+        assert not hasattr(self.data, 'next')
         return self.__class__(
             self.type,
             self._mandatoryparams,
@@ -1137,9 +1137,7 @@
                 msg.append(b')')
             if not self.data:
                 msg.append(b' empty payload')
-            elif util.safehasattr(self.data, 'next') or util.safehasattr(
-                self.data, '__next__'
-            ):
+            elif hasattr(self.data, 'next') or hasattr(self.data, '__next__'):
                 msg.append(b' streamed payload')
             else:
                 msg.append(b' %i bytes payload' % len(self.data))
@@ -1233,9 +1231,7 @@
         Exists to handle the different methods to provide data to a part."""
         # we only support fixed size data now.
         # This will be improved in the future.
-        if util.safehasattr(self.data, 'next') or util.safehasattr(
-            self.data, '__next__'
-        ):
+        if hasattr(self.data, 'next') or hasattr(self.data, '__next__'):
             buff = util.chunkbuffer(self.data)
             chunk = buff.read(preferedchunksize)
             while chunk:
@@ -1380,9 +1376,7 @@
 
     def __init__(self, ui, header, fp):
         super(unbundlepart, self).__init__(fp)
-        self._seekable = util.safehasattr(fp, 'seek') and util.safehasattr(
-            fp, 'tell'
-        )
+        self._seekable = hasattr(fp, 'seek') and hasattr(fp, 'tell')
         self.ui = ui
         # unbundle state attr
         self._headerdata = header
--- a/mercurial/bundlerepo.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/bundlerepo.py	Thu Aug 31 23:56:15 2023 +0200
@@ -245,7 +245,7 @@
 class bundlephasecache(phases.phasecache):
     def __init__(self, *args, **kwargs):
         super(bundlephasecache, self).__init__(*args, **kwargs)
-        if util.safehasattr(self, 'opener'):
+        if hasattr(self, 'opener'):
             self.opener = vfsmod.readonlyvfs(self.opener)
 
     def write(self):
--- a/mercurial/changegroup.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/changegroup.py	Thu Aug 31 23:56:15 2023 +0200
@@ -1043,7 +1043,7 @@
                         return i
                 # We failed to resolve a parent for this node, so
                 # we crash the changegroup construction.
-                if util.safehasattr(store, 'target'):
+                if hasattr(store, 'target'):
                     target = store.display_id
                 else:
                     # some revlog not actually a revlog
--- a/mercurial/chgserver.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/chgserver.py	Thu Aug 31 23:56:15 2023 +0200
@@ -236,7 +236,7 @@
             # will behave differently (i.e. write to stdout).
             if (
                 out is not self.fout
-                or not util.safehasattr(self.fout, 'fileno')
+                or not hasattr(self.fout, 'fileno')
                 or self.fout.fileno() != procutil.stdout.fileno()
                 or self._finoutredirected
             ):
@@ -262,7 +262,7 @@
     newui = srcui.__class__.load()
     for a in ['fin', 'fout', 'ferr', 'environ']:
         setattr(newui, a, getattr(srcui, a))
-    if util.safehasattr(srcui, '_csystem'):
+    if hasattr(srcui, '_csystem'):
         newui._csystem = srcui._csystem
 
     # command line args
@@ -603,7 +603,7 @@
         }
     )
 
-    if util.safehasattr(procutil, 'setprocname'):
+    if hasattr(procutil, 'setprocname'):
 
         def setprocname(self):
             """Change process title"""
--- a/mercurial/cmdutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/cmdutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -1449,7 +1449,7 @@
         if returnrevlog:
             if isinstance(r, revlog.revlog):
                 pass
-            elif util.safehasattr(r, '_revlog'):
+            elif hasattr(r, '_revlog'):
                 r = r._revlog  # pytype: disable=attribute-error
             elif r is not None:
                 raise error.InputError(
--- a/mercurial/commandserver.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/commandserver.py	Thu Aug 31 23:56:15 2023 +0200
@@ -332,7 +332,7 @@
             # any kind of interaction must use server channels, but chg may
             # replace channels by fully functional tty files. so nontty is
             # enforced only if cin is a channel.
-            if not util.safehasattr(self.cin, 'fileno'):
+            if not hasattr(self.cin, 'fileno'):
                 ui.setconfig(b'ui', b'nontty', b'true', b'commandserver')
 
         req = dispatch.request(
@@ -384,7 +384,7 @@
         if self.cmsg:
             hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding
         hellomsg += b'pid: %d' % procutil.getpid()
-        if util.safehasattr(os, 'getpgid'):
+        if hasattr(os, 'getpgid'):
             hellomsg += b'\n'
             hellomsg += b'pgid: %d' % os.getpgid(0)
 
@@ -559,7 +559,7 @@
         self.ui = ui
         self.repo = repo
         self.address = opts[b'address']
-        if not util.safehasattr(socket, 'AF_UNIX'):
+        if not hasattr(socket, 'AF_UNIX'):
             raise error.Abort(_(b'unsupported platform'))
         if not self.address:
             raise error.Abort(_(b'no socket path specified with --address'))
@@ -588,7 +588,7 @@
         o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
         self._mainipc, self._workeripc = o
         self._servicehandler.bindsocket(self._sock, self.address)
-        if util.safehasattr(procutil, 'unblocksignal'):
+        if hasattr(procutil, 'unblocksignal'):
             procutil.unblocksignal(signal.SIGCHLD)
         o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
         self._oldsigchldhandler = o
--- a/mercurial/crecord.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/crecord.py	Thu Aug 31 23:56:15 2023 +0200
@@ -573,7 +573,7 @@
     ui.write(_(b'starting interactive selection\n'))
     chunkselector = curseschunkselector(headerlist, ui, operation)
     origsigtstp = sentinel = object()
-    if util.safehasattr(signal, 'SIGTSTP'):
+    if hasattr(signal, 'SIGTSTP'):
         origsigtstp = signal.getsignal(signal.SIGTSTP)
     try:
         with util.with_lc_ctype():
@@ -1944,7 +1944,7 @@
         """
 
         origsigwinch = sentinel = object()
-        if util.safehasattr(signal, 'SIGWINCH'):
+        if hasattr(signal, 'SIGWINCH'):
             origsigwinch = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
         try:
             return self._main(stdscr)
--- a/mercurial/debugcommands.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/debugcommands.py	Thu Aug 31 23:56:15 2023 +0200
@@ -1282,7 +1282,7 @@
     if opts.get(b'old'):
 
         def doit(pushedrevs, remoteheads, remote=remote):
-            if not util.safehasattr(remote, 'branches'):
+            if not hasattr(remote, 'branches'):
                 # enable in-client legacy support
                 remote = localrepo.locallegacypeer(remote.local())
                 if remote_revs:
@@ -1482,7 +1482,7 @@
         isinternal = extensions.ismoduleinternal(extmod)
         extsource = None
 
-        if util.safehasattr(extmod, '__file__'):
+        if hasattr(extmod, '__file__'):
             extsource = pycompat.fsencode(extmod.__file__)
         elif getattr(sys, 'oxidized', False):
             extsource = pycompat.sysexecutable
@@ -1722,7 +1722,7 @@
     if fm.isplain():
 
         def formatvalue(value):
-            if util.safehasattr(value, 'startswith'):
+            if hasattr(value, 'startswith'):
                 return value
             if value:
                 return b'yes'
@@ -1947,7 +1947,7 @@
     """show stats related to the changelog index"""
     repo.changelog.shortest(repo.nullid, 1)
     index = repo.changelog.index
-    if not util.safehasattr(index, 'stats'):
+    if not hasattr(index, 'stats'):
         raise error.Abort(_(b'debugindexstats only works with native code'))
     for k, v in sorted(index.stats().items()):
         ui.write(b'%s: %d\n' % (k, v))
@@ -1983,7 +1983,7 @@
 
     # Python
     pythonlib = None
-    if util.safehasattr(os, '__file__'):
+    if hasattr(os, '__file__'):
         pythonlib = os.path.dirname(pycompat.fsencode(os.__file__))
     elif getattr(sys, 'oxidized', False):
         pythonlib = pycompat.sysexecutable
@@ -2065,7 +2065,7 @@
 
     # compiled modules
     hgmodules = None
-    if util.safehasattr(sys.modules[__name__], '__file__'):
+    if hasattr(sys.modules[__name__], '__file__'):
         hgmodules = os.path.dirname(pycompat.fsencode(__file__))
     elif getattr(sys, 'oxidized', False):
         hgmodules = pycompat.sysexecutable
@@ -2649,7 +2649,7 @@
     if isinstance(r, (manifest.manifestrevlog, filelog.filelog)):
         r = r._revlog
     if opts['dump_new']:
-        if util.safehasattr(r.index, "nodemap_data_all"):
+        if hasattr(r.index, "nodemap_data_all"):
             data = r.index.nodemap_data_all()
         else:
             data = nodemap.persistent_data(r.index)
--- a/mercurial/dirstatemap.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/dirstatemap.py	Thu Aug 31 23:56:15 2023 +0200
@@ -377,7 +377,7 @@
             return
 
         # TODO: adjust this estimate for dirstate-v2
-        if util.safehasattr(parsers, 'dict_new_presized'):
+        if hasattr(parsers, 'dict_new_presized'):
             # Make an estimate of the number of files in the dirstate based on
             # its size. This trades wasting some memory for avoiding costly
             # resizes. Each entry have a prefix of 17 bytes followed by one or
--- a/mercurial/dispatch.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/dispatch.py	Thu Aug 31 23:56:15 2023 +0200
@@ -107,7 +107,7 @@
 def _flushstdio(ui, err):
     status = None
     # In all cases we try to flush stdio streams.
-    if util.safehasattr(ui, 'fout'):
+    if hasattr(ui, 'fout'):
         assert ui is not None  # help pytype
         assert ui.fout is not None  # help pytype
         try:
@@ -116,7 +116,7 @@
             err = e
             status = -1
 
-    if util.safehasattr(ui, 'ferr'):
+    if hasattr(ui, 'ferr'):
         assert ui is not None  # help pytype
         assert ui.ferr is not None  # help pytype
         try:
@@ -170,7 +170,7 @@
             "newline": "\n",
             "line_buffering": sys.stdout.line_buffering,
         }
-        if util.safehasattr(sys.stdout, "write_through"):
+        if hasattr(sys.stdout, "write_through"):
             # pytype: disable=attribute-error
             kwargs["write_through"] = sys.stdout.write_through
             # pytype: enable=attribute-error
@@ -183,7 +183,7 @@
             "newline": "\n",
             "line_buffering": sys.stderr.line_buffering,
         }
-        if util.safehasattr(sys.stderr, "write_through"):
+        if hasattr(sys.stderr, "write_through"):
             # pytype: disable=attribute-error
             kwargs["write_through"] = sys.stderr.write_through
             # pytype: enable=attribute-error
@@ -520,7 +520,7 @@
 def aliasargs(fn, givenargs):
     args = []
     # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
-    if not util.safehasattr(fn, '_origfunc'):
+    if not hasattr(fn, '_origfunc'):
         args = getattr(fn, 'args', args)
     if args:
         cmd = b' '.join(map(procutil.shellquote, args))
@@ -708,7 +708,7 @@
         }
         if name not in adefaults:
             raise AttributeError(name)
-        if self.badalias or util.safehasattr(self, 'shell'):
+        if self.badalias or hasattr(self, 'shell'):
             return adefaults[name]
         return getattr(self.fn, name)
 
@@ -734,7 +734,7 @@
             self.name,
             self.definition,
         )
-        if util.safehasattr(self, 'shell'):
+        if hasattr(self, 'shell'):
             return self.fn(ui, *args, **opts)
         else:
             try:
@@ -1024,7 +1024,7 @@
     cmd = aliases[0]
     fn = entry[0]
 
-    if cmd and util.safehasattr(fn, 'shell'):
+    if cmd and hasattr(fn, 'shell'):
         # shell alias shouldn't receive early options which are consumed by hg
         _earlyopts, args = _earlysplitopts(args)
         d = lambda: fn(ui, *args[1:])
--- a/mercurial/extensions.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/extensions.py	Thu Aug 31 23:56:15 2023 +0200
@@ -172,7 +172,7 @@
     """Check if extension commands have required attributes"""
     for c, e in cmdtable.items():
         f = e[0]
-        missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
+        missing = [a for a in _cmdfuncattrs if not hasattr(f, a)]
         if not missing:
             continue
         msg = b'missing attributes: %s'
@@ -742,7 +742,7 @@
 
     # The hgext might not have a __file__ attribute (e.g. in PyOxidizer) and
     # it might not be on a filesystem even if it does.
-    if util.safehasattr(hgext, '__file__'):
+    if hasattr(hgext, '__file__'):
         extpath = os.path.dirname(
             util.abspath(pycompat.fsencode(hgext.__file__))
         )
@@ -857,7 +857,7 @@
 
             # The extensions are filesystem based, so either an error occurred
             # or all are enabled.
-            if util.safehasattr(hgext, '__file__'):
+            if hasattr(hgext, '__file__'):
                 return
 
             if name in _order:  # enabled
@@ -987,13 +987,13 @@
 
 def moduleversion(module):
     '''return version information from given module as a string'''
-    if util.safehasattr(module, 'getversion') and callable(module.getversion):
+    if hasattr(module, 'getversion') and callable(module.getversion):
         try:
             version = module.getversion()
         except Exception:
             version = b'unknown'
 
-    elif util.safehasattr(module, '__version__'):
+    elif hasattr(module, '__version__'):
         version = module.__version__
     else:
         version = b''
--- a/mercurial/help.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/help.py	Thu Aug 31 23:56:15 2023 +0200
@@ -43,7 +43,6 @@
     templatefuncs,
     templatekw,
     ui as uimod,
-    util,
 )
 from .hgweb import webcommands
 from .utils import (
@@ -810,7 +809,7 @@
         doc = gettext(pycompat.getdoc(entry[0]))
         if not doc:
             doc = _(b"(no help text available)")
-        if util.safehasattr(entry[0], 'definition'):  # aliased command
+        if hasattr(entry[0], 'definition'):  # aliased command
             source = entry[0].source
             if entry[0].definition.startswith(b'!'):  # shell alias
                 doc = _(b'shell alias for: %s\n\n%s\n\ndefined by: %s\n') % (
--- a/mercurial/hg.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/hg.py	Thu Aug 31 23:56:15 2023 +0200
@@ -66,7 +66,7 @@
 
 
 def addbranchrevs(lrepo, other, branches, revs, remotehidden=False):
-    if util.safehasattr(other, 'peer'):
+    if hasattr(other, 'peer'):
         # a courtesy to callers using a localrepo for other
         peer = other.peer(remotehidden=remotehidden)
     else:
@@ -174,7 +174,7 @@
             cls.instance  # make sure we load the module
         else:
             cls = LocalFactory
-        if util.safehasattr(cls, 'islocal'):
+        if hasattr(cls, 'islocal'):
             return cls.islocal(repo)  # pytype: disable=module-attr
         return False
     repo.ui.deprecwarn(b"use obj.local() instead of islocal(obj)", b"6.4")
@@ -254,7 +254,7 @@
     '''return a repository peer for the specified path'''
     ui = getattr(uiorrepo, 'ui', uiorrepo)
     rui = remoteui(uiorrepo, opts)
-    if util.safehasattr(path, 'url'):
+    if hasattr(path, 'url'):
         # this is already a urlutil.path object
         peer_path = path
     else:
@@ -317,7 +317,7 @@
     if repo.sharedpath == repo.path:
         return None
 
-    if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
+    if hasattr(repo, 'srcrepo') and repo.srcrepo:
         return repo.srcrepo
 
     # the sharedpath always ends in the .hg; we want the path to the repo
@@ -340,7 +340,7 @@
     '''create a shared repository'''
 
     not_local_msg = _(b'can only share local repositories')
-    if util.safehasattr(source, 'local'):
+    if hasattr(source, 'local'):
         if source.local() is None:
             raise error.Abort(not_local_msg)
     elif not islocal(source):
@@ -729,7 +729,7 @@
             branches = (src_path.branch, branch or [])
             source = src_path.loc
     else:
-        if util.safehasattr(source, 'peer'):
+        if hasattr(source, 'peer'):
             srcpeer = source.peer()  # in case we were called with a localrepo
         else:
             srcpeer = source
@@ -1567,7 +1567,7 @@
 
 def remoteui(src, opts):
     """build a remote ui from ui or repo and opts"""
-    if util.safehasattr(src, 'baseui'):  # looks like a repository
+    if hasattr(src, 'baseui'):  # looks like a repository
         dst = src.baseui.copy()  # drop repo-specific config
         src = src.ui  # copy target options from repo
     else:  # assume it's a global ui object
--- a/mercurial/hgweb/hgweb_mod.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Thu Aug 31 23:56:15 2023 +0200
@@ -34,7 +34,6 @@
     templater,
     templateutil,
     ui as uimod,
-    util,
     wireprotoserver,
 )
 
@@ -403,7 +402,7 @@
                 cmd = cmd[style + 1 :]
 
             # avoid accepting e.g. style parameter as command
-            if util.safehasattr(webcommands, pycompat.sysstr(cmd)):
+            if hasattr(webcommands, pycompat.sysstr(cmd)):
                 req.qsparams[b'cmd'] = cmd
 
             if cmd == b'static':
@@ -478,7 +477,7 @@
 
         except (error.LookupError, error.RepoLookupError) as err:
             msg = pycompat.bytestr(err)
-            if util.safehasattr(err, 'name') and not isinstance(
+            if hasattr(err, 'name') and not isinstance(
                 err, error.ManifestLookupError
             ):
                 msg = b'revision not found: %s' % err.name
--- a/mercurial/hgweb/server.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/hgweb/server.py	Thu Aug 31 23:56:15 2023 +0200
@@ -100,7 +100,7 @@
 
     def log_request(self, code='-', size='-'):
         xheaders = []
-        if util.safehasattr(self, 'headers'):
+        if hasattr(self, 'headers'):
             xheaders = [
                 h for h in self.headers.items() if h[0].startswith('x-')
             ]
@@ -214,7 +214,7 @@
         env['wsgi.multithread'] = isinstance(
             self.server, socketserver.ThreadingMixIn
         )
-        if util.safehasattr(socketserver, 'ForkingMixIn'):
+        if hasattr(socketserver, 'ForkingMixIn'):
             env['wsgi.multiprocess'] = isinstance(
                 self.server, socketserver.ForkingMixIn
             )
@@ -344,7 +344,7 @@
     threading.active_count()  # silence pyflakes and bypass demandimport
     _mixin = socketserver.ThreadingMixIn
 except ImportError:
-    if util.safehasattr(os, "fork"):
+    if hasattr(os, "fork"):
         _mixin = socketserver.ForkingMixIn
     else:
 
--- a/mercurial/hgweb/webutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/hgweb/webutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -211,7 +211,7 @@
             b'description': s.description(),
             b'branch': s.branch(),
         }
-        if util.safehasattr(s, 'path'):
+        if hasattr(s, 'path'):
             d[b'file'] = s.path()
         yield d
 
--- a/mercurial/httppeer.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/httppeer.py	Thu Aug 31 23:56:15 2023 +0200
@@ -65,7 +65,7 @@
 class _multifile:
     def __init__(self, *fileobjs):
         for f in fileobjs:
-            if not util.safehasattr(f, 'length'):
+            if not hasattr(f, 'length'):
                 raise ValueError(
                     b'_multifile only supports file objects that '
                     b'have a length but this one does not:',
@@ -180,7 +180,7 @@
     qs = b'?%s' % urlreq.urlencode(q)
     cu = b"%s%s" % (repobaseurl, qs)
     size = 0
-    if util.safehasattr(data, 'length'):
+    if hasattr(data, 'length'):
         size = data.length
     elif data is not None:
         size = len(data)
--- a/mercurial/localrepo.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/localrepo.py	Thu Aug 31 23:56:15 2023 +0200
@@ -420,7 +420,7 @@
             try:
                 bundle = exchange.readbundle(self.ui, bundle, None)
                 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
-                if util.safehasattr(ret, 'getchunks'):
+                if hasattr(ret, 'getchunks'):
                     # This is a bundle20 object, turn it into an unbundler.
                     # This little dance should be dropped eventually when the
                     # API is finally improved.
@@ -1461,7 +1461,7 @@
         if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
             b'devel', b'check-locks'
         ):
-            if util.safehasattr(self.svfs, 'vfs'):  # this is filtervfs
+            if hasattr(self.svfs, 'vfs'):  # this is filtervfs
                 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
             else:  # standard vfs
                 self.svfs.audit = self._getsvfsward(self.svfs.audit)
@@ -1523,8 +1523,8 @@
             repo = rref()
             if (
                 repo is None
-                or not util.safehasattr(repo, '_wlockref')
-                or not util.safehasattr(repo, '_lockref')
+                or not hasattr(repo, '_wlockref')
+                or not hasattr(repo, '_lockref')
             ):
                 return
             if mode in (None, b'r', b'rb'):
@@ -1572,7 +1572,7 @@
         def checksvfs(path, mode=None):
             ret = origfunc(path, mode=mode)
             repo = rref()
-            if repo is None or not util.safehasattr(repo, '_lockref'):
+            if repo is None or not hasattr(repo, '_lockref'):
                 return
             if mode in (None, b'r', b'rb'):
                 return
--- a/mercurial/manifest.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/manifest.py	Thu Aug 31 23:56:15 2023 +0200
@@ -1628,7 +1628,7 @@
 
     def _setupmanifestcachehooks(self, repo):
         """Persist the manifestfulltextcache on lock release"""
-        if not util.safehasattr(repo, '_wlockref'):
+        if not hasattr(repo, '_wlockref'):
             return
 
         self._fulltextcache._opener = repo.wcachevfs
--- a/mercurial/mdiff.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/mdiff.py	Thu Aug 31 23:56:15 2023 +0200
@@ -211,11 +211,7 @@
 
 
 def chooseblocksfunc(opts=None):
-    if (
-        opts is None
-        or not opts.xdiff
-        or not util.safehasattr(bdiff, 'xdiffblocks')
-    ):
+    if opts is None or not opts.xdiff or not hasattr(bdiff, 'xdiffblocks'):
         return bdiff.blocks
     else:
         return bdiff.xdiffblocks
--- a/mercurial/patch.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/patch.py	Thu Aug 31 23:56:15 2023 +0200
@@ -168,7 +168,7 @@
 
     mimeheaders = [b'content-type']
 
-    if not util.safehasattr(stream, 'next'):
+    if not hasattr(stream, 'next'):
         # http responses, for example, have readline but not next
         stream = fiter(stream)
 
@@ -1703,7 +1703,7 @@
 
     newhunks = []
     for c in hunks:
-        if util.safehasattr(c, 'reversehunk'):
+        if hasattr(c, 'reversehunk'):
             c = c.reversehunk()
         newhunks.append(c)
     return newhunks
--- a/mercurial/pathutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/pathutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -377,7 +377,7 @@
         return d in self._dirs
 
 
-if util.safehasattr(parsers, 'dirs'):
+if hasattr(parsers, 'dirs'):
     dirs = parsers.dirs
 
 if rustdirs is not None:
--- a/mercurial/pvec.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/pvec.py	Thu Aug 31 23:56:15 2023 +0200
@@ -159,7 +159,7 @@
 def ctxpvec(ctx):
     '''construct a pvec for ctx while filling in the cache'''
     r = ctx.repo()
-    if not util.safehasattr(r, "_pveccache"):
+    if not hasattr(r, "_pveccache"):
         r._pveccache = {}
     pvc = r._pveccache
     if ctx.rev() not in pvc:
--- a/mercurial/registrar.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/registrar.py	Thu Aug 31 23:56:15 2023 +0200
@@ -10,7 +10,6 @@
     configitems,
     error,
     pycompat,
-    util,
 )
 
 # unlike the other registered items, config options are neither functions or
@@ -64,7 +63,7 @@
             msg = b'duplicate registration for name: "%s"' % name
             raise error.ProgrammingError(msg)
 
-        if func.__doc__ and not util.safehasattr(func, '_origdoc'):
+        if func.__doc__ and not hasattr(func, '_origdoc'):
             func._origdoc = func.__doc__.strip()
             doc = pycompat.sysbytes(func._origdoc)
             func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
--- a/mercurial/repoview.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/repoview.py	Thu Aug 31 23:56:15 2023 +0200
@@ -296,13 +296,12 @@
         This returns a version of 'revs' to be used thereafter by the caller.
         In particular, if revs is an iterator, it is converted into a set.
         """
-        safehasattr = util.safehasattr
-        if safehasattr(revs, '__next__'):
+        if hasattr(revs, '__next__'):
             # Note that inspect.isgenerator() is not true for iterators,
             revs = set(revs)
 
         filteredrevs = self.filteredrevs
-        if safehasattr(revs, 'first'):  # smartset
+        if hasattr(revs, 'first'):  # smartset
             offenders = revs & filteredrevs
         else:
             offenders = filteredrevs.intersection(revs)
--- a/mercurial/revlog.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/revlog.py	Thu Aug 31 23:56:15 2023 +0200
@@ -167,7 +167,7 @@
 # We also consider we have a "fast" implementation in "pure" python because
 # people using pure don't really have performance consideration (and a
 # wheelbarrow of other slowness source)
-HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
+HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or hasattr(
     parsers, 'BaseIndexObject'
 )
 
@@ -214,7 +214,7 @@
     return index, cache
 
 
-if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
+if hasattr(parsers, 'parse_index_devel_nodemap'):
 
     def parse_index_v1_nodemap(data, inline):
         index, cache = parsers.parse_index_devel_nodemap(data, inline)
@@ -730,7 +730,7 @@
             use_nodemap = (
                 not self._inline
                 and self._nodemap_file is not None
-                and util.safehasattr(index, 'update_nodemap_data')
+                and hasattr(index, 'update_nodemap_data')
             )
             if use_nodemap:
                 nodemap_data = nodemaputil.persisted_data(self)
@@ -911,7 +911,7 @@
         use_nodemap = (
             not self._inline
             and self._nodemap_file is not None
-            and util.safehasattr(self.index, 'update_nodemap_data')
+            and hasattr(self.index, 'update_nodemap_data')
         )
         if use_nodemap:
             nodemap_data = nodemaputil.persisted_data(self)
@@ -1887,7 +1887,7 @@
         """tells whether rev is a snapshot"""
         if not self._sparserevlog:
             return self.deltaparent(rev) == nullrev
-        elif util.safehasattr(self.index, 'issnapshot'):
+        elif hasattr(self.index, 'issnapshot'):
             # directly assign the method to cache the testing and access
             self.issnapshot = self.index.issnapshot
             return self.issnapshot(rev)
--- a/mercurial/revlogutils/debug.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/revlogutils/debug.py	Thu Aug 31 23:56:15 2023 +0200
@@ -13,7 +13,6 @@
     mdiff,
     node as nodemod,
     revlogutils,
-    util,
 )
 
 from . import (
@@ -409,7 +408,7 @@
                     numother_nad += 1
 
         # Obtain data on the raw chunks in the revlog.
-        if util.safehasattr(r, '_getsegmentforrevs'):
+        if hasattr(r, '_getsegmentforrevs'):
             segment = r._getsegmentforrevs(rev, rev)[1]
         else:
             segment = r._revlog._getsegmentforrevs(rev, rev)[1]
--- a/mercurial/revlogutils/deltas.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/revlogutils/deltas.py	Thu Aug 31 23:56:15 2023 +0200
@@ -1060,7 +1060,7 @@
             end_rev < self._start_rev or end_rev > self._end_rev
         ), (self._start_rev, self._end_rev, start_rev, end_rev)
         cache = self.snapshots
-        if util.safehasattr(revlog.index, 'findsnapshots'):
+        if hasattr(revlog.index, 'findsnapshots'):
             revlog.index.findsnapshots(cache, start_rev, end_rev)
         else:
             deltaparent = revlog.deltaparent
--- a/mercurial/revlogutils/nodemap.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/revlogutils/nodemap.py	Thu Aug 31 23:56:15 2023 +0200
@@ -174,9 +174,9 @@
             msg = "calling persist nodemap on a revlog without the feature enabled"
             raise error.ProgrammingError(msg)
 
-    can_incremental = util.safehasattr(revlog.index, "nodemap_data_incremental")
+    can_incremental = hasattr(revlog.index, "nodemap_data_incremental")
     ondisk_docket = revlog._nodemap_docket
-    feed_data = util.safehasattr(revlog.index, "update_nodemap_data")
+    feed_data = hasattr(revlog.index, "update_nodemap_data")
     use_mmap = revlog.opener.options.get(b"persistent-nodemap.mmap")
 
     data = None
@@ -216,7 +216,7 @@
         # otherwise fallback to a full new export
         target_docket = NodeMapDocket()
         datafile = _rawdata_filepath(revlog, target_docket)
-        if util.safehasattr(revlog.index, "nodemap_data_all"):
+        if hasattr(revlog.index, "nodemap_data_all"):
             data = revlog.index.nodemap_data_all()
         else:
             data = persistent_data(revlog.index)
--- a/mercurial/rewriteutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/rewriteutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -21,7 +21,6 @@
     obsutil,
     revset,
     scmutil,
-    util,
 )
 
 
@@ -77,7 +76,7 @@
         hint = _(b"no changeset checked out")
         raise error.InputError(msg, hint=hint)
 
-    if any(util.safehasattr(r, 'rev') for r in revs):
+    if any(hasattr(r, 'rev') for r in revs):
         repo.ui.develwarn(b"rewriteutil.precheck called with ctx not revs")
         revs = (r.rev() for r in revs)
 
--- a/mercurial/scmutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/scmutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -233,11 +233,7 @@
             reason = encoding.unitolocal(reason)
         ui.error(_(b"abort: error: %s\n") % stringutil.forcebytestr(reason))
     except (IOError, OSError) as inst:
-        if (
-            util.safehasattr(inst, "args")
-            and inst.args
-            and inst.args[0] == errno.EPIPE
-        ):
+        if hasattr(inst, "args") and inst.args and inst.args[0] == errno.EPIPE:
             pass
         elif getattr(inst, "strerror", None):  # common IOError or OSError
             if getattr(inst, "filename", None) is not None:
@@ -561,11 +557,11 @@
             if cache is not None:
                 nodetree = cache.get(b'disambiguationnodetree')
             if not nodetree:
-                if util.safehasattr(parsers, 'nodetree'):
+                if hasattr(parsers, 'nodetree'):
                     # The CExt is the only implementation to provide a nodetree
                     # class so far.
                     index = cl.index
-                    if util.safehasattr(index, 'get_cindex'):
+                    if hasattr(index, 'get_cindex'):
                         # the rust wrapped need to give access to its internal index
                         index = index.get_cindex()
                     nodetree = parsers.nodetree(index, len(revs))
@@ -1066,7 +1062,7 @@
         return
 
     # translate mapping's other forms
-    if not util.safehasattr(replacements, 'items'):
+    if not hasattr(replacements, 'items'):
         replacements = {(n,): () for n in replacements}
     else:
         # upgrading non tuple "source" to tuple ones for BC
--- a/mercurial/shelve.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/shelve.py	Thu Aug 31 23:56:15 2023 +0200
@@ -516,7 +516,7 @@
 
 def getcommitfunc(extra, interactive, editor=False):
     def commitfunc(ui, repo, message, match, opts):
-        hasmq = util.safehasattr(repo, 'mq')
+        hasmq = hasattr(repo, 'mq')
         if hasmq:
             saved, repo.mq.checkapplied = repo.mq.checkapplied, False
 
--- a/mercurial/smartset.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/smartset.py	Thu Aug 31 23:56:15 2023 +0200
@@ -137,7 +137,7 @@
 
         This is part of the mandatory API for smartset."""
         # builtin cannot be cached. but do not needs to
-        if cache and util.safehasattr(condition, '__code__'):
+        if cache and hasattr(condition, '__code__'):
             condition = util.cachefunc(condition)
         return filteredset(self, condition, condrepr)
 
@@ -1127,7 +1127,7 @@
         This boldly assumes the other contains valid revs only.
         """
         # other not a smartset, make is so
-        if not util.safehasattr(other, 'isascending'):
+        if not hasattr(other, 'isascending'):
             # filter out hidden revision
             # (this boldly assumes all smartset are pure)
             #
--- a/mercurial/sslutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/sslutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -50,11 +50,11 @@
 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
 # support. At the mentioned commit, they were unconditionally defined.
 supportedprotocols = set()
-if getattr(ssl, 'HAS_TLSv1', util.safehasattr(ssl, 'PROTOCOL_TLSv1')):
+if getattr(ssl, 'HAS_TLSv1', hasattr(ssl, 'PROTOCOL_TLSv1')):
     supportedprotocols.add(b'tls1.0')
-if getattr(ssl, 'HAS_TLSv1_1', util.safehasattr(ssl, 'PROTOCOL_TLSv1_1')):
+if getattr(ssl, 'HAS_TLSv1_1', hasattr(ssl, 'PROTOCOL_TLSv1_1')):
     supportedprotocols.add(b'tls1.1')
-if getattr(ssl, 'HAS_TLSv1_2', util.safehasattr(ssl, 'PROTOCOL_TLSv1_2')):
+if getattr(ssl, 'HAS_TLSv1_2', hasattr(ssl, 'PROTOCOL_TLSv1_2')):
     supportedprotocols.add(b'tls1.2')
 
 
@@ -312,7 +312,7 @@
     # is loaded and contains that removed CA, you've just undone the user's
     # choice.
 
-    if util.safehasattr(ssl, 'TLSVersion'):
+    if hasattr(ssl, 'TLSVersion'):
         # python 3.7+
         sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
         minimumprotocol = settings[b'minimumprotocol']
@@ -419,7 +419,7 @@
             pass
 
         # Try to print more helpful error messages for known failures.
-        if util.safehasattr(e, 'reason'):
+        if hasattr(e, 'reason'):
             # This error occurs when the client and server don't share a
             # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
             # outright. Hopefully the reason for this error is that we require
@@ -546,7 +546,7 @@
                 _(b'referenced certificate file (%s) does not exist') % f
             )
 
-    if util.safehasattr(ssl, 'TLSVersion'):
+    if hasattr(ssl, 'TLSVersion'):
         # python 3.7+
         sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
         sslcontext.options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
@@ -628,7 +628,7 @@
     # Otherwise, use the list of more secure ciphers if found in the ssl module.
     if exactprotocol:
         sslcontext.set_ciphers('DEFAULT:@SECLEVEL=0')
-    elif util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
+    elif hasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
         sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
         # pytype: disable=module-attr
         sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
--- a/mercurial/streamclone.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/streamclone.py	Thu Aug 31 23:56:15 2023 +0200
@@ -428,7 +428,7 @@
             with repo.svfs.backgroundclosing(repo.ui, expectedcount=filecount):
                 for i in range(filecount):
                     # XXX doesn't support '\n' or '\r' in filenames
-                    if util.safehasattr(fp, 'readline'):
+                    if hasattr(fp, 'readline'):
                         l = fp.readline()
                     else:
                         # inline clonebundles use a chunkbuffer, so no readline
--- a/mercurial/strip.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/strip.py	Thu Aug 31 23:56:15 2023 +0200
@@ -12,7 +12,6 @@
     registrar,
     repair,
     scmutil,
-    util,
 )
 
 release = lockmod.release
@@ -36,7 +35,7 @@
     currentbranch = repo[None].branch()
 
     if (
-        util.safehasattr(repo, 'mq')
+        hasattr(repo, 'mq')
         and p2 != repo.nullid
         and p2 in [x.node for x in repo.mq.applied]
     ):
--- a/mercurial/subrepo.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/subrepo.py	Thu Aug 31 23:56:15 2023 +0200
@@ -1136,7 +1136,7 @@
             # --non-interactive.
             if commands[0] in (b'update', b'checkout', b'commit'):
                 cmd.append(b'--non-interactive')
-        if util.safehasattr(subprocess, 'CREATE_NO_WINDOW'):
+        if hasattr(subprocess, 'CREATE_NO_WINDOW'):
             # On Windows, prevent command prompts windows from popping up when
             # running in pythonw.
             extrakw['creationflags'] = getattr(subprocess, 'CREATE_NO_WINDOW')
@@ -1511,7 +1511,7 @@
             # the end of git diff arguments is used for paths
             commands.insert(1, b'--color')
         extrakw = {}
-        if util.safehasattr(subprocess, 'CREATE_NO_WINDOW'):
+        if hasattr(subprocess, 'CREATE_NO_WINDOW'):
             # On Windows, prevent command prompts windows from popping up when
             # running in pythonw.
             extrakw['creationflags'] = getattr(subprocess, 'CREATE_NO_WINDOW')
--- a/mercurial/subrepoutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/subrepoutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -384,7 +384,7 @@
     Either absolute or relative the outermost repo"""
     parent = repo
     chunks = []
-    while util.safehasattr(parent, '_subparent'):
+    while hasattr(parent, '_subparent'):
         source = urlutil.url(parent._subsource)
         chunks.append(bytes(source))
         if source.isabs():
@@ -400,7 +400,7 @@
     # type: (localrepo.localrepository) -> bytes
     """return path to this (sub)repo as seen from outermost repo"""
     parent = repo
-    while util.safehasattr(parent, '_subparent'):
+    while hasattr(parent, '_subparent'):
         parent = parent._subparent
     return repo.root[len(pathutil.normasprefix(parent.root)) :]
 
@@ -415,7 +415,7 @@
     # type: (localrepo.localrepository, bool, bool) -> Optional[bytes]
     """return pull/push path of repo - either based on parent repo .hgsub info
     or on the top repo config. Abort or return None if no source found."""
-    if util.safehasattr(repo, '_subparent'):
+    if hasattr(repo, '_subparent'):
         source = urlutil.url(repo._subsource)
         if source.isabs():
             return bytes(source)
@@ -428,7 +428,7 @@
             return bytes(parent)
     else:  # recursion reached top repo
         path = None
-        if util.safehasattr(repo, '_subtoppath'):
+        if hasattr(repo, '_subtoppath'):
             path = repo._subtoppath
         elif push and repo.ui.config(b'paths', b'default-push'):
             path = repo.ui.config(b'paths', b'default-push')
--- a/mercurial/templatefilters.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/templatefilters.py	Thu Aug 31 23:56:15 2023 +0200
@@ -339,14 +339,14 @@
         raise error.ProgrammingError(
             b'Mercurial only does output with bytes: %r' % obj
         )
-    elif util.safehasattr(obj, 'keys'):
+    elif hasattr(obj, 'keys'):
         out = [
             b'"%s": %s'
             % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
             for k, v in sorted(obj.items())
         ]
         return b'{' + b', '.join(out) + b'}'
-    elif util.safehasattr(obj, '__iter__'):
+    elif hasattr(obj, '__iter__'):
         out = [json(i, paranoid) for i in obj]
         return b'[' + b', '.join(out) + b']'
     raise error.ProgrammingError(b'cannot encode %r' % obj)
--- a/mercurial/templateutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/templateutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -281,7 +281,7 @@
 
     def getmember(self, context, mapping, key):
         # TODO: maybe split hybrid list/dict types?
-        if not util.safehasattr(self._values, 'get'):
+        if not hasattr(self._values, 'get'):
             raise error.ParseError(_(b'not a dictionary'))
         key = unwrapastype(context, mapping, key, self._keytype)
         return self._wrapvalue(key, self._values.get(key))
@@ -301,13 +301,13 @@
     def _wrapvalue(self, key, val):
         if val is None:
             return
-        if util.safehasattr(val, '_makemap'):
+        if hasattr(val, '_makemap'):
             # a nested hybrid list/dict, which has its own way of map operation
             return val
         return hybriditem(None, key, val, self._makemap)
 
     def filter(self, context, mapping, select):
-        if util.safehasattr(self._values, 'get'):
+        if hasattr(self._values, 'get'):
             values = {
                 k: v
                 for k, v in self._values.items()
@@ -341,7 +341,7 @@
     def tovalue(self, context, mapping):
         # TODO: make it non-recursive for trivial lists/dicts
         xs = self._values
-        if util.safehasattr(xs, 'get'):
+        if hasattr(xs, 'get'):
             return {k: unwrapvalue(context, mapping, v) for k, v in xs.items()}
         return [unwrapvalue(context, mapping, x) for x in xs]
 
@@ -858,7 +858,7 @@
         )
     elif thing is None:
         pass
-    elif not util.safehasattr(thing, '__iter__'):
+    elif not hasattr(thing, '__iter__'):
         yield pycompat.bytestr(thing)
     else:
         for i in thing:
@@ -868,7 +868,7 @@
                 yield i
             elif i is None:
                 pass
-            elif not util.safehasattr(i, '__iter__'):
+            elif not hasattr(i, '__iter__'):
                 yield pycompat.bytestr(i)
             else:
                 for j in flatten(context, mapping, i):
--- a/mercurial/ui.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/ui.py	Thu Aug 31 23:56:15 2023 +0200
@@ -1467,7 +1467,7 @@
         self.flush()
 
         wasformatted = self.formatted()
-        if util.safehasattr(signal, "SIGPIPE"):
+        if hasattr(signal, "SIGPIPE"):
             signal.signal(signal.SIGPIPE, _catchterm)
         if self._runpager(pagercmd, pagerenv):
             self.pageractive = True
@@ -1547,7 +1547,7 @@
 
         @self.atexit
         def killpager():
-            if util.safehasattr(signal, "SIGINT"):
+            if hasattr(signal, "SIGINT"):
                 signal.signal(signal.SIGINT, signal.SIG_IGN)
             # restore original fds, closing pager.stdin copies in the process
             os.dup2(stdoutfd, procutil.stdout.fileno())
--- a/mercurial/upgrade_utils/actions.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/upgrade_utils/actions.py	Thu Aug 31 23:56:15 2023 +0200
@@ -671,7 +671,7 @@
     newactions = []
 
     for d in format_upgrades:
-        if util.safehasattr(d, '_requirement'):
+        if hasattr(d, '_requirement'):
             name = d._requirement
         else:
             name = None
--- a/mercurial/url.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/url.py	Thu Aug 31 23:56:15 2023 +0200
@@ -190,7 +190,7 @@
     return _sendfile
 
 
-has_https = util.safehasattr(urlreq, 'httpshandler')
+has_https = hasattr(urlreq, 'httpshandler')
 
 
 class httpconnection(keepalive.HTTPConnection):
--- a/mercurial/util.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/util.py	Thu Aug 31 23:56:15 2023 +0200
@@ -2583,7 +2583,7 @@
             self._fp.close()
 
     def __del__(self):
-        if safehasattr(self, '_fp'):  # constructor actually did something
+        if hasattr(self, '_fp'):  # constructor actually did something
             self.discard()
 
     def __enter__(self):
--- a/mercurial/utils/compression.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/utils/compression.py	Thu Aug 31 23:56:15 2023 +0200
@@ -16,8 +16,6 @@
 )
 from . import stringutil
 
-safehasattr = pycompat.safehasattr
-
 
 _ = i18n._
 
@@ -340,7 +338,7 @@
 
 class _CompressedStreamReader:
     def __init__(self, fh):
-        if safehasattr(fh, 'unbufferedread'):
+        if hasattr(fh, 'unbufferedread'):
             self._reader = fh.unbufferedread
         else:
             self._reader = fh.read
--- a/mercurial/utils/resourceutil.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/utils/resourceutil.py	Thu Aug 31 23:56:15 2023 +0200
@@ -22,8 +22,8 @@
     (portable, not much used).
     """
     return (
-        pycompat.safehasattr(sys, "frozen")  # new py2exe
-        or pycompat.safehasattr(sys, "importers")  # old py2exe
+        hasattr(sys, "frozen")  # new py2exe
+        or hasattr(sys, "importers")  # old py2exe
         or _imp.is_frozen("__main__")  # tools/freeze
     )
 
@@ -59,7 +59,7 @@
     from importlib import resources  # pytype: disable=import-error
 
     # Force loading of the resources module
-    if pycompat.safehasattr(resources, 'files'):
+    if hasattr(resources, 'files'):
         resources.files  # pytype: disable=module-attr
     else:
         resources.open_binary  # pytype: disable=module-attr
@@ -95,7 +95,7 @@
     from .. import encoding
 
     def open_resource(package, name):
-        if pycompat.safehasattr(resources, 'files'):
+        if hasattr(resources, 'files'):
             return (
                 resources.files(  # pytype: disable=module-attr
                     pycompat.sysstr(package)
--- a/mercurial/wireprotov1peer.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/wireprotov1peer.py	Thu Aug 31 23:56:15 2023 +0200
@@ -499,7 +499,7 @@
         else:
             heads = wireprototypes.encodelist(heads)
 
-        if util.safehasattr(bundle, 'deltaheader'):
+        if hasattr(bundle, 'deltaheader'):
             # this a bundle10, do the old style call sequence
             ret, output = self._callpush(b"unbundle", bundle, heads=heads)
             if ret == b"":
--- a/mercurial/wireprotov1server.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/mercurial/wireprotov1server.py	Thu Aug 31 23:56:15 2023 +0200
@@ -721,7 +721,7 @@
                 r = exchange.unbundle(
                     repo, gen, their_heads, b'serve', proto.client()
                 )
-                if util.safehasattr(r, 'addpart'):
+                if hasattr(r, 'addpart'):
                     # The return looks streamable, we are in the bundle2 case
                     # and should return a stream.
                     return wireprototypes.streamreslegacy(gen=r.getchunks())
--- a/tests/test-ancestor.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/tests/test-ancestor.py	Thu Aug 31 23:56:15 2023 +0200
@@ -12,7 +12,6 @@
     debugcommands,
     hg,
     ui as uimod,
-    util,
 )
 
 
@@ -416,7 +415,7 @@
     for i, (dag, tests) in enumerate(dagtests):
         repo = hg.repository(u, b'gca%d' % i, create=1)
         cl = repo.changelog
-        if not util.safehasattr(cl.index, 'ancestors'):
+        if not hasattr(cl.index, 'ancestors'):
             # C version not available
             return
 
--- a/tests/test-demandimport.py	Thu Dec 08 15:33:19 2022 +0100
+++ b/tests/test-demandimport.py	Thu Aug 31 23:56:15 2023 +0200
@@ -179,15 +179,13 @@
         'cannot import name unknownattr'
     )
 
-from mercurial import util
-
 # Unlike the import statement, __import__() function should not raise
 # ImportError even if fromlist has an unknown item
 # (see Python/import.c:import_module_level() and ensure_fromlist())
 assert 'ftplib' not in sys.modules
 zipfileimp = __import__('ftplib', globals(), locals(), ['unknownattr'])
 assert f(zipfileimp) == "<module 'ftplib' from '?'>", f(zipfileimp)
-assert not util.safehasattr(zipfileimp, 'unknownattr')
+assert not hasattr(zipfileimp, 'unknownattr')
 
 
 # test deactivation for issue6725
--- a/tests/test-remotefilelog-bundle2-legacy.t	Thu Dec 08 15:33:19 2022 +0100
+++ b/tests/test-remotefilelog-bundle2-legacy.t	Thu Aug 31 23:56:15 2023 +0200
@@ -11,7 +11,7 @@
   > command = registrar.command(cmdtable)
   > @command('testcg2', norepo=True)
   > def testcg2(ui):
-  >     if not util.safehasattr(changegroup, 'cg2packer'):
+  >     if not hasattr(changegroup, 'cg2packer'):
   >         sys.exit(80)
   > EOF
   $ cat >> $HGRCPATH << EOF