mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-20 00:08:10 +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);
|
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());
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -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}`;
|
||||||
|
@ -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}`);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user