Tucker McKnight <tucker@pangolin.lan> | Mon Nov 03 2025
add more clear types for getBranchInfo
14 15 16 17 18 19
const ajv = new Ajv()
const exec = util.promisify(childProcess.exec)
export default async function repoViewer(eleventyConfig, reposConfiguration: ReposConfiguration) {
const validator = ajv.compile(ConfigSchema)
const valid = validator(reposConfiguration)
if (!valid) {14 15 16 17 18 19
const ajv = new Ajv()
const exec = util.promisify(childProcess.exec)
export default async function repoViewer(eleventyConfig: any, reposConfiguration: ReposConfiguration) {
const validator = ajv.compile(ConfigSchema)
const valid = validator(reposConfiguration)
if (!valid) {33 34 35 36 37 38
const branchesData = branches(reposData)
eleventyConfig.addGlobalData("branches", branchesData)
eleventyConfig.addFilter("getFileName", (filePath) => {
const pathParts = filePath.split("/")
return pathParts[pathParts.length - 1]
})33 34 35 36 37 38
const branchesData = branches(reposData)
eleventyConfig.addGlobalData("branches", branchesData)
eleventyConfig.addFilter("getFileName", (filePath: string) => {
const pathParts = filePath.split("/")
return pathParts[pathParts.length - 1]
})0 1 2
type Repository = {
name: string,
description?: string,
cloneUrl: string,0 1 2
export type Repository = {
name: string,
description?: string,
cloneUrl: string,0 1 2 3
import _ from 'lodash'
import * as Diff from 'diff'
type NavValues = {
repoName: string | ((data: any) => string),0 1 2 3 4
import _ from 'lodash'
import * as Diff from 'diff'
import {type Repository} from './dataTypes.ts'
type NavValues = {
repoName: string | ((data: any) => string),13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
afterText: string,
}
type DiffInfo = {
file: string,
lineNumber: number,
previousText: string,
afterText: string,
}
const getGitDiffsFromPatchText = (patchText: string): Array<DiffInfo> => {
const lines = patchText.split("\n")
const hunks: Array<Hunk> = []
let previousHunk = -1
const filenameRegex = RegExp(/diff --git a\/(.*?) b\/(.*?)/)
const lineNumberRegex = RegExp(/@@ -(.*?)[,| ].*/)13 14 15 16 17 18 19 20 21 22 23
afterText: string,
}
type Diffs = ReturnType<Repository['commits']['get']>['diffs']
const getGitDiffsFromPatchText = (patchText: string): Diffs => {
const lines = patchText.split("\n")
const hunks: Diffs = []
let previousHunk = -1
const filenameRegex = RegExp(/diff --git a\/(.*?) b\/(.*?)/)
const lineNumberRegex = RegExp(/@@ -(.*?)[,| ].*/)54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
lastHunkBefore = _.escape(lastHunkBefore)
lastHunkAfter = _.escape(lastHunkAfter)
const changeObject = Diff.diffWordsWithSpace(lastHunkBefore, lastHunkAfter)
let previousText = ""
let afterText = ""
changeObject.forEach((obj) => {
if (!obj.added && !obj.removed) {
previousText = previousText + obj.value
afterText = afterText + obj.value
}
if (obj.added) {
afterText = afterText + "<mark>" + obj.value + "</mark>"
}
if (obj.removed) {
previousText = previousText + "<mark>" + obj.value + "</mark>"
}
})
hunks.push({
file: previousFilename !== '' ? previousFilename : currentFilename,
lineNumber: parseInt(lines[previousHunk].match(lineNumberRegex)[1]),
previousText,
afterText,
})
previousFilename = ''54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
lastHunkBefore = _.escape(lastHunkBefore)
lastHunkAfter = _.escape(lastHunkAfter)
const changeObject = Diff.diffWordsWithSpace(lastHunkBefore, lastHunkAfter)
let beforeText = ""
let afterText = ""
changeObject.forEach((obj) => {
if (!obj.added && !obj.removed) {
beforeText = beforeText + obj.value
afterText = afterText + obj.value
}
if (obj.added) {
afterText = afterText + "<mark>" + obj.value + "</mark>"
}
if (obj.removed) {
beforeText = beforeText + "<mark>" + obj.value + "</mark>"
}
})
hunks.push({
fileName: previousFilename !== '' ? previousFilename : currentFilename,
lineNumber: parseInt(lines[previousHunk].match(lineNumberRegex)[1]),
beforeText,
afterText,
})
previousFilename = ''3 4 5 6 7
import {type BranchInfo} from './dataTypes.ts'
import repoOperations from './vcses/operations.ts'
import { getLocation} from './helpers.ts'
import repoHelpers from './vcses/helpers.ts'
3 4 5 6 7 8
import {type BranchInfo} from './dataTypes.ts'
import repoOperations from './vcses/operations.ts'
import { getBranchInfo } from './vcses/git/operations.ts'
import { getLocation} from './helpers.ts'
import repoHelpers from './vcses/helpers.ts'
34 35 36 37 38 39
const branchTuples: BranchInfoTuple[] = await Promise.all(branchNames.map(async (branchName): Promise<BranchInfoTuple> => {
const repoLocation = getLocation(reposConfig, branchName, repoName)
const files = await repoOperations[vcs].getFileList(repoName, branchName, repoLocation)
const patches = await repoOperations[vcs].getBranchInfo(repoName, branchName, repoLocation)
return [branchName, {
files,
patches,34 35 36 37 38 39
const branchTuples: BranchInfoTuple[] = await Promise.all(branchNames.map(async (branchName): Promise<BranchInfoTuple> => {
const repoLocation = getLocation(reposConfig, branchName, repoName)
const files = await repoOperations[vcs].getFileList(repoName, branchName, repoLocation)
const patches = await getBranchInfo(branchName, repoLocation)
return [branchName, {
files,
patches,1 2 3 4 5
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import { getGitDiffsFromPatchText} from '../../helpers.ts'
export const getFileList = async (repoName: string, branchName: string, repoLocation: string) => {
const command = `git ls-tree -r --name-only ${branchName}`1 2 3 4 5 6
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import { getGitDiffsFromPatchText} from '../../helpers.ts'
import { type Repository } from '../../dataTypes.ts'
export const getFileList = async (repoName: string, branchName: string, repoLocation: string) => {
const command = `git ls-tree -r --name-only ${branchName}`36 37 38 39 40 41 42
return Array.from(fileSet)
}
export const getBranchInfo = async (repoName: string, branchName: string, repoLocation: string) => {
const patches = new Map()
const totalPatchesCountRes = await exec(`(cd ${repoLocation} && git rev-list --count ${branchName})`)
const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)36 37 38 39 40 41 42 43 44 45 46 47 48 49
return Array.from(fileSet)
}
export const getBranchInfo = async (branchName: string, repoLocation: string) => {
const patches: Map<string, {
name: string,
description: string,
author: string,
date: string,
hash: string,
diffs: ReturnType<Repository['commits']['get']>['diffs'],
}> = new Map()
const totalPatchesCountRes = await exec(`(cd ${repoLocation} && git rev-list --count ${branchName})`)
const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)53 54 55 56 57 58
gitLogSubset = gitLogSubset.slice(nextPatchStart)
const hash = currentPatch[0].replace("commit ", "").trim()
let author, date
[1, 2, 3].forEach((lineNumber) => {
if (currentPatch[lineNumber].startsWith("Author")) {
author = currentPatch[lineNumber].replace("Author: ", "").trim()53 54 55 56 57 58
gitLogSubset = gitLogSubset.slice(nextPatchStart)
const hash = currentPatch[0].replace("commit ", "").trim()
let author: string, date: string
[1, 2, 3].forEach((lineNumber) => {
if (currentPatch[lineNumber].startsWith("Author")) {
author = currentPatch[lineNumber].replace("Author: ", "").trim()0 1 2 3
import {
getBranchInfo as getGitBranchInfo,
getFileList as getGitFileList,
getFileLastTouchInfo as getGitFileLastTouchInfo,
} from './git/operations.ts'0 1 2
import {
getFileList as getGitFileList,
getFileLastTouchInfo as getGitFileLastTouchInfo,
} from './git/operations.ts'6 7 8 9 10 11
type RepoOperationsType = {
[vcs: string]: {
getBranchInfo: (repoName: string, branchName: string, repoLocation: string) => Promise<BranchInfo['patches']>,
getFileList: (repoName: string, branchName: string, repoLocation: string) => Promise<BranchInfo['files']>,
getFileLastTouchInfo: (repoName: string, branchName: string, filename: string, repoLocation: string) => Promise<Array<{sha: string, author: string}>>,
}6 7 8 9 10
type RepoOperationsType = {
[vcs: string]: {
getFileList: (repoName: string, branchName: string, repoLocation: string) => Promise<BranchInfo['files']>,
getFileLastTouchInfo: (repoName: string, branchName: string, filename: string, repoLocation: string) => Promise<Array<{sha: string, author: string}>>,
}14 15 16 17 18 19
const repoOperations: RepoOperationsType = {
git: {
getBranchInfo: getGitBranchInfo,
getFileList: getGitFileList,
getFileLastTouchInfo: getGitFileLastTouchInfo,
},14 15 16 17 18
const repoOperations: RepoOperationsType = {
git: {
getFileList: getGitFileList,
getFileLastTouchInfo: getGitFileLastTouchInfo,
},26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
{% set patchHunks = patchInfo.patch.diffs %}
{% for hunk in patchHunks %}
<div class=hunk>
<span class="font-monospace fw-bold"><a href="{{reposPath}}/{{patchInfo.repoName | slugify}}/branches/{{patchInfo.branchName | slugify}}/files/{{ hunk.file | slugify}}.html">{{ hunk.file }}:{{ hunk.lineNumber }}</a></span>
<div class="diff d-flex">
<div class="flex-grow-1 diff-left pe-2">
<span class='font-monospace text-secondary'>Before</span>
<div class="row">
<div class="col-auto border-end">
<code>
{%- for lineNumber in hunk.previousText | lineNumbers -%}
{{ lineNumber + hunk.lineNumber - 1}}
{% endfor -%}
</code>
</div>
<div class="col overflow-scroll">
<pre data-start="{{hunk.lineNumber}}"><code data-type="before" class="line-numbers language-{{hunk.file | languageExtension(patchInfo.repoName)}}">{{hunk.previousText | safe}}</code></pre>
</div>
</div>
</div>26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
{% set patchHunks = patchInfo.patch.diffs %}
{% for hunk in patchHunks %}
<div class=hunk>
<span class="font-monospace fw-bold"><a href="{{reposPath}}/{{patchInfo.repoName | slugify}}/branches/{{patchInfo.branchName | slugify}}/files/{{ hunk.fileName | slugify}}.html">{{ hunk.fileName }}:{{ hunk.lineNumber }}</a></span>
<div class="diff d-flex">
<div class="flex-grow-1 diff-left pe-2">
<span class='font-monospace text-secondary'>Before</span>
<div class="row">
<div class="col-auto border-end">
<code>
{%- for lineNumber in hunk.beforeText | lineNumbers -%}
{{ lineNumber + hunk.lineNumber - 1}}
{% endfor -%}
</code>
</div>
<div class="col overflow-scroll">
<pre data-start="{{hunk.lineNumber}}"><code data-type="before" class="line-numbers language-{{hunk.fileName | languageExtension(patchInfo.repoName)}}">{{hunk.beforeText | safe}}</code></pre>
</div>
</div>
</div>54 55 56 57 58 59
</code>
</div>
<div class="col overflow-scroll">
<pre data-start="{{hunk.lineNumber}}"><code data-type="after" class="line-numbers language-{{hunk.file | languageExtension(patchInfo.repoName)}}">{{ hunk.afterText | safe}}</code></pre>
</div>
</div>
</div>54 55 56 57 58 59
</code>
</div>
<div class="col overflow-scroll">
<pre data-start="{{hunk.lineNumber}}"><code data-type="after" class="line-numbers language-{{hunk.fileName | languageExtension(patchInfo.repoName)}}">{{ hunk.afterText | safe}}</code></pre>
</div>
</div>
</div>