Skip to content

Commit 1f62c35

Browse files
authored
feat: Add commit hash and GH user to commit info (#347)
1 parent ff0402a commit 1f62c35

File tree

6 files changed

+350
-15
lines changed

6 files changed

+350
-15
lines changed

docusaurus.config.ts

+25
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import footer from "./config/footer.config";
66
import { env } from "process";
77
import { Config } from "@docusaurus/types";
88
import { Options } from "@docusaurus/plugin-content-docs";
9+
import { getFileCommitHash } from "@docusaurus/utils/src/gitUtils";
10+
import { AUTHOR_FALLBACK, AuthorData, commitCache, cacheAuthorData } from "./src/util/authorUtils";
911

1012
const preview = env.VERCEL_ENV === "preview";
13+
cacheAuthorData(preview);
1114

1215
const url = (preview && `https://${env.VERCEL_URL}`) || "https://docs.papermc.io";
1316

@@ -80,6 +83,28 @@ const config: Config = {
8083
headingIds: false,
8184
},
8285
format: "detect",
86+
parseFrontMatter: async (params) => {
87+
const result = await params.defaultParseFrontMatter(params);
88+
let author: AuthorData = {
89+
...AUTHOR_FALLBACK,
90+
};
91+
if (process.env.NODE_ENV !== "development") {
92+
const { commit } = await getFileCommitHash(params.filePath);
93+
const username = commitCache.get(commit);
94+
author = {
95+
commit,
96+
username: username ?? AUTHOR_FALLBACK.username,
97+
};
98+
}
99+
100+
return {
101+
...result,
102+
frontMatter: {
103+
...result.frontMatter,
104+
author: author,
105+
},
106+
};
107+
},
83108
},
84109

85110
themes: [

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@
7373
"packageManager": "[email protected]+sha256.01c01eeb990e379b31ef19c03e9d06a14afa5250b82e81303f88721c99ff2e6f",
7474
"pnpm": {
7575
"patchedDependencies": {
76-
"@docusaurus/[email protected]": "patches/@[email protected]"
76+
"@docusaurus/[email protected]": "patches/@[email protected]",
77+
"@docusaurus/[email protected]": "patches/@[email protected]"
7778
}
7879
}
7980
}

