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.
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('; ')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('; ')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`)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`)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) {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) {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) {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) {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 dirs138 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 dirs17 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) {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) {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.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.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)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)242 243 244 245 246
break
}
if (fileMap.has(`${filename}-${currentCommit.hash}`)) {
return fileMap.get(`${filename}-${currentCommit.hash}`)
}
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}`)
}
256 257 258 259 260
blameLines,
}
fileMap.set(key, commit)
return commit
},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
},