Allow globs to be used instead of branch names

e2d4fdec19aca05ed79683f775ca083cab61baf9

Tucker McKnight <tmcknight@instructure.com> | Sun Jan 18 2026

Allow globs to be used instead of branch names

The max value still doesn't do anything, but the glob string does.
All branches matching a glob string will be added to the list of branches
to be included in the site.

This is also the list of branches that is used to determine which branches
should be pulled when the eleventy.beforeConfig clones its copy of the
repository.
main.ts:1
Before
0
1
2
3
4
5
import fsImport from 'fs'
import util from 'util'
import childProcess from 'child_process'
import repos from './src/repos.ts'
import branches from './src/branches.ts'
import flatFiles from './src/flatFiles.ts'
import flatPatches from './src/flatPatches.ts'
After
0
1
2
3
4
5
import fsImport from 'fs'
import util from 'util'
import childProcess from 'child_process'
import {repos, getBranchNames} from './src/repos.ts'
import branches from './src/branches.ts'
import flatFiles from './src/flatFiles.ts'
import flatPatches from './src/flatPatches.ts'
main.ts:42
Before
41
42
43
44
45
46
        // 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 fetchCommands = repoConfig.branchesToPull.map(branch => `git -C ${location} fetch origin ${branch}:${branch}`).join('; ')
        await exec(`${fetchCommands} && git -C ${location} update-server-info`)
      } else {
        // If it is not there, do git clone
After
41
42
43
44
45
46
        // 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 fetchCommands = (await getBranchNames(repoConfig, repoName)).map(branch => `git -C ${location} fetch origin ${branch}:${branch}`).join('; ')
        await exec(`${fetchCommands} && git -C ${location} update-server-info`)
      } else {
        // If it is not there, do git clone
main.ts:110
Before
109
110
111
112
113
114
          const tempDirRepoPath = `${tempDir}/${eleventyConfig.getFilter("slugify")(repoName)}`
          await exec(`mkdir ${tempDir}`)
          await exec(`git clone -s ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}.git ${tempDirRepoPath}`)
          for (let branch of repoConfig.branchesToPull) {
            // TODO why doesn't git -C checkout work? Says that repo doesn't exist
            await exec(`(cd ${tempDirRepoPath} && git checkout ${branch})`)
            for (let buildStep of repoConfig.buildSteps) {
After
109
110
111
112
113
114
          const tempDirRepoPath = `${tempDir}/${eleventyConfig.getFilter("slugify")(repoName)}`
          await exec(`mkdir ${tempDir}`)
          await exec(`git clone -s ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}.git ${tempDirRepoPath}`)
          for (let branch of await getBranchNames(repoConfig, repoName)) {
            // TODO why doesn't git -C checkout work? Says that repo doesn't exist
            await exec(`(cd ${tempDirRepoPath} && git checkout ${branch})`)
            for (let buildStep of repoConfig.buildSteps) {
src/repos.ts:2
Before
1
2
3

4
5

6
7
8
9
10
11
12
13
14


15










16
17
18
19
20

21











22
23






24
25


  type ReposConfiguration,
  type GitConfig,
} from './configTypes.ts'
⁣
import { type Repository} from './dataTypes.ts'
import util from 'util'
⁣
import childProcess from 'child_process'
import cloneUrl from './vcses/git/helpers.ts'

import { addBranchToCommitsMap } from './vcses/git/operations.ts'
import { getLocation} from './helpers.ts'
import { getFileList } from './vcses/git/operations.ts'

const exec = util.promisify(childProcess.exec)

⁣
⁣
const getBranchNames = (repoConfig: GitConfig): Array<string> => {
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
  return repoConfig.branchesToPull.map((branch) => {
    if (typeof branch === "string") {
      return branch
    }
    else {
⁣
      return "" // todo
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
    }
  })
⁣
⁣
⁣
⁣
⁣
⁣
}

⁣
⁣
⁣
let cachedRepos: Array<Repository> | null = null
After
1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  type ReposConfiguration,
  type GitConfig,
} from './configTypes.ts'
import util from 'util'
import childProcess from 'child_process'
import path from 'path'

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'
import { getFileList } from './vcses/git/operations.ts'

const exec = util.promisify(childProcess.exec)

const branchesForReposMap: Map<string, string[]> = new Map()

const getBranchNames = async (repoConfig: GitConfig, repoName: string): Promise<Array<string>> => {
  const cachedBranchNames = branchesForReposMap.get(repoName)
  if (cachedBranchNames !== undefined) {
    return cachedBranchNames
  }

  // Get all branches available in the repository
  const allBranches = (await exec(`git -C ${repoConfig.location} branch --format="%(refname:short)"`)).stdout.split("\n")
  const matchingBranchesFromGlobs: Map<string, string[]> = new Map()
  const literalBranchNames: string[] = []

  repoConfig.branchesToPull.forEach((branch) => {
    if (typeof branch === "string") {
      literalBranchNames.push(branch)
    }
    else {
      // If we're here, then the "branch" was an object like:
      // { glob: string, max: number }
      // TODO: figure out which branch is the newest/oldest, and
// keep them less than `max` by kicking out the oldest from the list.
      const glob = branch['glob']
      const matching = allBranches.filter((possibleBranch) => {
        const match = path.matchesGlob(possibleBranch, glob)
        return match
      })
      matchingBranchesFromGlobs.set(
        glob,
        (matchingBranchesFromGlobs.get(glob) || []).concat(matching)
      )
    }
  })

  const matchedBranchesFromGlobs = Array.from(matchingBranchesFromGlobs.values()).flat()
  const branchNames: string[] = Array.from(new Set(matchedBranchesFromGlobs.concat(literalBranchNames)))

  if (branchesForReposMap.get(repoName) === undefined) {
    branchesForReposMap.set(repoName, branchNames)
  }

  return branchNames
}

let cachedRepos: Array<Repository> | null = null
src/repos.ts:33
Before
32
33
34

35
36
37
38
39
40
  cachedRepos = []

  for (const repoName of repoNames) {
⁣
    const branchNames = getBranchNames(reposConfig.repos[repoName])
    const commits: Repository['commits'] = new Map()
    for (const branchName of branchNames) {
      const repoLocation = getLocation(reposConfig, outputDir, repoName)
      await addBranchToCommitsMap(branchName, repoLocation, commits)
    }
After
32
33
34
35
36
37
38

39
40
  cachedRepos = []

  for (const repoName of repoNames) {
    const repoLocation = getLocation(reposConfig, outputDir, repoName)
    const branchNames = await getBranchNames(reposConfig.repos[repoName], repoName)
    const commits: Repository['commits'] = new Map()
    for (const branchName of branchNames) {
⁣
      await addBranchToCommitsMap(branchName, repoLocation, commits)
    }
src/repos.ts:65
Before
64
65
66
  return cachedRepos
}

export default repos
After
64
65
66
  return cachedRepos
}

export {repos, getBranchNames}
wiki/projects/branch-globs.md:21
Before
20
21




















```js
path.matchesGlob('/foo/bar', '/foo/*'); // true
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
```
After
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
```js
path.matchesGlob('/foo/bar', '/foo/*'); // true
```


## Jan 17, 2026

- Update getBranchNames so that it 1) pulls all branches from the repo, and 2)
  figures out which ones match the given branch globs.
- where does that get called?
  - it gets called when `repos()` is called
  - and `repos()` get called when main.ts runs, so after the beforehook is called.
  - we'll need to decide which branches get pulled in the before hook, so that the clone
    only pulls the branches we want
  - for a first-attempt solution, make a function that figures out which branches
    are available and call it multiple times: once when pulling the branches, and then also
    in subsequent calls when we're seeing which branches are available in the repo.
    - So, stop calling `repoConfig.branchesToPull` directly
  - currently failing locally because I don't have the updated beforeHook on this
    laptop. Push current work, update local repo to use the beforeHook from the
    clone-early branch, and see if that works. (update: Yes, that was the problem.)
    - this is a good reason to put the deployed repo config on the public site, too.
      I currently have different example site configs between my laptop and the deployed
      site.