Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(engine): Implement STEM #91

Merged
merged 7 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/analysis-engine/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"object-shorthand": "off",
"class-methods-use-this": "off",
"import/extensions": "off",
"import/no-unresolved": "off"
"import/no-unresolved": "off",
"no-continue": "off"
}
}
18 changes: 18 additions & 0 deletions packages/analysis-engine/src/commit.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CommitNode } from "./types/CommitNode";
import { CommitRaw } from "./types/CommitRaw";

type CommitDict = Map<string, CommitNode>;

export function generateCommitNodeDict(commits: CommitRaw[]): CommitDict {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return new Map(
commits.map((commit) => [commit.id, { commit } as CommitNode])
);
}

export function getLeafNodes(commitDict: CommitDict): CommitNode[] {
const leafNodes: CommitNode[] = [];
commitDict.forEach(
(node) => node.commit.branches.length && leafNodes.push(node)
);
return leafNodes;
}
27 changes: 0 additions & 27 deletions packages/analysis-engine/src/dag.ts

This file was deleted.

30 changes: 30 additions & 0 deletions packages/analysis-engine/src/queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default class Queue<T> {
private queue: Array<T> = [];

private readonly compareFn: (a: T, b: T) => number;

constructor(compareFn: (a: T, b: T) => number) {
this.compareFn = compareFn;
}

push(node: T): void {
this.queue.push(node);
this.queue.sort(this.compareFn);
}

pop(): T | undefined {
return this.queue.shift();
}

isEmpty(): boolean {
return this.queue.length === 0;
}

pushFront(node: T): void {
this.queue.unshift(node);
}

pushBack(node: T): void {
this.queue.push(node);
}
}
200 changes: 197 additions & 3 deletions packages/analysis-engine/src/stem.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,201 @@
import stem from "./stem";
import { generateCommitNodeDict, getLeafNodes } from "./commit.util";
import { buildStemDict } from "./stem";
import { CommitNode } from "./types/CommitNode";
import { CommitRaw } from "./types/CommitRaw";

type FakeCommitData = Pick<
CommitRaw,
"id" | "parents" | "branches" | "committerDate"
>;

const dummy: FakeCommitData[] = [
// 1
{
id: "a1a605b38df7462e14503a2a0bb8d7453df7c8ae",
parents: [],
branches: [],
committerDate: "Sat Sep 3 19:30:40 2022 +0900",
},
// 2
{
id: "10d086ff54073d5e9b634ffb378a3da4c15b05a9",
parents: ["a1a605b38df7462e14503a2a0bb8d7453df7c8ae"],
branches: [],
committerDate: "Sat Sep 3 19:30:58 2022 +0900",
},
// 3
{
id: "f72a762bd84cae2da0933e637f800a30d6a1840c",
parents: [
"10d086ff54073d5e9b634ffb378a3da4c15b05a9",
"718352fcc051c2a8691d3d96e968d76f7bc6d846",
],
branches: [],
committerDate: "Sat Sep 3 19:32:44 2022 +0900",
},
// 4
{
id: "699116bdd4e2de914e5f5df76cd5aac3e4b5babe",
parents: ["f72a762bd84cae2da0933e637f800a30d6a1840c"],
branches: [],
committerDate: "Sat Sep 3 19:32:55 2022 +0900",
},
// 5
{
id: "421fb1cbabae99dc314f3076ea17c0bfd16457cb",
parents: [
"699116bdd4e2de914e5f5df76cd5aac3e4b5babe",
"704ba519d85c2d78914a1b7e5100bae19d36814b",
],
branches: [],
committerDate: "Sat Sep 3 19:33:50 2022 +0900",
},
// 6
{
id: "d5fd016fac43cef3d270736cb169753495f58282",
parents: ["421fb1cbabae99dc314f3076ea17c0bfd16457cb"],
branches: ["main"],
committerDate: "Sat Sep 3 19:34:04 2022 +0900",
},
// 8
{
id: "718352fcc051c2a8691d3d96e968d76f7bc6d846",
parents: ["a1a605b38df7462e14503a2a0bb8d7453df7c8ae"],
branches: [],
committerDate: "Sat Sep 3 19:31:25 2022 +0900",
},
// 9
{
id: "e40da20d4c80677f272b6ed104c55b237ecaa601",
parents: ["718352fcc051c2a8691d3d96e968d76f7bc6d846"],
branches: [],
committerDate: "Sat Sep 3 19:33:10 2022 +0900",
},
// 10
{
id: "704ba519d85c2d78914a1b7e5100bae19d36814b",
parents: ["e40da20d4c80677f272b6ed104c55b237ecaa601"],
branches: [],
committerDate: "Sat Sep 3 19:33:14 2022 +0900",
},
// 11
{
id: "6f77d7232b08e9444791e3931acd3b1aa59abe32",
parents: ["f72a762bd84cae2da0933e637f800a30d6a1840c"],
branches: [],
committerDate: "Sat Sep 3 19:34:24 2022 +0900",
},
// 12
{
id: "48f1d4f2a1b7b0ac21095f3aa43f35f2cb733918",
parents: ["6f77d7232b08e9444791e3931acd3b1aa59abe32"],
branches: [],
committerDate: "Sat Sep 3 19:34:44 2022 +0900",
},
// 13
{
id: "0beb987c722838f5d4bfadfa631c0792cd83849b",
parents: ["48f1d4f2a1b7b0ac21095f3aa43f35f2cb733918"],
branches: [],
committerDate: "Sat Sep 3 19:34:45 2022 +0900",
},
// 14
{
id: "b6d5e7979c71e7e7e7b537838c0d249ec5e63375",
parents: ["0beb987c722838f5d4bfadfa631c0792cd83849b"],
branches: ["dev"],
committerDate: "Sat Sep 3 19:34:57 2022 +0900",
},
// 15
{
id: "dcb3b0752a99d5c9449710f7f781dc2b5bc22cd9",
parents: [
"0beb987c722838f5d4bfadfa631c0792cd83849b",
"704ba519d85c2d78914a1b7e5100bae19d36814b",
],
branches: [],
committerDate: "Sat Sep 3 19:37:35 2022 +0900",
},
// 16
{
id: "04b02e3efb9e432e3ce91f1b36bbdeb284fddcf5",
parents: ["dcb3b0752a99d5c9449710f7f781dc2b5bc22cd9"],
branches: ["HEAD"],
committerDate: "Sat Sep 3 19:37:53 2022 +0900",
},
];

