From 1bd444a33b212015f068bc62f220a5d6ef5b68e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dandelion=20Man=C3=A9?=
Date: Tue, 15 May 2018 19:10:30 -0700
Subject: [PATCH] Refactor Git plugin to use NodeReference (#288)
See #286 for context.
I also upgraded client code in src/app.
Test plan:
Unit tests are extensive, including testing that `get`, `ref`, and
constructors are overriden on every `GitReference` and `GitPorcelain`
type that is exposed to clients.
Paired with @wchargin
---
src/app/credExplorer/pagerankTable.js | 2 +-
src/plugins/git/porcelain.js | 373 +++++++++++++++++---------
src/plugins/git/porcelain.test.js | 131 ++++++---
src/plugins/git/render.js | 20 +-
src/plugins/git/render.test.js | 26 +-
5 files changed, 366 insertions(+), 186 deletions(-)
diff --git a/src/app/credExplorer/pagerankTable.js b/src/app/credExplorer/pagerankTable.js
index d344278..48fe2ee 100644
--- a/src/app/credExplorer/pagerankTable.js
+++ b/src/app/credExplorer/pagerankTable.js
@@ -31,7 +31,7 @@ function nodeDescription(ref) {
return githubNodeDescription(ref);
}
case GIT_PLUGIN_NAME: {
- return gitNodeDescription(ref.graph(), ref.address());
+ return gitNodeDescription(ref);
}
default: {
return stringify(ref.address());
diff --git a/src/plugins/git/porcelain.js b/src/plugins/git/porcelain.js
index 03243f6..8bdb633 100644
--- a/src/plugins/git/porcelain.js
+++ b/src/plugins/git/porcelain.js
@@ -21,7 +21,8 @@
import stringify from "json-stable-stringify";
import {Graph} from "../../core/graph";
-import type {Node} from "../../core/graph";
+import {NodeReference, NodePorcelain} from "../../core/porcelain";
+import type {Edge} from "../../core/graph";
import type {Address} from "../../core/address";
import type {
@@ -30,214 +31,320 @@ import type {
BlobNodePayload,
TreeNodePayload,
CommitNodePayload,
+ IncludesEdgePayload,
NodePayload,
NodeType,
Hash,
} from "./types";
-import {GIT_PLUGIN_NAME} from "./types";
+import {
+ BECOMES_EDGE_TYPE,
+ BLOB_NODE_TYPE,
+ COMMIT_NODE_TYPE,
+ GIT_PLUGIN_NAME,
+ HAS_CONTENTS_EDGE_TYPE,
+ HAS_PARENT_EDGE_TYPE,
+ HAS_TREE_EDGE_TYPE,
+ INCLUDES_EDGE_TYPE,
+ SUBMODULE_COMMIT_NODE_TYPE,
+ TREE_ENTRY_NODE_TYPE,
+ TREE_NODE_TYPE,
+} from "./types";
import {commitAddress} from "./address";
-export class PorcelainGraph {
+function assertAddressType(address: Address, t: NodeType) {
+ if (address.type !== t) {
+ throw new Error(
+ `Expected entity at ${stringify(address)} to have type ${t}`
+ );
+ }
+}
+
+export class GraphPorcelain {
graph: Graph;
constructor(graph: Graph) {
this.graph = graph;
}
- // Note that this method is presently unsafe, as the hash may not exist.
- // In the future, we will come up with a general case solution to have
- // the type system verify that returned porcelains must be existence-tested
- // before their properties are usable.
- commitByHash(h: Hash): Commit {
+ commitByHash(h: Hash): CommitReference {
const addr = commitAddress(h);
- return new Commit(this.graph, addr);
+ const nodeReference = new NodeReference(this.graph, addr);
+ return new CommitReference(nodeReference);
}
}
-export type GitPorcelain = Commit | Blob | Tree | TreeEntry | SubmoduleCommit;
-
-class BaseGitPorcelain {
- graph: Graph;
- nodeAddress: Address;
-
- constructor(graph: Graph, nodeAddress: Address) {
- if (nodeAddress.pluginName !== GIT_PLUGIN_NAME) {
- throw new Error(
- `Tried to create Git porcelain for node from plugin: ${
- nodeAddress.pluginName
- }`
- );
+export class GitReference<+T: NodePayload> extends NodeReference {
+ constructor(ref: NodeReference) {
+ const addr = ref.address();
+ if (addr.pluginName !== GIT_PLUGIN_NAME) {
+ throw new Error(`Wrong plugin name ${addr.pluginName} for Git plugin!`);
}
- this.graph = graph;
- this.nodeAddress = nodeAddress;
+ super(ref.graph(), addr);
}
type(): NodeType {
- return (this.address().type: any);
+ return ((super.type(): string): any);
}
- node(): Node {
- return this.graph.node(this.nodeAddress);
- }
-
- address(): Address {
- return this.nodeAddress;
+ get(): ?GitPorcelain {
+ const nodePorcelain = super.get();
+ if (nodePorcelain != null) {
+ return new GitPorcelain(nodePorcelain);
+ }
}
}
-export class Commit extends BaseGitPorcelain {
- static from(n: BaseGitPorcelain): Commit {
- if (n.type() !== "COMMIT") {
- throw new Error(`Unable to cast ${n.type()} to Commit`);
+export class GitPorcelain<+T: NodePayload> extends NodePorcelain {
+ constructor(nodePorcelain: NodePorcelain) {
+ if (nodePorcelain.ref().address().pluginName !== GIT_PLUGIN_NAME) {
+ throw new Error(
+ `Wrong plugin name ${
+ nodePorcelain.ref().address().pluginName
+ } for Git plugin!`
+ );
}
- return new Commit(n.graph, n.nodeAddress);
+ super(nodePorcelain.ref(), nodePorcelain.node());
+ }
+}
+
+export class CommitReference extends GitReference {
+ constructor(ref: NodeReference) {
+ super(ref);
+ assertAddressType(ref.address(), COMMIT_NODE_TYPE);
}
hash(): Hash {
return this.address().id;
}
- parents(): Commit[] {
- return this.graph
- .neighborhood(this.nodeAddress, {
- nodeType: "COMMIT",
- edgeType: "HAS_PARENT",
- direction: "OUT",
- })
- .map(({neighbor}) => new Commit(this.graph, neighbor));
+ parents(): CommitReference[] {
+ return this.neighbors({
+ nodeType: COMMIT_NODE_TYPE,
+ edgeType: HAS_PARENT_EDGE_TYPE,
+ direction: "OUT",
+ }).map(({ref}) => new CommitReference(ref));
}
- tree(): Tree {
- const trees = this.graph
- .neighborhood(this.nodeAddress, {
- nodeType: "TREE",
- edgeType: "HAS_TREE",
- direction: "OUT",
- })
- .map(({neighbor}) => new Tree(this.graph, neighbor));
+ tree(): TreeReference {
+ const trees = this.neighbors({
+ nodeType: TREE_NODE_TYPE,
+ edgeType: HAS_TREE_EDGE_TYPE,
+ direction: "OUT",
+ }).map(({ref}) => new TreeReference(ref));
if (trees.length !== 1) {
throw new Error(
- `Commit ${stringify(this.nodeAddress)} has wrong number of trees`
+ `Commit ${stringify(this.address())} has wrong number of trees`
);
}
return trees[0];
}
+
+ get(): ?CommitPorcelain {
+ const x = super.get();
+ if (x != null) {
+ return new CommitPorcelain(x);
+ }
+ }
}
-export class Tree extends BaseGitPorcelain {
- static from(n: BaseGitPorcelain): Tree {
- if (n.type() !== "TREE") {
- throw new Error(`Unable to cast ${n.type()} to Tree`);
- }
- return new Tree(n.graph, n.nodeAddress);
+export class CommitPorcelain extends GitPorcelain {
+ constructor(nodePorcelain: NodePorcelain) {
+ assertAddressType(nodePorcelain.ref().address(), COMMIT_NODE_TYPE);
+ super(nodePorcelain);
+ }
+ ref(): CommitReference {
+ return new CommitReference(super.ref());
+ }
+}
+
+export class TreeReference extends GitReference {
+ constructor(ref: NodeReference) {
+ super(ref);
+ assertAddressType(ref.address(), TREE_NODE_TYPE);
}
hash(): Hash {
return this.address().id;
}
- entries(): TreeEntry[] {
- return this.graph
- .neighborhood(this.nodeAddress, {
- nodeType: "TREE_ENTRY",
- edgeType: "INCLUDES",
- direction: "OUT",
- })
- .map(({neighbor}) => new TreeEntry(this.graph, neighbor));
+ entries(): TreeEntryReference[] {
+ return this.neighbors({
+ nodeType: TREE_ENTRY_NODE_TYPE,
+ edgeType: INCLUDES_EDGE_TYPE,
+ direction: "OUT",
+ }).map(({ref}) => new TreeEntryReference(ref));
}
- entry(name: string): ?TreeEntry {
- return this.entries().filter((te) => te.name() === name)[0];
+ entry(name: string): ?TreeEntryReference {
+ return this.neighbors({
+ nodeType: TREE_ENTRY_NODE_TYPE,
+ edgeType: INCLUDES_EDGE_TYPE,
+ direction: "OUT",
+ })
+ .filter(
+ ({edge}) => (edge: Edge).payload.name === name
+ )
+ .map(({ref}) => new TreeEntryReference(ref))[0];
+ }
+
+ get(): ?TreePorcelain {
+ const x = super.get();
+ if (x != null) {
+ return new TreePorcelain(x);
+ }
}
}
-export class TreeEntry extends BaseGitPorcelain {
- static from(n: BaseGitPorcelain): TreeEntry {
- if (n.type() !== "TREE_ENTRY") {
- throw new Error(`Unable to cast ${n.type()} to TreeEntry`);
- }
- return new TreeEntry(n.graph, n.nodeAddress);
+export class TreePorcelain extends GitPorcelain {
+ constructor(nodePorcelain: NodePorcelain) {
+ assertAddressType(nodePorcelain.ref().address(), TREE_NODE_TYPE);
+ super(nodePorcelain);
+ }
+ ref(): TreeReference {
+ return new TreeReference(super.ref());
+ }
+}
+
+export class TreeEntryReference extends GitReference {
+ constructor(ref: NodeReference) {
+ super(ref);
+ assertAddressType(ref.address(), TREE_ENTRY_NODE_TYPE);
}
name(): string {
- return this.node().payload.name;
+ const includesEdges = this.neighbors({
+ nodeType: TREE_NODE_TYPE,
+ edgeType: INCLUDES_EDGE_TYPE,
+ direction: "IN",
+ }).map(({edge}) => edge);
+ if (includesEdges.length !== 1) {
+ throw new Error(
+ `Malformed tree structure at ${stringify(this.address())}`
+ );
+ }
+ return (includesEdges[0]: Edge).payload.name;
}
- evolvesTo(): TreeEntry[] {
- return this.graph
- .neighborhood(this.nodeAddress, {
- nodeType: "TREE_ENTRY",
- edgeType: "BECOMES",
- direction: "OUT",
- })
- .map(({neighbor}) => new TreeEntry(this.graph, neighbor));
+ evolvesTo(): TreeEntryReference[] {
+ return this.neighbors({
+ nodeType: TREE_ENTRY_NODE_TYPE,
+ edgeType: BECOMES_EDGE_TYPE,
+ direction: "OUT",
+ }).map(({ref}) => new TreeEntryReference(ref));
}
- evolvesFrom(): TreeEntry[] {
- return this.graph
- .neighborhood(this.nodeAddress, {
- nodeType: "TREE_ENTRY",
- edgeType: "BECOMES",
- direction: "IN",
- })
- .map(({neighbor}) => new TreeEntry(this.graph, neighbor));
+ evolvesFrom(): TreeEntryReference[] {
+ return this.neighbors({
+ nodeType: TREE_ENTRY_NODE_TYPE,
+ edgeType: BECOMES_EDGE_TYPE,
+ direction: "IN",
+ }).map(({ref}) => new TreeEntryReference(ref));
}
- /*
- * May be a single Tree, single Blob, or zero or more
- * SubmoduleCommits. The Tree or Blob are put in an array for
- * consistency.
- *
- */
- contents(): Tree[] | Blob[] | SubmoduleCommit[] {
- // Note: the function has the correct type signature,
- // but as-implemented it should be a flow error.
- // When flow fixes this, maintain the current method signature.
- return this.graph
- .neighborhood(this.nodeAddress, {
- edgeType: "HAS_CONTENTS",
- direction: "OUT",
- })
- .map(({neighbor}) => {
- switch (neighbor.type) {
- case "BLOB":
- return new Blob(this.graph, neighbor);
- case "TREE":
- return new Tree(this.graph, neighbor);
- case "SUBMODULE_COMMIT":
- return new SubmoduleCommit(this.graph, neighbor);
- default:
- throw new Error(`Neighbor had invalid type ${neighbor.type}`);
- }
- });
+ blob(): ?BlobReference {
+ return this.neighbors({
+ edgeType: HAS_CONTENTS_EDGE_TYPE,
+ nodeType: BLOB_NODE_TYPE,
+ direction: "OUT",
+ }).map(({ref}) => new BlobReference(ref))[0];
+ }
+
+ tree(): ?TreeReference {
+ return this.neighbors({
+ edgeType: HAS_CONTENTS_EDGE_TYPE,
+ nodeType: TREE_NODE_TYPE,
+ direction: "OUT",
+ }).map(({ref}) => new TreeReference(ref))[0];
+ }
+
+ submoduleCommits(): SubmoduleCommitReference[] {
+ return this.neighbors({
+ edgeType: HAS_CONTENTS_EDGE_TYPE,
+ nodeType: SUBMODULE_COMMIT_NODE_TYPE,
+ direction: "OUT",
+ }).map(({ref}) => new SubmoduleCommitReference(ref));
+ }
+
+ get(): ?TreeEntryPorcelain {
+ const x = super.get();
+ if (x != null) {
+ return new TreeEntryPorcelain(x);
+ }
}
}
-export class Blob extends BaseGitPorcelain {
- static from(n: BaseGitPorcelain): Blob {
- if (n.type() !== "BLOB") {
- throw new Error(`Unable to cast ${n.type()} to Blob`);
- }
- return new Blob(n.graph, n.nodeAddress);
+export class TreeEntryPorcelain extends GitPorcelain {
+ constructor(nodePorcelain: NodePorcelain) {
+ assertAddressType(nodePorcelain.ref().address(), TREE_ENTRY_NODE_TYPE);
+ super(nodePorcelain);
+ }
+ ref(): TreeEntryReference {
+ return new TreeEntryReference(super.ref());
+ }
+}
+
+export class BlobReference extends GitReference {
+ constructor(ref: NodeReference) {
+ super(ref);
+ assertAddressType(ref.address(), BLOB_NODE_TYPE);
}
hash(): Hash {
- return this.nodeAddress.id;
+ return this.address().id;
+ }
+
+ get(): ?BlobPorcelain {
+ const x = super.get();
+ if (x != null) {
+ return new BlobPorcelain(x);
+ }
}
}
-export class SubmoduleCommit extends BaseGitPorcelain {
- static from(n: BaseGitPorcelain): SubmoduleCommit {
- if (n.type() !== "SUBMODULE_COMMIT") {
- throw new Error(`Unable to cast ${n.type()} to SubmoduleCommit`);
+export class BlobPorcelain extends GitPorcelain {
+ constructor(nodePorcelain: NodePorcelain) {
+ assertAddressType(nodePorcelain.ref().address(), BLOB_NODE_TYPE);
+ super(nodePorcelain);
+ }
+ ref(): BlobReference {
+ return new BlobReference(super.ref());
+ }
+}
+
+export class SubmoduleCommitReference extends GitReference<
+ SubmoduleCommitPayload
+> {
+ constructor(ref: NodeReference) {
+ super(ref);
+ assertAddressType(ref.address(), SUBMODULE_COMMIT_NODE_TYPE);
+ }
+
+ get(): ?SubmoduleCommitPorcelain {
+ const x = super.get();
+ if (x != null) {
+ return new SubmoduleCommitPorcelain(x);
}
- return new SubmoduleCommit(n.graph, n.nodeAddress);
+ }
+}
+
+export class SubmoduleCommitPorcelain extends GitPorcelain<
+ SubmoduleCommitPayload
+> {
+ constructor(nodePorcelain: NodePorcelain) {
+ assertAddressType(
+ nodePorcelain.ref().address(),
+ SUBMODULE_COMMIT_NODE_TYPE
+ );
+ super(nodePorcelain);
}
url(): string {
- return this.node().payload.url;
+ return this.payload().url;
}
hash(): Hash {
- return this.node().payload.hash;
+ return this.payload().hash;
+ }
+ ref(): SubmoduleCommitReference {
+ return new SubmoduleCommitReference(super.ref());
}
}
diff --git a/src/plugins/git/porcelain.test.js b/src/plugins/git/porcelain.test.js
index 98d884f..a605914 100644
--- a/src/plugins/git/porcelain.test.js
+++ b/src/plugins/git/porcelain.test.js
@@ -1,14 +1,32 @@
// @flow
import cloneDeep from "lodash.clonedeep";
-import {PorcelainGraph, Blob, Tree, SubmoduleCommit} from "./porcelain";
+import {
+ COMMIT_NODE_TYPE,
+ TREE_NODE_TYPE,
+ SUBMODULE_COMMIT_NODE_TYPE,
+ BLOB_NODE_TYPE,
+} from "./types";
+import {
+ GraphPorcelain,
+ BlobReference,
+ TreeReference,
+ BlobPorcelain,
+ TreePorcelain,
+ TreeEntryReference,
+ TreeEntryPorcelain,
+ SubmoduleCommitPorcelain,
+ SubmoduleCommitReference,
+ CommitReference,
+ CommitPorcelain,
+} from "./porcelain";
import {createGraph} from "./createGraph";
-const makePorcelainGraph = () =>
- new PorcelainGraph(createGraph(cloneDeep(require("./demoData/example-git"))));
+const makeGraphPorcelain = () =>
+ new GraphPorcelain(createGraph(cloneDeep(require("./demoData/example-git"))));
const getCommit = () => {
- const graph = makePorcelainGraph();
+ const graph = makeGraphPorcelain();
const commitHash = "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f";
const commit = graph.commitByHash(commitHash);
if (commit.hash() !== commitHash) {
@@ -18,6 +36,30 @@ const getCommit = () => {
}
return commit;
};
+
+function really(x: ?T): T {
+ if (x == null) {
+ throw new Error(`It wasn't, really`);
+ }
+ return x;
+}
+
+const getTree = () => {
+ return getCommit().tree();
+};
+
+const getTreeEntry = (name: string) => {
+ return really(getTree().entry(name));
+};
+
+const getBlob = () => {
+ return really(getTreeEntry("science.txt").blob());
+};
+
+const getSubmoduleCommit = () => {
+ return getTreeEntry("pygravitydefier").submoduleCommits()[0];
+};
+
describe("Git porcelain", () => {
it("commits have hashes", () => {
const commit = getCommit();
@@ -34,18 +76,17 @@ describe("Git porcelain", () => {
it("some commits have no parents", () => {
const commitHash = "c2b51945e7457546912a8ce158ed9d294558d294";
- const commit = makePorcelainGraph().commitByHash(commitHash);
+ const commit = makeGraphPorcelain().commitByHash(commitHash);
expect(commit.parents()).toEqual([]);
});
it("Commits have a unique, hash-identified tree", () => {
- const tree = getCommit().tree();
+ const tree = getTree();
expect(tree.hash()).toEqual("7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed");
});
it("Trees have tree entries", () => {
- const tree = getCommit().tree();
- const entries = tree.entries();
+ const entries = getTree().entries();
expect(entries).toHaveLength(5);
const entryNames = entries.map((x) => x.name());
expect(entryNames).toEqual(
@@ -59,35 +100,18 @@ describe("Git porcelain", () => {
);
});
it("Tree entries can have blobs", () => {
- const entry = getCommit()
- .tree()
- .entry("science.txt");
- if (entry == null) {
- throw new Error("Where is science?!");
- }
- const blob: Blob = Blob.from(entry.contents()[0]);
+ const blob = getBlob();
expect(blob.hash()).toEqual("f1f2514ca6d7a6a1a0511957021b1995bf9ace1c");
});
it("Tree entries can have trees", () => {
- const entry = getCommit()
- .tree()
- .entry("src");
- if (entry == null) {
- throw new Error("Where is src?!");
- }
- const tree: Tree = Tree.from(entry.contents()[0]);
+ const treeEntry = getTreeEntry("src");
+ const tree = really(treeEntry.tree());
expect(tree.hash()).toEqual("78fc9c83023386854c6bfdc5761c0e58f68e226f");
});
it("Tree entries can have submodule commits", () => {
- const entry = getCommit()
- .tree()
- .entry("pygravitydefier");
- if (entry == null) {
- throw new Error("We've stopped defying gravity :(");
- }
- const sc: SubmoduleCommit = SubmoduleCommit.from(entry.contents()[0]);
+ const sc = really(getSubmoduleCommit().get());
expect(sc.hash()).toEqual("29ef158bc982733e2ba429fcf73e2f7562244188");
expect(sc.url()).toEqual(
"https://github.com/sourcecred/example-git-submodule.git"
@@ -97,7 +121,7 @@ describe("Git porcelain", () => {
it("Tree entries can evolve to/from other tree entries", () => {
const parentCommitHash = "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc";
const childCommitHash = "69c5aad50eec8f2a0a07c988c3b283a6490eb45b";
- const graph = makePorcelainGraph();
+ const graph = makeGraphPorcelain();
const parentEntry = graph
.commitByHash(parentCommitHash)
.tree()
@@ -112,4 +136,51 @@ describe("Git porcelain", () => {
expect(parentEntry.evolvesTo()).toEqual([childEntry]);
expect(childEntry.evolvesFrom()).toEqual([parentEntry]);
});
+
+ describe("flow and runtime type verification", () => {
+ it("for Commit", () => {
+ const c: CommitReference = getCommit();
+ const p: CommitPorcelain = really(c.get());
+ const _: CommitReference = p.ref();
+ const err = `to have type ${COMMIT_NODE_TYPE}`;
+ expect(() => new CommitReference(getTree())).toThrow(err);
+ expect(() => new CommitPorcelain(really(getTree().get()))).toThrow(err);
+ });
+ it("for Tree", () => {
+ const c: TreeReference = getTree();
+ const p: TreePorcelain = really(c.get());
+ const _: TreeReference = p.ref();
+ const err = `to have type ${TREE_NODE_TYPE}`;
+ expect(() => new TreeReference(getCommit())).toThrow(err);
+ expect(() => new TreePorcelain(really(getCommit().get()))).toThrow(err);
+ });
+ it("for TreeEntry", () => {
+ const c: TreeEntryReference = getTreeEntry("src");
+ const p: TreeEntryPorcelain = really(c.get());
+ const _: TreeEntryReference = p.ref();
+ const err = `to have type ${TREE_NODE_TYPE}`;
+ expect(() => new TreeEntryReference(getCommit())).toThrow(err);
+ expect(() => new TreeEntryPorcelain(really(getTree().get()))).toThrow(
+ err
+ );
+ });
+ it("for Blob", () => {
+ const c: BlobReference = getBlob();
+ const p: BlobPorcelain = really(c.get());
+ const _: BlobReference = p.ref();
+ const err = `to have type ${BLOB_NODE_TYPE}`;
+ expect(() => new BlobReference(getCommit())).toThrow(err);
+ expect(() => new BlobPorcelain(really(getTree().get()))).toThrow(err);
+ });
+ it("for SubmoduleCommit", () => {
+ const c: SubmoduleCommitReference = getSubmoduleCommit();
+ const p: SubmoduleCommitPorcelain = really(c.get());
+ const _: SubmoduleCommitReference = p.ref();
+ const err = `to have type ${SUBMODULE_COMMIT_NODE_TYPE}`;
+ expect(() => new SubmoduleCommitReference(getCommit())).toThrow(err);
+ expect(
+ () => new SubmoduleCommitPorcelain(really(getTree().get()))
+ ).toThrow(err);
+ });
+ });
});
diff --git a/src/plugins/git/render.js b/src/plugins/git/render.js
index d809b44..cfd395c 100644
--- a/src/plugins/git/render.js
+++ b/src/plugins/git/render.js
@@ -1,14 +1,15 @@
// @flow
-import type {Address} from "../../core/address";
-import type {Graph} from "../../core/graph";
-import type {NodeType, SubmoduleCommitPayload} from "./types";
+import {NodeReference} from "../../core/porcelain";
+import {SubmoduleCommitReference, GitReference} from "./porcelain";
/**
* Describe a node provided by the Git plugin.
*/
-export function nodeDescription(graph: Graph, address: Address) {
- const type: NodeType = (address.type: any);
+export function nodeDescription(nodeReference: NodeReference) {
+ const gReference = new GitReference(nodeReference);
+ const type = gReference.type();
+ const address = gReference.address();
switch (type) {
case "COMMIT":
return `commit ${address.id}`;
@@ -17,8 +18,13 @@ export function nodeDescription(graph: Graph, address: Address) {
case "BLOB":
return `blob ${address.id}`;
case "SUBMODULE_COMMIT": {
- const payload: SubmoduleCommitPayload = graph.node(address).payload;
- return `submodule commit ${payload.hash} in ${payload.url}`;
+ const scRef = new SubmoduleCommitReference(gReference);
+ const scPorcelain = scRef.get();
+ if (scPorcelain != null) {
+ return `submodule commit ${scPorcelain.hash()} in ${scPorcelain.url()}`;
+ } else {
+ return `submodule commit [unknown]`;
+ }
}
case "TREE_ENTRY":
return `entry ${address.id}`;
diff --git a/src/plugins/git/render.test.js b/src/plugins/git/render.test.js
index 5c6458a..d62e2a8 100644
--- a/src/plugins/git/render.test.js
+++ b/src/plugins/git/render.test.js
@@ -9,6 +9,7 @@ import type {
} from "./types";
import type {Node} from "../../core/graph";
import {Graph} from "../../core/graph";
+import {NodeReference} from "../../core/porcelain";
import {_makeAddress, commitAddress} from "./address";
import {nodeDescription} from "./render";
import {submoduleCommitId, treeEntryId} from "./types";
@@ -19,9 +20,8 @@ describe("nodeDescription", () => {
address: commitAddress("cafebabe"),
payload: (({}: any): {||}),
};
- expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
- "commit cafebabe"
- );
+ const ref = new NodeReference(new Graph().addNode(node), node.address);
+ expect(nodeDescription(ref)).toEqual("commit cafebabe");
});
it("describes trees", () => {
@@ -29,9 +29,8 @@ describe("nodeDescription", () => {
address: _makeAddress("TREE", "deadbeef"),
payload: (({}: any): {||}),
};
- expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
- "tree deadbeef"
- );
+ const ref = new NodeReference(new Graph().addNode(node), node.address);
+ expect(nodeDescription(ref)).toEqual("tree deadbeef");
});
it("describes blobs", () => {
@@ -39,9 +38,8 @@ describe("nodeDescription", () => {
address: _makeAddress("BLOB", "01010101"),
payload: (({}: any): {||}),
};
- expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
- "blob 01010101"
- );
+ const ref = new NodeReference(new Graph().addNode(node), node.address);
+ expect(nodeDescription(ref)).toEqual("blob 01010101");
});
it("describes submodule commits", () => {
@@ -51,9 +49,8 @@ describe("nodeDescription", () => {
address: _makeAddress("SUBMODULE_COMMIT", submoduleCommitId(hash, url)),
payload: {hash, url},
};
- expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
- `submodule commit ${hash} in ${url}`
- );
+ const ref = new NodeReference(new Graph().addNode(node), node.address);
+ expect(nodeDescription(ref)).toEqual(`submodule commit ${hash} in ${url}`);
});
it("describes tree entries", () => {
@@ -63,8 +60,7 @@ describe("nodeDescription", () => {
address: _makeAddress("TREE_ENTRY", treeEntryId(tree, name)),
payload: {name},
};
- expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
- `entry ${tree}:${name}`
- );
+ const ref = new NodeReference(new Graph().addNode(node), node.address);
+ expect(nodeDescription(ref)).toEqual(`entry ${tree}:${name}`);
});
});