Skip to content

Commit

Permalink
feat(engine): Implement STEM (#91)
Browse files Browse the repository at this point in the history
* feat: implement Stem

* feat: change implicit branch stem naming

* test: add test for HEAD

* chore: set off the eslint no-continue rule

* refactor: 리뷰 반영

* feat: queue 생성 시, constructor에 정렬 비교 함수 파라미터 추가

* chore: edit test name
  • Loading branch information
ooooorobo authored Sep 8, 2022
1 parent 3240fde commit e215305
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 33 deletions.
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 {
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);
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 {
// 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

0 comments on commit e215305

Please sign in to comment.