mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-17 06:56:36 +00:00
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:
parent
bb77c36626
commit
1bd444a33b
@ -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());
|
||||
|
@ -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<any, any>;
|
||||
constructor(graph: Graph<any, any>) {
|
||||
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<T: NodePayload> {
|
||||
graph: Graph<any, any>;
|
||||
nodeAddress: Address;
|
||||
|
||||
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
|
||||
}`
|
||||
);
|
||||
export class GitReference<+T: NodePayload> extends NodeReference<T> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
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<T> {
|
||||
return this.graph.node(this.nodeAddress);
|
||||
}
|
||||
|
||||
address(): Address {
|
||||
return this.nodeAddress;
|
||||
get(): ?GitPorcelain<T> {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new GitPorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Commit extends BaseGitPorcelain<CommitNodePayload> {
|
||||
static from(n: BaseGitPorcelain<any>): Commit {
|
||||
if (n.type() !== "COMMIT") {
|
||||
throw new Error(`Unable to cast ${n.type()} to Commit`);
|
||||
export class GitPorcelain<+T: NodePayload> extends NodePorcelain<T> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
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<CommitNodePayload> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
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<TreeNodePayload> {
|
||||
static from(n: BaseGitPorcelain<any>): 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<CommitNodePayload> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(nodePorcelain.ref().address(), COMMIT_NODE_TYPE);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
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 {
|
||||
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<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> {
|
||||
static from(n: BaseGitPorcelain<any>): 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<TreeNodePayload> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(nodePorcelain.ref().address(), TREE_NODE_TYPE);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
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 {
|
||||
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[] {
|
||||
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<BlobNodePayload> {
|
||||
static from(n: BaseGitPorcelain<any>): 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<TreeEntryNodePayload> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(nodePorcelain.ref().address(), TREE_ENTRY_NODE_TYPE);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
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 {
|
||||
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> {
|
||||
static from(n: BaseGitPorcelain<any>): SubmoduleCommit {
|
||||
if (n.type() !== "SUBMODULE_COMMIT") {
|
||||
throw new Error(`Unable to cast ${n.type()} to SubmoduleCommit`);
|
||||
export class BlobPorcelain extends GitPorcelain<BlobNodePayload> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
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<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 {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -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<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", () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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<any, any>, address: Address) {
|
||||
const type: NodeType = (address.type: any);
|
||||
export function nodeDescription(nodeReference: NodeReference<any>) {
|
||||
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<any, any>, 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}`;
|
||||
|
@ -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}`);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user