function createTestCommit(fakeCommitData: FakeCommitData): CommitRaw {
return {
tags: [],
author: {
name: "",
email: "",
},
authorDate: "",
committer: {
name: "",
email: "",
},
message: "",
differenceStatistic: {
totalInsertionCount: 0,
totalDeletionCount: 0,
fileDictionary: {},
},
...fakeCommitData,
} as CommitRaw;
}

describe("stem", () => {
it("temp test", () => {
expect(stem()).toBe("stem");
let commits: CommitRaw[] = [];
let commitDict: Map<string, CommitNode>;

beforeEach(() => {
commits = dummy.map(createTestCommit);
commitDict = generateCommitNodeDict(commits);
});

it("should make instance of Map", () => {
const stemDict = buildStemDict(commitDict);
expect(stemDict).toBeInstanceOf(Map);
});

it("should get leaf nodes", () => {
const leafNodes = getLeafNodes(commitDict);
expect(leafNodes.map((node) => node.commit.id)).toEqual([
"d5fd016fac43cef3d270736cb169753495f58282",
"b6d5e7979c71e7e7e7b537838c0d249ec5e63375",
"04b02e3efb9e432e3ce91f1b36bbdeb284fddcf5",
]);
});

it("should make stem", () => {
const stemDict = buildStemDict(commitDict);
expect(stemDict.get("main")?.nodes.map((node) => node.commit.id)).toEqual([
"d5fd016fac43cef3d270736cb169753495f58282",
"421fb1cbabae99dc314f3076ea17c0bfd16457cb",
"699116bdd4e2de914e5f5df76cd5aac3e4b5babe",
"f72a762bd84cae2da0933e637f800a30d6a1840c",
"10d086ff54073d5e9b634ffb378a3da4c15b05a9",
"a1a605b38df7462e14503a2a0bb8d7453df7c8ae",
]);
expect(
stemDict.get("implicit-1")?.nodes.map((node) => node.commit.id)
).toEqual([
"704ba519d85c2d78914a1b7e5100bae19d36814b",
"e40da20d4c80677f272b6ed104c55b237ecaa601",
"718352fcc051c2a8691d3d96e968d76f7bc6d846",
]);
expect(stemDict.get("dev")?.nodes.map((node) => node.commit.id)).toEqual([
"b6d5e7979c71e7e7e7b537838c0d249ec5e63375",
"0beb987c722838f5d4bfadfa631c0792cd83849b",
"48f1d4f2a1b7b0ac21095f3aa43f35f2cb733918",
"6f77d7232b08e9444791e3931acd3b1aa59abe32",
]);
expect(stemDict.get("HEAD")?.nodes.map((node) => node.commit.id)).toEqual([
"04b02e3efb9e432e3ce91f1b36bbdeb284fddcf5",
"dcb3b0752a99d5c9449710f7f781dc2b5bc22cd9",
]);
});
});
98 changes: 96 additions & 2 deletions packages/analysis-engine/src/stem.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,97 @@
export const stem = () => "stem";
import { getLeafNodes } from "./commit.util";
import Queue from "./queue";
import { CommitNode } from "./types/CommitNode";
import { Stem } from "./types/Stem";

export default stem;
export function getStemNodes(
tailId: string,
commitDict: Map<string, CommitNode>,
q: Queue<CommitNode>,
stemId: string
): CommitNode[] {
let now = commitDict.get(tailId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 잘 모르는 것일수도 있겠습니다. type이 자동으로 고정이 되나요?

후행하는 라인에 now = 3;과 같은 부분이 가능하지 않을까 해서,
let now: CommitNode = commitDict.get(tailId);가 필요하지 않을까 합니다.
만약에 type 고정이 된다면 상단의 처리는 필요 없을 것 같습니다~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommitNode | undefined로 고정되어서, now = 3; 같은 할당을 시도하면 타입 에러가 발생하네요!
commitDict.get의 return type으로 고정된 것 같아요

if (!now) return [];

const nodes: CommitNode[] = [];
while (now && !now.stemId) {
now.stemId = stemId;
if (now.commit.parents.length > 1) {
now.commit.parents.forEach((parent, idx) => {
if (idx === 0) return;
const parentNode = commitDict.get(parent);
if (parentNode) {
q.push(parentNode);
}
}, q);
}
nodes.push(now);
now = commitDict.get(now.commit.parents?.[0]);
}
return nodes;
}

function compareCommitPriority(a: CommitNode, b: CommitNode): number {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// branches 값 존재하는 노드 => leaf / main / HEAD 노드.
// 이 노드는 큐에 들어올 때 순서가 정해져 있기 때문에 순서를 바꾸지 않음.
if (a.commit.branches.length || b.commit.branches.length) {
return 0;
}
// 나중에 커밋된 것을 먼저 담기
return (
new Date(b.commit.committerDate).getTime() -
new Date(a.commit.committerDate).getTime()
);
}

export function buildStemDict(
commitDict: Map<string, CommitNode>
): Map<string, Stem> {
const q = new Queue<CommitNode>(compareCommitPriority);

/**
* 처음 큐에 담기는 순서
* 1. main
* 2. sub-branches
* 3. HEAD
*/
const stemDict = new Map<string, Stem>();
const leafNodes = getLeafNodes(commitDict);
const mainNode = leafNodes.find(
(node) =>
node.commit.branches.includes("main") ||
node.commit.branches.includes("master")
);
const headNode = leafNodes.find((node) =>
node.commit.branches.includes("HEAD")
);
leafNodes
.filter(
(node) =>
node.commit.id !== mainNode?.commit.id &&
node.commit.id !== headNode?.commit.id
)
.forEach((node) => q.push(node), q);
if (mainNode) q.pushFront(mainNode);
if (headNode) q.pushBack(headNode);

let implicitBranchNumber = 1;

while (!q.isEmpty()) {
const tail = q.pop();
if (!tail) continue;

const stemId =
tail.commit.branches[0] ?? `implicit-${implicitBranchNumber}`;
if (tail.commit.branches.length === 0) {
implicitBranchNumber += 1;
}

const nodes = getStemNodes(tail.commit.id, commitDict, q, stemId);
if (nodes.length === 0) continue;

const stem: Stem = { nodes };
stemDict.set(stemId, stem);
}

return stemDict;
}
7 changes: 7 additions & 0 deletions packages/analysis-engine/src/types/CommitNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { CommitRaw } from "./CommitRaw";

export interface CommitNode {
// 순회 이전에는 stemId가 존재하지 않음.
stemId?: string;
commit: CommitRaw;
}
Loading