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
This commit is contained in:
Dandelion Mané 2018-05-15 19:10:30 -07:00 committed by GitHub
parent bb77c36626
commit 1bd444a33b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 366 additions and 186 deletions

View File

@ -31,7 +31,7 @@ function nodeDescription(ref) {
return githubNodeDescription(ref); return githubNodeDescription(ref);
} }
case GIT_PLUGIN_NAME: { case GIT_PLUGIN_NAME: {
return gitNodeDescription(ref.graph(), ref.address()); return gitNodeDescription(ref);
} }
default: { default: {
return stringify(ref.address()); return stringify(ref.address());

View File

@ -21,7 +21,8 @@
import stringify from "json-stable-stringify"; import stringify from "json-stable-stringify";
import {Graph} from "../../core/graph"; 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 {Address} from "../../core/address";
import type { import type {
@ -30,214 +31,320 @@ import type {
BlobNodePayload, BlobNodePayload,
TreeNodePayload, TreeNodePayload,
CommitNodePayload, CommitNodePayload,
IncludesEdgePayload,
NodePayload, NodePayload,
NodeType, NodeType,
Hash, Hash,
} from "./types"; } 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"; 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<any, any>; graph: Graph<any, any>;
constructor(graph: Graph<any, any>) { constructor(graph: Graph<any, any>) {
this.graph = graph; this.graph = graph;
} }
// Note that this method is presently unsafe, as the hash may not exist. commitByHash(h: Hash): CommitReference {
// 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 {
const addr = commitAddress(h); 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; export class GitReference<+T: NodePayload> extends NodeReference<T> {
constructor(ref: NodeReference<any>) {
class BaseGitPorcelain<T: NodePayload> { const addr = ref.address();
graph: Graph<any, any>; if (addr.pluginName !== GIT_PLUGIN_NAME) {
nodeAddress: Address; throw new Error(`Wrong plugin name ${addr.pluginName} for Git plugin!`);
constructor(graph: Graph<any, any>, nodeAddress: Address) {
if (nodeAddress.pluginName !== GIT_PLUGIN_NAME) {
throw new Error(
`Tried to create Git porcelain for node from plugin: ${
nodeAddress.pluginName
}`
);
} }
this.graph = graph; super(ref.graph(), addr);
this.nodeAddress = nodeAddress;
} }
type(): NodeType { type(): NodeType {
return (this.address().type: any); return ((super.type(): string): any);
} }
node(): Node<T> { get(): ?GitPorcelain<T> {
return this.graph.node(this.nodeAddress); const nodePorcelain = super.get();
} if (nodePorcelain != null) {
return new GitPorcelain(nodePorcelain);
address(): Address { }
return this.nodeAddress;
} }
} }
export class Commit extends BaseGitPorcelain<CommitNodePayload> { export class GitPorcelain<+T: NodePayload> extends NodePorcelain<T> {
static from(n: BaseGitPorcelain<any>): Commit { constructor(nodePorcelain: NodePorcelain<any>) {
if (n.type() !== "COMMIT") { if (nodePorcelain.ref().address().pluginName !== GIT_PLUGIN_NAME) {
throw new Error(`Unable to cast ${n.type()} to Commit`); 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<CommitNodePayload> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), COMMIT_NODE_TYPE);
} }
hash(): Hash { hash(): Hash {
return this.address().id; return this.address().id;
} }
parents(): Commit[] { parents(): CommitReference[] {
return this.graph return this.neighbors({
.neighborhood(this.nodeAddress, { nodeType: COMMIT_NODE_TYPE,
nodeType: "COMMIT", edgeType: HAS_PARENT_EDGE_TYPE,
edgeType: "HAS_PARENT", direction: "OUT",
direction: "OUT", }).map(({ref}) => new CommitReference(ref));
})
.map(({neighbor}) => new Commit(this.graph, neighbor));
} }
tree(): Tree { tree(): TreeReference {
const trees = this.graph const trees = this.neighbors({
.neighborhood(this.nodeAddress, { nodeType: TREE_NODE_TYPE,
nodeType: "TREE", edgeType: HAS_TREE_EDGE_TYPE,
edgeType: "HAS_TREE", direction: "OUT",
direction: "OUT", }).map(({ref}) => new TreeReference(ref));
})
.map(({neighbor}) => new Tree(this.graph, neighbor));
if (trees.length !== 1) { if (trees.length !== 1) {
throw new Error( throw new Error(
`Commit ${stringify(this.nodeAddress)} has wrong number of trees` `Commit ${stringify(this.address())} has wrong number of trees`
); );
} }
return trees[0]; return trees[0];
} }
get(): ?CommitPorcelain {
const x = super.get();
if (x != null) {
return new CommitPorcelain(x);
}
}
} }
export class Tree extends BaseGitPorcelain<TreeNodePayload> { export class CommitPorcelain extends GitPorcelain<CommitNodePayload> {
static from(n: BaseGitPorcelain<any>): Tree { constructor(nodePorcelain: NodePorcelain<any>) {
if (n.type() !== "TREE") { assertAddressType(nodePorcelain.ref().address(), COMMIT_NODE_TYPE);
throw new Error(`Unable to cast ${n.type()} to Tree`); super(nodePorcelain);
} }
return new Tree(n.graph, n.nodeAddress); ref(): CommitReference {
return new CommitReference(super.ref());
}
}
export class TreeReference extends GitReference<TreeNodePayload> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), TREE_NODE_TYPE);
} }
hash(): Hash { hash(): Hash {
return this.address().id; return this.address().id;
} }
entries(): TreeEntry[] { entries(): TreeEntryReference[] {
return this.graph return this.neighbors({
.neighborhood(this.nodeAddress, { nodeType: TREE_ENTRY_NODE_TYPE,
nodeType: "TREE_ENTRY", edgeType: INCLUDES_EDGE_TYPE,
edgeType: "INCLUDES", direction: "OUT",
direction: "OUT", }).map(({ref}) => new TreeEntryReference(ref));
})
.map(({neighbor}) => new TreeEntry(this.graph, neighbor));
} }
entry(name: string): ?TreeEntry { entry(name: string): ?TreeEntryReference {
return this.entries().filter((te) => te.name() === name)[0]; return this.neighbors({
nodeType: TREE_ENTRY_NODE_TYPE,
edgeType: INCLUDES_EDGE_TYPE,
direction: "OUT",
})
.filter(
({edge}) => (edge: Edge<IncludesEdgePayload>).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<TreeEntryNodePayload> { export class TreePorcelain extends GitPorcelain<TreeNodePayload> {
static from(n: BaseGitPorcelain<any>): TreeEntry { constructor(nodePorcelain: NodePorcelain<any>) {
if (n.type() !== "TREE_ENTRY") { assertAddressType(nodePorcelain.ref().address(), TREE_NODE_TYPE);
throw new Error(`Unable to cast ${n.type()} to TreeEntry`); super(nodePorcelain);
} }
return new TreeEntry(n.graph, n.nodeAddress); ref(): TreeReference {
return new TreeReference(super.ref());
}
}
export class TreeEntryReference extends GitReference<TreeEntryNodePayload> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), TREE_ENTRY_NODE_TYPE);
} }
name(): string { 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<IncludesEdgePayload>).payload.name;
} }
evolvesTo(): TreeEntry[] { evolvesTo(): TreeEntryReference[] {
return this.graph return this.neighbors({
.neighborhood(this.nodeAddress, { nodeType: TREE_ENTRY_NODE_TYPE,
nodeType: "TREE_ENTRY", edgeType: BECOMES_EDGE_TYPE,
edgeType: "BECOMES", direction: "OUT",
direction: "OUT", }).map(({ref}) => new TreeEntryReference(ref));
})
.map(({neighbor}) => new TreeEntry(this.graph, neighbor));
} }
evolvesFrom(): TreeEntry[] { evolvesFrom(): TreeEntryReference[] {
return this.graph return this.neighbors({
.neighborhood(this.nodeAddress, { nodeType: TREE_ENTRY_NODE_TYPE,
nodeType: "TREE_ENTRY", edgeType: BECOMES_EDGE_TYPE,
edgeType: "BECOMES", direction: "IN",
direction: "IN", }).map(({ref}) => new TreeEntryReference(ref));
})
.map(({neighbor}) => new TreeEntry(this.graph, neighbor));
} }
/* blob(): ?BlobReference {
* May be a single Tree, single Blob, or zero or more return this.neighbors({
* SubmoduleCommits. The Tree or Blob are put in an array for edgeType: HAS_CONTENTS_EDGE_TYPE,
* consistency. nodeType: BLOB_NODE_TYPE,
* direction: "OUT",
*/ }).map(({ref}) => new BlobReference(ref))[0];
contents(): Tree[] | Blob[] | SubmoduleCommit[] { }
// Note: the function has the correct type signature,
// but as-implemented it should be a flow error. tree(): ?TreeReference {
// When flow fixes this, maintain the current method signature. return this.neighbors({
return this.graph edgeType: HAS_CONTENTS_EDGE_TYPE,
.neighborhood(this.nodeAddress, { nodeType: TREE_NODE_TYPE,
edgeType: "HAS_CONTENTS", direction: "OUT",
direction: "OUT", }).map(({ref}) => new TreeReference(ref))[0];
}) }
.map(({neighbor}) => {
switch (neighbor.type) { submoduleCommits(): SubmoduleCommitReference[] {
case "BLOB": return this.neighbors({
return new Blob(this.graph, neighbor); edgeType: HAS_CONTENTS_EDGE_TYPE,
case "TREE": nodeType: SUBMODULE_COMMIT_NODE_TYPE,
return new Tree(this.graph, neighbor); direction: "OUT",
case "SUBMODULE_COMMIT": }).map(({ref}) => new SubmoduleCommitReference(ref));
return new SubmoduleCommit(this.graph, neighbor); }
default:
throw new Error(`Neighbor had invalid type ${neighbor.type}`); get(): ?TreeEntryPorcelain {
} const x = super.get();
}); if (x != null) {
return new TreeEntryPorcelain(x);
}
} }
} }
export class Blob extends BaseGitPorcelain<BlobNodePayload> { export class TreeEntryPorcelain extends GitPorcelain<TreeEntryNodePayload> {
static from(n: BaseGitPorcelain<any>): Blob { constructor(nodePorcelain: NodePorcelain<any>) {
if (n.type() !== "BLOB") { assertAddressType(nodePorcelain.ref().address(), TREE_ENTRY_NODE_TYPE);
throw new Error(`Unable to cast ${n.type()} to Blob`); super(nodePorcelain);
} }
return new Blob(n.graph, n.nodeAddress); ref(): TreeEntryReference {
return new TreeEntryReference(super.ref());
}
}
export class BlobReference extends GitReference<BlobNodePayload> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), BLOB_NODE_TYPE);
} }
hash(): Hash { 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<SubmoduleCommitPayload> { export class BlobPorcelain extends GitPorcelain<BlobNodePayload> {
static from(n: BaseGitPorcelain<any>): SubmoduleCommit { constructor(nodePorcelain: NodePorcelain<any>) {
if (n.type() !== "SUBMODULE_COMMIT") { assertAddressType(nodePorcelain.ref().address(), BLOB_NODE_TYPE);
throw new Error(`Unable to cast ${n.type()} to SubmoduleCommit`); super(nodePorcelain);
}
ref(): BlobReference {
return new BlobReference(super.ref());
}
}
export class SubmoduleCommitReference extends GitReference<
SubmoduleCommitPayload
> {
constructor(ref: NodeReference<any>) {
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<any>) {
assertAddressType(
nodePorcelain.ref().address(),
SUBMODULE_COMMIT_NODE_TYPE
);
super(nodePorcelain);
} }
url(): string { url(): string {
return this.node().payload.url; return this.payload().url;
} }
hash(): Hash { hash(): Hash {
return this.node().payload.hash; return this.payload().hash;
}
ref(): SubmoduleCommitReference {
return new SubmoduleCommitReference(super.ref());
} }
} }

View File

@ -1,14 +1,32 @@
// @flow // @flow
import cloneDeep from "lodash.clonedeep"; 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"; import {createGraph} from "./createGraph";
const makePorcelainGraph = () => const makeGraphPorcelain = () =>
new PorcelainGraph(createGraph(cloneDeep(require("./demoData/example-git")))); new GraphPorcelain(createGraph(cloneDeep(require("./demoData/example-git"))));
const getCommit = () => { const getCommit = () => {
const graph = makePorcelainGraph(); const graph = makeGraphPorcelain();
const commitHash = "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f"; const commitHash = "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f";
const commit = graph.commitByHash(commitHash); const commit = graph.commitByHash(commitHash);
if (commit.hash() !== commitHash) { if (commit.hash() !== commitHash) {
@ -18,6 +36,30 @@ const getCommit = () => {
} }
return commit; return commit;
}; };
function really<T>(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", () => { describe("Git porcelain", () => {
it("commits have hashes", () => { it("commits have hashes", () => {
const commit = getCommit(); const commit = getCommit();
@ -34,18 +76,17 @@ describe("Git porcelain", () => {
it("some commits have no parents", () => { it("some commits have no parents", () => {
const commitHash = "c2b51945e7457546912a8ce158ed9d294558d294"; const commitHash = "c2b51945e7457546912a8ce158ed9d294558d294";
const commit = makePorcelainGraph().commitByHash(commitHash); const commit = makeGraphPorcelain().commitByHash(commitHash);
expect(commit.parents()).toEqual([]); expect(commit.parents()).toEqual([]);
}); });
it("Commits have a unique, hash-identified tree", () => { it("Commits have a unique, hash-identified tree", () => {
const tree = getCommit().tree(); const tree = getTree();
expect(tree.hash()).toEqual("7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed"); expect(tree.hash()).toEqual("7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed");
}); });
it("Trees have tree entries", () => { it("Trees have tree entries", () => {
const tree = getCommit().tree(); const entries = getTree().entries();
const entries = tree.entries();
expect(entries).toHaveLength(5); expect(entries).toHaveLength(5);
const entryNames = entries.map((x) => x.name()); const entryNames = entries.map((x) => x.name());
expect(entryNames).toEqual( expect(entryNames).toEqual(
@ -59,35 +100,18 @@ describe("Git porcelain", () => {
); );
}); });
it("Tree entries can have blobs", () => { it("Tree entries can have blobs", () => {
const entry = getCommit() const blob = getBlob();
.tree()
.entry("science.txt");
if (entry == null) {
throw new Error("Where is science?!");
}
const blob: Blob = Blob.from(entry.contents()[0]);
expect(blob.hash()).toEqual("f1f2514ca6d7a6a1a0511957021b1995bf9ace1c"); expect(blob.hash()).toEqual("f1f2514ca6d7a6a1a0511957021b1995bf9ace1c");
}); });
it("Tree entries can have trees", () => { it("Tree entries can have trees", () => {
const entry = getCommit() const treeEntry = getTreeEntry("src");
.tree() const tree = really(treeEntry.tree());
.entry("src");
if (entry == null) {
throw new Error("Where is src?!");
}
const tree: Tree = Tree.from(entry.contents()[0]);
expect(tree.hash()).toEqual("78fc9c83023386854c6bfdc5761c0e58f68e226f"); expect(tree.hash()).toEqual("78fc9c83023386854c6bfdc5761c0e58f68e226f");
}); });
it("Tree entries can have submodule commits", () => { it("Tree entries can have submodule commits", () => {
const entry = getCommit() const sc = really(getSubmoduleCommit().get());
.tree()
.entry("pygravitydefier");
if (entry == null) {
throw new Error("We've stopped defying gravity :(");
}
const sc: SubmoduleCommit = SubmoduleCommit.from(entry.contents()[0]);
expect(sc.hash()).toEqual("29ef158bc982733e2ba429fcf73e2f7562244188"); expect(sc.hash()).toEqual("29ef158bc982733e2ba429fcf73e2f7562244188");
expect(sc.url()).toEqual( expect(sc.url()).toEqual(
"https://github.com/sourcecred/example-git-submodule.git" "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", () => { it("Tree entries can evolve to/from other tree entries", () => {
const parentCommitHash = "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc"; const parentCommitHash = "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc";
const childCommitHash = "69c5aad50eec8f2a0a07c988c3b283a6490eb45b"; const childCommitHash = "69c5aad50eec8f2a0a07c988c3b283a6490eb45b";
const graph = makePorcelainGraph(); const graph = makeGraphPorcelain();
const parentEntry = graph const parentEntry = graph
.commitByHash(parentCommitHash) .commitByHash(parentCommitHash)
.tree() .tree()
@ -112,4 +136,51 @@ describe("Git porcelain", () => {
expect(parentEntry.evolvesTo()).toEqual([childEntry]); expect(parentEntry.evolvesTo()).toEqual([childEntry]);
expect(childEntry.evolvesFrom()).toEqual([parentEntry]); 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);
});
});
}); });

View File

@ -1,14 +1,15 @@
// @flow // @flow
import type {Address} from "../../core/address"; import {NodeReference} from "../../core/porcelain";
import type {Graph} from "../../core/graph"; import {SubmoduleCommitReference, GitReference} from "./porcelain";
import type {NodeType, SubmoduleCommitPayload} from "./types";
/** /**
* Describe a node provided by the Git plugin. * Describe a node provided by the Git plugin.
*/ */
export function nodeDescription(graph: Graph<any, any>, address: Address) { export function nodeDescription(nodeReference: NodeReference<any>) {
const type: NodeType = (address.type: any); const gReference = new GitReference(nodeReference);
const type = gReference.type();
const address = gReference.address();
switch (type) { switch (type) {
case "COMMIT": case "COMMIT":
return `commit ${address.id}`; return `commit ${address.id}`;
@ -17,8 +18,13 @@ export function nodeDescription(graph: Graph<any, any>, address: Address) {
case "BLOB": case "BLOB":
return `blob ${address.id}`; return `blob ${address.id}`;
case "SUBMODULE_COMMIT": { case "SUBMODULE_COMMIT": {
const payload: SubmoduleCommitPayload = graph.node(address).payload; const scRef = new SubmoduleCommitReference(gReference);
return `submodule commit ${payload.hash} in ${payload.url}`; const scPorcelain = scRef.get();
if (scPorcelain != null) {
return `submodule commit ${scPorcelain.hash()} in ${scPorcelain.url()}`;
} else {
return `submodule commit [unknown]`;
}
} }
case "TREE_ENTRY": case "TREE_ENTRY":
return `entry ${address.id}`; return `entry ${address.id}`;

View File

@ -9,6 +9,7 @@ import type {
} from "./types"; } from "./types";
import type {Node} from "../../core/graph"; import type {Node} from "../../core/graph";
import {Graph} from "../../core/graph"; import {Graph} from "../../core/graph";
import {NodeReference} from "../../core/porcelain";
import {_makeAddress, commitAddress} from "./address"; import {_makeAddress, commitAddress} from "./address";
import {nodeDescription} from "./render"; import {nodeDescription} from "./render";
import {submoduleCommitId, treeEntryId} from "./types"; import {submoduleCommitId, treeEntryId} from "./types";
@ -19,9 +20,8 @@ describe("nodeDescription", () => {
address: commitAddress("cafebabe"), address: commitAddress("cafebabe"),
payload: (({}: any): {||}), payload: (({}: any): {||}),
}; };
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual( const ref = new NodeReference(new Graph().addNode(node), node.address);
"commit cafebabe" expect(nodeDescription(ref)).toEqual("commit cafebabe");
);
}); });
it("describes trees", () => { it("describes trees", () => {
@ -29,9 +29,8 @@ describe("nodeDescription", () => {
address: _makeAddress("TREE", "deadbeef"), address: _makeAddress("TREE", "deadbeef"),
payload: (({}: any): {||}), payload: (({}: any): {||}),
}; };
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual( const ref = new NodeReference(new Graph().addNode(node), node.address);
"tree deadbeef" expect(nodeDescription(ref)).toEqual("tree deadbeef");
);
}); });
it("describes blobs", () => { it("describes blobs", () => {
@ -39,9 +38,8 @@ describe("nodeDescription", () => {
address: _makeAddress("BLOB", "01010101"), address: _makeAddress("BLOB", "01010101"),
payload: (({}: any): {||}), payload: (({}: any): {||}),
}; };
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual( const ref = new NodeReference(new Graph().addNode(node), node.address);
"blob 01010101" expect(nodeDescription(ref)).toEqual("blob 01010101");
);
}); });
it("describes submodule commits", () => { it("describes submodule commits", () => {
@ -51,9 +49,8 @@ describe("nodeDescription", () => {
address: _makeAddress("SUBMODULE_COMMIT", submoduleCommitId(hash, url)), address: _makeAddress("SUBMODULE_COMMIT", submoduleCommitId(hash, url)),
payload: {hash, url}, payload: {hash, url},
}; };
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual( const ref = new NodeReference(new Graph().addNode(node), node.address);
`submodule commit ${hash} in ${url}` expect(nodeDescription(ref)).toEqual(`submodule commit ${hash} in ${url}`);
);
}); });
it("describes tree entries", () => { it("describes tree entries", () => {
@ -63,8 +60,7 @@ describe("nodeDescription", () => {
address: _makeAddress("TREE_ENTRY", treeEntryId(tree, name)), address: _makeAddress("TREE_ENTRY", treeEntryId(tree, name)),
payload: {name}, payload: {name},
}; };
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual( const ref = new NodeReference(new Graph().addNode(node), node.address);
`entry ${tree}:${name}` expect(nodeDescription(ref)).toEqual(`entry ${tree}:${name}`);
);
}); });
}); });