mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 14:58:03 +03:00
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:
parent
285e883c0a
commit
791ec223ba
@ -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]
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user