Skip to content

Commit

Permalink
feat(plugins/languages): count verified commits using user's gpg keys (
Browse files Browse the repository at this point in the history
  • Loading branch information
lowlighter authored Mar 10, 2022
1 parent e60d74a commit 96ea0be
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 3 deletions.
37 changes: 35 additions & 2 deletions source/plugins/languages/analyzers.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
import linguist from "linguist-js"

/**Indepth analyzer */
export async function indepth({login, data, imports, repositories}, {skipped, categories, timeout}) {
export async function indepth({login, data, imports, repositories, gpg}, {skipped, categories, timeout}) {
return new Promise(async (solve, reject) => {
//Timeout
if (Number.isFinite(timeout)) {
console.debug(`metrics/compute/${login}/plugins > languages > timeout set to ${timeout}m`)
setTimeout(() => reject(`Reached maximum execution time of ${timeout}m for analysis`), timeout * 60 * 1000)
}

//GPG keys imports
for (const {id, pub} of gpg) {
const path = imports.paths.join(imports.os.tmpdir(), `${data.user.databaseId}.${id}.gpg`)
console.debug(`metrics/compute/${login}/plugins > languages > saving gpg ${id} to ${path}`)
try {
await imports.fs.writeFile(path, pub)
if (process.env.GITHUB_ACTIONS) {
console.debug(`metrics/compute/${login}/plugins > languages > importing gpg ${id}`)
await imports.run(`gpg --import ${path}`)
}
else
console.debug(`metrics/compute/${login}/plugins > languages > skipping import of gpg ${id}`)
}
catch (error) {
console.debug(`metrics/compute/${login}/plugins > languages > indepth > an error occured while importing gpg ${id}, skipping...`)
}
finally {
//Cleaning
console.debug(`metrics/compute/${login}/plugins > languages > indepth > cleaning ${path}`)
await imports.fs.rm(path, {recursive:true, force:true})
}
}

//Compute repositories stats from fetched repositories
const results = {total:0, lines:{}, stats:{}, colors:{}, commits:0, files:0, missed:0}
const results = {total:0, lines:{}, stats:{}, colors:{}, commits:0, files:0, missed:0, verified:{signature:0}}
for (const repository of repositories) {
//Skip repository if asked
if ((skipped.includes(repository.name.toLocaleLowerCase())) || (skipped.includes(`${repository.owner.login}/${repository.name}`.toLocaleLowerCase()))) {
Expand Down Expand Up @@ -170,6 +193,7 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr
console.debug(`metrics/compute/${login}/plugins > languages > indepth > repo seems empty or impossible to git log, skipping`)
return
}
const pending = []
for (let page = 0; ; page++) {
try {
console.debug(`metrics/compute/${login}/plugins > languages > indepth > processing commits ${page * per_page} from ${(page + 1) * per_page}`)
Expand All @@ -182,6 +206,14 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr
empty = false
//Commits counter
if (/^commit [0-9a-f]{40}$/.test(line)) {
if (results.verified) {
const sha = line.match(/[0-9a-f]{40}/)?.[0]
if (sha) {
pending.push(imports.run(`git verify-commit ${sha}`, {cwd:path, env:{LANG:"en_GB"}}, {log:false, prefixed:false})
.then(() => results.verified.signature++)
.catch(() => null))
}
}
results.commits++
return
}
Expand Down Expand Up @@ -223,6 +255,7 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr
results.missed += per_page
}
}
await Promise.allSettled(pending)
results.files += edited.size
}

Expand Down
21 changes: 20 additions & 1 deletion source/plugins/languages/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,29 @@ export default async function({login, data, imports, q, rest, account}, {enabled

//Indepth mode
if (indepth) {
//Fetch gpg keys (web-flow is GitHub's public key when making changes from web ui)
const gpg = []
try {
for (const username of [login, "web-flow"]) {
const {data:keys} = await rest.users.listGpgKeysForUser({username})
gpg.push(...keys.map(({key_id:id, raw_key:pub, emails}) => ({id, pub, emails})))
if (username === login) {
for (const {email} of gpg.flatMap(({emails}) => emails)) {
console.debug(`metrics/compute/${login}/plugins > languages > auto-adding ${email} to commits_authoring (fetched from gpg)`)
data.shared["commits.authoring"].push(email)
}
}
}
}
catch (error) {
console.debug(`metrics/compute/${login}/plugins > languages > ${error}`)
}

//Analyze languages
try {
console.debug(`metrics/compute/${login}/plugins > languages > switching to indepth mode (this may take some time)`)
const existingColors = languages.colors
Object.assign(languages, await indepth_analyzer({login, data, imports, repositories}, {skipped, categories, timeout}))
Object.assign(languages, await indepth_analyzer({login, data, imports, repositories, gpg}, {skipped, categories, timeout}))
Object.assign(languages.colors, existingColors)
console.debug(`metrics/compute/${login}/plugins > languages > indepth analysis missed ${languages.missed} commits`)
}
Expand Down
8 changes: 8 additions & 0 deletions source/templates/classic/partials/languages.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@
<% } %>
</div>
<% } %>
<% if (plugins.languages.verified?.signature) { %>
<div class="row footnote">
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M9.585.52a2.678 2.678 0 00-3.17 0l-.928.68a1.178 1.178 0 01-.518.215L3.83 1.59a2.678 2.678 0 00-2.24 2.24l-.175 1.14a1.178 1.178 0 01-.215.518l-.68.928a2.678 2.678 0 000 3.17l.68.928c.113.153.186.33.215.518l.175 1.138a2.678 2.678 0 002.24 2.24l1.138.175c.187.029.365.102.518.215l.928.68a2.678 2.678 0 003.17 0l.928-.68a1.17 1.17 0 01.518-.215l1.138-.175a2.678 2.678 0 002.241-2.241l.175-1.138c.029-.187.102-.365.215-.518l.68-.928a2.678 2.678 0 000-3.17l-.68-.928a1.179 1.179 0 01-.215-.518L14.41 3.83a2.678 2.678 0 00-2.24-2.24l-1.138-.175a1.179 1.179 0 01-.518-.215L9.585.52zM7.303 1.728c.415-.305.98-.305 1.394 0l.928.68c.348.256.752.423 1.18.489l1.136.174c.51.078.909.478.987.987l.174 1.137c.066.427.233.831.489 1.18l.68.927c.305.415.305.98 0 1.394l-.68.928a2.678 2.678 0 00-.489 1.18l-.174 1.136a1.178 1.178 0 01-.987.987l-1.137.174a2.678 2.678 0 00-1.18.489l-.927.68c-.415.305-.98.305-1.394 0l-.928-.68a2.678 2.678 0 00-1.18-.489l-1.136-.174a1.178 1.178 0 01-.987-.987l-.174-1.137a2.678 2.678 0 00-.489-1.18l-.68-.927a1.178 1.178 0 010-1.394l.68-.928c.256-.348.423-.752.489-1.18l.174-1.136c.078-.51.478-.909.987-.987l1.137-.174a2.678 2.678 0 001.18-.489l.927-.68zM11.28 6.78a.75.75 0 00-1.06-1.06L7 8.94 5.78 7.72a.75.75 0 00-1.06 1.06l1.75 1.75a.75.75 0 001.06 0l3.75-3.75z"></path></svg>
<%= plugins.languages.verified.signature %> commit<%= s(plugins.languages.verified.signature) %> verified by GPG
</div>
</div>
<% } %>
<% } %>
</section>
<% } %>
Expand Down
6 changes: 6 additions & 0 deletions source/templates/classic/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@
margin-right: 6px;
}

.footnote {
width: 100%;
justify-content: flex-end;
font-size: 12px;
}

/* Follow-up */
.followup.legend {
font-size: 12px;
Expand Down
25 changes: 25 additions & 0 deletions tests/mocks/api/github/rest/users/listGpgKeysForUser.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**Mocked data */
export default function({ faker }, target, that, [{ username }]) {
console.debug("metrics/compute/mocks > mocking rest api result > rest.users.listGpgKeysForUser")
return ({
status: 200,
url: `https://api.github.com/users/${username}/`,
headers: {
server: "GitHub.com",
status: "200 OK",
"x-oauth-scopes": "repo",
},
data: [
{
key_id: faker.datatype.hexaDecimal(16),
raw_key: "-----BEGIN PGP PUBLIC KEY BLOCK-----\n(dummy content)\n-----END PGP PUBLIC KEY BLOCK-----",
emails: [
{
email: faker.internet.email(),
verified: true
}
]
}
],
})
}

0 comments on commit 96ea0be

Please sign in to comment.