Find BECOMES edges for high-level commits (#201)

Test Plan:
Unit tests included. I verified that the hashes in the snapshot are
correct.

wchargin-branch: high-level-becomes-commits
This commit is contained in:
William Chargin 2018-05-03 13:46:03 -07:00 committed by GitHub
parent a76d01ab75
commit c572b7f880
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 306 additions and 1 deletions

View File

@ -1392,3 +1392,35 @@ Object {
},
}
`;
exports[`findBecomesEdgesForCommits works on the example repository 1`] = `
Array [
Object {
"from": Object {
"name": "src",
"tree": "819fc546cea489476ce8dc90785e9ba7753d0a8f",
},
"path": Array [
"src",
],
"to": Object {
"name": "src",
"tree": "bbf3b8b3d26a4f884b5c022d46851f593d329192",
},
},
Object {
"from": Object {
"name": "quantum_gravity.py",
"tree": "7b79d579b62994faba3b69fdf8aa442586c32681",
},
"path": Array [
"src",
"quantum_gravity.py",
],
"to": Object {
"name": "quantum_gravity.py",
"tree": "78fc9c83023386854c6bfdc5761c0e58f68e226f",
},
},
]
`;

View File

@ -196,6 +196,59 @@ class GitGraphCreator {
}
}
export type BecomesEdge = {|
+from: {|
+tree: Hash,
+name: string,
|},
+to: {|
+tree: Hash,
+name: string,
|},
+path: $ReadOnlyArray<string>,
|};
export function* findBecomesEdgesForCommits(
repository: Repository,
childCommit: Hash,
parentCommit: Hash
): Iterator<BecomesEdge> {
const workUnits = [
{
path: [],
beforeTreeHash: repository.commits[parentCommit].treeHash,
afterTreeHash: repository.commits[childCommit].treeHash,
},
];
while (workUnits.length > 0) {
const {path, beforeTreeHash, afterTreeHash} = workUnits.pop();
const beforeTree = repository.trees[beforeTreeHash];
const afterTree = repository.trees[afterTreeHash];
for (const name of Object.keys(beforeTree.entries)) {
if (!(name in afterTree.entries)) {
continue;
}
const beforeEntry = beforeTree.entries[name];
const afterEntry = afterTree.entries[name];
const subpath = [...path, name];
if (beforeEntry.hash !== afterEntry.hash) {
yield {
from: {tree: beforeTreeHash, name},
to: {tree: afterTreeHash, name},
path: subpath,
};
}
if (beforeEntry.type === "tree" && afterEntry.type === "tree") {
workUnits.push({
path: subpath,
beforeTreeHash: beforeEntry.hash,
afterTreeHash: afterEntry.hash,
});
}
}
}
}
export function createGraph(
repository: Repository
): Graph<NodePayload, EdgePayload> {

View File

@ -2,7 +2,9 @@
import cloneDeep from "lodash.clonedeep";
import {createGraph} from "./createGraph";
import type {BecomesEdge} from "./createGraph";
import type {Hash, Tree} from "./types";
import {createGraph, findBecomesEdgesForCommits} from "./createGraph";
import {
BLOB_NODE_TYPE,
COMMIT_NODE_TYPE,
@ -241,3 +243,221 @@ describe("createGraph", () => {
});
});
});
describe("findBecomesEdgesForCommits", () => {
function fromTrees(
beforeTree: Hash,
afterTree: Hash,
trees: {[Hash]: Tree}
): BecomesEdge[] {
const repo = {
commits: {
commit1: {
hash: "commit1",
parentHashes: [],
treeHash: beforeTree,
submoduleUrls: {},
},
commit2: {
hash: "commit2",
parentHashes: ["commit1"],
treeHash: afterTree,
submoduleUrls: {},
},
},
trees,
};
return Array.from(findBecomesEdgesForCommits(repo, "commit2", "commit1"));
}
it("works on the example repository", () => {
const data = makeData();
const childCommitHash = "69c5aad50eec8f2a0a07c988c3b283a6490eb45b";
expect(data.commits[childCommitHash]).toEqual(expect.anything());
expect(data.commits[childCommitHash].parentHashes).toHaveLength(1);
const parentCommitHash = data.commits[childCommitHash].parentHashes[0];
expect(
Array.from(
findBecomesEdgesForCommits(data, childCommitHash, parentCommitHash)
)
).toMatchSnapshot();
});
it("works on empty trees", () => {
expect(
fromTrees("tree1", "tree2", {
tree1: {
hash: "tree1",
entries: {},
},
tree2: {
hash: "tree2",
entries: {},
},
})
).toEqual([]);
});
it("finds differences and non-differences at the root", () => {
expect(
fromTrees("tree1", "tree2", {
tree1: {
hash: "tree1",
entries: {
"color.txt": {
type: "blob",
name: "color.txt",
hash: "blue",
},
"number.txt": {
type: "blob",
name: "number.txt",
hash: "twelve",
},
},
},
tree2: {
hash: "tree2",
entries: {
"color.txt": {
type: "blob",
name: "color.txt",
hash: "yellow",
},
"number.txt": {
type: "blob",
name: "number.txt",
hash: "twelve",
},
},
},
})
).toEqual([
{
from: {
tree: "tree1",
name: "color.txt",
},
to: {
tree: "tree2",
name: "color.txt",
},
path: ["color.txt"],
},
]);
});
it("handles cases where files of the same name appear in different trees", () => {
const result = fromTrees("tree1", "tree2", {
tree1: {
hash: "tree1",
entries: {
"color.txt": {
type: "blob",
name: "color.txt",
hash: "blue",
},
"number.txt": {
type: "blob",
name: "number.txt",
hash: "twelve",
},
mirror_universe: {
type: "tree",
name: "mirror_universe",
hash: "eert1",
},
},
},
eert1: {
hash: "eert1",
entries: {
"color.txt": {
type: "blob",
name: "color.txt",
hash: "eulb",
},
"number.txt": {
type: "blob",
name: "number.txt",
hash: "evlewt",
},
},
},
tree2: {
hash: "tree2",
entries: {
"color.txt": {
type: "blob",
name: "color.txt",
hash: "yellow",
},
"number.txt": {
type: "blob",
name: "number.txt",
hash: "twelve",
},
mirror_universe: {
type: "tree",
name: "mirror_universe",
hash: "eert2",
},
},
},
eert2: {
hash: "eert1",
entries: {
"color.txt": {
type: "blob",
name: "color.txt",
hash: "eulb",
},
"number.txt": {
type: "blob",
name: "number.txt",
hash: "neetneves",
},
},
},
});
const expected = [
{
from: {
tree: "tree1",
name: "color.txt",
},
to: {
tree: "tree2",
name: "color.txt",
},
path: ["color.txt"],
},
{
from: {
tree: "eert1",
name: "number.txt",
},
to: {
tree: "eert2",
name: "number.txt",
},
path: ["mirror_universe", "number.txt"],
},
{
from: {
tree: "tree1",
name: "mirror_universe",
},
to: {
tree: "tree2",
name: "mirror_universe",
},
path: ["mirror_universe"],
},
];
expect(result).toEqual(expect.arrayContaining(expected));
expect(expected).toEqual(
expect.arrayContaining((result.slice(): $ReadOnlyArray<mixed>).slice())
);
});
});