mercurial/branchmap.py
changeset 48687 f8f2ecdde4b5
parent 48677 8e5effbf52d0
child 48718 8b393f40a5e6
--- a/mercurial/branchmap.py	Fri Jan 28 19:07:52 2022 +0300
+++ b/mercurial/branchmap.py	Fri Jan 07 11:53:23 2022 +0300
@@ -17,6 +17,7 @@
 from . import (
     encoding,
     error,
+    obsolete,
     pycompat,
     scmutil,
     util,
@@ -184,7 +185,7 @@
 
     The first line is used to check if the cache is still valid. If the
     branch cache is for a filtered repo view, an optional third hash is
-    included that hashes the hashes of all filtered revisions.
+    included that hashes the hashes of all filtered and obsolete revisions.
 
     The open/closed state is represented by a single letter 'o' or 'c'.
     This field can be used to avoid changelog reads when determining if a
@@ -357,7 +358,8 @@
         - True when cache is up to date or a subset of current repo."""
         try:
             return (self.tipnode == repo.changelog.node(self.tiprev)) and (
-                self.filteredhash == scmutil.filteredhash(repo, self.tiprev)
+                self.filteredhash
+                == scmutil.filteredhash(repo, self.tiprev, needobsolete=True)
             )
         except IndexError:
             return False
@@ -478,6 +480,9 @@
         # use the faster unfiltered parent accessor.
         parentrevs = repo.unfiltered().changelog.parentrevs
 
+        # Faster than using ctx.obsolete()
+        obsrevs = obsolete.getrevs(repo, b'obsolete')
+
         for branch, newheadrevs in pycompat.iteritems(newbranches):
             # For every branch, compute the new branchheads.
             # A branchhead is a revision such that no descendant is on
@@ -518,6 +523,11 @@
             bheadset = {cl.rev(node) for node in bheads}
             uncertain = set()
             for newrev in sorted(newheadrevs):
+                if newrev in obsrevs:
+                    # We ignore obsolete changesets as they shouldn't be
+                    # considered heads.
+                    continue
+
                 if not bheadset:
                     bheadset.add(newrev)
                     continue
@@ -525,13 +535,22 @@
                 parents = [p for p in parentrevs(newrev) if p != nullrev]
                 samebranch = set()
                 otherbranch = set()
+                obsparents = set()
                 for p in parents:
-                    if p in bheadset or getbranchinfo(p)[0] == branch:
+                    if p in obsrevs:
+                        # We ignored this obsolete changeset earlier, but now
+                        # that it has non-ignored children, we need to make
+                        # sure their ancestors are not considered heads. To
+                        # achieve that, we will simply treat this obsolete
+                        # changeset as a parent from other branch.
+                        obsparents.add(p)
+                    elif p in bheadset or getbranchinfo(p)[0] == branch:
                         samebranch.add(p)
                     else:
                         otherbranch.add(p)
-                if otherbranch and not (len(bheadset) == len(samebranch) == 1):
+                if not (len(bheadset) == len(samebranch) == 1):
                     uncertain.update(otherbranch)
+                    uncertain.update(obsparents)
                 bheadset.difference_update(samebranch)
                 bheadset.add(newrev)
 
@@ -540,11 +559,12 @@
                     topoheads = set(cl.headrevs())
                 if bheadset - topoheads:
                     floorrev = min(bheadset)
-                    ancestors = set(cl.ancestors(newheadrevs, floorrev))
-                    bheadset -= ancestors
+                    if floorrev <= max(uncertain):
+                        ancestors = set(cl.ancestors(uncertain, floorrev))
+                        bheadset -= ancestors
             bheadrevs = sorted(bheadset)
             self[branch] = [cl.node(rev) for rev in bheadrevs]
-            tiprev = bheadrevs[-1]
+            tiprev = max(newheadrevs)
             if tiprev > ntiprev:
                 ntiprev = tiprev
 
@@ -553,15 +573,24 @@
             self.tipnode = cl.node(ntiprev)
 
         if not self.validfor(repo):
-            # cache key are not valid anymore
+            # old cache key is now invalid for the repo, but we've just updated
+            # the cache and we assume it's valid, so let's make the cache key
+            # valid as well by recomputing it from the cached data
             self.tipnode = repo.nullid
             self.tiprev = nullrev
             for heads in self.iterheads():
+                if not heads:
+                    # all revisions on a branch are obsolete
+                    continue
+                # note: tiprev is not necessarily the tip revision of repo,
+                # because the tip could be obsolete (i.e. not a head)
                 tiprev = max(cl.rev(node) for node in heads)
                 if tiprev > self.tiprev:
                     self.tipnode = cl.node(tiprev)
                     self.tiprev = tiprev
-        self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
+        self.filteredhash = scmutil.filteredhash(
+            repo, self.tiprev, needobsolete=True
+        )
 
         duration = util.timer() - starttime
         repo.ui.log(