diff --git a/src/plugins/github/__snapshots__/api.test.js.snap b/src/plugins/github/__snapshots__/api.test.js.snap index 0f64938..fac1948 100644 --- a/src/plugins/github/__snapshots__/api.test.js.snap +++ b/src/plugins/github/__snapshots__/api.test.js.snap @@ -49,7 +49,7 @@ Object { } `; -exports[`GitHub porcelain API has wrappers for PullRequests 1`] = ` +exports[`GitHub porcelain API has wrappers for PullRequests Merged 1`] = ` Object { "address": Object { "id": "https://github.com/sourcecred/example-github/pull/3", diff --git a/src/plugins/github/__snapshots__/parser.test.js.snap b/src/plugins/github/__snapshots__/parser.test.js.snap index 08b00e3..fa308b4 100644 --- a/src/plugins/github/__snapshots__/parser.test.js.snap +++ b/src/plugins/github/__snapshots__/parser.test.js.snap @@ -199,6 +199,21 @@ Object { "type": "PULL_REQUEST_REVIEW", }, }, + "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/5\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"6d5b3aa31ebb68a06ceb46bbd6cf49b6ccd6f5e6\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/git-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"COMMIT\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/github-beta\\",\\"type\\":\\"MERGED_AS\\"}": Object { + "dst": Object { + "id": "6d5b3aa31ebb68a06ceb46bbd6cf49b6ccd6f5e6", + "pluginName": "sourcecred/git-beta", + "type": "COMMIT", + }, + "payload": Object { + "hash": "6d5b3aa31ebb68a06ceb46bbd6cf49b6ccd6f5e6", + }, + "src": Object { + "id": "https://github.com/sourcecred/example-github/pull/5", + "pluginName": "sourcecred/github-beta", + "type": "PULL_REQUEST", + }, + }, "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/5\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100313899\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST_REVIEW\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/github-beta\\",\\"type\\":\\"CONTAINS\\"}": Object { "dst": Object { "id": "https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100313899", @@ -358,6 +373,21 @@ Object { "type": "PULL_REQUEST", }, }, + "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/3\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"0a223346b4e6dec0127b1e6aa892c4ee0424b66a\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/git-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"COMMIT\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/github-beta\\",\\"type\\":\\"MERGED_AS\\"}": Object { + "dst": Object { + "id": "0a223346b4e6dec0127b1e6aa892c4ee0424b66a", + "pluginName": "sourcecred/git-beta", + "type": "COMMIT", + }, + "payload": Object { + "hash": "0a223346b4e6dec0127b1e6aa892c4ee0424b66a", + }, + "src": Object { + "id": "https://github.com/sourcecred/example-github/pull/3", + "pluginName": "sourcecred/github-beta", + "type": "PULL_REQUEST", + }, + }, "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/3\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/3#issuecomment-369162222\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"COMMENT\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/github-beta\\",\\"type\\":\\"CONTAINS\\"}": Object { "dst": Object { "id": "https://github.com/sourcecred/example-github/pull/3#issuecomment-369162222", @@ -1830,6 +1860,21 @@ Object { "type": "COMMENT", }, }, + "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/3\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"0a223346b4e6dec0127b1e6aa892c4ee0424b66a\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/git-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"COMMIT\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/github-beta\\",\\"type\\":\\"MERGED_AS\\"}": Object { + "dst": Object { + "id": "0a223346b4e6dec0127b1e6aa892c4ee0424b66a", + "pluginName": "sourcecred/git-beta", + "type": "COMMIT", + }, + "payload": Object { + "hash": "0a223346b4e6dec0127b1e6aa892c4ee0424b66a", + }, + "src": Object { + "id": "https://github.com/sourcecred/example-github/pull/3", + "pluginName": "sourcecred/github-beta", + "type": "PULL_REQUEST", + }, + }, "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/3\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/3#issuecomment-369162222\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"COMMENT\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/github-beta\\",\\"type\\":\\"CONTAINS\\"}": Object { "dst": Object { "id": "https://github.com/sourcecred/example-github/pull/3#issuecomment-369162222", @@ -1856,6 +1901,21 @@ Object { "type": "PULL_REQUEST_REVIEW", }, }, + "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/5\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"6d5b3aa31ebb68a06ceb46bbd6cf49b6ccd6f5e6\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/git-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"COMMIT\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/github-beta\\",\\"type\\":\\"MERGED_AS\\"}": Object { + "dst": Object { + "id": "6d5b3aa31ebb68a06ceb46bbd6cf49b6ccd6f5e6", + "pluginName": "sourcecred/git-beta", + "type": "COMMIT", + }, + "payload": Object { + "hash": "6d5b3aa31ebb68a06ceb46bbd6cf49b6ccd6f5e6", + }, + "src": Object { + "id": "https://github.com/sourcecred/example-github/pull/5", + "pluginName": "sourcecred/github-beta", + "type": "PULL_REQUEST", + }, + }, "{\\"id\\":\\"[{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/5\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST\\\\\\"},{\\\\\\"id\\\\\\":\\\\\\"https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100313899\\\\\\",\\\\\\"pluginName\\\\\\":\\\\\\"sourcecred/github-beta\\\\\\",\\\\\\"type\\\\\\":\\\\\\"PULL_REQUEST_REVIEW\\\\\\"}]\\",\\"pluginName\\":\\"sourcecred/github-beta\\",\\"type\\":\\"CONTAINS\\"}": Object { "dst": Object { "id": "https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100313899", diff --git a/src/plugins/github/api.js b/src/plugins/github/api.js index 110a67e..95c5941 100644 --- a/src/plugins/github/api.js +++ b/src/plugins/github/api.js @@ -6,31 +6,35 @@ import {Graph} from "../../core/graph"; import type {Node} from "../../core/graph"; import type {Address} from "../../core/address"; import type { - NodePayload, - EdgePayload, - NodeType, - IssueNodePayload, - PullRequestNodePayload, - PullRequestReviewNodePayload, - PullRequestReviewCommentNodePayload, - PullRequestReviewState, - CommentNodePayload, AuthorNodePayload, AuthorSubtype, + CommentNodePayload, + EdgePayload, + IssueNodePayload, + MergedAsEdgePayload, + NodePayload, + NodeType, + PullRequestNodePayload, + PullRequestReviewCommentNodePayload, + PullRequestReviewNodePayload, + PullRequestReviewState, } from "./types"; import { - CONTAINS_EDGE_TYPE, - COMMENT_NODE_TYPE, AUTHORS_EDGE_TYPE, AUTHOR_NODE_TYPE, + COMMENT_NODE_TYPE, + CONTAINS_EDGE_TYPE, ISSUE_NODE_TYPE, + MERGED_AS_EDGE_TYPE, PULL_REQUEST_NODE_TYPE, - PULL_REQUEST_REVIEW_NODE_TYPE, PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE, + PULL_REQUEST_REVIEW_NODE_TYPE, REFERENCES_EDGE_TYPE, } from "./types"; +import {COMMIT_NODE_TYPE} from "../git/types"; + export type Entity = | Issue | PullRequest @@ -221,6 +225,25 @@ export class PullRequest extends Commentable { }) .map(({neighbor}) => new PullRequestReview(this.graph, neighbor)); } + mergeCommitHash(): ?string { + const mergeEdge = this.graph + .neighborhood(this.nodeAddress, { + edgeType: MERGED_AS_EDGE_TYPE, + nodeType: COMMIT_NODE_TYPE, + direction: "OUT", + }) + .map(({edge}) => edge); + if (mergeEdge.length > 1) { + throw new Error( + `Node at ${this.nodeAddress.id} has too many MERGED_AS edges` + ); + } + if (mergeEdge.length === 0) { + return null; + } + const payload: MergedAsEdgePayload = (mergeEdge[0].payload: any); + return payload.hash; + } } export class Issue extends Commentable { diff --git a/src/plugins/github/api.test.js b/src/plugins/github/api.test.js index 60b2553..c7bc348 100644 --- a/src/plugins/github/api.test.js +++ b/src/plugins/github/api.test.js @@ -35,18 +35,26 @@ describe("GitHub porcelain API", () => { expect(issue.address()).toEqual(issue.node().address); expect(issue.authors().map((x) => x.login())).toEqual(["decentralion"]); }); - - it("PullRequests", () => { - const pullRequest = issueOrPRByNumber(3); - expect(pullRequest.body()).toBe("Oh look, it's a pull request."); - expect(pullRequest.title()).toBe("Add README, merge via PR."); - expect(pullRequest.url()).toBe( - "https://github.com/sourcecred/example-github/pull/3" - ); - expect(pullRequest.number()).toBe(3); - expect(pullRequest.type()).toBe(PULL_REQUEST_NODE_TYPE); - expect(pullRequest.node()).toMatchSnapshot(); - expect(pullRequest.address()).toEqual(pullRequest.node().address); + describe("PullRequests", () => { + it("Merged", () => { + const pullRequest = PullRequest.from(issueOrPRByNumber(3)); + expect(pullRequest.body()).toBe("Oh look, it's a pull request."); + expect(pullRequest.title()).toBe("Add README, merge via PR."); + expect(pullRequest.url()).toBe( + "https://github.com/sourcecred/example-github/pull/3" + ); + expect(pullRequest.number()).toBe(3); + expect(pullRequest.type()).toBe(PULL_REQUEST_NODE_TYPE); + expect(pullRequest.node()).toMatchSnapshot(); + expect(pullRequest.address()).toEqual(pullRequest.node().address); + expect(pullRequest.mergeCommitHash()).toEqual( + "0a223346b4e6dec0127b1e6aa892c4ee0424b66a" + ); + }); + it("Unmerged", () => { + const pullRequest = PullRequest.from(issueOrPRByNumber(9)); + expect(pullRequest.mergeCommitHash()).toEqual(null); + }); }); it("Pull Request Reviews", () => { diff --git a/src/plugins/github/parser.js b/src/plugins/github/parser.js index f5a2fe1..1a5ef2b 100644 --- a/src/plugins/github/parser.js +++ b/src/plugins/github/parser.js @@ -19,6 +19,7 @@ import type { AuthorSubtype, } from "./types"; +import {MERGED_AS_EDGE_TYPE} from "./types"; import type { RepositoryJSON, PullRequestReviewJSON, @@ -32,6 +33,7 @@ import type {Address} from "../../core/address"; import {PLUGIN_NAME} from "./pluginName"; import {Graph, edgeID} from "../../core/graph"; import {findReferences} from "./findReferences"; +import {commitAddress} from "../git/address"; export function parse( repositoryJSON: RepositoryJSON @@ -217,6 +219,22 @@ class GithubParser { prJson.reviews.nodes.forEach((r) => this.addPullRequestReview(pullRequestNode, r) ); + + if (prJson.mergeCommit != null) { + const hash = prJson.mergeCommit.oid; + const dstAddr = commitAddress(hash); + const mergedAsEdge = { + address: this.makeEdgeAddress( + MERGED_AS_EDGE_TYPE, + pullRequestNode.address, + dstAddr + ), + payload: {hash}, + src: pullRequestNode.address, + dst: dstAddr, + }; + this.graph.addEdge(mergedAsEdge); + } } addPullRequestReview( diff --git a/src/plugins/github/types.js b/src/plugins/github/types.js index 55a0a1f..534fcd4 100644 --- a/src/plugins/github/types.js +++ b/src/plugins/github/types.js @@ -98,6 +98,8 @@ export type AuthorsEdgePayload = {}; export const AUTHORS_EDGE_TYPE: "AUTHORS" = "AUTHORS"; export type ContainsEdgePayload = {}; export const CONTAINS_EDGE_TYPE: "CONTAINS" = "CONTAINS"; +export type MergedAsEdgePayload = {|+hash: string|}; +export const MERGED_AS_EDGE_TYPE: "MERGED_AS" = "MERGED_AS"; export type ReferencesEdgePayload = {}; export const REFERENCES_EDGE_TYPE: "REFERENCES" = "REFERENCES"; @@ -110,6 +112,10 @@ export type EdgeTypes = {| payload: ContainsEdgePayload, type: typeof CONTAINS_EDGE_TYPE, }, + MERGED_AS: { + payload: MergedAsEdgePayload, + type: typeof MERGED_AS_EDGE_TYPE, + }, REFERENCES: { payload: ReferencesEdgePayload, type: typeof REFERENCES_EDGE_TYPE, @@ -119,6 +125,7 @@ export type EdgeTypes = {| export type EdgeType = | typeof AUTHORS_EDGE_TYPE | typeof CONTAINS_EDGE_TYPE + | typeof MERGED_AS_EDGE_TYPE | typeof REFERENCES_EDGE_TYPE; export type EdgePayload =