Patchwork [02,of,13] cleanupnodes: accept multiple predecessors in 'replacements' (API)

login
register
mail settings
Submitter Boris Feld
Date Sept. 27, 2018, 5:08 p.m.
Message ID <55f1732982232e65d0cc.1538068114@localhost.localdomain>
Download mbox | patch
Permalink /patch/35143/
State Accepted
Headers show

Comments

Boris Feld - Sept. 27, 2018, 5:08 p.m.
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1537981319 -7200
#      Wed Sep 26 19:01:59 2018 +0200
# Node ID 55f1732982232e65d0ccabbfe4e3e38036c028f9
# Parent  4b1a3cbca27b5b8465435d29a476c8c1391d2904
# EXP-Topic trackfold
# Available At https://bitbucket.org/octobus/mercurial-devel/
#              hg pull https://bitbucket.org/octobus/mercurial-devel/ -r 55f173298223
cleanupnodes: accept multiple predecessors in 'replacements' (API)

This changeset makes 'cleanupnodes' accepts multiple predecessors as
`replacements` keys. The same as it accepts multiple successors as
`replacements` values. To avoid breaking all callers, the old and new ways are
currently valid at the same time. We'll deprecate and drop the old way later.

This change is the first step toward a better tracking of "fold" event in the
evolution history. While working on the "rewind" command (in the evolve
extension), we realized that first class tracking of folds are necessary.

We already have good tracking of splits. When walking the evolution history
from predecessors to successors, that makes for a clear distinction between
having multiple successors because of the actual splitting of a changeset or
content-divergences.
The "rewind" command allows restoring older evolution of a stack of
changesets. One of its mode walks the evolution history to automatically find
appropriate predecessors. This means walking from successors to predecessors.
In this case, we need to be able to make the same distinction between an
actual fold and other cases. So we will have to track folds explicitly.

This changesets only focus on making it possible to express fold at the
`cleanupnodes` API level. The actual tracking will be implemented later.

Patch

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -875,39 +875,52 @@  def cleanupnodes(repo, replacements, ope
 
     # translate mapping's other forms
     if not util.safehasattr(replacements, 'items'):
-        replacements = {n: () for n in replacements}
+        replacements = {(n,): () for n in replacements}
+    else:
+        # upgrading non tuple "source" to tuple ones for BC
+        repls = {}
+        for key, value in replacements.items():
+            if not isinstance(key, tuple):
+                key = (key,)
+            repls[key] = value
+        replacements = repls
 
     # Calculate bookmark movements
     if moves is None:
         moves = {}
     # Unfiltered repo is needed since nodes in replacements might be hidden.
     unfi = repo.unfiltered()
-    for oldnode, newnodes in replacements.items():
-        if oldnode in moves:
-            continue
-        if len(newnodes) > 1:
-            # usually a split, take the one with biggest rev number
-            newnode = next(unfi.set('max(%ln)', newnodes)).node()
-        elif len(newnodes) == 0:
-            # move bookmark backwards
-            roots = list(unfi.set('max((::%n) - %ln)', oldnode,
-                                  list(replacements)))
-            if roots:
-                newnode = roots[0].node()
+    for oldnodes, newnodes in replacements.items():
+        for oldnode in oldnodes:
+            if oldnode in moves:
+                continue
+            if len(newnodes) > 1:
+                # usually a split, take the one with biggest rev number
+                newnode = next(unfi.set('max(%ln)', newnodes)).node()
+            elif len(newnodes) == 0:
+                # move bookmark backwards
+                allreplaced = []
+                for rep in replacements:
+                    allreplaced.extend(rep)
+                roots = list(unfi.set('max((::%n) - %ln)', oldnode,
+                                      allreplaced))
+                if roots:
+                    newnode = roots[0].node()
+                else:
+                    newnode = nullid
             else:
-                newnode = nullid
-        else:
-            newnode = newnodes[0]
-        moves[oldnode] = newnode
+                newnode = newnodes[0]
+            moves[oldnode] = newnode
 
     allnewnodes = [n for ns in replacements.values() for n in ns]
     toretract = {}
     toadvance = {}
     if fixphase:
         precursors = {}
-        for oldnode, newnodes in replacements.items():
-            for newnode in newnodes:
-                precursors.setdefault(newnode, []).append(oldnode)
+        for oldnodes, newnodes in replacements.items():
+            for oldnode in oldnodes:
+                for newnode in newnodes:
+                    precursors.setdefault(newnode, []).append(oldnode)
 
         allnewnodes.sort(key=lambda n: unfi[n].rev())
         newphases = {}
@@ -967,18 +980,19 @@  def cleanupnodes(repo, replacements, ope
             # NOTE: the filtering and sorting might belong to createmarkers.
             isobs = unfi.obsstore.successors.__contains__
             torev = unfi.changelog.rev
-            sortfunc = lambda ns: torev(ns[0])
+            sortfunc = lambda ns: torev(ns[0][0])
             rels = []
-            for n, s in sorted(replacements.items(), key=sortfunc):
-                if s or not isobs(n):
-                    rel = (unfi[n], tuple(unfi[m] for m in s))
-                    rels.append(rel)
+            for ns, s in sorted(replacements.items(), key=sortfunc):
+                for n in ns:
+                    if s or not isobs(n):
+                        rel = (unfi[n], tuple(unfi[m] for m in s))
+                        rels.append(rel)
             if rels:
                 obsolete.createmarkers(repo, rels, operation=operation,
                                        metadata=metadata)
         else:
             from . import repair # avoid import cycle
-            tostrip = list(replacements)
+            tostrip = list(n for ns in replacements for n in ns)
             if tostrip:
                 repair.delayedstrip(repo.ui, repo, tostrip, operation,
                                     backup=backup)