Revamp of Commit Authors in Changelog (#829)

[SKIP]
This commit is contained in:
Integer Limit 2024-07-28 23:03:36 +10:00 committed by GitHub
parent 51fe4e5a29
commit a520c819d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 216 additions and 78 deletions

View File

@ -17,10 +17,10 @@ import {
} from "#types/changelogTypes.ts";
import dedent from "dedent-js";
import mustache from "mustache";
import { modChangesAllocations, repoLink } from "./definitions.ts";
import { modChangesAllocations } from "./definitions.ts";
import ChangelogData from "./changelogData.ts";
import { SpecialChangelogFormatting } from "#types/changelogTypes.ts";
import { sortCommitListReverse } from "./pusher.ts";
import { formatMessage, sortCommitListReverse } from "./pusher.ts";
import { logError } from "#utils/log.ts";
/**
@ -30,32 +30,11 @@ const getModChangesFormatting: (
commits?: Commit[],
) => SpecialChangelogFormatting<Commit[] | undefined> = (commits) => {
return {
formatting: (message, subMessage, indentation, commits) => {
formatting: async (message, subMessage, indentation, commits) => {
// Sub messages are details, so make them bold & italic
if (subMessage) return `${indentation}* ***${message}***`;
// Edge Case
if (!commits) return `${indentation}* ${message}`;
if (commits.length > 1) {
const authors: string[] = [];
const formattedCommits: string[] = [];
commits.forEach((commit) => {
if (!authors.includes(commit.author_name))
authors.push(commit.author_name);
formattedCommits.push(
`[\`${commit.hash.substring(0, 7)}\`](${repoLink}commit/${commit.hash})`,
);
});
authors.sort();
return `${indentation}* ${message} - **${authors.join("**, **")}** (${formattedCommits.join(", ")})`;
}
const commit = commits[0];
const shortSHA = commit.hash.substring(0, 7);
const author = commit.author_name;
return `${indentation}* ${message} - **${author}** ([\`${shortSHA}\`](${repoLink}commit/${commit.hash}))`;
return formatMessage(message, indentation, commits, subMessage);
},
storage: commits,
} as SpecialChangelogFormatting<Commit[] | undefined>;

View File

@ -3,7 +3,12 @@ import { categories, defaultIndentation } from "./definitions.ts";
import { Category, ChangelogMessage, Commit } from "#types/changelogTypes.ts";
import { repoLink } from "./definitions.ts";
import { Octokit } from "@octokit/rest";
import { getIssueURL, getNewestIssueURLs } from "#utils/util.ts";
import {
formatAuthor,
getIssueURL,
getNewestCommitAuthors,
getNewestIssueURLs,
} from "#utils/util.ts";
let data: ChangelogData;
let octokit: Octokit;
@ -14,6 +19,9 @@ const sectionLinesBeforeCommitLogExcluded = 50;
// How many lines the commit log can be before its is excluded.
const logLinesBeforeCommitLogExcluded = 20;
// How many commits to include after a message.
const maxIncludeCommits = 3;
export default async function pushAll(inputData: ChangelogData): Promise<void> {
pushTitle(inputData);
await pushChangelog(inputData);
@ -75,12 +83,14 @@ export async function pushChangelog(inputData: ChangelogData): Promise<void> {
data.builder.length < sectionLinesBeforeCommitLogExcluded &&
data.commitList.length < logLinesBeforeCommitLogExcluded
) {
// Commit List is relatively short, and most commits would have been handled via category pushing anyway.
// Just retrieve each author info sequentially.
sortCommitList(data.commitList, (commit) => commit);
data.builder.push("## Commits");
data.commitList.forEach((commit) => {
data.builder.push(formatCommit(commit));
});
for (const commit of data.commitList) {
data.builder.push(await formatCommit(commit));
}
}
} else {
// No Commit List = No Changes
@ -118,19 +128,31 @@ async function pushCategory(category: Category) {
categoryLog.push(`### ${subCategory.keyName}:`);
}
// Format Main Messages (Async so Author Fetch is Fast)
await getNewestCommitAuthors(octokit);
const formatted: { message: ChangelogMessage; formatted: string }[] =
await Promise.all(
list.map((message) =>
formatChangelogMessage(message).then((formatted) => {
return { message, formatted };
}),
),
);
// Sort Log
sortCommitList(
list,
(message) => message.commitObject,
(a, b) => a.commitMessage.localeCompare(b.commitMessage),
formatted,
(formatted) => formatted.message.commitObject,
(a, b) =>
a.message.commitMessage.localeCompare(b.message.commitMessage),
);
// Push Log
for (const changelogMessage of list) {
categoryLog.push(await formatChangelogMessage(changelogMessage));
// Push Sub Messages
if (changelogMessage.subChangelogMessages) {
for (const subMessage of changelogMessage.subChangelogMessages)
for (const format of formatted) {
categoryLog.push(format.formatted);
// Push Sub Messages (No need for Async, Author Info Not Calculated in Sub Messages)
if (format.message.subChangelogMessages) {
for (const subMessage of format.message.subChangelogMessages)
categoryLog.push(await formatChangelogMessage(subMessage, true));
}
}
@ -222,58 +244,110 @@ async function formatChangelogMessage(
changelogMessage.specialFormatting.storage,
);
if (changelogMessage.commitObject && !subMessage) {
if (!changelogMessage.commitObject || subMessage) {
return formatMessage(message, indentation, undefined, subMessage);
}
if (data.combineList.has(changelogMessage.commitObject.hash)) {
const commits =
data.combineList.get(changelogMessage.commitObject.hash) ?? [];
commits.push(changelogMessage.commitObject);
return formatMessage(message, indentation, commits, subMessage);
}
return formatMessage(
message,
indentation,
[changelogMessage.commitObject],
subMessage,
);
}
/**
* Formats a Changelog Message
* @param message The message to format.
* @param indentation Indentation to use.
* @param commits List of Commits
* @param subMessage Whether this message is a subMessage (used in details). Set to true to make it a subMessage (different parsing). Defaults to false.
* @return string Formatted Changelog Message
*/
export async function formatMessage(
message: string,
indentation: string,
commits?: Commit[],
subMessage = false,
): Promise<string> {
if (!commits || commits.length == 0 || subMessage) {
return `${indentation}* ${message}`;
}
if (commits.length === 1) {
const commit = commits[0];
const shortSHA = commit.hash.substring(0, 7);
const formattedCommit = `[\`${shortSHA}\`](${repoLink}commit/${commit.hash})`;
const author = await formatAuthor(commit, octokit);
return `${indentation}* ${message} - ${author} (${formattedCommit})`;
}
// Sort original array so newest commits appear at the end instead of start of commit string
sortCommitListReverse(commits);
const formattedCommits: string[] = [];
const authors: string[] = [];
const authorEmails: Set<string> = new Set<string>();
const retrievedAuthors: { commit: Commit; formatted: string }[] =
await Promise.all(
commits.map((commit) =>
formatAuthor(commit, octokit).then((formatted) => {
return { commit, formatted };
}),
),
);
const processedAuthors: Set<string> = new Set<string>();
const processedEmails: Set<string> = new Set<string>();
const processedSHAs: Set<string> = new Set<string>();
commits.forEach((commit) => {
if (processedSHAs.has(commit.hash)) return;
sortCommitList(
retrievedAuthors,
(author) => author.commit,
(a, b) => a.formatted.localeCompare(b.formatted),
);
retrievedAuthors.forEach((pAuthor) => {
if (processedSHAs.has(pAuthor.commit.hash)) return;
if (
!authors.includes(commit.author_name) &&
!authorEmails.has(commit.author_email)
!processedAuthors.has(pAuthor.formatted) &&
!processedEmails.has(pAuthor.commit.author_email)
) {
authors.push(commit.author_name);
authorEmails.add(commit.author_email);
authors.push(pAuthor.formatted);
processedAuthors.add(pAuthor.formatted);
processedEmails.add(pAuthor.commit.author_email);
}
formattedCommits.push(
`[\`${commit.hash.substring(0, 7)}\`](${repoLink}commit/${commit.hash})`,
`[\`${pAuthor.commit.hash.substring(0, 7)}\`](${repoLink}commit/${pAuthor.commit.hash})`,
);
processedSHAs.add(commit.hash);
processedSHAs.add(pAuthor.commit.hash);
});
authors.sort();
return `${indentation}* ${message} - **${authors.join("**, **")}** (${formattedCommits.join(", ")})`;
}
const commit = changelogMessage.commitObject;
const shortSHA = commit.hash.substring(0, 7);
const author = commit.author_name;
return `${indentation}* ${message} - **${author}** ([\`${shortSHA}\`](${repoLink}commit/${commit.hash}))`;
// Delete all Formatted Commits after MaxIncludeCommits elements, replace with '...'
if (formattedCommits.length > maxIncludeCommits) {
formattedCommits.splice(maxIncludeCommits, Infinity, "...");
}
return `${indentation}* ${message}`;
return `${indentation}* ${message} - ${authors.join(", ")} (${formattedCommits.join(", ")})`;
}
/**
* Returns a formatted commit
*/
function formatCommit(commit: Commit): string {
async function formatCommit(commit: Commit): Promise<string> {
const date = new Date(commit.date).toLocaleDateString("en-us", {
year: "numeric",
month: "short",
day: "numeric",
});
const formattedCommit = `${commit.message} - **${commit.author_name}** (${date})`;
const formattedCommit = `${commit.message} - ${await formatAuthor(commit, octokit)} (${date})`;
const shortSHA = commit.hash.substring(0, 7);

View File

@ -134,7 +134,7 @@ export interface SpecialChangelogFormatting<T> {
subMessage: boolean,
indentation: string,
storage?: T,
) => string;
) => Promise<string>;
/**
* Storage

View File

@ -593,9 +593,15 @@ export async function getVersionManifest(
*/
export function cleanupVersion(version?: string): string {
if (!version) return "";
if (!version.replace(/[\d+.?]+/g, "")) return version;
version = version.replace(/1\.12\.2|1\.12|\.jar/g, "");
const list = version.match(/[\d+.?]+/g);
if (!list) return version;
if (list[list.length - 1] == "0") return version;
return list[list.length - 1];
}
@ -650,20 +656,99 @@ export async function getIssueURL(
logError(
`Failed to get the Issue/PR Info for Issue/PR #${issueNumber}. Returned Status Code ${issueInfo.status}, expected Status 200.`,
);
issueURLCache.set(issueNumber, "");
return "";
}
logInfo(
`No Issue URL Cache for Issue Number ${issueNumber}. Retrieved Specifically.`,
);
issueURLCache.set(issueNumber, issueInfo.data.html_url);
return issueInfo.data.html_url;
} catch (e) {
logError(
`Failed to get the Issue/PR Info for Issue/PR #${issueNumber}. This may be because this is not a PR or Issue, or could be because of rate limits.`,
);
issueURLCache.set(issueNumber, "");
return "";
}
}
// Map of Commit SHA -> Formatted Author
const commitAuthorCache: Map<string, string> = new Map<string, string>();
/**
* Fills the Commit Author Cache with the newest 100 commits from the repo.
*/
export async function getNewestCommitAuthors(octokit: Octokit): Promise<void> {
if (commitAuthorCache.size > 0) return;
try {
const commits = await octokit.repos.listCommits({
owner: repoOwner,
repo: repoName,
per_page: 100,
});
if (commits.status !== 200) {
logError(
`Failed to get all Commit Authors. Returned Status Code ${commits.status}, expected Status 200.`,
);
return;
}
commits.data.forEach((commit) => {
if (!commitAuthorCache.has(commit.sha))
commitAuthorCache.set(commit.sha, commit.author?.login ?? "");
});
} catch (e) {
logError(
"Failed to get all Commit Authors of Repo. This may be because there are no commits, or because of rate limits.",
);
}
}
/**
* Gets the Author, in mentionable form (@login), or default (**Display Name**), from a Commit.
*/
export async function formatAuthor(commit: Commit, octokit: Octokit) {
const defaultFormat = `**${commit.author_name}**`;
if (commitAuthorCache.has(commit.hash)) {
const login = commitAuthorCache.get(commit.hash);
if (login) return `@${login}`;
return defaultFormat;
}
try {
const commitInfo = await octokit.repos.getCommit({
owner: repoOwner,
repo: repoName,
ref: commit.hash,
});
if (commitInfo.status !== 200) {
logError(
`Failed to get the Author Info for Commit ${commit.hash}. Returned Status Code ${commitInfo.status}, expected Status 200.`,
);
commitAuthorCache.set(commit.hash, "");
return defaultFormat;
}
if (!commitInfo.data.author?.login) {
logError(
`Failed to get the Author Info for Commit ${commit.hash}. Returned Null Data, Author or Login.`,
);
commitAuthorCache.set(commit.hash, "");
return defaultFormat;
}
logInfo(
`No Author Cache for Commit ${commit.hash}. Retrieved Specifically.`,
);
return `@${commitInfo.data.author.login}`;
} catch (e) {
logError(
`Failed to get Commit Author for Commit ${commit.hash}. This may be because there are no commits, or because of rate limits.`,
);
commitAuthorCache.set(commit.hash, "");
return defaultFormat;
}
}
export const FORGE_VERSION_REG = /forge-(.+)/;
export const FORGE_MAVEN = "https://files.minecraftforge.net/maven/";