diff --git a/Dockerfile b/Dockerfile index 3ff5209a48a..1d1648e8b7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 \ @@ -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 diff --git a/source/plugins/languages/analyzer/recent.mjs b/source/plugins/languages/analyzer/recent.mjs index 927c91a7189..6eb371b58ef 100644 --- a/source/plugins/languages/analyzer/recent.mjs +++ b/source/plugins/languages/analyzer/recent.mjs @@ -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) @@ -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})) @@ -57,8 +58,47 @@ 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 @@ -66,7 +106,6 @@ export class RecentAnalyzer extends Analyzer { 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), @@ -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 = { @@ -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))