patches/@[email protected]

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
diff --git a/src/gitUtils.ts b/src/gitUtils.ts
2+
index 869982b2241c30629df3b83c7c75b9ce9f6a35d1..c3116d7a725d4a5420286baac8147f4e821e5631 100644
3+
--- a/src/gitUtils.ts
4+
+++ b/src/gitUtils.ts
5+
@@ -95,6 +95,13 @@ export async function getFileCommitDate(
6+
timestamp: number;
7+
author?: string;
8+
}> {
9+
+ await throwGitErrors(file);
10+
+ const regex = retrieveCorrectRegex(includeAuthor);
11+
+ const result = await runGitCommandOnFile(file, createArgs('', includeAuthor, age));
12+
+ const match = matchFromRegex(file, regex, result);
13+
+ return matchDateAndTimestamp(match, includeAuthor);
14+
+}
15+
+async function throwGitErrors(file: string) {
16+
if (!hasGit()) {
17+
throw new GitNotFoundError(
18+
`Failed to retrieve git history for "${file}" because git is not installed.`,
19+
@@ -106,22 +113,39 @@ export async function getFileCommitDate(
20+
`Failed to retrieve git history for "${file}" because the file does not exist.`,
21+
);
22+
}
23+
+}
24+
+
25+
+function createArgs(format: string, includeAuthor?: boolean, age: 'oldest' | 'newest' = 'oldest') {
26+
+ // We add a "RESULT:" prefix to make parsing easier
27+
+ // See why: https://github.com/facebook/docusaurus/pull/10022
28+
+ const resultFormat = includeAuthor ? 'RESULT:%ct,%an' : 'RESULT:%ct';
29+
30+
const args = [
31+
- `--format=%ct${includeAuthor ? ',%an' : ''}`,
32+
+ `--format=${includeAuthor === undefined ? format : resultFormat}`,
33+
'--max-count=1',
34+
age === 'oldest' ? '--follow --diff-filter=A' : undefined,
35+
]
36+
.filter(Boolean)
37+
.join(' ');
38+
39+
+ return args;
40+
+}
41+
+async function runGitCommandOnFile(file: string, args: string): Promise<{
42+
+ code: number;
43+
+ stdout: string;
44+
+ stderr: string;
45+
+}> {
46+
+ const command = `git -c log.showSignature=false log ${args} -- "${path.basename(
47+
+ file,
48+
+ )}"`;
49+
+
50+
const result = await new Promise<{
51+
code: number;
52+
stdout: string;
53+
stderr: string;
54+
}>((resolve) => {
55+
shell.exec(
56+
- `git log ${args} -- "${path.basename(file)}"`,
57+
+ command,
58+
{
59+
// Setting cwd is important, see: https://github.com/facebook/docusaurus/pull/5048
60+
cwd: path.dirname(file),
61+
@@ -138,11 +162,19 @@ export async function getFileCommitDate(
62+
`Failed to retrieve the git history for file "${file}" with exit code ${result.code}: ${result.stderr}`,
63+
);
64+
}
65+
- let regex = /^(?<timestamp>\d+)$/;
66+
- if (includeAuthor) {
67+
- regex = /^(?<timestamp>\d+),(?<author>.+)$/;
68+
- }
69+
70+
+ return result;
71+
+}
72+
+function retrieveCorrectRegex(includeAuthor: boolean) {
73+
+ // We only parse the output line starting with our "RESULT:" prefix
74+
+ // See why https://github.com/facebook/docusaurus/pull/10022
75+
+ const regex = includeAuthor
76+
+ ? /(?:^|\n)RESULT:(?<timestamp>\d+),(?<author>.+)(?:$|\n)/
77+
+ : /(?:^|\n)RESULT:(?<timestamp>\d+)(?:$|\n)/;
78+
+
79+
+ return regex;
80+
+}
81+
+function matchFromRegex(file: string, regex: RegExp, result: { code: number; stdout: string; stderr: string; }): RegExpMatchArray {
82+
const output = result.stdout.trim();
83+
84+
if (!output) {
85+
@@ -159,6 +191,13 @@ export async function getFileCommitDate(
86+
);
87+
}
88+
89+
+ return match;
90+
+}
91+
+function matchDateAndTimestamp(match: RegExpMatchArray, includeAuthor: boolean): {
92+
+ date: Date;
93+
+ timestamp: number;
94+
+ author?: string;
95+
+} {
96+
const timestampInSeconds = Number(match.groups!.timestamp);
97+
const timestamp = timestampInSeconds * 1_000;
98+
const date = new Date(timestamp);
99+
@@ -168,3 +207,29 @@ export async function getFileCommitDate(
100+
}
101+
return {date, timestamp};
102+
}
103+
+
104+
+/**
105+
+ * Fetches the git history of a file and returns a relevant commit hash.
106+
+ *
107+
+ * @throws {@link GitNotFoundError} If git is not found in `PATH`.
108+
+ * @throws {@link FileNotTrackedError} If the current file is not tracked by git.
109+
+ * @throws Also throws when `git log` exited with non-zero, or when it outputs
110+
+ * unexpected text.
111+
+ */
112+
+export async function getFileCommitHash(
113+
+ /** Absolute path to the file. */
114+
+ file: string,
115+
+): Promise<{
116+
+ /** The author's commit hash, as returned from git. */
117+
+ commit: string;
118+
+}>;
119+
+
120+
+export async function getFileCommitHash(file: string): Promise<{
121+
+ commit: string;
122+
+}> {
123+
+ throwGitErrors(file);
124+
+ const result = await runGitCommandOnFile(file, createArgs('RESULT:%h', undefined, 'newest'))
125+
+ const regex = /(?:^|\n)RESULT:(?<commit>\w+)(?:$|\n)/;
126+
+ const match = matchFromRegex(file, regex, result);
127+
+ return {commit: match.groups!.commit!};
128+
+}

pnpm-lock.yaml

+18-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)