GitHub: Mint cred only for merged PRs (#1933)

This commit modifies the GitHub createGraph method so that it now
returns a WeightedGraph, and that WeightedGraph sets weight 0 for any
pull request which hasn't been merged.

This improves Cred robustness, since it's easy to (non-maliciously)
create a bunch of unmerged PRs, but getting them merged is a signal of
quality.

Test plan: Unit tests added.
This commit is contained in:
Dandelion Mané 2020-07-06 16:26:04 -07:00 committed by GitHub
parent e54b0a3f5b
commit 4b49f48911
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 59 additions and 13 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3320,3 +3320,12 @@ Array [
}, },
] ]
`; `;
exports[`plugins/github/createGraph example weights matches snapshot 1`] = `
Object {
"edgeWeights": Map {},
"nodeWeights": Map {
"NsourcecredgithubPULLsourcecred-testexample-github9" => 0,
},
}
`;

View File

@ -9,8 +9,11 @@ import type {CacheProvider} from "../../backend/cache";
import type {CliPlugin, PluginDirectoryContext} from "../../cli/cliPlugin"; import type {CliPlugin, PluginDirectoryContext} from "../../cli/cliPlugin";
import type {PluginDeclaration} from "../../analysis/pluginDeclaration"; import type {PluginDeclaration} from "../../analysis/pluginDeclaration";
import type {ReferenceDetector} from "../../core/references/referenceDetector"; import type {ReferenceDetector} from "../../core/references/referenceDetector";
import type {WeightedGraph} from "../../core/weightedGraph"; import {
import {Graph} from "../../core/graph"; type WeightedGraph,
merge as mergeWeightedGraph,
} from "../../core/weightedGraph";
import {merge as mergeWeights} from "../../core/weights";
import {RelationalView} from "./relationalView"; import {RelationalView} from "./relationalView";
import {createGraph} from "./createGraph"; import {createGraph} from "./createGraph";
import {declaration} from "./declaration"; import {declaration} from "./declaration";
@ -87,15 +90,16 @@ export class GithubCliPlugin implements CliPlugin {
for (const repoId of config.repoIds) { for (const repoId of config.repoIds) {
repositories.push(await fetchGithubRepoFromCache(repoId, {token, cache})); repositories.push(await fetchGithubRepoFromCache(repoId, {token, cache}));
} }
const graph = Graph.merge( const wg = mergeWeightedGraph(
repositories.map((r) => { repositories.map((r) => {
const rv = new RelationalView(); const rv = new RelationalView();
rv.addRepository(r); rv.addRepository(r);
return createGraph(rv); return createGraph(rv);
}) })
); );
const weights = weightsForDeclaration(declaration); const pluginDefaultWeights = weightsForDeclaration(declaration);
return {graph, weights}; const weights = mergeWeights([wg.weights, pluginDefaultWeights]);
return {graph: wg.graph, weights};
} }
async referenceDetector( async referenceDetector(

View File

@ -2,23 +2,27 @@
import * as NullUtil from "../../util/null"; import * as NullUtil from "../../util/null";
import {Graph} from "../../core/graph"; import {Graph} from "../../core/graph";
import {type WeightedGraph} from "../../core/weightedGraph";
import {type Weights, empty as emptyWeights} from "../../core/weights";
import * as GitNode from "../git/nodes"; import * as GitNode from "../git/nodes";
import * as N from "./nodes"; import * as N from "./nodes";
import * as R from "./relationalView"; import * as R from "./relationalView";
import {createEdge} from "./edges"; import {createEdge} from "./edges";
import {ReactionContent$Values as Reactions} from "./graphqlTypes"; import {ReactionContent$Values as Reactions} from "./graphqlTypes";
export function createGraph(view: R.RelationalView): Graph { export function createGraph(view: R.RelationalView): WeightedGraph {
const creator = new GraphCreator(); const creator = new GraphCreator();
creator.addData(view); creator.addData(view);
return creator.graph; return {graph: creator.graph, weights: creator.weights};
} }
class GraphCreator { class GraphCreator {
graph: Graph; graph: Graph;
weights: Weights;
constructor() { constructor() {
this.graph = new Graph(); this.graph = new Graph();
this.weights = emptyWeights();
} }
addData(view: R.RelationalView) { addData(view: R.RelationalView) {
@ -46,6 +50,10 @@ class GraphCreator {
this.graph.addEdge( this.graph.addEdge(
createEdge.mergedAs(pull.address(), commit, commitTimestamp) createEdge.mergedAs(pull.address(), commit, commitTimestamp)
); );
} else {
const addr = N.toRaw(pull.address());
// Un-merged PRs do not mint cred.
this.weights.nodeWeights.set(addr, 0);
} }
} }

View File

@ -1,9 +1,34 @@
// @flow // @flow
import {exampleGraph} from "./example/example"; import {exampleGraph, exampleRelationalView} from "./example/example";
import {empty as emptyWeights} from "../../core/weights";
import {createGraph} from "./createGraph";
import * as N from "./nodes";
describe("plugins/github/createGraph", () => { describe("plugins/github/createGraph", () => {
it("example graph matches snapshot", () => { it("example graph matches snapshot", () => {
expect(exampleGraph()).toMatchSnapshot(); expect(exampleGraph().graph).toMatchSnapshot();
});
it("example weights matches snapshot", () => {
expect(exampleGraph().weights).toMatchSnapshot();
});
it("sets weight to 0 for un-merged PRs", () => {
const view = exampleRelationalView();
const unmergedPrs = Array.from(view.pulls()).filter(
(x) => x.mergedAs() == null
);
const mergedPrs = Array.from(view.pulls()).filter(
(x) => x.mergedAs() != null
);
expect(unmergedPrs).not.toHaveLength(0);
expect(mergedPrs).not.toHaveLength(0);
const expectedWeights = emptyWeights();
for (const unmerged of unmergedPrs) {
const addr = N.toRaw(unmerged.address());
expectedWeights.nodeWeights.set(addr, 0);
}
const {weights} = createGraph(view);
expect(weights).toEqual(expectedWeights);
}); });
}); });

View File

@ -2,7 +2,7 @@
import {RelationalView} from "../relationalView"; import {RelationalView} from "../relationalView";
import type {Repository} from "../graphqlTypes"; import type {Repository} from "../graphqlTypes";
import {Graph} from "../../../core/graph"; import {type WeightedGraph} from "../../../core/weightedGraph";
import cloneDeep from "lodash.clonedeep"; import cloneDeep from "lodash.clonedeep";
import {createGraph} from "../createGraph"; import {createGraph} from "../createGraph";
@ -16,7 +16,7 @@ export function exampleRelationalView(): RelationalView {
return rv; return rv;
} }
export function exampleGraph(): Graph { export function exampleGraph(): WeightedGraph {
return createGraph(exampleRelationalView()); return createGraph(exampleRelationalView());
} }