Add `mergeRepository` for merging Git repositories (#871)
This commit adds a utility method, `mergeRepository`, which can merge multiple Git repository data structures. `loadGitData` has been updated to create a merged repository and then subsequently generate a graph from it. Test plan: New unit tests were added. `yarn test --full` passes. Loading a project and viewing its git data in the cred explorer works.
This commit is contained in:
parent
8442255db3
commit
12aa5b7439
|
@ -3,10 +3,10 @@
|
|||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
|
||||
import {Graph} from "../../core/graph";
|
||||
import type {Repo} from "../../core/repo";
|
||||
import cloneAndLoadRepository from "./cloneAndLoadRepository";
|
||||
import {createMinimalGraph} from "./createMinimalGraph";
|
||||
import type {Repo} from "../../core/repo";
|
||||
import {mergeRepository} from "./mergeRepository";
|
||||
|
||||
export type Options = {|
|
||||
+repos: $ReadOnlyArray<Repo>,
|
||||
|
@ -15,11 +15,9 @@ export type Options = {|
|
|||
|};
|
||||
|
||||
export function loadGitData(options: Options): Promise<void> {
|
||||
const graphs = options.repos.map((repo) => {
|
||||
const repository = cloneAndLoadRepository(repo);
|
||||
return createMinimalGraph(repository);
|
||||
});
|
||||
const graph = Graph.merge(graphs);
|
||||
const repositories = options.repos.map((r) => cloneAndLoadRepository(r));
|
||||
const repository = mergeRepository(repositories);
|
||||
const graph = createMinimalGraph(repository);
|
||||
const blob = JSON.stringify(graph);
|
||||
const outputFilename = path.join(options.outputDirectory, "graph.json");
|
||||
return fs.writeFile(outputFilename, blob);
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// @flow
|
||||
|
||||
import deepEqual from "lodash.isequal";
|
||||
import type {Repository} from "./types";
|
||||
|
||||
export function mergeRepository(
|
||||
repositories: $ReadOnlyArray<Repository>
|
||||
): Repository {
|
||||
const newRepository = {commits: {}, trees: {}};
|
||||
for (const {trees, commits} of repositories) {
|
||||
for (const treeHash of Object.keys(trees)) {
|
||||
const existingTree = newRepository.trees[treeHash];
|
||||
if (existingTree != null && !deepEqual(existingTree, trees[treeHash])) {
|
||||
throw new Error(`Conflict between trees at ${treeHash}`);
|
||||
}
|
||||
newRepository.trees[treeHash] = trees[treeHash];
|
||||
}
|
||||
for (const commitHash of Object.keys(commits)) {
|
||||
const existingCommit = newRepository.commits[commitHash];
|
||||
if (
|
||||
existingCommit != null &&
|
||||
!deepEqual(existingCommit, commits[commitHash])
|
||||
) {
|
||||
throw new Error(`Conflict between commits at ${commitHash}`);
|
||||
}
|
||||
newRepository.commits[commitHash] = commits[commitHash];
|
||||
}
|
||||
}
|
||||
return newRepository;
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
//@flow
|
||||
|
||||
import type {Repository} from "./types";
|
||||
import {mergeRepository} from "./mergeRepository";
|
||||
|
||||
describe("plugins/git/mergeRepository", () => {
|
||||
describe("mergeRepository", () => {
|
||||
const empty: Repository = Object.freeze({commits: {}, trees: {}});
|
||||
const repository1: Repository = Object.freeze({
|
||||
commits: {
|
||||
commit1: {
|
||||
hash: "commit1",
|
||||
parentHashes: [],
|
||||
treeHash: "tree1",
|
||||
},
|
||||
commit2: {
|
||||
hash: "commit2",
|
||||
parentHashes: ["commit1"],
|
||||
treeHash: "tree2",
|
||||
},
|
||||
},
|
||||
trees: {
|
||||
tree1: {
|
||||
hash: "tree1",
|
||||
entries: {},
|
||||
},
|
||||
tree2: {
|
||||
hash: "tree2",
|
||||
entries: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
const repository2: Repository = Object.freeze({
|
||||
commits: {
|
||||
commit1: {
|
||||
hash: "commit1",
|
||||
parentHashes: [],
|
||||
treeHash: "tree1",
|
||||
},
|
||||
commit3: {
|
||||
hash: "commit3",
|
||||
parentHashes: ["commit1"],
|
||||
treeHash: "tree3",
|
||||
},
|
||||
},
|
||||
trees: {
|
||||
tree1: {
|
||||
hash: "tree1",
|
||||
entries: {},
|
||||
},
|
||||
tree3: {
|
||||
hash: "tree3",
|
||||
entries: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
it("returns empty repository with no arguments", () => {
|
||||
expect(mergeRepository([])).toEqual(empty);
|
||||
});
|
||||
it("returns copy of the input repository when n=1", () => {
|
||||
const merged = mergeRepository([repository1]);
|
||||
expect(merged).toEqual(repository1);
|
||||
expect(merged).not.toBe(repository1);
|
||||
});
|
||||
it("treats empty repository as identity", () => {
|
||||
const merged = mergeRepository([empty, repository1, empty]);
|
||||
expect(merged).toEqual(repository1);
|
||||
});
|
||||
it("merged contains every hash and tree for every constituent repository", () => {
|
||||
const merged = mergeRepository([repository1, repository2]);
|
||||
for (const {trees, commits} of [repository1, repository2]) {
|
||||
for (const treeHash in trees) {
|
||||
expect(merged.trees[treeHash]).toEqual(trees[treeHash]);
|
||||
}
|
||||
for (const commitHash in commits) {
|
||||
expect(merged.commits[commitHash]).toEqual(commits[commitHash]);
|
||||
}
|
||||
}
|
||||
});
|
||||
it("throws an error if merging a repository with conflicting commits", () => {
|
||||
const conflictingRepository: Repository = Object.freeze({
|
||||
commits: {
|
||||
commit1: {
|
||||
hash: "commit1",
|
||||
parentHashes: [],
|
||||
treeHash: "tree2",
|
||||
},
|
||||
},
|
||||
trees: {
|
||||
tree2: {
|
||||
hash: "tree2",
|
||||
entries: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(() =>
|
||||
mergeRepository([repository1, conflictingRepository])
|
||||
).toThrowError("Conflict between commits");
|
||||
});
|
||||
it("throws an error if merging a repository with conflicting trees", () => {
|
||||
const conflictingRepository: Repository = Object.freeze({
|
||||
commits: {
|
||||
commit1: {
|
||||
hash: "commit1",
|
||||
parentHashes: [],
|
||||
treeHash: "tree1",
|
||||
},
|
||||
},
|
||||
trees: {
|
||||
tree1: {
|
||||
hash: "tree1",
|
||||
entries: {blob: {type: "blob", name: "blob", hash: "blob"}},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(() =>
|
||||
mergeRepository([repository1, conflictingRepository])
|
||||
).toThrowError("Conflict between trees");
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue