commitcloud: use Rust dag abstractions for smartlog rendering

Summary:
This simplifies a lot of the code, and makes it possible to do DAG queries like
ancestors, descendants on the commit cloud graph.

Reviewed By: markbt

Differential Revision: D21554674

fbshipit-source-id: ee08cddfc162a7546d63d4bf385f2948fc799fd3
This commit is contained in:
Jun Wu 2020-05-14 12:01:01 -07:00 committed by Facebook GitHub Bot
parent 285e883c0a
commit 791ec223ba
2 changed files with 160 additions and 287 deletions

View File

@ -9,6 +9,7 @@ from __future__ import absolute_import
import abc
import collections
import bindings
from edenscm.mercurial import dagop, json, node as nodemod, pycompat
from edenscm.mercurial.graphmod import CHANGESET, GRANDPARENT, MISSINGPARENT, PARENT
from edenscm.mercurial.pycompat import ensurestr
@ -34,6 +35,8 @@ NodeInfo = collections.namedtuple(
"NodeInfo", "node bookmarks parents author date message phase"
)
PUBLICPHASE = "public"
class FakeCtx(object):
"""fake ctx for fake smartlog from fake nodes"""
@ -243,200 +246,65 @@ class BaseService(pycompat.ABC):
"""Gets the workspace smartlog
"""
@staticmethod
def builddag(nodeinfos):
"""Returns a DAG that supports DAG operations like heads, parents,
roots, ancestors, descendants, etc.
nodeinfos is a dict: {hexnode: NodeInfo}
"""
public = _getpublic(nodeinfos)
# Sort public by date. Connect them. Assume they form a linear history.
# XXX: This can be incorrect if public history is not linear or not
# sorted by date. However, nodeinfos only have limited information and
# sort by date is the best effort we can do here.
public.sort(key=lambda node: (nodeinfos[node].date, node), reverse=True)
# {node: [parentnode]}
publicparents = {node: public[i + 1 : i + 2] for i, node in enumerate(public)}
def getparents(node):
parents = publicparents.get(node)
if parents is None:
parents = [p for p in nodeinfos[node].parents if p in nodeinfos]
return parents
dag = bindings.dag.memnamedag()
dag.addheads(sorted(nodeinfos.keys()), getparents)
return dag
def _makefakedag(self, nodeinfos, repo):
"""cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
This generator function walks the given fake nodes.
Return firstbranch, dagwalker tuple.
"""
if not nodeinfos:
return [], []
DRAFTPHASE = "draft"
parentchildmap = {}
##### HELPER FUNCTIONS #####
def sortbydate(listofnodes):
# also sort by node to disambiguate the ordering with the same date
return sorted(
listofnodes, key=lambda node: (nodeinfos[node].date, node), reverse=True
)
def isfinalnode(node):
return node not in allnodes or all(
p not in allnodes for p in nodeinfos[node].parents
)
def isdraftnode(node):
return node in nodeinfos and nodeinfos[node].phase == DRAFTPHASE
def ispublicnode(node):
return not isdraftnode(node)
def publicpathtop(publicnode):
"""returns the top-most node in public nodes path for the given node"""
# for the example below returns 028179 for the given node 33254d
#
# o 028179 (public) Jun 29 at 11:30
# |
# o 33254d (public) Jun 28 at 09:45
while True:
nodechildlist = [
p for p in parentchildmap.get(publicnode, []) if ispublicnode(p)
]
if not nodechildlist:
break
publicnode = nodechildlist[0]
return publicnode
##### HELPER FUNCTIONS END #####
# set of all nodes (excluding their parents)
allnodes = set(nodeinfos.keys())
# Initial parent child map
for n in sorted(allnodes):
for p in nodeinfos[n].parents:
parentchildmap.setdefault(p, []).append(n)
# originally data is a set of trees where draft stacks teminate with a public node
# connect these trees is the first step
# select nodes that don't have parents present in all nodes
# let's call them 'final nodes'
finalnodes = [n for n in allnodes if isfinalnode(n)]
# sort the final nodes by date
# glue them and add these edges to the grapth
#
# for the example below on this pass the following additional edges will be added:
#
# o 2c008f (draft) Jun 29 at 11:33 (none)
# / some commit
# |
# o 028179 (public) Jun 29 at 11:30 (edge this node -> 33254d)
# |
# | o 7c2d07 (draft) Jun 28 at 14:25 (none)
# |/ some commit
# |
# o 33254d (public) Jun 28 at 09:45
# XXX: This adds faked edges. Practically finalnodes are usually public
# nodes that exist in the repo. A better approach is to check the real
# repo to figure out the real edges of them, and do not add faked edges.
# Ideally, grandparent edges and direct parent edges can be
# distinguished that way.
finalnodes = sortbydate(finalnodes)
for i, node in enumerate(finalnodes[:-1]):
nextnode = finalnodes[i + 1]
gluenode = publicpathtop(nextnode)
parentchildmap.setdefault(gluenode, []).append(node)
# Build the reversed map. Useful for "parentrevs" used by
# "dagop.topsort".
childparentmap = {}
for parent, children in sorted(parentchildmap.items()):
for child in children:
childparentmap.setdefault(child, []).append(parent)
# Add missing nodes
for info in nodeinfos.values():
for node in [info.node] + info.parents:
childparentmap.setdefault(node, [])
parentchildmap.setdefault(node, [])
# Assign revision numbers. Useful for functions like "dagop.topsort".
revnodemap = {}
noderevmap = {}
for i, node in enumerate(topological(parentchildmap)):
rev = 1000000000 + i
revnodemap[rev] = node
noderevmap[node] = rev
# Replacement of repo.changelog.parentrevs
def parentrevs(rev):
node = revnodemap[rev]
result = [noderevmap[n] for n in childparentmap[node]]
return result
# Set "first branch" to "finalnodes". They are usually public commits.
firstbranch = set([noderevmap[finalnodes[0]]])
repo.ui.debug("building dag: firstbranch: %r" % finalnodes[0])
# Use "dagop.toposort" to sort them. This helps beautify the graph.
allrevs = sorted(noderevmap[n] for n in allnodes)
sortedrevs = list(dagop.toposort(allrevs, parentrevs, firstbranch))
public = _getpublic(nodeinfos)
dag = self.builddag(nodeinfos).beautify(public)
def createctx(repo, node):
return FakeCtx(repo, nodeinfos[node], noderevmap[node])
return FakeCtx(repo, nodeinfos[node], node)
# Copied from graphmod.dagwalker. Revised.
def dagwalker(repo, revs):
"""cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
This generator function walks through revisions (which should be ordered
from bigger to lower). It returns a tuple for each node.
Each parentinfo entry is a tuple with (edgetype, parentid), where edgetype
is one of PARENT, GRANDPARENT or MISSINGPARENT. The node and parent ids
are arbitrary integers which identify a node in the context of the graph
returned.
"""
minroot = min(revs)
gpcache = {}
for rev in revs:
node = revnodemap[rev]
def dagwalker():
for node in dag.all():
ctx = createctx(repo, node)
# TODO: Consider generating faked nodes (missing parents) for
# missing parents.
parentctxs = [
createctx(repo, n) for n in childparentmap[node] if n in nodeinfos
]
# partition into parents in the rev set and missing parents, then
# augment the lists with markers, to inform graph drawing code about
# what kind of edge to draw between nodes.
pset = set(p.rev() for p in parentctxs if p.rev() in revs)
mpars = [p.rev() for p in parentctxs if p.rev() not in pset]
# Heuristic: finalnodes only have grandparents
if node in finalnodes:
# XXX: This does not actually take the real parent information
# into consideration.
if node in public:
parentstyle = GRANDPARENT
else:
parentstyle = PARENT
parents = [(parentstyle, p) for p in sorted(pset)]
parents = [(parentstyle, p) for p in dag.parentnames(node)]
yield (node, CHANGESET, ctx, parents)
for mpar in mpars:
gp = gpcache.get(mpar)
if gp is None:
gp = gpcache[mpar] = sorted(
set(
dagop._reachablerootspure(
repo,
minroot,
revs,
[mpar],
False,
parentrevs=parentrevs,
)
)
)
if not gp:
parents.append((MISSINGPARENT, mpar))
pset.add(mpar)
else:
parents.extend((GRANDPARENT, g) for g in gp if g not in pset)
pset.update(gp)
yield (ctx.rev(), CHANGESET, ctx, parents)
return firstbranch, dagwalker(repo, sortedrevs)
firstbranch = [dag.sort(public).first()]
return firstbranch, dagwalker()
def _makenodes(self, data):
nodes = {}
@ -452,3 +320,8 @@ class BaseService(pycompat.ABC):
node, bookmarks, parents, author, date, message, phase
)
return nodes
def _getpublic(nodeinfos):
"""Get binary public nodes"""
return [hexnode for hexnode, info in nodeinfos.items() if info.phase == PUBLICPHASE]

