diff mercurial/phases.py @ 44558:fdc802f29b2c

transactions: convert changes['phases'] to list of ranges Consecutive revisions are often in the same phase, especially public revisions. This means that a dictionary keyed by the revision for the phase transitions is highly redundant. Build a list of (range, (old, new)) entries instead and aggressively merge ranges with the same transition. For the test case in issue5691, this reduces memory use by ~20MB. Differential Revision: https://phab.mercurial-scm.org/D8125
author Joerg Sonnenberger <joerg@bec.de>
date Fri, 08 Dec 2017 02:29:02 +0100
parents 9d2b2df2c2ba
children fa151f7af275
line wrap: on
line diff
--- a/mercurial/phases.py	Wed Mar 11 17:42:56 2020 +0100
+++ b/mercurial/phases.py	Fri Dec 08 02:29:02 2017 +0100
@@ -216,17 +216,101 @@
     return headsbyphase
 
 
+def _sortedrange_insert(data, idx, rev, t):
+    merge_before = False
+    if idx:
+        r1, t1 = data[idx - 1]
+        merge_before = r1[-1] + 1 == rev and t1 == t
+    merge_after = False
+    if idx < len(data):
+        r2, t2 = data[idx]
+        merge_after = r2[0] == rev + 1 and t2 == t
+
+    if merge_before and merge_after:
+        data[idx - 1] = (pycompat.xrange(r1[0], r2[-1] + 1), t)
+        data.pop(idx)
+    elif merge_before:
+        data[idx - 1] = (pycompat.xrange(r1[0], rev + 1), t)
+    elif merge_after:
+        data[idx] = (pycompat.xrange(rev, r2[-1] + 1), t)
+    else:
+        data.insert(idx, (pycompat.xrange(rev, rev + 1), t))
+
+
+def _sortedrange_split(data, idx, rev, t):
+    r1, t1 = data[idx]
+    if t == t1:
+        return
+    t = (t1[0], t[1])
+    if len(r1) == 1:
+        data.pop(idx)
+        _sortedrange_insert(data, idx, rev, t)
+    elif r1[0] == rev:
+        data[idx] = (pycompat.xrange(rev + 1, r1[-1] + 1), t1)
+        _sortedrange_insert(data, idx, rev, t)
+    elif r1[-1] == rev:
+        data[idx] = (pycompat.xrange(r1[0], rev), t1)
+        _sortedrange_insert(data, idx + 1, rev, t)
+    else:
+        data[idx : idx + 1] = [
+            (pycompat.xrange(r1[0], rev), t1),
+            (pycompat.xrange(rev, rev + 1), t),
+            (pycompat.xrange(rev + 1, r1[-1] + 1), t1),
+        ]
+
+
 def _trackphasechange(data, rev, old, new):
-    """add a phase move the <data> dictionnary
+    """add a phase move to the <data> list of ranges
 
     If data is None, nothing happens.
     """
     if data is None:
         return
-    existing = data.get(rev)
-    if existing is not None:
-        old = existing[0]
-    data[rev] = (old, new)
+
+    # If data is empty, create a one-revision range and done
+    if not data:
+        data.insert(0, (pycompat.xrange(rev, rev + 1), (old, new)))
+        return
+
+    low = 0
+    high = len(data)
+    t = (old, new)
+    while low < high:
+        mid = (low + high) // 2
+        revs = data[mid][0]
+
+        if rev in revs:
+            _sortedrange_split(data, mid, rev, t)
+            return
+
+        if revs[0] == rev + 1:
+            if mid and data[mid - 1][0][-1] == rev:
+                _sortedrange_split(data, mid - 1, rev, t)
+            else:
+                _sortedrange_insert(data, mid, rev, t)
+            return
+
+        if revs[-1] == rev - 1:
+            if mid + 1 < len(data) and data[mid + 1][0][0] == rev:
+                _sortedrange_split(data, mid + 1, rev, t)
+            else:
+                _sortedrange_insert(data, mid + 1, rev, t)
+            return
+
+        if revs[0] > rev:
+            high = mid
+        else:
+            low = mid + 1
+
+    if low == len(data):
+        data.append((pycompat.xrange(rev, rev + 1), t))
+        return
+
+    r1, t1 = data[low]
+    if r1[0] > rev:
+        data.insert(low, (pycompat.xrange(rev, rev + 1), t))
+    else:
+        data.insert(low + 1, (pycompat.xrange(rev, rev + 1), t))
 
 
 class phasecache(object):
@@ -400,8 +484,9 @@
             phasetracking = tr.changes[b'phases']
             torev = repo.changelog.rev
             phase = self.phase
-            for n in nodes:
-                rev = torev(n)
+            revs = [torev(node) for node in nodes]
+            revs.sort()
+            for rev in revs:
                 revphase = phase(repo, rev)
                 _trackphasechange(phasetracking, rev, None, revphase)
         repo.invalidatevolatilesets()
@@ -485,7 +570,7 @@
                     affected -= revs
                 else:  # public phase
                     revs = affected
-                for r in revs:
+                for r in sorted(revs):
                     _trackphasechange(phasetracking, r, phase, targetphase)
         repo.invalidatevolatilesets()