Skip to content
21 changes: 13 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# Base image
FROM node:20-bookworm-slim

# Copy repository
COPY . /metrics
WORKDIR /metrics
FROM node:20-bookworm-slim AS base

# Setup
RUN chmod +x /metrics/source/app/action/index.mjs \
RUN echo -n \
# Install latest chrome dev package, fonts to support major charsets and skip chromium download on puppeteer install
# Based on https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#running-puppeteer-in-docker
&& apt-get update \
Expand All @@ -19,12 +15,21 @@ RUN chmod +x /metrics/source/app/action/index.mjs \
&& apt-get install -y curl unzip \
&& curl -fsSL https://deno.land/x/install/install.sh | DENO_INSTALL=/usr/local sh \
# Install ruby to support github licensed gem
&& apt-get install -y ruby-full git g++ cmake pkg-config libssl-dev \
&& apt-get install -y ruby-full git g++ cmake pkg-config libssl-dev xz-utils \
&& gem install licensed \
# Install python for node-gyp
&& apt-get install -y python3 \
# Clean apt/lists
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/apt/lists/*


FROM base

# Copy repository
COPY . /metrics
WORKDIR /metrics

RUN chmod +x /metrics/source/app/action/index.mjs\
# Install node modules and rebuild indexes
&& npm ci \
&& npm run build
Expand Down
59 changes: 49 additions & 10 deletions source/plugins/languages/analyzer/recent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class RecentAnalyzer extends Analyzer {
async patches() {
//Fetch commits from recent activity
this.debug(`fetching patches from last ${this.days || ""} days up to ${this.load || "∞"} events`)
const commits = [], pages = Math.ceil((this.load || Infinity) / 100)
const pages = Math.ceil((this.load || Infinity) / 100)
if (this.context.mode === "repository") {
try {
const {data: {default_branch: branch}} = await this.rest.repos.get(this.context)
Expand All @@ -42,10 +42,11 @@ export class RecentAnalyzer extends Analyzer {
this.debug(`failed to get default branch for ${this.context.owner}/${this.context.repo} (${error})`)
}
}
const events = []
try {
for (let page = 1; page <= pages; page++) {
this.debug(`fetching events page ${page}`)
commits.push(
events.push(
...(await (this.context.mode === "repository" ? this.rest.activity.listRepoEvents(this.context) : this.rest.activity.listEventsForAuthenticatedUser({username: this.login, per_page: 100, page}))).data
.filter(({type, payload}) => (type === "PushEvent") && ((this.context.mode !== "repository") || ((this.context.mode === "repository") && (payload?.ref?.includes?.(`refs/heads/${this.context.branch}`)))))
.filter(({actor}) => (this.account === "organization") || (this.context.mode === "repository") ? true : !filters.text(actor.login, [this.login], {debug: false}))
Expand All @@ -57,16 +58,54 @@ export class RecentAnalyzer extends Analyzer {
catch {
this.debug("no more page to load")
}
this.debug(`fetched ${events.length} events`)

const wanted = new Map()
events.forEach(event => {
let key = `${event.repo.name}@${event.payload.ref}`
let item = wanted.get(key) ?? { commits: [] }
item.repo = event.repo.name
item.ref = event.payload.ref
item.commits.push(event.payload.before)
item.commits.push(event.payload.head)
wanted.set(key, item)
})

const commits = []
for (const item of wanted.values()) {
try {
for (let page = 1; page <= pages; page++) {
this.debug(`fetching commits page ${page}`)
this.debug(`https://api.github.com/repos/${item.repo}/commits?sha=${item.ref}&per_page=20&page=${page}`)
commits.push(
...(await this.rest.request(`https://api.github.com/repos/${item.repo}/commits?sha=${item.ref}&per_page=20&page=${page}`)).data
.map(x => {
item.commits = item.commits.filter(c => c !== x.sha)
return x
})
.filter(({ committer }) => (this.account === "organization") || (this.context.mode === "repository") ? true : !filters.text(committer.login, [this.login], { debug: false }))
.filter(({ commit }) => ((!this.days) || (new Date(commit.committer.date) > new Date(Date.now() - this.days * 24 * 60 * 60 * 1000)))),
)
if (item.commits < 1) {
this.debug("found expected commits")
break
}
}
}
catch {
this.debug("no more page to load")
}
}

this.debug(`fetched ${commits.length} commits`)
this.results.latest = Math.round((new Date().getTime() - new Date(commits.slice(-1).shift()?.created_at).getTime()) / (1000 * 60 * 60 * 24))
this.results.latest = Math.round((new Date().getTime() - new Date(events.slice(-1).shift()?.created_at).getTime()) / (1000 * 60 * 60 * 24))
this.results.commits = commits.length

//Retrieve edited files and filter edited lines (those starting with +/-) from patches
this.debug("fetching patches")
const patches = [
...await Promise.allSettled(
commits
.flatMap(({payload}) => payload.commits)
.filter(({committer}) => filters.text(committer?.email, this.authoring, {debug: false}))
.map(commit => commit.url)
.map(async commit => (await this.rest.request(commit)).data),
Expand All @@ -75,9 +114,9 @@ export class RecentAnalyzer extends Analyzer {
.filter(({status}) => status === "fulfilled")
.map(({value}) => value)
.filter(({parents}) => parents.length <= 1)
.map(({sha, commit: {message, committer}, verification, files}) => ({
.map(({ sha, commit: { message, author }, verification, files }) => ({
sha,
name: `${message} (authored by ${committer.name} on ${committer.date})`,
name: `${message} (authored by ${author.name} on ${author.date})`,
verified: verification?.verified ?? null,
editions: files.map(({filename, patch = ""}) => {
const edition = {
Expand All @@ -104,15 +143,15 @@ export class RecentAnalyzer extends Analyzer {
}

/**Run linguist against a commit and compute edited lines and bytes*/
async linguist(_, {commit, cache: {languages}}) {
const cache = {files: {}, languages}
const result = {total: 0, files: 0, missed: {lines: 0, bytes: 0}, lines: {}, stats: {}, languages: {}}
async linguist(_, { commit, cache: { languages } }) {
const cache = { files: {}, languages }
const result = { total: 0, files: 0, missed: { lines: 0, bytes: 0 }, lines: {}, stats: {}, languages: {} }
const edited = new Set()
for (const edition of commit.editions) {
edited.add(edition.path)

//Guess file language with linguist
const {files: {results: files}, languages: {results: languages}, unknown} = await linguist(edition.path, {fileContent: edition.patch})
const { files: { results: files }, languages: { results: languages }, unknown } = await linguist(edition.path, { fileContent: edition.patch })
Object.assign(cache.files, files)
Object.assign(cache.languages, languages)
if (!(edition.path in cache.files))
Expand Down
Loading