View File

@ -107,112 +107,112 @@ Tests for hg cloud sl
commitcloud: searching draft commits for the 'user/test/default' workspace for the 'server' repo
Smartlog:
o b8d4ca Test User 2018-07-19 23:03 +0000
o 53d3b5 Test User 2018-07-19 20:53 +0000
| some commit
|
o b4a0b0 Test User 2018-07-19 21:47 +0000
o fcd541 Test User 2018-07-19 20:53 +0000
| some commit
|
o 67dd68 Test User 2018-07-19 20:53 +0000
o 89e915 Test User 2018-07-19 20:53 +0000
| some commit
|
o 1114f6 Test User 2018-07-19 20:53 +0000
o 4bebf9 Test User 2018-07-19 20:53 +0000
| some commit
|
o ccbc70 Test User 2018-07-19 20:53 +0000
o e248c6 Test User 2018-07-19 20:53 +0000
| some commit
|
o 73238e Test User 2018-07-19 20:53 +0000
o a54c70 Test User 2018-07-19 20:53 +0000
| some commit
|
o 44b320 Test User 2018-07-19 20:53 +0000
o 0c8dbc Test User 2018-07-19 20:53 +0000
| some commit
|
o f238a8 Test User 2018-07-19 21:14 +0000
o 3b2c64 Test User 2018-07-19 20:53 +0000
| some commit
|
o 197156 Test User 2018-07-19 21:14 +0000
o f6c426 Test User 2018-07-19 20:53 +0000
| some commit
|
| o 53d3b5 Test User 2018-07-19 20:53 +0000
o 35ca84 Test User 2018-07-19 20:53 +0000
| some commit
|
o 669c33 Test User 2018-07-19 20:53 +0000
| some commit
|
o f5428e Test User 2018-07-19 20:53 +0000
| some commit
|
o 4c0aab Test User 2018-07-19 20:53 +0000
| some commit
|
o bdaee4 Test User 2018-07-19 20:53 +0000
| some commit
|
o d84918 Test User 2018-07-19 20:53 +0000
| some commit
|
o 277a2f Test User 2018-07-19 20:53 +0000
| some commit
|
o 0c7afd Test User 2018-07-19 20:53 +0000
| some commit
|
o 2bb232 Test User 2018-07-19 20:53 +0000
| some commit
|
o aefc18 Test User 2018-07-19 20:53 +0000
| some commit
|
o 7c0a61 Test User 2018-07-19 20:53 +0000
| some commit
|
o 334a42 Test User 2018-07-19 20:53 +0000
| some commit
|
o 63d3b0 Test User 2018-07-19 20:53 +0000
| some commit
|
o 193082 Test User 2018-07-19 20:53 +0000
| some commit
|
o ea72aa Test User 2018-07-19 20:53 +0000
| some commit
|
o 5ef0df Test User 2018-07-19 20:53 +0000
| some commit
|
o b02028 Test User 2018-07-19 20:53 +0000
| some commit
|
o 8a486e Test User 2018-07-19 20:53 +0000
| some commit
|
| o b8d4ca Test User 2018-07-19 23:03 +0000
| | some commit
| |
| o fcd541 Test User 2018-07-19 20:53 +0000
| o b4a0b0 Test User 2018-07-19 21:47 +0000
| | some commit
| |
| o 89e915 Test User 2018-07-19 20:53 +0000
| o 67dd68 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 4bebf9 Test User 2018-07-19 20:53 +0000
| o 1114f6 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o e248c6 Test User 2018-07-19 20:53 +0000
| o ccbc70 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o a54c70 Test User 2018-07-19 20:53 +0000
| o 73238e Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 0c8dbc Test User 2018-07-19 20:53 +0000
| o 44b320 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 3b2c64 Test User 2018-07-19 20:53 +0000
| o f238a8 Test User 2018-07-19 21:14 +0000
| | some commit
| |
| o f6c426 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 35ca84 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 669c33 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o f5428e Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 4c0aab Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o bdaee4 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o d84918 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 277a2f Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 0c7afd Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 2bb232 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o aefc18 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 7c0a61 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 334a42 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 63d3b0 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 193082 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o ea72aa Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 5ef0df Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o b02028 Test User 2018-07-19 20:53 +0000
| | some commit
| |
| o 8a486e Test User 2018-07-19 20:53 +0000
| o 197156 Test User 2018-07-19 21:14 +0000
|/ some commit
|
o f311da Test User 2018-07-19 20:53 +0000
@ -263,13 +263,13 @@ Tests for hg cloud sl
o e0a850 (public) 2018-06-05 16:31 +0000
. some commit
.
. o d78d6e Test User 2018-05-23 18:02 +0000
. o 786c1d Test User 2018-05-23 18:02 +0000
./ some commit
|
| o 834264 Test User 2018-05-23 18:02 +0000
|/ some commit
|
| o 786c1d Test User 2018-05-23 18:02 +0000
| o d78d6e Test User 2018-05-23 18:02 +0000
|/ some commit
|
o 7e1ae2 (public) 2018-05-23 16:51 +0000
@ -735,10 +735,10 @@ Tests for hg cloud sl
./ some commit
|
o 390b78 (public) 2018-06-26 16:53 +0000
| some commit
|
| o b71712 Test User 2018-06-27 22:20 +0000
|/ some commit
. some commit
.
. o b71712 Test User 2018-06-27 22:20 +0000
./ some commit
|
o 0c157b (public) 2018-06-26 16:53 +0000
. some commit
@ -1015,13 +1015,13 @@ Tests for hg cloud sl
o 33254d (public) 2018-06-28 08:45 +0000
. some commit
.
. o 026cdd Test User 2018-06-26 09:23 +0000 somebookmark
. o 9be605 Test User 2018-06-26 09:23 +0000
. | some commit
. |
. | o 9be605 Test User 2018-06-26 09:23 +0000
. | | some commit
. | |
. | o 85930e Test User 2018-06-26 09:23 +0000
. o 85930e Test User 2018-06-26 09:23 +0000
. | some commit
. |
. | o 026cdd Test User 2018-06-26 09:23 +0000 somebookmark
. |/ some commit
. |
. o 1bae8c Test User 2018-06-26 09:23 +0000
@ -1513,43 +1513,43 @@ Tests for hg cloud sl
o 9ecb3e (public) 2018-06-18 14:16 +0000
. some commit
.
. o 2b9a52 Test User 2018-06-17 15:13 +0000
. o 4ac122 Test User 2018-06-18 15:38 +0000
. | some commit
. |
. o 5c45cd Test User 2018-06-17 15:13 +0000
. o 2ad8ad Test User 2018-06-18 15:38 +0000
. | some commit
. |
. o 25d91f Test User 2018-06-17 15:13 +0000
. o a35c2d Test User 2018-06-18 15:38 +0000
. | some commit
. |
. | o 4ac122 Test User 2018-06-18 15:38 +0000
. o 18ead8 Test User 2018-06-18 15:38 +0000
. | some commit
. |
. o 343314 Test User 2018-06-18 15:38 +0000
. | some commit
. |
. o d50e12 Test User 2018-06-18 15:38 +0000
. | some commit
. |
. o 192484 Test User 2018-06-18 15:38 +0000
. | some commit
. |
. o d982aa Test User 2018-06-18 15:38 +0000
. | some commit
. |
. o 5fdfe2 Test User 2018-06-18 15:38 +0000
. | some commit
. |
. o 7e8e6a Test User 2018-06-18 15:38 +0000
. | some commit
. |
. | o 2b9a52 Test User 2018-06-17 15:13 +0000
. | | some commit
. | |
. | o 2ad8ad Test User 2018-06-18 15:38 +0000
. | o 5c45cd Test User 2018-06-17 15:13 +0000
. | | some commit
. | |
. | o a35c2d Test User 2018-06-18 15:38 +0000
. | | some commit
. | |
. | o 18ead8 Test User 2018-06-18 15:38 +0000
. | | some commit
. | |
. | o 343314 Test User 2018-06-18 15:38 +0000
. | | some commit
. | |
. | o d50e12 Test User 2018-06-18 15:38 +0000
. | | some commit
. | |
. | o 192484 Test User 2018-06-18 15:38 +0000
. | | some commit
. | |
. | o d982aa Test User 2018-06-18 15:38 +0000
. | | some commit
. | |
. | o 5fdfe2 Test User 2018-06-18 15:38 +0000
. | | some commit
. | |
. | o 7e8e6a Test User 2018-06-18 15:38 +0000
. | o 25d91f Test User 2018-06-17 15:13 +0000
. |/ some commit
. |
. o c4e2cd Test User 2018-06-17 15:13 +0000