Audit trails for hierarchy objects in ReGraph (aka versioning)

ReGraph implements a framework for the version control (VC) of graph transformations in hierarchies.

The data structure VersionedHierarchy allows to store the history of transformations of a hierarchy and perform the following VC operations:

  • Rewrite: perform a rewriting of the hierarchy with a commit to the revision history
  • Branch: create a new branch (with a diverged version of the graph object)
  • Merge branches: merge branches
  • Rollback: rollback to a point in the history of transformations

In [1]:
from regraph import NXGraph, NXHierarchy
from regraph.audit import VersionedHierarchy
from regraph.rules import Rule
from regraph import print_graph, plot_rule, plot_graph

Let us start by creating a small hierarchy.


In [2]:
hierarchy = NXHierarchy()

shapes = NXGraph()
shapes.add_nodes_from(["circle", "square"])
hierarchy.add_graph("shapes", shapes)

colors = NXGraph()
colors.add_nodes_from(["white", "black"])
hierarchy.add_graph("colors", colors)

ag = NXGraph()
ag.add_nodes_from(
    ["wc", "bc", "ws", "bs"])
hierarchy.add_graph("metamodel", ag)

nugget = NXGraph()
nugget.add_nodes_from(
    ["wc1", "wc2", "bc1", "ws1", "bs2"])
hierarchy.add_graph("data", nugget)

hierarchy.add_typing(
    "metamodel", "shapes", {
        "wc": "circle",
        "bc": "circle",
        "ws": "square",
        "bs": "square"
    })
hierarchy.add_typing(
    "metamodel", "colors", {
        "wc": "white",
        "bc": "black",
        "ws": "white",
        "bs": "black"
    })
hierarchy.add_typing(
    "data", "metamodel", {
        "wc1": "wc",
        "wc2": "wc",
        "bc1": "bc",
        "ws1": "ws",
        "bs2": "bs"
    })
hierarchy.add_typing(
    "data", "colors", {
        "wc1": "white",
        "wc2": "white",
        "bc1": "black",
        "ws1": "white",
        "bs2": "black"
    })

base = NXGraph()
base.add_nodes_from(["node"])
hierarchy.add_graph("base", base)
hierarchy.add_typing(
    "colors",
    "base", {
        "white": "node",
        "black": "node"
    })

Let us have a look at the hierarchy and its graphs.


In [3]:
print(hierarchy)

for g in hierarchy.graphs():
    print("Graph: ", g, " nodes: ", hierarchy.get_graph(g).nodes())


Graphs:

shapes {}

colors {}

metamodel {}

data {}

base {}

Typing homomorphisms: 
colors -> base: {}
metamodel -> shapes: {}
metamodel -> colors: {}
data -> metamodel: {}
data -> colors: {}

Relations:

Graph:  shapes  nodes:  ['circle', 'square']
Graph:  colors  nodes:  ['white', 'black']
Graph:  metamodel  nodes:  ['wc', 'bc', 'ws', 'bs']
Graph:  data  nodes:  ['wc1', 'wc2', 'bc1', 'ws1', 'bs2']
Graph:  base  nodes:  ['node']

We pass the hierarchy to the VersionedHierarchy wrapper that will take care of the version control.


In [4]:
h = VersionedHierarchy(hierarchy)
print("Branches: ", h.branches())
print("Current branch: ", h.current_branch())


Branches:  ['master']
Current branch:  master

Let us create a new branch test1


In [5]:
h.branch("test1")
print("Branches: ", h.branches())
print("Current branch: ", h.current_branch())


Branches:  ['master', 'test1']
Current branch:  test1

We will now rewrite our hierarchy at the current branch of the audit trail


In [6]:
pattern = NXGraph()
pattern.add_nodes_from(["s"])
rule = Rule.from_transform(pattern)
rule.inject_remove_node("s")

rhs_instances, commit_id = h.rewrite(
    "shapes",
    rule, {"s": "square"},
    message="Remove square in shapes")

