Only read from locally-cloned repo for operations that require local repos

c617a3ccbc54c566548bb3dbcecb72a47e2833b8

Tucker McKnight <tmcknight@instructure.com> | Sat Jun 20 2026

Only read from locally-cloned repo for operations that require local repos

Commands called with `git -C` would fail if the location in the repo config
was a remote URL. This is because `git -C` only works on local repos.

For git commands that require a local repo, we need to only call then on
the already-cloned, local repository. (This is what we should be doing all
the time anyway.)

Some functions now have to know the slugify function and output directory,
then, in order to find the path to the cloned repo.

This should be done more cleanly in the future, like with a globally-available
"localRepoPath" function.
main.ts:37
Before
36
37
38

39
40
41
42
43
44
45
46
47
48
49
50
51
52
  return async ({directories}) => {
    const cwd = process.cwd()
    const reposPath = reposConfiguration.path || ""
⁣
    // Check to see if there is already a repo in all of the locations
    // that should have one.
    for (let repoName in reposConfiguration.repos) {
      const repoConfig = reposConfiguration.repos[repoName]
      const repoPath = directories.output + reposPath + "/" + eleventyConfig.getFilter("slugify")(repoName)
      const gitRepoName = eleventyConfig.getFilter("slugify")(repoName) + ".git"
      // If it is there, do git pull
      if (fsImport.existsSync(repoPath + ".git")) {
        // git repos are just in the repos folder, not in their subdir
        // create string of commands saying 'git fetch origin branch:branch' for each branch
        const location = directories.output + reposPath + "/" + gitRepoName
        const {branches, tags} = await getBranchesAndTags(repoConfig, repoName)
        const branchNames = branches.map(branch => branch.name)
        const tagNames = branches.map(tag => tag.name)
        const fetchCommands = branchNames.concat(tagNames).map(ref => `git -C ${location} fetch origin ${ref}:${ref}`).join('; ')
After
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  return async ({directories}) => {
    const cwd = process.cwd()
    const reposPath = reposConfiguration.path || ""
    const slugify = eleventyConfig.getFilter("slugify")
    // Check to see if there is already a repo in all of the locations
    // that should have one.
    for (let repoName in reposConfiguration.repos) {
      const repoConfig = reposConfiguration.repos[repoName]
      const repoPath = directories.output + reposPath + "/" + slugify(repoName)
      const gitRepoName = eleventyConfig.getFilter("slugify")(repoName) + ".git"
      // If it is there, do git pull
      if (fsImport.existsSync(repoPath + ".git")) {
        // git repos are just in the repos folder, not in their subdir
        // create string of commands saying 'git fetch origin branch:branch' for each branch
        const location = directories.output + reposPath + "/" + gitRepoName
        const {branches, tags} = await getBranchesAndTags(reposConfiguration, repoName, directories.output, slugify)
        const branchNames = branches.map(branch => branch.name)
        const tagNames = branches.map(tag => tag.name)
        const fetchCommands = branchNames.concat(tagNames).map(ref => `git -C ${location} fetch origin ${ref}:${ref}`).join('; ')
main.ts:57
Before
56
57
58

59








60
61
62
63
        // If it is not there, do git clone
        // todo: does this work if the latest branch is not checked
        // out locally?
⁣
        let originalLocation = cwd + "/" + repoConfig.location
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
        if (repoConfig.location.startsWith("https://") || repoConfig.location.startsWith("ssh://")) {
          originalLocation = repoConfig.location
        }
        await exec(`git clone ${originalLocation} ${directories.output + reposPath + "/" + gitRepoName} --bare`)
        await exec(`git -C ${directories.output + reposPath + "/" + gitRepoName} update-server-info`)
After
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
        // If it is not there, do git clone
        // todo: does this work if the latest branch is not checked
        // out locally?
        const location = repoConfig.location
        let originalLocation = cwd + "/" + location
        // If the location starts with https, ssh, a /, or is formatted like "user@something.com:foo",
        // then treat is as a path that can be cloned directly. (It should be either a URL or an absolute path,
        // in this case. Otherwise, keep the appended cwd/ to it, because we assume it's a relative path.
        if (
          location.startsWith("https://")
          || location.startsWith("ssh://")
          || location.startsWith("/")
          || location.match(/(.)+@(.)+:(.)+/)
        ) {
          originalLocation = location
        }
        await exec(`git clone ${originalLocation} ${directories.output + reposPath + "/" + gitRepoName} --bare`)
        await exec(`git -C ${directories.output + reposPath + "/" + gitRepoName} update-server-info`)
main.ts:112
Before
111
112
113
114

115
116
117
118
119
120
121
122
123
124
    async ({ directories }) => {
      for (let repoName in reposConfiguration.repos) {
        const repoConfig = reposConfiguration.repos[repoName]

⁣
        if (typeof repoConfig.buildSteps !== 'undefined') {
          // make a temp directory for things to run in
          const tempDirName = `temp_${Math.floor(Math.random() * 10000).toString()}`
          const tempDir = `${directories.output.replace("./", "")}${reposPath}${tempDirName}`
          const tempDirRepoPath = `${tempDir}/${eleventyConfig.getFilter("slugify")(repoName)}`
          await exec(`mkdir ${tempDir}`)
          await exec(`git clone -s ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}.git ${tempDirRepoPath}`)
          const branchesAndTags = await getBranchesAndTags(repoConfig, repoName)
          const branchNames = branchesAndTags.branches.map(branch => branch.name)
          const tagNames = branchesAndTags.tags.map(tag => tag.name)
          for (let branch of branchNames) {
After
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
    async ({ directories }) => {
      for (let repoName in reposConfiguration.repos) {
        const repoConfig = reposConfiguration.repos[repoName]
        const slugify = eleventyConfig.getFilter("slugify")

        if (typeof repoConfig.buildSteps !== 'undefined') {
          // make a temp directory for things to run in
          const tempDirName = `temp_${Math.floor(Math.random() * 10000).toString()}`
          const tempDir = `${directories.output.replace("./", "")}${reposPath}${tempDirName}`
          const tempDirRepoPath = `${tempDir}/${slugify(repoName)}`
          await exec(`mkdir ${tempDir}`)
          await exec(`git clone -s ${directories.output}${slugify(repoName)}.git ${tempDirRepoPath}`)
          const branchesAndTags = await getBranchesAndTags(reposConfiguration, repoName, directories.output, slugify)
          const branchNames = branchesAndTags.branches.map(branch => branch.name)
          const tagNames = branchesAndTags.tags.map(tag => tag.name)
          for (let branch of branchNames) {
main.ts:130
Before
129
130
131
132
133
134
              // Run the command for each step in each branch
              await exec(`(cd ${tempDirRepoPath} && ${buildStep.command})`)
              // Copy the specified folders from the "from" to the "to" dir
              await exec(`cp -r ${tempDirRepoPath}/${buildStep.copyFrom} ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branch)}/${buildStep.copyTo}`)
            }
          }
          for (let tag of tagNames) {
After
129
130
131
132
133
134
              // Run the command for each step in each branch
              await exec(`(cd ${tempDirRepoPath} && ${buildStep.command})`)
              // Copy the specified folders from the "from" to the "to" dir
              await exec(`cp -r ${tempDirRepoPath}/${buildStep.copyFrom} ${directories.output}${slugify(repoName)}/branches/${slugify(branch)}/${buildStep.copyTo}`)
            }
          }
          for (let tag of tagNames) {
main.ts:139
Before
138
139
140
141
142
143
              // Run the command for each step in each branch
              await exec(`(cd ${tempDirRepoPath} && ${buildStep.command})`)
              // Copy the specified folders from the "from" to the "to" dir
              await exec(`cp -r ${tempDirRepoPath}/${buildStep.copyFrom} ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}/tags/${eleventyConfig.getFilter("slugify")(tag)}/${buildStep.copyTo}`)
            }
          }
          // delete the temp dirs
After
138
139
140
141
142
143
              // Run the command for each step in each branch
              await exec(`(cd ${tempDirRepoPath} && ${buildStep.command})`)
              // Copy the specified folders from the "from" to the "to" dir
              await exec(`cp -r ${tempDirRepoPath}/${buildStep.copyFrom} ${directories.output}${slugify(repoName)}/tags/${slugify(tag)}/${buildStep.copyTo}`)
            }
          }
          // delete the temp dirs
src/repos.ts:18
Before
17
18
19
20
21


22
23
24
25

26
27
const tagsForReposMap: Map<string, Array<{name: string}>> = new Map()

const getBranchesAndTags = async (
  repoConfig: GitConfig,
  repoName: string
⁣
⁣
): Promise<{
  branches: Array<{name: string, description?: string, compareTo?: string}>,
  tags: Array<{name: string}>,
}> => {
⁣
  const cachedBranchNames = branchesForReposMap.get(repoName)
  const cachedTagNames = tagsForReposMap.get(repoName)
  if (cachedBranchNames !== undefined) {
After
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const tagsForReposMap: Map<string, Array<{name: string}>> = new Map()

const getBranchesAndTags = async (
  reposConfig: ReposConfiguration,
  repoName: string,
  outputDir: string,
  slugify: Function,
): Promise<{
  branches: Array<{name: string, description?: string, compareTo?: string}>,
  tags: Array<{name: string}>,
}> => {
  const repoConfig = reposConfig.repos[repoName]
  const cachedBranchNames = branchesForReposMap.get(repoName)
  const cachedTagNames = tagsForReposMap.get(repoName)
  if (cachedBranchNames !== undefined) {
src/repos.ts:31
Before
30
31
32



33
34
35
36
  }

  // Get all branches and tags available in the repository
⁣
⁣
⁣
  const allBranches = (await exec(`git -C ${repoConfig.location} branch --format="%(refname:short)"`)).stdout.split("\n").filter(branch => branch !== '')
  const allTags = (await exec(`git -C ${repoConfig.location} tag`)).stdout.split("\n").filter(tag => tag !== '')

  // Sort the list of branch descriptions from `branches` by the length
  // of their patterns.
After
30
31
32
33
34
35
36
37
38
39
  }

  // Get all branches and tags available in the repository
  // Get it from the local, cloned location, instead of the remote, since `git -C` commands
  // don't work on remote locations.
  const clonedLocation = getLocation(reposConfig, outputDir, repoName, slugify) 
  const allBranches = (await exec(`git -C ${clonedLocation} branch --format="%(refname:short)"`)).stdout.split("\n").filter(branch => branch !== '')
  const allTags = (await exec(`git -C ${clonedLocation} tag`)).stdout.split("\n").filter(tag => tag !== '')

  // Sort the list of branch descriptions from `branches` by the length
  // of their patterns.
src/repos.ts:151
Before
150
151
152
153
154
155
  for (const repoName of repoNames) {
    const repoLocation = getLocation(reposConfig, outputDir, repoName, slugify)
    const commits: Repository['commits'] = new Map()
    const branchesAndTags = await getBranchesAndTags(reposConfig.repos[repoName], repoName)
    const branchNames = branchesAndTags.branches.map(branch => branch.name)
    for (const branchName of branchNames) {
      await addBranchToCommitsMap(branchName, repoLocation, commits)
After
150
151
152
153
154
155
  for (const repoName of repoNames) {
    const repoLocation = getLocation(reposConfig, outputDir, repoName, slugify)
    const commits: Repository['commits'] = new Map()
    const branchesAndTags = await getBranchesAndTags(reposConfig, repoName, outputDir, slugify)
    const branchNames = branchesAndTags.branches.map(branch => branch.name)
    for (const branchName of branchNames) {
      await addBranchToCommitsMap(branchName, repoLocation, commits)
src/repos.ts:243
Before
242
243
244


245
246
              break
            }
            if (fileMap.has(`${filename}-${currentCommit.hash}`)) {
⁣
⁣
              return fileMap.get(`${filename}-${currentCommit.hash}`)
            }
After
242
243
244
245
246
247
248
              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}`)
            }
src/repos.ts:257
Before
256
257
258


259
260
          blameLines,
        }

⁣
⁣
        fileMap.set(key, commit)
        return commit
      },
After
256
257
258
259
260
261
262
          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
      },