-
Notifications
You must be signed in to change notification settings - Fork 82
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
Changes from all commits
502bce7
8bf268a
9de3230
bde4103
4898c30
9bc842e
4a4d69f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
} |
This file was deleted.
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); | ||
} | ||
} |
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", | ||
]); | ||
}); | ||
}); |
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 잘 모르는 것일수도 있겠습니다. type이 자동으로 고정이 되나요? 후행하는 라인에 now = 3;과 같은 부분이 가능하지 않을까 해서, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} |
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