The rewrite method of VersionedHierarchy returns the instances of the RHS of the applied rule in different graphs and the id of the newly created commit corresponding to this rewrite.


In [7]:
print("RHS instances", rhs_instances)
print("Commit ID: ", commit_id)


RHS instances {'shapes': {}, 'metamodel': {}, 'data': {}, 'colors': {'ws': 'white', 'bs': 'black'}, 'base': {'bs_ws': 'node'}}
Commit ID:  2db12401-b608-4084-9c3a-84b72d0edce5

We switch back to the master branch.


In [8]:
h.switch_branch("master")

We will now rewrite the hierarchy corresponding to the current branch


In [9]:
pattern = NXGraph()
pattern.add_nodes_from(["wc"])

rule = Rule.from_transform(pattern)
rule.inject_clone_node("wc")

_, clone_commit = h.rewrite(
    "metamodel",
    rule, {"wc": "wc"},
    message="Clone 'wc' in ag")

h.print_history()


2020-03-30 20:58:00.393303 7ba663de-aefa-470f-be95-0576cdd980bd master Initial commit
2020-03-30 20:58:00.401057 854fa822-1aad-4efc-9d1d-3a3e30195ec4 test1 Created branch 'test1'
2020-03-30 20:58:00.412800 2db12401-b608-4084-9c3a-84b72d0edce5 test1 Remove square in shapes
2020-03-30 20:58:00.462852 819f71a2-2d5d-442d-b5e8-e8f771ab256e master Clone 'wc' in ag

In [10]:
print("Clone commit ID: ", clone_commit)


Clone commit ID:  819f71a2-2d5d-442d-b5e8-e8f771ab256e

In [11]:
pattern = NXGraph()
pattern.add_nodes_from(["wc1"])

rule = Rule.from_transform(pattern)
rule.inject_add_node("new_node")
rule.inject_add_edge("new_node", "wc1")

_ = h.rewrite(
    "data",
    rule, {"wc1": "wc1"},
    message="Add a new node to 'data'")

We merge the branch test1 in into master.


In [12]:
h.merge_with("test1")


Out[12]:
'9f8c6cfd-4fd4-4696-871e-4cae4286d653'

In [13]:
h.print_history()


2020-03-30 20:58:00.393303 7ba663de-aefa-470f-be95-0576cdd980bd master Initial commit
2020-03-30 20:58:00.401057 854fa822-1aad-4efc-9d1d-3a3e30195ec4 test1 Created branch 'test1'
2020-03-30 20:58:00.412800 2db12401-b608-4084-9c3a-84b72d0edce5 test1 Remove square in shapes
2020-03-30 20:58:00.462852 819f71a2-2d5d-442d-b5e8-e8f771ab256e master Clone 'wc' in ag
2020-03-30 20:58:00.512761 47702b7d-d8d9-4fbc-820e-1a5022c4fed7 master Add a new node to 'data'
2020-03-30 20:58:00.533913 9f8c6cfd-4fd4-4696-871e-4cae4286d653 master Merged branch 'test1' into 'master'

Let us now try to rollback to the commit clone_commit.


In [14]:
h.rollback(clone_commit)


Created the new head for 'test1'
Created the new head for 'master'

In [15]:
h.print_history()
print("Branches: ", h.branches())


2020-03-30 20:58:00.393303 7ba663de-aefa-470f-be95-0576cdd980bd master Initial commit
2020-03-30 20:58:00.401057 854fa822-1aad-4efc-9d1d-3a3e30195ec4 test1 Created branch 'test1'
2020-03-30 20:58:00.412800 2db12401-b608-4084-9c3a-84b72d0edce5 test1 Remove square in shapes
2020-03-30 20:58:00.462852 819f71a2-2d5d-442d-b5e8-e8f771ab256e master Clone 'wc' in ag
Branches:  ['master', 'test1']

We can see that the revision history came back to the previous state (right after the clone commit), and we still have two branches master and test1.