Rough draft for cachine files instead of fetching from git show every time

095ed334dfa91227960ae776b4241a8f25bc1fff

Tucker McKnight <tmcknight@instructure.com> | Wed Jun 17 2026

Rough draft for cachine files instead of fetching from git show every time

Instead of getting the commit by calling `git show` and `git blame` every
time we want to see the contents of a file, save the contents of the file
in a map called fileMap. The file's contents and git blame info are cached
with a key of "filename-SHA".

Next time we want to see the contents of the file, before calling `git show`
and `git blame`, first check if the fileMap contains that filename/sha combo.

If it doesn't, look at parent commits to see if the fileMap has the file cached
for an earlier commit. However, if the commit modifies that file, then we
know that the cache won't have what we're looking for, and we'll have to call
`git show` and `git blame`.

TODO:
- performance test
- see if it benefits from caching at the earliest possible commit, so that
  more commit SHAs will find the same cached contents
- see if we should also cache at a higher-level commit
src/repos.ts:6
Before
5
6
7
8
9
10
import childProcess from 'child_process'
import {minimatch} from 'minimatch'

import { type Repository} from './dataTypes.ts'
import cloneUrl from './vcses/git/helpers.ts'
import { addBranchToCommitsMap } from './vcses/git/operations.ts'
import { getLocation} from './helpers.ts'
After
5
6
7
8
9
10
import childProcess from 'child_process'
import {minimatch} from 'minimatch'

import { type Repository, type FileInfo } from './dataTypes.ts'
import cloneUrl from './vcses/git/helpers.ts'
import { addBranchToCommitsMap } from './vcses/git/operations.ts'
import { getLocation} from './helpers.ts'
src/repos.ts:137
Before
136
137
138
139




140
}

let cachedRepos: Array<Repository> | null = null

⁣
⁣
⁣
⁣
const repos: (reposConfig: ReposConfiguration, outputDir: string, slugify: Function) => Promise<Array<Repository>> = async (reposConfig, outputDir, slugify) => {
  if (cachedRepos !== null) { return cachedRepos }
After
136
137
138
139
140
141
142
143
144
}

let cachedRepos: Array<Repository> | null = null
// A cached list of files that we have already found. Save things here
// when we read them from calling `git show`.
// The key for this map is a string like "filename-sha".
const fileMap: Map<string, FileInfo> = new Map()

const repos: (reposConfig: ReposConfiguration, outputDir: string, slugify: Function) => Promise<Array<Repository>> = async (reposConfig, outputDir, slugify) => {
  if (cachedRepos !== null) { return cachedRepos }
src/repos.ts:226
Before
225
226
227





















228
229
230
231
232
233
234
235





236
237
      defaultBranch: reposConfig.repos[repoName].defaultBranch,
      tags: tags,
      files: async (filename: string, sha: string) => {
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
        const gitShowCmd = `git -C ${repoLocation} show ${sha}:${filename}`
        const fileContents = (await exec(gitShowCmd)).stdout
        const blameLines = await getFileLastTouchInfo(filename, sha, repoLocation)
        return {
          contents: fileContents,
          lastModified: new Date(), //TODO: make lastModified check the actual modification date
          blameLines,
        }
⁣
⁣
⁣
⁣
⁣
      },
      commits,
    })
After
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
      defaultBranch: reposConfig.repos[repoName].defaultBranch,
      tags: tags,
      files: async (filename: string, sha: string) => {
        let key = `${filename}-${sha}`
        // First, see if fileMap already contains this file/sha combination.
        if (fileMap.has(key)) {
          return fileMap.get(key)
        }
        else {
          // If it didn't, look up the chain of commits that may have the same file contents.
          let currentCommit = commits.get(commits.get(sha).parent)
          while (currentCommit !== undefined) {
            if (currentCommit.diffs.map(d => d.fileName).includes(filename)) {
              break
            }
            if (fileMap.has(`${filename}-${currentCommit.hash}`)) {
              // TODO: maybe cache here as well, so that we don't have to loop as many
              // times next time?
              return fileMap.get(`${filename}-${currentCommit.hash}`)
            }

            currentCommit = commits.get(currentCommit.parent)
          }
        }
        const gitShowCmd = `git -C ${repoLocation} show ${sha}:${filename}`
        const fileContents = (await exec(gitShowCmd)).stdout
        const blameLines = await getFileLastTouchInfo(filename, sha, repoLocation)
        const commit = {
          contents: fileContents,
          lastModified: new Date(), //TODO: make lastModified check the actual modification date
          blameLines,
        }

// TODO: cache it as deeply in the git history as it can be, so that as many other commits
// benefit from this as possible.
        fileMap.set(key, commit)
        return commit
      },
      commits,
    })