Skip to content

Commit

Permalink
refactor(engine): extract functions from csm (#274)
Browse files Browse the repository at this point in the history
* refactor: extract functions from csm

* refactor: use map function

* refactor: add dict types

* refactor: move functions related with pr

* refactor: rename function name

* refactor: use pr in buildCSMNodeWithPullRequest

* chore: disable vulnerabilities check
  • Loading branch information
ooooorobo authored Mar 9, 2023
1 parent 5124659 commit 2e86532
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 163 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/analysis-engine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ jobs:
- name: check lint and prettier
run: npm run check:eslint

- name: check version vulnerabilities
run: npm run check:vulnerabilities
# - name: check version vulnerabilities
# run: npm run check:vulnerabilities

- name: test analysis-engine module
run: npm run test
Expand All @@ -48,4 +48,4 @@ jobs:
run: npm run test:stem

- name: test analysis-engine CSM and return its coverage
run: npm run test:CSM
run: npm run test:CSM
3 changes: 1 addition & 2 deletions packages/analysis-engine/src/commit.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import {
CommitNode,
CommitMessageType,
CommitMessageTypeList,
CommitDict,
} from "./types";

type CommitDict = Map<string, CommitNode>;

export function buildCommitDict(commits: CommitRaw[]): CommitDict {
return new Map(
commits.map((commit) => [commit.id, { commit } as CommitNode])
Expand Down
252 changes: 101 additions & 151 deletions packages/analysis-engine/src/csm.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,101 @@
/* eslint-disable import/prefer-default-export */
import type {
CommitRaw,
CommitNode,
Stem,
StemDict,
CSMDictionary,
CSMNode,
CommitDict,
PullRequest,
PullRequestDict,
} from "./types";
import type { PullRequest } from "./types/Github";

/**
* PR 기반 CSM-Source 생성
*/
const buildCSMSourceFromPRCommits = (baseCSMNode: CSMNode, pr: PullRequest) =>
pr.commitDetails.data.map((commitDetail) => {
const {
sha,
parents,
commit: { author, committer, message },
files,
} = commitDetail;

let totalInsertionCount = 0;
let totalDeletionCount = 0;
const fileDictionary =
files?.reduce((dict, f) => {
totalInsertionCount += f.additions;
totalDeletionCount += f.deletions;
return {
...dict,
[f.filename]: {
insertionCount: f.additions,
deletionCount: f.deletions,
},
};
}, {}) ?? {};

const prCommitRaw: CommitRaw = {
sequence: -1, // ignore
id: sha,
parents: parents.map((p) => p.sha),
branches: [], // ignore
tags: [], // ignore
author: {
name: author?.name ?? "",
email: author?.email ?? "",
},
authorDate: author?.date
? new Date(author.date)
: baseCSMNode.base.commit.authorDate,
committer: {
name: committer?.name ?? "",
email: committer?.email ?? "",
},
committerDate: committer?.date
? new Date(committer.date)
: baseCSMNode.base.commit.committerDate,
message,
differenceStatistic: {
fileDictionary,
totalInsertionCount,
totalDeletionCount,
},
commitMessageType: "",
import {
convertPRCommitsToCommitNodes,
convertPRDetailToCommitRaw,
} from "./pullRequest";

const buildCSMNode = (
baseCommitNode: CommitNode,
commitDict: CommitDict,
stemDict: StemDict
): CSMNode => {
const mergeParentCommit = commitDict.get(baseCommitNode.commit.parents[1]);
if (!mergeParentCommit) {
return {
base: baseCommitNode,
source: [],
};
}

return { commit: prCommitRaw };
});
const squashCommitNodes: CommitNode[] = [];

const squashTaskQueue: CommitNode[] = [mergeParentCommit];
while (squashTaskQueue.length > 0) {
// get target
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const squashStartNode = squashTaskQueue.shift()!;

// get target's stem
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const squashStemId = squashStartNode.stemId!;
const squashStem = stemDict.get(squashStemId);
if (!squashStem) {
continue;
}

// prepare squash
const squashStemLastIndex = squashStem.nodes.length - 1;
const squashStartNodeIndex = squashStem.nodes.findIndex(
({ commit: { id } }) => id === squashStartNode.commit.id
);
const spliceCount = squashStemLastIndex - squashStartNodeIndex + 1;

// squash
const spliceCommitNodes = squashStem.nodes.splice(
squashStartNodeIndex,
spliceCount
);
squashCommitNodes.push(...spliceCommitNodes);

// check nested-merge
const nestedMergeParentCommitIds = spliceCommitNodes
.filter((node) => node.commit.parents.length > 1)
.map((node) => node.commit.parents)
.reduce((pCommitIds, parents) => [...pCommitIds, ...parents], []);
const nestedMergeParentCommits = nestedMergeParentCommitIds
.map((commitId) => commitDict.get(commitId))
.filter((node): node is CommitNode => node !== undefined)
.filter(
(node) =>
node.stemId !== baseCommitNode.stemId && node.stemId !== squashStemId
);

squashTaskQueue.push(...nestedMergeParentCommits);
}

squashCommitNodes.sort((a, b) => a.commit.sequence - b.commit.sequence);

return {
base: baseCommitNode,
source: squashCommitNodes,
};
};

const buildCSMNodeWithPullRequest = (
csmNode: CSMNode,
pr: PullRequest
): CSMNode => {
const convertedCommit = convertPRDetailToCommitRaw(csmNode.base.commit, pr);

return {
base: {
stemId: csmNode.base.stemId,
commit: convertedCommit,
},
source: csmNode.source.length
? csmNode.source
: convertPRCommitsToCommitNodes(convertedCommit, pr),
};
};

/**
* CSM 생성
Expand All @@ -77,8 +107,8 @@ const buildCSMSourceFromPRCommits = (baseCSMNode: CSMNode, pr: PullRequest) =>
* @returns {CSMDictionary}
*/
export const buildCSMDict = (
commitDict: Map<string, CommitNode>,
stemDict: Map<string, Stem>,
commitDict: CommitDict,
stemDict: StemDict,
baseBranchName: string,
pullRequests: Array<PullRequest> = []
): CSMDictionary => {
Expand All @@ -87,105 +117,25 @@ export const buildCSMDict = (
// return {};
}

const prDictByMergedCommitSha = pullRequests.reduce(
(dict, pr) => dict.set(`${pr.detail.data.merge_commit_sha}`, pr),
new Map<string, PullRequest>()
);

const csmDict: CSMDictionary = {};

// v0.1 에서는 master STEM 으로만 CSM 생성함
const masterStem = stemDict.get(baseBranchName);
if (!masterStem) {
throw new Error("no master-stem");
// return {};
}
const stemNodes = masterStem.nodes.reverse(); // start on root-node

const csmNodes: CSMNode[] = [];

stemNodes.forEach((commitNode) => {
const csmNode: CSMNode = {
base: commitNode,
source: [],
};

const mergeParentCommit = commitDict.get(csmNode.base.commit.parents[1]);
if (mergeParentCommit) {
const squashCommitNodes: CommitNode[] = [];

const squashTaskQueue: CommitNode[] = [mergeParentCommit];
while (squashTaskQueue.length > 0) {
// get target
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const squashStartNode = squashTaskQueue.shift()!;

// get target's stem
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const squashStemId = squashStartNode.stemId!;
const squashStem = stemDict.get(squashStemId);
if (!squashStem) {
continue;
}

// prepare squash
const squashStemLastIndex = squashStem.nodes.length - 1;
const squashStartNodeIndex = squashStem.nodes.findIndex(
({ commit: { id } }) => id === squashStartNode.commit.id
);
const spliceCount = squashStemLastIndex - squashStartNodeIndex + 1;

// squash
const spliceCommitNodes = squashStem.nodes.splice(
squashStartNodeIndex,
spliceCount
);
squashCommitNodes.push(...spliceCommitNodes);

// check nested-merge
const nestedMergeParentCommitIds = spliceCommitNodes
.filter((node) => node.commit.parents.length > 1)
.map((node) => node.commit.parents)
.reduce((pCommitIds, parents) => [...pCommitIds, ...parents], []);
const nestedMergeParentCommits = nestedMergeParentCommitIds
.map((commitId) => commitDict.get(commitId))
.filter((node): node is CommitNode => node !== undefined)
.filter(
(node) =>
node.stemId !== csmNode.base.stemId &&
node.stemId !== squashStemId
);

squashTaskQueue.push(...nestedMergeParentCommits);
}

squashCommitNodes.sort((a, b) => a.commit.sequence - b.commit.sequence);

csmNode.source = squashCommitNodes;
}
const prDictByMergedCommitSha = pullRequests.reduce(
(dict, pr) => dict.set(`${pr.detail.data.merge_commit_sha}`, pr),
new Map<string, PullRequest>() as PullRequestDict
);

// check pr based merged-commit
const csmDict: CSMDictionary = {};
const stemNodes = masterStem.nodes.reverse(); // start on root-node
csmDict[baseBranchName] = stemNodes.map((commitNode) => {
const csmNode = buildCSMNode(commitNode, commitDict, stemDict);
const pr = prDictByMergedCommitSha.get(csmNode.base.commit.id);
if (pr) {
const {
data: { title, body, additions, deletions },
} = pr.detail;

// csm.base 커밋내용을 pr.detail 으로 교체
csmNode.base.commit.message = `${title}\n\n${body}`;
csmNode.base.commit.differenceStatistic.totalInsertionCount = additions;
csmNode.base.commit.differenceStatistic.totalDeletionCount = deletions;

// if squash-merge-commit
if (csmNode.source.length === 0) {
csmNode.source = buildCSMSourceFromPRCommits(csmNode, pr);
}
}

csmNodes.push(csmNode);
return pr ? buildCSMNodeWithPullRequest(csmNode, pr) : csmNode;
});

csmDict[baseBranchName] = csmNodes;

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

// eslint-disable-next-line import/prefer-default-export
export const convertPRCommitsToCommitNodes = (
baseCommit: CommitRaw,
pr: PullRequest
): CommitNode[] =>
pr.commitDetails.data.map((commitDetail) => {
const {
sha,
parents,
commit: { author, committer, message },
files,
} = commitDetail;

let totalInsertionCount = 0;
let totalDeletionCount = 0;
const fileDictionary =
files?.reduce((dict, f) => {
totalInsertionCount += f.additions;
totalDeletionCount += f.deletions;
return {
...dict,
[f.filename]: {
insertionCount: f.additions,
deletionCount: f.deletions,
},
};
}, {}) ?? {};

const prCommitRaw: CommitRaw = {
sequence: -1, // ignore
id: sha,
parents: parents.map((p) => p.sha),
branches: [], // ignore
tags: [], // ignore
author: {
name: author?.name ?? "",
email: author?.email ?? "",
},
authorDate: author?.date ? new Date(author.date) : baseCommit.authorDate,
committer: {
name: committer?.name ?? "",
email: committer?.email ?? "",
},
committerDate: committer?.date
? new Date(committer.date)
: baseCommit.committerDate,
message,
differenceStatistic: {
fileDictionary,
totalInsertionCount,
totalDeletionCount,
},
commitMessageType: "",
};

return { commit: prCommitRaw } as CommitNode;
});

export const convertPRDetailToCommitRaw = (
baseCommit: CommitRaw,
pr: PullRequest
): CommitRaw => {
const {
data: { title, body, additions, deletions },
} = pr.detail;

return {
...baseCommit,
message: `${title}\n\n${body}`,
differenceStatistic: {
fileDictionary: {},
totalInsertionCount: additions,
totalDeletionCount: deletions,
},
};
};
Loading

0 comments on commit 2e86532

Please sign in to comment.