diff --git a/eden/scm/edenscm/hgext/commitcloud/baseservice.py b/eden/scm/edenscm/hgext/commitcloud/baseservice.py index 1bc98207d5..41b5d4c3bd 100644 --- a/eden/scm/edenscm/hgext/commitcloud/baseservice.py +++ b/eden/scm/edenscm/hgext/commitcloud/baseservice.py @@ -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] diff --git a/eden/scm/tests/test-commitcloud-smartlog.t b/eden/scm/tests/test-commitcloud-smartlog.t index e9313f293f..5c2e1d5adf 100644 --- a/eden/scm/tests/test-commitcloud-smartlog.t +++ b/eden/scm/tests/test-commitcloud-smartlog.t @@ -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