changeset 7015:6ffc4d7635fa

topic: patch localrepo._makedirstate() to avoid touching global dirstate This should avoid incompatibility with other extension altering the dirstate. There are 2 cases here when we patch dirstate class on the fly: in repo._makedirstate() and in peer.local(). The first one is the "normal" way of making a dirstate for a repo and covers almost all cases, and the second one is special. In some cases during clone our dirstate is not correctly handled, so we need this band-aid solution just for clones on Mercurial 6.9 and older. See also 0f2268783c11 in core.
author Anton Shestakov <av6@dwimlabs.net>
date Sat, 16 Nov 2024 18:15:11 +0400
parents 42c10cc2ef03
children cc80c5c793a7
files hgext3rd/topic/__init__.py
diffstat 1 files changed, 80 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/hgext3rd/topic/__init__.py	Fri Jan 31 10:51:43 2025 +0100
+++ b/hgext3rd/topic/__init__.py	Sat Nov 16 18:15:11 2024 +0400
@@ -644,6 +644,63 @@
         repo.ui.setconfig(b'rebase', b'experimental.inmemory', b'False',
                           source=b'topic-extension')
 
+    class _topicdirstate:
+        @dirstate.repocache(b'topic')
+        def _topic(self):
+            try:
+                return self._opener.read(b'topic').strip() or b''
+            except IOError as inst:
+                if inst.errno != errno.ENOENT:
+                    raise
+                return b''
+
+        def topic(self):
+            return encoding.tolocal(self._topic)
+
+        def settopic(self, topic, tr):
+            self.__class__._topic.set(self, encoding.fromlocal(topic))
+            del topic  # safeguard to not use it after adjusting encoding
+            vfs = self._opener
+            if self._topic != b'':
+                with vfs(b'topic', b'w', atomictemp=True, checkambig=True) as f:
+                    f.write(self._topic + b'\n')
+            else:
+                vfs.tryunlink(b'topic')
+            ce = self._filecache[b'_topic']
+            if ce:
+                ce.refresh()
+
+        @dirstate.repocache(b'topic-namespace')
+        def _tns(self):
+            try:
+                return self._opener.read(b'topic-namespace').strip() or b'none'
+            except IOError as inst:
+                if inst.errno != errno.ENOENT:
+                    raise
+                return b'none'
+
+        def tns(self):
+            return encoding.tolocal(self._tns)
+
+        def settns(self, tns, tr):
+            self.__class__._tns.set(self, encoding.fromlocal(tns))
+            del tns  # safeguard to not use it after adjusting encoding
+            vfs = self._opener
+            if self._tns != b'none':
+                with vfs(b'topic-namespace', b'w', atomictemp=True, checkambig=True) as f:
+                    f.write(self._tns + b'\n')
+            else:
+                vfs.tryunlink(b'topic-namespace')
+            ce = self._filecache[b'_tns']
+            if ce:
+                ce.refresh()
+
+        def fqbn(self, length=common.FQBN_NORMAL):
+            branch = encoding.tolocal(self._branch)
+            tns = encoding.tolocal(self._tns)
+            topic = encoding.tolocal(self._topic)
+            return common.formatfqbn(branch, tns, topic, length=length)
+
     class topicrepo(repo.__class__):
 
         # attribute for other code to distinct between repo with topic and repo without
@@ -721,6 +778,15 @@
                         raise
             return wlock
 
+        def _makedirstate(self):
+            dirstate = super(topicrepo, self)._makedirstate()
+
+            class topicdirstate(dirstate.__class__, _topicdirstate):
+                pass
+
+            dirstate.__class__ = topicdirstate
+            return dirstate
+
         @property
         def currenttns(self):
             return self.dirstate.tns()
@@ -819,6 +885,20 @@
                                 if tns == b'none' or tns in namespaces:
                                     h.extend(nodes)
                             return h
+
+                    def local(self):
+                        repo = super(topicpeer, self).local()
+                        # work around wild assignement of dirstate during
+                        # copy-clone prior to hg <= 6.9
+                        # hg <= 6.9 (0f2268783c11)
+                        dirstate = vars(repo.unfiltered()).get('dirstate')
+                        if dirstate is not None and not isinstance(dirstate, _topicdirstate):
+                            class topicdirstate(dirstate.__class__, _topicdirstate):
+                                pass
+
+                            dirstate.__class__ = topicdirstate
+                        return repo
+
                 peer.__class__ = topicpeer
             return peer
 
@@ -924,65 +1004,6 @@
             b'topics', b'topic', namemap=_namemap, nodemap=_nodemap,
             listnames=lambda repo: repo.topics))
 
-    class topicdirstate(dirstate.dirstate):
-        @dirstate.repocache(b'topic')
-        def _topic(self):
-            try:
-                return self._opener.read(b'topic').strip() or b''
-            except IOError as inst:
-                if inst.errno != errno.ENOENT:
-                    raise
-                return b''
-
-        def topic(self):
-            return encoding.tolocal(self._topic)
-
-        def settopic(self, topic, tr):
-            self.__class__._topic.set(self, encoding.fromlocal(topic))
-            del topic  # safeguard to not use it after adjusting encoding
-            vfs = self._opener
-            if self._topic != b'':
-                with vfs(b'topic', b'w', atomictemp=True, checkambig=True) as f:
-                    f.write(self._topic + b'\n')
-            else:
-                vfs.tryunlink(b'topic')
-            ce = self._filecache[b'_topic']
-            if ce:
-                ce.refresh()
-
-        @dirstate.repocache(b'topic-namespace')
-        def _tns(self):
-            try:
-                return self._opener.read(b'topic-namespace').strip() or b'none'
-            except IOError as inst:
-                if inst.errno != errno.ENOENT:
-                    raise
-                return b'none'
-
-        def tns(self):
-            return encoding.tolocal(self._tns)
-
-        def settns(self, tns, tr):
-            self.__class__._tns.set(self, encoding.fromlocal(tns))
-            del tns  # safeguard to not use it after adjusting encoding
-            vfs = self._opener
-            if self._tns != b'none':
-                with vfs(b'topic-namespace', b'w', atomictemp=True, checkambig=True) as f:
-                    f.write(self._tns + b'\n')
-            else:
-                vfs.tryunlink(b'topic-namespace')
-            ce = self._filecache[b'_tns']
-            if ce:
-                ce.refresh()
-
-        def fqbn(self, length=common.FQBN_NORMAL):
-            branch = encoding.tolocal(self._branch)
-            tns = encoding.tolocal(self._tns)
-            topic = encoding.tolocal(self._topic)
-            return common.formatfqbn(branch, tns, topic, length=length)
-
-    dirstate.dirstate = topicdirstate
-
 templatekeyword = registrar.templatekeyword()
 
 @templatekeyword(b'topic', requires={b'ctx'})