From 0522894a8d5abc45eaac5397ee0cb893e2b6ad3b Mon Sep 17 00:00:00 2001 From: William Chargin Date: Tue, 26 Jun 2018 13:54:47 -0700 Subject: [PATCH] Create Git graph (#406) Summary: This commit adds logic to create the Git graph, modeled after the GitHub graph creator in #405. In this commit, we do not include the corresponding porcelain; a Git `GraphView` will be added subsequently. Kudos to @decentralion for suggesting in #187 that I write the logic to detect BECOMES edges against the high-level data structures. Due to that decision, the logic and tests are copied directly from the V1 code without change, because the high-level data structures are the same. The new code is exactly the body of the `GraphCreator` class. Test Plan: Verify that the new snapshot is likely equivalent to the V1 snapshot, using the heuristic that the two graphs have the same numbers of nodes (59) and edges (84). (I have performed this check.) wchargin-branch: git-v3-create-graph --- .../__snapshots__/createGraph.test.js.snap | 1604 +++++++++++++++++ src/v3/plugins/git/createGraph.js | 217 +++ src/v3/plugins/git/createGraph.test.js | 246 +++ 3 files changed, 2067 insertions(+) create mode 100644 src/v3/plugins/git/__snapshots__/createGraph.test.js.snap create mode 100644 src/v3/plugins/git/createGraph.js create mode 100644 src/v3/plugins/git/createGraph.test.js diff --git a/src/v3/plugins/git/__snapshots__/createGraph.test.js.snap b/src/v3/plugins/git/__snapshots__/createGraph.test.js.snap new file mode 100644 index 0000000..260ae69 --- /dev/null +++ b/src/v3/plugins/git/__snapshots__/createGraph.test.js.snap @@ -0,0 +1,1604 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`plugins/git/createGraph createGraph processes a simple repository 1`] = ` +Array [ + Object { + "type": "sourcecred/graph", + "version": "0.4.0", + }, + Object { + "edges": Array [ + Object { + "address": Array [ + "sourcecred", + "git", + "BECOMES", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "pygravitydefier", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "pygravitydefier", + ], + "dstIndex": 49, + "srcIndex": 34, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "BECOMES", + "3", + "TREE_ENTRY", + "7b79d579b62994faba3b69fdf8aa442586c32681", + "quantum_gravity.py", + "3", + "TREE_ENTRY", + "78fc9c83023386854c6bfdc5761c0e58f68e226f", + "quantum_gravity.py", + ], + "dstIndex": 38, + "srcIndex": 40, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "BECOMES", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "src", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "src", + ], + "dstIndex": 57, + "srcIndex": 51, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "2f7155e359fd0ecb96ffdca66fa45b6ed5792809", + "README.txt", + ], + "dstIndex": 0, + "srcIndex": 25, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "2f7155e359fd0ecb96ffdca66fa45b6ed5792809", + "science.txt", + ], + "dstIndex": 6, + "srcIndex": 26, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + ".gitmodules", + ], + "dstIndex": 3, + "srcIndex": 27, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "README.txt", + ], + "dstIndex": 0, + "srcIndex": 28, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "pygravitydefier", + ], + "dstIndex": 15, + "srcIndex": 29, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "science.txt", + ], + "dstIndex": 6, + "srcIndex": 30, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + ".gitmodules", + ], + "dstIndex": 3, + "srcIndex": 31, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "README.txt", + ], + "dstIndex": 0, + "srcIndex": 32, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "TODOS.txt", + ], + "dstIndex": 5, + "srcIndex": 33, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "pygravitydefier", + ], + "dstIndex": 15, + "srcIndex": 34, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "science.txt", + ], + "dstIndex": 6, + "srcIndex": 35, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "src", + ], + "dstIndex": 20, + "srcIndex": 36, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "78fc9c83023386854c6bfdc5761c0e58f68e226f", + "index.py", + ], + "dstIndex": 1, + "srcIndex": 37, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "78fc9c83023386854c6bfdc5761c0e58f68e226f", + "quantum_gravity.py", + ], + "dstIndex": 4, + "srcIndex": 38, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "7b79d579b62994faba3b69fdf8aa442586c32681", + "index.py", + ], + "dstIndex": 1, + "srcIndex": 39, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "7b79d579b62994faba3b69fdf8aa442586c32681", + "quantum_gravity.py", + ], + "dstIndex": 2, + "srcIndex": 40, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + ".gitmodules", + ], + "dstIndex": 3, + "srcIndex": 41, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "README.txt", + ], + "dstIndex": 0, + "srcIndex": 42, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "pygravitydefier", + ], + "dstIndex": 14, + "srcIndex": 43, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "science.txt", + ], + "dstIndex": 6, + "srcIndex": 44, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "src", + ], + "dstIndex": 19, + "srcIndex": 45, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + ".gitmodules", + ], + "dstIndex": 3, + "srcIndex": 46, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "README.txt", + ], + "dstIndex": 0, + "srcIndex": 47, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "TODOS.txt", + ], + "dstIndex": 5, + "srcIndex": 48, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "pygravitydefier", + ], + "dstIndex": 14, + "srcIndex": 49, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "science.txt", + ], + "dstIndex": 6, + "srcIndex": 50, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "src", + ], + "dstIndex": 20, + "srcIndex": 51, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + ".gitmodules", + ], + "dstIndex": 3, + "srcIndex": 52, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "README.txt", + ], + "dstIndex": 0, + "srcIndex": 53, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "TODOS.txt", + ], + "dstIndex": 5, + "srcIndex": 54, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "pygravitydefier", + ], + "dstIndex": 14, + "srcIndex": 55, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "science.txt", + ], + "dstIndex": 6, + "srcIndex": 56, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "src", + ], + "dstIndex": 19, + "srcIndex": 57, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_CONTENTS", + "3", + "TREE_ENTRY", + "bdff5d94193170015d6cbb549b7b630649428b1f", + "README.txt", + ], + "dstIndex": 0, + "srcIndex": 58, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_PARENT", + "2", + "COMMIT", + "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f", + "2", + "COMMIT", + "69c5aad50eec8f2a0a07c988c3b283a6490eb45b", + ], + "dstIndex": 8, + "srcIndex": 7, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_PARENT", + "2", + "COMMIT", + "69c5aad50eec8f2a0a07c988c3b283a6490eb45b", + "2", + "COMMIT", + "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + ], + "dstIndex": 13, + "srcIndex": 8, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_PARENT", + "2", + "COMMIT", + "8d287c3bfbf8455ef30187bf5153ffc1b6eef268", + "2", + "COMMIT", + "c08ee3a4edea384d5291ffcbf06724a13ed72325", + ], + "dstIndex": 10, + "srcIndex": 9, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_PARENT", + "2", + "COMMIT", + "c08ee3a4edea384d5291ffcbf06724a13ed72325", + "2", + "COMMIT", + "c2b51945e7457546912a8ce158ed9d294558d294", + ], + "dstIndex": 11, + "srcIndex": 10, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_PARENT", + "2", + "COMMIT", + "d160cca97611e9dfed642522ad44408d0292e8ea", + "2", + "COMMIT", + "8d287c3bfbf8455ef30187bf5153ffc1b6eef268", + ], + "dstIndex": 9, + "srcIndex": 12, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_PARENT", + "2", + "COMMIT", + "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + "2", + "COMMIT", + "d160cca97611e9dfed642522ad44408d0292e8ea", + ], + "dstIndex": 12, + "srcIndex": 13, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_TREE", + "2", + "COMMIT", + "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f", + ], + "dstIndex": 21, + "srcIndex": 7, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_TREE", + "2", + "COMMIT", + "69c5aad50eec8f2a0a07c988c3b283a6490eb45b", + ], + "dstIndex": 23, + "srcIndex": 8, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_TREE", + "2", + "COMMIT", + "8d287c3bfbf8455ef30187bf5153ffc1b6eef268", + ], + "dstIndex": 17, + "srcIndex": 9, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_TREE", + "2", + "COMMIT", + "c08ee3a4edea384d5291ffcbf06724a13ed72325", + ], + "dstIndex": 16, + "srcIndex": 10, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_TREE", + "2", + "COMMIT", + "c2b51945e7457546912a8ce158ed9d294558d294", + ], + "dstIndex": 24, + "srcIndex": 11, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_TREE", + "2", + "COMMIT", + "d160cca97611e9dfed642522ad44408d0292e8ea", + ], + "dstIndex": 18, + "srcIndex": 12, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "HAS_TREE", + "2", + "COMMIT", + "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + ], + "dstIndex": 22, + "srcIndex": 13, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "2f7155e359fd0ecb96ffdca66fa45b6ed5792809", + "README.txt", + ], + "dstIndex": 25, + "srcIndex": 16, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "2f7155e359fd0ecb96ffdca66fa45b6ed5792809", + "science.txt", + ], + "dstIndex": 26, + "srcIndex": 16, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + ".gitmodules", + ], + "dstIndex": 27, + "srcIndex": 17, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "README.txt", + ], + "dstIndex": 28, + "srcIndex": 17, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "pygravitydefier", + ], + "dstIndex": 29, + "srcIndex": 17, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "science.txt", + ], + "dstIndex": 30, + "srcIndex": 17, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + ".gitmodules", + ], + "dstIndex": 31, + "srcIndex": 18, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "README.txt", + ], + "dstIndex": 32, + "srcIndex": 18, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "TODOS.txt", + ], + "dstIndex": 33, + "srcIndex": 18, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "pygravitydefier", + ], + "dstIndex": 34, + "srcIndex": 18, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "science.txt", + ], + "dstIndex": 35, + "srcIndex": 18, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "src", + ], + "dstIndex": 36, + "srcIndex": 18, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "78fc9c83023386854c6bfdc5761c0e58f68e226f", + "index.py", + ], + "dstIndex": 37, + "srcIndex": 19, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "78fc9c83023386854c6bfdc5761c0e58f68e226f", + "quantum_gravity.py", + ], + "dstIndex": 38, + "srcIndex": 19, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "7b79d579b62994faba3b69fdf8aa442586c32681", + "index.py", + ], + "dstIndex": 39, + "srcIndex": 20, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "7b79d579b62994faba3b69fdf8aa442586c32681", + "quantum_gravity.py", + ], + "dstIndex": 40, + "srcIndex": 20, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + ".gitmodules", + ], + "dstIndex": 41, + "srcIndex": 21, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "README.txt", + ], + "dstIndex": 42, + "srcIndex": 21, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "pygravitydefier", + ], + "dstIndex": 43, + "srcIndex": 21, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "science.txt", + ], + "dstIndex": 44, + "srcIndex": 21, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "src", + ], + "dstIndex": 45, + "srcIndex": 21, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + ".gitmodules", + ], + "dstIndex": 46, + "srcIndex": 22, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "README.txt", + ], + "dstIndex": 47, + "srcIndex": 22, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "TODOS.txt", + ], + "dstIndex": 48, + "srcIndex": 22, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "pygravitydefier", + ], + "dstIndex": 49, + "srcIndex": 22, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "science.txt", + ], + "dstIndex": 50, + "srcIndex": 22, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "src", + ], + "dstIndex": 51, + "srcIndex": 22, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + ".gitmodules", + ], + "dstIndex": 52, + "srcIndex": 23, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "README.txt", + ], + "dstIndex": 53, + "srcIndex": 23, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "TODOS.txt", + ], + "dstIndex": 54, + "srcIndex": 23, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "pygravitydefier", + ], + "dstIndex": 55, + "srcIndex": 23, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "science.txt", + ], + "dstIndex": 56, + "srcIndex": 23, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "src", + ], + "dstIndex": 57, + "srcIndex": 23, + }, + Object { + "address": Array [ + "sourcecred", + "git", + "INCLUDES", + "3", + "TREE_ENTRY", + "bdff5d94193170015d6cbb549b7b630649428b1f", + "README.txt", + ], + "dstIndex": 58, + "srcIndex": 24, + }, + ], + "nodes": Array [ + Array [ + "sourcecred", + "git", + "BLOB", + "0fb31858c8e3710be77e1dbb8880acf8a5543d82", + ], + Array [ + "sourcecred", + "git", + "BLOB", + "674b0b476989384510304846248b3acd16206782", + ], + Array [ + "sourcecred", + "git", + "BLOB", + "887ad856bbc1373da146106c86cb581ad78cdafe", + ], + Array [ + "sourcecred", + "git", + "BLOB", + "8c6cac301e763aa6d36466e0a775eb804be2c311", + ], + Array [ + "sourcecred", + "git", + "BLOB", + "aea4f28abb23abde151b0ead4063227f8bf6c0b0", + ], + Array [ + "sourcecred", + "git", + "BLOB", + "ddec7477206c30c31b81482e56b877a0b3c2638b", + ], + Array [ + "sourcecred", + "git", + "BLOB", + "f1f2514ca6d7a6a1a0511957021b1995bf9ace1c", + ], + Array [ + "sourcecred", + "git", + "COMMIT", + "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f", + ], + Array [ + "sourcecred", + "git", + "COMMIT", + "69c5aad50eec8f2a0a07c988c3b283a6490eb45b", + ], + Array [ + "sourcecred", + "git", + "COMMIT", + "8d287c3bfbf8455ef30187bf5153ffc1b6eef268", + ], + Array [ + "sourcecred", + "git", + "COMMIT", + "c08ee3a4edea384d5291ffcbf06724a13ed72325", + ], + Array [ + "sourcecred", + "git", + "COMMIT", + "c2b51945e7457546912a8ce158ed9d294558d294", + ], + Array [ + "sourcecred", + "git", + "COMMIT", + "d160cca97611e9dfed642522ad44408d0292e8ea", + ], + Array [ + "sourcecred", + "git", + "COMMIT", + "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + ], + Array [ + "sourcecred", + "git", + "SUBMODULE_COMMIT", + "https://github.com/sourcecred/example-git-submodule.git", + "29ef158bc982733e2ba429fcf73e2f7562244188", + ], + Array [ + "sourcecred", + "git", + "SUBMODULE_COMMIT", + "https://github.com/sourcecred/example-git-submodule.git", + "762c062fbdc7ec198cd693e95d55b374a08ff3e3", + ], + Array [ + "sourcecred", + "git", + "TREE", + "2f7155e359fd0ecb96ffdca66fa45b6ed5792809", + ], + Array [ + "sourcecred", + "git", + "TREE", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + ], + Array [ + "sourcecred", + "git", + "TREE", + "569e1d383759903134df75230d63c0090196d4cb", + ], + Array [ + "sourcecred", + "git", + "TREE", + "78fc9c83023386854c6bfdc5761c0e58f68e226f", + ], + Array [ + "sourcecred", + "git", + "TREE", + "7b79d579b62994faba3b69fdf8aa442586c32681", + ], + Array [ + "sourcecred", + "git", + "TREE", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + ], + Array [ + "sourcecred", + "git", + "TREE", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + ], + Array [ + "sourcecred", + "git", + "TREE", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + ], + Array [ + "sourcecred", + "git", + "TREE", + "bdff5d94193170015d6cbb549b7b630649428b1f", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "2f7155e359fd0ecb96ffdca66fa45b6ed5792809", + "README.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "2f7155e359fd0ecb96ffdca66fa45b6ed5792809", + "science.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + ".gitmodules", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "README.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "pygravitydefier", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8", + "science.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + ".gitmodules", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "README.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "TODOS.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "pygravitydefier", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "science.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "569e1d383759903134df75230d63c0090196d4cb", + "src", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "78fc9c83023386854c6bfdc5761c0e58f68e226f", + "index.py", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "78fc9c83023386854c6bfdc5761c0e58f68e226f", + "quantum_gravity.py", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "7b79d579b62994faba3b69fdf8aa442586c32681", + "index.py", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "7b79d579b62994faba3b69fdf8aa442586c32681", + "quantum_gravity.py", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + ".gitmodules", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "README.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "pygravitydefier", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "science.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed", + "src", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + ".gitmodules", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "README.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "TODOS.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "pygravitydefier", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "science.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "819fc546cea489476ce8dc90785e9ba7753d0a8f", + "src", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + ".gitmodules", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "README.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "TODOS.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "pygravitydefier", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "science.txt", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "bbf3b8b3d26a4f884b5c022d46851f593d329192", + "src", + ], + Array [ + "sourcecred", + "git", + "TREE_ENTRY", + "bdff5d94193170015d6cbb549b7b630649428b1f", + "README.txt", + ], + ], + }, +] +`; + +exports[`plugins/git/createGraph findBecomesEdges works on the example repository 1`] = ` +Array [ + Object { + "becomesEdge": Object { + "from": Object { + "name": "src", + "tree": "819fc546cea489476ce8dc90785e9ba7753d0a8f", + }, + "path": Array [ + "src", + ], + "to": Object { + "name": "src", + "tree": "bbf3b8b3d26a4f884b5c022d46851f593d329192", + }, + }, + "childCommit": "69c5aad50eec8f2a0a07c988c3b283a6490eb45b", + "parentCommit": "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + }, + Object { + "becomesEdge": Object { + "from": Object { + "name": "quantum_gravity.py", + "tree": "7b79d579b62994faba3b69fdf8aa442586c32681", + }, + "path": Array [ + "src", + "quantum_gravity.py", + ], + "to": Object { + "name": "quantum_gravity.py", + "tree": "78fc9c83023386854c6bfdc5761c0e58f68e226f", + }, + }, + "childCommit": "69c5aad50eec8f2a0a07c988c3b283a6490eb45b", + "parentCommit": "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + }, + Object { + "becomesEdge": Object { + "from": Object { + "name": "pygravitydefier", + "tree": "569e1d383759903134df75230d63c0090196d4cb", + }, + "path": Array [ + "pygravitydefier", + ], + "to": Object { + "name": "pygravitydefier", + "tree": "819fc546cea489476ce8dc90785e9ba7753d0a8f", + }, + }, + "childCommit": "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + "parentCommit": "d160cca97611e9dfed642522ad44408d0292e8ea", + }, +] +`; + +exports[`plugins/git/createGraph findBecomesEdgesForCommits works on the example repository 1`] = ` +Array [ + Object { + "from": Object { + "name": "src", + "tree": "819fc546cea489476ce8dc90785e9ba7753d0a8f", + }, + "path": Array [ + "src", + ], + "to": Object { + "name": "src", + "tree": "bbf3b8b3d26a4f884b5c022d46851f593d329192", + }, + }, + Object { + "from": Object { + "name": "quantum_gravity.py", + "tree": "7b79d579b62994faba3b69fdf8aa442586c32681", + }, + "path": Array [ + "src", + "quantum_gravity.py", + ], + "to": Object { + "name": "quantum_gravity.py", + "tree": "78fc9c83023386854c6bfdc5761c0e58f68e226f", + }, + }, +] +`; diff --git a/src/v3/plugins/git/createGraph.js b/src/v3/plugins/git/createGraph.js new file mode 100644 index 0000000..c58592e --- /dev/null +++ b/src/v3/plugins/git/createGraph.js @@ -0,0 +1,217 @@ +// @flow + +import {Graph} from "../../core/graph"; + +import * as GT from "./types"; +import * as GN from "./nodes"; +import * as GE from "./edges"; + +export function createGraph(repository: GT.Repository): Graph { + const creator = new GraphCreator(); + creator.addRepository(repository); + return creator.graph; +} + +class GraphCreator { + graph: Graph; + + constructor() { + this.graph = new Graph(); + } + + addNode(a: GN.StructuredAddress) { + this.graph.addNode(GN.toRaw(a)); + } + + addRepository(repository: GT.Repository) { + const treeAndNameToSubmoduleUrls = this.treeAndNameToSubmoduleUrls( + repository + ); + for (const treeHash of Object.keys(repository.trees)) { + this.addTree(repository.trees[treeHash], treeAndNameToSubmoduleUrls); + } + for (const commitHash of Object.keys(repository.commits)) { + this.addCommit(repository.commits[commitHash]); + } + this.addBecomesEdges(repository); + } + + treeAndNameToSubmoduleUrls(repository: GT.Repository) { + const result: {[tree: GT.Hash]: {[name: string]: string[]}} = {}; + Object.keys(repository.commits).forEach((commitHash) => { + const {treeHash: rootTreeHash, submoduleUrls} = repository.commits[ + commitHash + ]; + Object.keys(submoduleUrls).forEach((path) => { + const parts = path.split("/"); + const [treePath, name] = [ + parts.slice(0, parts.length - 1), + parts[parts.length - 1], + ]; + let tree = repository.trees[rootTreeHash]; + for (const pathComponent of treePath) { + tree = repository.trees[tree.entries[pathComponent].hash]; + if (tree == null) { + return; + } + } + if (result[tree.hash] == null) { + result[tree.hash] = {}; + } + const url = submoduleUrls[path]; + if (result[tree.hash][name] == null) { + result[tree.hash][name] = []; + } + result[tree.hash][name].push(url); + }); + }); + return result; + } + + addCommit(commit: GT.Commit) { + const node: GN.CommitAddress = {type: GN.COMMIT_TYPE, hash: commit.hash}; + const tree: GN.TreeAddress = {type: GN.TREE_TYPE, hash: commit.treeHash}; + this.graph.addNode(GN.toRaw(node)); + this.graph.addNode(GN.toRaw(tree)); + this.graph.addEdge(GE.createEdge.hasTree(node, tree)); + for (const parentHash of commit.parentHashes) { + const parent: GN.CommitAddress = {type: GN.COMMIT_TYPE, hash: parentHash}; + this.graph.addNode(GN.toRaw(parent)); + this.graph.addEdge(GE.createEdge.hasParent(node, parent)); + } + } + + addTree(tree: GT.Tree, treeAndNameToSubmoduleUrls) { + const treeNode: GN.TreeAddress = {type: GN.TREE_TYPE, hash: tree.hash}; + this.graph.addNode(GN.toRaw(treeNode)); + for (const name of Object.keys(tree.entries)) { + const entry = tree.entries[name]; + const entryNode: GN.TreeEntryAddress = { + type: GN.TREE_ENTRY_TYPE, + treeHash: tree.hash, + name: entry.name, + }; + this.graph.addNode(GN.toRaw(entryNode)); + this.graph.addEdge(GE.createEdge.includes(treeNode, entryNode)); + let targets: GN.TreeEntryContentsAddress[] = []; + switch (entry.type) { + case "blob": + targets.push({type: GN.BLOB_TYPE, hash: entry.hash}); + break; + case "tree": + targets.push({type: GN.TREE_TYPE, hash: entry.hash}); + break; + case "commit": + // One entry for each possible URL. + const urls = treeAndNameToSubmoduleUrls[tree.hash][name]; + for (const url of urls) { + targets.push({ + type: GN.SUBMODULE_COMMIT_TYPE, + submoduleUrl: url, + commitHash: entry.hash, + }); + } + break; + default: + // eslint-disable-next-line no-unused-expressions + (entry.type: empty); + throw new Error(String(entry.type)); + } + for (const target of targets) { + this.graph.addNode(GN.toRaw(target)); + this.graph.addEdge(GE.createEdge.hasContents(entryNode, target)); + } + } + } + + addBecomesEdges(repository: GT.Repository) { + for (const { + becomesEdge: {from, to}, + } of findBecomesEdges(repository)) { + const was: GN.TreeEntryAddress = { + type: GN.TREE_ENTRY_TYPE, + treeHash: from.tree, + name: from.name, + }; + const becomes: GN.TreeEntryAddress = { + type: GN.TREE_ENTRY_TYPE, + treeHash: to.tree, + name: to.name, + }; + this.graph.addEdge(GE.createEdge.becomes(was, becomes)); + } + } +} + +export type BecomesEdge = {| + +from: {| + +tree: GT.Hash, + +name: string, + |}, + +to: {| + +tree: GT.Hash, + +name: string, + |}, + +path: $ReadOnlyArray, +|}; + +export function* findBecomesEdgesForCommits( + repository: GT.Repository, + childCommit: GT.Hash, + parentCommit: GT.Hash +): Iterator { + const workUnits = [ + { + path: [], + beforeTreeHash: repository.commits[parentCommit].treeHash, + afterTreeHash: repository.commits[childCommit].treeHash, + }, + ]; + while (workUnits.length > 0) { + const {path, beforeTreeHash, afterTreeHash} = workUnits.pop(); + const beforeTree = repository.trees[beforeTreeHash]; + const afterTree = repository.trees[afterTreeHash]; + for (const name of Object.keys(beforeTree.entries)) { + if (!(name in afterTree.entries)) { + continue; + } + const beforeEntry = beforeTree.entries[name]; + const afterEntry = afterTree.entries[name]; + const subpath = [...path, name]; + if (beforeEntry.hash !== afterEntry.hash) { + yield { + from: {tree: beforeTreeHash, name}, + to: {tree: afterTreeHash, name}, + path: subpath, + }; + } + if (beforeEntry.type === "tree" && afterEntry.type === "tree") { + workUnits.push({ + path: subpath, + beforeTreeHash: beforeEntry.hash, + afterTreeHash: afterEntry.hash, + }); + } + } + } +} + +export function* findBecomesEdges( + repository: GT.Repository +): Iterator<{| + +childCommit: GT.Hash, + +parentCommit: GT.Hash, + +becomesEdge: BecomesEdge, +|}> { + for (const childCommit of Object.keys(repository.commits)) { + for (const parentCommit of repository.commits[childCommit].parentHashes) { + for (const becomesEdge of findBecomesEdgesForCommits( + repository, + childCommit, + parentCommit + )) { + yield {childCommit, parentCommit, becomesEdge}; + } + } + } +} diff --git a/src/v3/plugins/git/createGraph.test.js b/src/v3/plugins/git/createGraph.test.js new file mode 100644 index 0000000..56fff1b --- /dev/null +++ b/src/v3/plugins/git/createGraph.test.js @@ -0,0 +1,246 @@ +// @flow + +import cloneDeep from "lodash.clonedeep"; + +import { + type BecomesEdge, + createGraph, + findBecomesEdges, + findBecomesEdgesForCommits, +} from "./createGraph"; +import type {Hash, Tree} from "./types"; + +const makeData = () => cloneDeep(require("./demoData/example-git")); + +describe("plugins/git/createGraph", () => { + describe("createGraph", () => { + it("processes a simple repository", () => { + expect(createGraph(makeData())).toMatchSnapshot(); + }); + }); + + describe("findBecomesEdgesForCommits", () => { + function fromTrees( + beforeTree: Hash, + afterTree: Hash, + trees: {[Hash]: Tree} + ): BecomesEdge[] { + const repo = { + commits: { + commit1: { + hash: "commit1", + parentHashes: [], + treeHash: beforeTree, + submoduleUrls: {}, + }, + commit2: { + hash: "commit2", + parentHashes: ["commit1"], + treeHash: afterTree, + submoduleUrls: {}, + }, + }, + trees, + }; + return Array.from(findBecomesEdgesForCommits(repo, "commit2", "commit1")); + } + + it("works on the example repository", () => { + const data = makeData(); + const childCommitHash = "69c5aad50eec8f2a0a07c988c3b283a6490eb45b"; + expect(data.commits[childCommitHash]).toEqual(expect.anything()); + expect(data.commits[childCommitHash].parentHashes).toHaveLength(1); + const parentCommitHash = data.commits[childCommitHash].parentHashes[0]; + expect( + Array.from( + findBecomesEdgesForCommits(data, childCommitHash, parentCommitHash) + ) + ).toMatchSnapshot(); + }); + + it("works on empty trees", () => { + expect( + fromTrees("tree1", "tree2", { + tree1: { + hash: "tree1", + entries: {}, + }, + tree2: { + hash: "tree2", + entries: {}, + }, + }) + ).toEqual([]); + }); + + it("finds differences and non-differences at the root", () => { + expect( + fromTrees("tree1", "tree2", { + tree1: { + hash: "tree1", + entries: { + "color.txt": { + type: "blob", + name: "color.txt", + hash: "blue", + }, + "number.txt": { + type: "blob", + name: "number.txt", + hash: "twelve", + }, + }, + }, + tree2: { + hash: "tree2", + entries: { + "color.txt": { + type: "blob", + name: "color.txt", + hash: "yellow", + }, + "number.txt": { + type: "blob", + name: "number.txt", + hash: "twelve", + }, + }, + }, + }) + ).toEqual([ + { + from: { + tree: "tree1", + name: "color.txt", + }, + to: { + tree: "tree2", + name: "color.txt", + }, + path: ["color.txt"], + }, + ]); + }); + + it("handles cases where files of the same name appear in different trees", () => { + const result = fromTrees("tree1", "tree2", { + tree1: { + hash: "tree1", + entries: { + "color.txt": { + type: "blob", + name: "color.txt", + hash: "blue", + }, + "number.txt": { + type: "blob", + name: "number.txt", + hash: "twelve", + }, + mirror_universe: { + type: "tree", + name: "mirror_universe", + hash: "eert1", + }, + }, + }, + eert1: { + hash: "eert1", + entries: { + "color.txt": { + type: "blob", + name: "color.txt", + hash: "eulb", + }, + "number.txt": { + type: "blob", + name: "number.txt", + hash: "evlewt", + }, + }, + }, + tree2: { + hash: "tree2", + entries: { + "color.txt": { + type: "blob", + name: "color.txt", + hash: "yellow", + }, + "number.txt": { + type: "blob", + name: "number.txt", + hash: "twelve", + }, + mirror_universe: { + type: "tree", + name: "mirror_universe", + hash: "eert2", + }, + }, + }, + eert2: { + hash: "eert1", + entries: { + "color.txt": { + type: "blob", + name: "color.txt", + hash: "eulb", + }, + "number.txt": { + type: "blob", + name: "number.txt", + hash: "neetneves", + }, + }, + }, + }); + const expected = [ + { + from: { + tree: "tree1", + name: "color.txt", + }, + to: { + tree: "tree2", + name: "color.txt", + }, + path: ["color.txt"], + }, + { + from: { + tree: "eert1", + name: "number.txt", + }, + to: { + tree: "eert2", + name: "number.txt", + }, + path: ["mirror_universe", "number.txt"], + }, + { + from: { + tree: "tree1", + name: "mirror_universe", + }, + to: { + tree: "tree2", + name: "mirror_universe", + }, + path: ["mirror_universe"], + }, + ]; + expect(result).toEqual(expect.arrayContaining(expected)); + expect(expected).toEqual( + expect.arrayContaining((result.slice(): $ReadOnlyArray).slice()) + ); + }); + }); + + describe("findBecomesEdges", () => { + it("works on the example repository", () => { + const data = makeData(); + expect(Array.from(findBecomesEdges(data))).toMatchSnapshot(); + }); + }); +});