Last active
April 4, 2022 19:57
-
-
Save danyill/b050f04258b12967b47a8f2156caf5b3 to your computer and use it in GitHub Desktop.
Recent Changes Antora Extension
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
const fs = require("fs"); | |
const git = requireGit(); | |
const http = requireHttp(); | |
function requireGit() { | |
return require(require.resolve("isomorphic-git", { | |
paths: [ | |
require.resolve("@antora/content-aggregator", { paths: module.paths }) + | |
"/..", | |
], | |
})); | |
} | |
function requireHttp() { | |
return require(require.resolve("isomorphic-git/http/node", { | |
paths: [ | |
require.resolve("@antora/content-aggregator", { paths: module.paths }) + | |
"/..", | |
], | |
})); | |
} | |
function date_str(date) { | |
return date.toLocaleString("en-NZ", { | |
day: "numeric", | |
month: "short", | |
year: "numeric", | |
hour: "numeric", | |
minute: "2-digit", | |
}); | |
} | |
function convertUTCDateToLocalDate(date) { | |
var newDate = new Date(date.getTime()); | |
return newDate; | |
} | |
/* from Antora in content-catalog.js */ | |
function generateResourceSpec( | |
{ component, version, module: module_, family, relative }, | |
shorthand = true | |
) { | |
return ( | |
`${version}@${component}:${ | |
shorthand && module_ === "ROOT" ? "" : module_ | |
}:` + | |
(family === "page" || family === "alias" ? "" : `${family}$`) + | |
relative | |
); | |
} | |
// based on https://github.com/isomorphic-git/isomorphic-git/issues/732 | |
async function commitDiff(gitdir, oidA, oidB) { | |
// Use git log to get the SHA-1 object ids of the previous two commits | |
const commits = await git.log({ fs, gitdir, depth: 5, ref: "HEAD" }); | |
const oids = commits.map((commit) => commit.oid); | |
// Make TREE objects for the first and last commits | |
const A = git.TREE({ fs, gitdir, ref: oidA }); | |
const B = git.TREE({ fs, gitdir, ref: oidB }); | |
// Get a list of the files that changed | |
let results = await git.walk({ | |
fs, | |
gitdir, | |
trees: [A, B], | |
map: async function (filepath, [A, B]) { | |
// ignore directories | |
if (filepath === ".") { | |
return; | |
} | |
// this appears to be required but it is unclear exactly why -- the initial commit? | |
if (A == null || B == null) { | |
return; | |
} | |
if ( | |
(A !== null && (await A.type()) === "tree") || | |
(B !== null && (await B.type())) === "tree" | |
) { | |
return; | |
} | |
// generate ids | |
const Aoid = await A.oid(); | |
const Boid = await B.oid(); | |
// determine modification type | |
let type = "equal"; | |
if (Aoid !== Boid) { | |
type = "modify"; | |
} | |
if (Aoid === undefined) { | |
type = "add"; | |
} | |
if (Boid === undefined) { | |
type = "remove"; | |
} | |
if (Aoid === undefined && Boid === undefined) { | |
console.log("Something weird happened:"); | |
} | |
return { | |
path: `/${filepath}`, | |
type: type, | |
}; | |
}, | |
}); | |
return results; | |
} | |
class GitCommitHistoryExtension { | |
static register({ config }) { | |
console.log("Registering an extension to build recent changes page"); | |
new GitCommitHistoryExtension(this, config); | |
} | |
constructor(generatorContext, config) { | |
this.config = config; | |
this.commitData = []; | |
(this.context = generatorContext) | |
.on("contentAggregated", this.onContentAggregated.bind(this)) | |
.on("contentClassified", this.onContentClassified.bind(this)); | |
this.logger = this.context.require("@antora/logger")( | |
"source-list-extension" | |
); | |
} | |
async onContentAggregated({ contentAggregate }) { | |
this.logger.info("onContentAggregated: Building commit history data"); | |
for (const componentVersionData of contentAggregate) { | |
const { name, version, files, nav } = componentVersionData; | |
const referenceFilePath = nav ? nav[0] : "modules/ROOT/pages/index.adoc"; | |
let referenceFile = files.find(({ path }) => path === referenceFilePath); | |
referenceFile = referenceFile ? referenceFile : files[0]; | |
const { gitdir, refhash } = referenceFile.src.origin; | |
let result = await git.fetch({ | |
fs, | |
http, | |
gitdir: gitdir + '/.git', | |
depth: 10, | |
ref: referenceFile.src.origin.branch, | |
singleBranch: true, | |
tags: false | |
}) | |
// url: referenceFile.src.origin.url, | |
// ref: referenceFile.src.origin.branch, | |
console.log(result) | |
const commitData = []; | |
if (gitdir) { | |
const commits = await git.log({ | |
fs, | |
gitdir, | |
depth: 10, | |
ref: referenceFile.src.origin.branch, | |
}); | |
await Promise.all( | |
commits.map(async (commit) => { | |
const lastCommit = commit["commit"]; | |
const lastHash = commit["oid"]; | |
let res = [] | |
// try { | |
res = await commitDiff( | |
gitdir, | |
commit.oid, | |
commit.commit.parent[0] | |
); | |
// } catch (e) { | |
// // TODO: If it is the initial HEAD commit we don't want a diff, we just want everything in that commit. | |
// // if (e instanceof NotFoundError) { | |
// // // probably no commit parent, this is OK | |
// // } else { | |
// // throw e; // re-throw the error unchanged | |
// // } | |
// } | |
const commitSummary = { | |
name: `${version ? version + "@" : ""}${name}`, | |
shortHash: lastHash.substr(0, 7), | |
// subject: lastCommit.message.split(/$/m)[0], | |
message: lastCommit.message, | |
date: convertUTCDateToLocalDate( | |
new Date(lastCommit.author.timestamp * 1000) | |
), | |
author: lastCommit.author.name, | |
files: res | |
.filter((file) => file.type !== "equal") | |
.map((file) => file.path), | |
}; | |
commitData.push(commitSummary); | |
}) | |
); | |
this.commitData.push({ | |
name: name, | |
version: version, | |
commitData: commitData.sort((a, b) => b.date - a.date), | |
}); | |
} | |
} | |
} | |
getGitlabCommitUrlPrefix(contentCatalog, name, version, path) { | |
const fileData = contentCatalog.getByPath({ | |
component: name, | |
version: version, | |
path: path.slice(1), | |
}); | |
return `${fileData.src.origin.webUrl}/-/commit/`; | |
} | |
async onContentClassified({ | |
_playbook, | |
_siteAsciiDocConfig, | |
_siteCatalog, | |
_uiCatalog, | |
contentCatalog, | |
}) { | |
this.logger.info("onContentClassified: Creating and adding new page"); | |
this.commitData.forEach((componentVersion) => { | |
componentVersion.commitData.forEach((commit) => { | |
commit["fileIds"] = []; | |
commit.files.forEach((file) => { | |
// this does not follow path changes but those will be a bit rare. | |
const fileData = contentCatalog.getByPath({ | |
component: componentVersion.name, | |
version: componentVersion.version, | |
path: file.slice(1), | |
}); | |
if (fileData !== undefined) { | |
commit["fileIds"].push(generateResourceSpec(fileData.src)); | |
commit["url"] = this.getGitlabCommitUrlPrefix( | |
contentCatalog, | |
componentVersion.name, | |
componentVersion.version, | |
file | |
); | |
} | |
}); | |
}); | |
let rows = []; | |
componentVersion.commitData.forEach((cData) => { | |
if (cData["fileIds"].length !== 0) { | |
rows.push( | |
`| ${date_str(cData["date"])} | |
| ${cData["author"]} | |
| ${cData["url"]}${cData["shortHash"]}[${cData["shortHash"]}] | |
${cData["message"]} | |
| ${cData["fileIds"].map((id) => `* xref:${id}[]`).join("\n")}` | |
); | |
} | |
}); | |
const contents = Buffer.from( | |
`= Recent Changes | |
[.small]#Generated at ${date_str(convertUTCDateToLocalDate(new Date()))}# | |
[cols="2,3,6,8a",options="header,unbreakable"] | |
|==== | |
| Date | Author | Commit | Changed Files | |
${rows.join("\n")} | |
|==== | |
`.trim() | |
); | |
contentCatalog.addFile({ | |
path: "modules/ROOT/pages/recent-changes.adoc", | |
src: { | |
component: componentVersion.name, | |
version: componentVersion.version, | |
module: "ROOT", | |
family: "page", | |
relative: "recent-changes.adoc", | |
}, | |
contents: contents, | |
}); | |
}); | |
} | |
} | |
module.exports = GitCommitHistoryExtension; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment