From 16e8e399e6c869ebdcb350b41cdba62680750d43 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Mon, 30 Apr 2018 18:08:40 -0700 Subject: [PATCH] Add commit parent edges in the Git graph (#178) Test Plan: To verify the snapshot change, either believe the programmatic tests, or use the following script to verify that the right edges were added: ```bash #!/bin/bash set -eu example_repo="$(mktemp -d)" yarn backend >/dev/null 2>/dev/null node bin/createExampleRepo.js "${example_repo}" expected() { git -C "${example_repo}" log --format='%H %P' \ | awk '{ if (NF > 1) { print $2 " " $1 } }' \ | sort } actual() { git diff HEAD^..HEAD | grep -A 1 -F -e src -e dst \ | sed -n 's/^+.*"id": "\(.\+\)".*/\1 /p' \ | tr -d $'\n' | cat - <(echo) \ | fold -s -w 82 | sed 's/ *$//' \ | sort } diff -u <(expected) <(actual) ``` wchargin-branch: graph-commit-parents --- .../__snapshots__/createGraph.test.js.snap | 102 ++++++++++++++++++ src/plugins/git/createGraph.js | 25 ++++- src/plugins/git/createGraph.test.js | 12 ++- src/plugins/git/types.js | 24 +++++ 4 files changed, 159 insertions(+), 4 deletions(-) diff --git a/src/plugins/git/__snapshots__/createGraph.test.js.snap b/src/plugins/git/__snapshots__/createGraph.test.js.snap index 7b00eaf..770b7e0 100644 --- a/src/plugins/git/__snapshots__/createGraph.test.js.snap +++ b/src/plugins/git/__snapshots__/createGraph.test.js.snap @@ -33,6 +33,23 @@ Object { "type": "TREE", }, }, + "{\\"id\\":\\"3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f^1\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"HAS_PARENT\\"}": Object { + "dst": Object { + "id": "69c5aad50eec8f2a0a07c988c3b283a6490eb45b", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + "payload": Object { + "parentIndex": 1, + }, + "src": Object { + "id": "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + }, "{\\"id\\":\\"3dfb84795e07341b05fad3a0d5a55f8304b2d7d8:.gitmodules\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"INCLUDES\\"}": Object { "dst": Object { "id": "3dfb84795e07341b05fad3a0d5a55f8304b2d7d8:.gitmodules", @@ -183,6 +200,23 @@ Object { "type": "TREE", }, }, + "{\\"id\\":\\"69c5aad50eec8f2a0a07c988c3b283a6490eb45b^1\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"HAS_PARENT\\"}": Object { + "dst": Object { + "id": "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + "payload": Object { + "parentIndex": 1, + }, + "src": Object { + "id": "69c5aad50eec8f2a0a07c988c3b283a6490eb45b", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + }, "{\\"id\\":\\"78fc9c83023386854c6bfdc5761c0e58f68e226f:index.py\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"INCLUDES\\"}": Object { "dst": Object { "id": "78fc9c83023386854c6bfdc5761c0e58f68e226f:index.py", @@ -408,6 +442,23 @@ Object { "type": "TREE", }, }, + "{\\"id\\":\\"8d287c3bfbf8455ef30187bf5153ffc1b6eef268^1\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"HAS_PARENT\\"}": Object { + "dst": Object { + "id": "c08ee3a4edea384d5291ffcbf06724a13ed72325", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + "payload": Object { + "parentIndex": 1, + }, + "src": Object { + "id": "8d287c3bfbf8455ef30187bf5153ffc1b6eef268", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + }, "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"2f7155e359fd0ecb96ffdca66fa45b6ed5792809:README.txt\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/git-beta\\\\\\",\\\\\\"repositoryName\\\\\\":\\\\\\"sourcecred/example-git\\\\\\",\\\\\\"type\\\\\\":\\\\\\"TREE_ENTRY\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"0fb31858c8e3710be77e1dbb8880acf8a5543d82\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/git-beta\\\\\\",\\\\\\"repositoryName\\\\\\":\\\\\\"sourcecred/example-git\\\\\\",\\\\\\"type\\\\\\":\\\\\\"BLOB\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"HAS_CONTENTS\\"}": Object { "dst": Object { "id": "0fb31858c8e3710be77e1dbb8880acf8a5543d82", @@ -1053,6 +1104,57 @@ Object { "type": "TREE", }, }, + "{\\"id\\":\\"c08ee3a4edea384d5291ffcbf06724a13ed72325^1\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"HAS_PARENT\\"}": Object { + "dst": Object { + "id": "c2b51945e7457546912a8ce158ed9d294558d294", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + "payload": Object { + "parentIndex": 1, + }, + "src": Object { + "id": "c08ee3a4edea384d5291ffcbf06724a13ed72325", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + }, + "{\\"id\\":\\"d160cca97611e9dfed642522ad44408d0292e8ea^1\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"HAS_PARENT\\"}": Object { + "dst": Object { + "id": "8d287c3bfbf8455ef30187bf5153ffc1b6eef268", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + "payload": Object { + "parentIndex": 1, + }, + "src": Object { + "id": "d160cca97611e9dfed642522ad44408d0292e8ea", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + }, + "{\\"id\\":\\"e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc^1\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"HAS_PARENT\\"}": Object { + "dst": Object { + "id": "d160cca97611e9dfed642522ad44408d0292e8ea", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + "payload": Object { + "parentIndex": 1, + }, + "src": Object { + "id": "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc", + "pluginName": "sourcecred/git-beta", + "repositoryName": "sourcecred/example-git", + "type": "COMMIT", + }, + }, }, "nodes": Object { "{\\"id\\":\\"0fb31858c8e3710be77e1dbb8880acf8a5543d82\\",\\"pluginName\\":\\"sourcecred/git-beta\\",\\"repositoryName\\":\\"sourcecred/example-git\\",\\"type\\":\\"BLOB\\"}": Object { diff --git a/src/plugins/git/createGraph.js b/src/plugins/git/createGraph.js index 2fab270..139ff6e 100644 --- a/src/plugins/git/createGraph.js +++ b/src/plugins/git/createGraph.js @@ -18,8 +18,10 @@ import { TREE_ENTRY_NODE_TYPE, INCLUDES_EDGE_TYPE, HAS_CONTENTS_EDGE_TYPE, + HAS_PARENT_EDGE_TYPE, HAS_TREE_EDGE_TYPE, GIT_PLUGIN_NAME, + hasParentEdgeId, includesEdgeId, treeEntryId, } from "./types"; @@ -61,7 +63,7 @@ class GitGraphCreator { address: this.makeAddress(TREE_NODE_TYPE, commit.treeHash), payload: {}, }; - const edge = { + const hasTreeEdge = { address: this.makeAddress( HAS_TREE_EDGE_TYPE, edgeID(commitNode.address, treeNode.address) @@ -70,10 +72,27 @@ class GitGraphCreator { dst: treeNode.address, payload: {}, }; - return new Graph() + const result = new Graph() .addNode(commitNode) .addNode(treeNode) - .addEdge(edge); + .addEdge(hasTreeEdge); + commit.parentHashes.forEach((parentHash, index) => { + const oneBasedParentIndex = index + 1; + const parentAddress = this.makeAddress(COMMIT_NODE_TYPE, parentHash); + const parentEdge = { + address: this.makeAddress( + HAS_PARENT_EDGE_TYPE, + hasParentEdgeId(commit.hash, oneBasedParentIndex) + ), + src: commitNode.address, + dst: parentAddress, + payload: { + parentIndex: oneBasedParentIndex, + }, + }; + result.addEdge(parentEdge); + }); + return result; } treeGraph(tree: Tree) { diff --git a/src/plugins/git/createGraph.test.js b/src/plugins/git/createGraph.test.js index 083da0a..7f76148 100644 --- a/src/plugins/git/createGraph.test.js +++ b/src/plugins/git/createGraph.test.js @@ -8,6 +8,7 @@ import { COMMIT_NODE_TYPE, GIT_PLUGIN_NAME, HAS_CONTENTS_EDGE_TYPE, + HAS_PARENT_EDGE_TYPE, HAS_TREE_EDGE_TYPE, INCLUDES_EDGE_TYPE, TREE_ENTRY_NODE_TYPE, @@ -41,13 +42,22 @@ describe("createGraph", () => { id: hash, }; expect(graph.node(address)).toEqual({address, payload: {}}); - expect(graph.neighborhood(address)).toHaveLength(1); expect( graph.neighborhood(address, { nodeType: TREE_NODE_TYPE, edgeType: HAS_TREE_EDGE_TYPE, }) ).toHaveLength(1); + expect( + graph.neighborhood(address, { + nodeType: COMMIT_NODE_TYPE, + edgeType: HAS_PARENT_EDGE_TYPE, + direction: "OUT", + }) + ).toHaveLength(data.commits[hash].parentHashes.length); + expect(graph.neighborhood(address, {direction: "OUT"})).toHaveLength( + 1 + data.commits[hash].parentHashes.length + ); }); }); diff --git a/src/plugins/git/types.js b/src/plugins/git/types.js index f8d549d..489f9fa 100644 --- a/src/plugins/git/types.js +++ b/src/plugins/git/types.js @@ -55,6 +55,28 @@ export type NodeType = // Edges +// CommitNode -> CommitNode +export const HAS_PARENT_EDGE_TYPE: "HAS_PARENT" = "HAS_PARENT"; +export type HasParentEdgePayload = {| + +parentIndex: number, // one-based +|}; +export function hasParentEdgeId( + childCommitHash: Hash, + oneBasedParentIndex: number +) { + if ( + !isFinite(oneBasedParentIndex) || + oneBasedParentIndex !== Math.floor(oneBasedParentIndex) || + oneBasedParentIndex < 1 + ) { + throw new Error( + "Expected positive integer parent index, " + + `but got: ${String(oneBasedParentIndex)}` + ); + } + return `${childCommitHash}^${String(oneBasedParentIndex)}`; +} + // CommitNode -> TreeNode export const HAS_TREE_EDGE_TYPE: "HAS_TREE" = "HAS_TREE"; export type HasTreeEdgePayload = {||}; @@ -76,12 +98,14 @@ export type HasContentsEdgePayload = {||}; export type EdgeType = | typeof HAS_TREE_EDGE_TYPE + | typeof HAS_PARENT_EDGE_TYPE | typeof INCLUDES_EDGE_TYPE | typeof BECOMES_EDGE_TYPE | typeof HAS_CONTENTS_EDGE_TYPE; export type EdgePayload = | HasTreeEdgePayload + | HasParentEdgePayload | IncludesEdgePayload | BecomesEdgePayload | HasContentsEdgePayload;