Allow all branch names to be glob patterns

01b85955ec11cb99ee24a5af62d7cbbb0e92151e

Tucker McKnight | Mon Jan 19 2026

Allow all branch names to be glob patterns

Change the way that branches are matched to those patterns so that,
if multiple patterns match a branch name, the most specific pattern
is the one that is used.

Also allows new fields on a branch: description and compareTo. Prints
out the branch description on the branches page.

Also makes the max value work -- kicks out branches from the list if
there are already the max number of branches in the list. TODO:
need to make sure this only kicks out the branches that are the oldest
(that is, not recently committed to).
js_templates/branches.ts:19
Before
19
                <div class="card-body">Data about branch goes here</div>
After
19
                <div class="card-body">${branch.description || &#39;'}&lt;/div>
schemas/ReposConfiguration.json:14
Before
14
15
16
                  "glob": {
                  "glob",
                  "max"
After
14
15
16
                  "compareTo": {
                    "type": "string"
                  },
                  "description": {
                  },
                  "pattern": {
                    "type": "string"
                  "pattern"
src/branches.ts:10
Before
10
      return {
After
10
      const result = {
      if (branch.description) { result['description'] = branch.description }
      if (branch.compareTo) { result['compareTo'] = branch.compareTo }
      return result
src/configTypes.ts:11
Before
11
 *       branches: ['main', 'develop']
After
11
 *       branchesToPull: ['main', 'develop']
src/configTypes.ts:53
Before
53
  branchesToPull: Array<string | {glob: string, max: number}>,
After
53
  branchesToPull: Array<string | {pattern: string, max?: number, compareTo?: string, description?: string}>,
src/dataTypes.ts:5
Before
5
After
5
    description?: string,
    compareTo?: string,
src/repos.ts:14
Before
14
15
const branchesForReposMap: Map<string, string[]> = new Map()
const getBranchNames = async (repoConfig: GitConfig, repoName: string): Promise<Array<string>> => {
After
14
15
const branchesForReposMap: Map<string, Array<{name: string, description?: string, compareTO?: string}>> = new Map()
const getBranches = async (repoConfig: GitConfig, repoName: string): Promise<Array<{name: string, description?: string, compareTo?: string}>> => {
src/repos.ts:24
Before
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  const matchingBranchesFromGlobs: Map<string, string[]> = new Map()
  const literalBranchNames: string[] = []
  repoConfig.branchesToPull.forEach((branch) => {
    if (typeof branch === &quot;string&quot;) {
      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[&#39;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)))
    branchesForReposMap.set(repoName, branchNames)
  return branchNames
After
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  // Sort the list of branch descriptions from branchesToPull by the length
  // of their patterns.
  // Then, for each branch from the repository, see if it matches a pattern.
  // If that pattern has rules associated with it (like a description or a max),
  // apply those.
  type RulesObject = {max?: number, description?: string, compareTo?: string}
  let branchRules: Array<{pattern: string, matches: Array<string>, rules: RulesObject}> = repoConfig.branchesToPull.map((branchDescription) => {
    const rules: RulesObject = {}

    if (typeof branchDescription !== &#39;string&#39;) {
      if (branchDescription.max) { rules.max = branchDescription.max }
      if (branchDescription.description) { rules.description = branchDescription.description }
      if (branchDescription.compareTo) { rules.compareTo = branchDescription.compareTo }

    return {
      pattern: (typeof branchDescription === 'string' ? branchDescription : branchDescription.pattern),
      matches: [],
      rules,
    }
  })

  branchRules = branchRules.toSorted((a, b) => b.pattern.length - a.pattern.length)

  allBranches.forEach((branchName) =&gt; {
    const matchingPatternIndex = branchRules.findIndex((branchRule) => {
      return path.matchesGlob(branchName, branchRule.pattern)
    })

    if (matchingPatternIndex === -1) { return }

    const matchedRule = branchRules[matchingPatternIndex]
    matchedRule.matches.push(branchName)
    if (
      matchedRule.rules?.max
      && matchedRule.rules?.max < matchedRule.matches.length
    ) {
      matchedRule.matches.pop()
  const branches: Array<{
    name: string,
    description?: string,
    compareTo?: string,
  }> = branchRules.map((branchRule) => {
    return branchRule.matches.map((match) => {
      const result = {name: match}
      if (branchRule.rules.description) { result['description'] = branchRule.rules.description }
      if (branchRule.rules.compareTo) { result['compareTo'] = branchRule.rules.compareTo }
      return result
    }).flat()
  }).flat()
    branchesForReposMap.set(repoName, branches)
  return branches
}

const getBranchNames = async (repoConfig, repoName) => {
  return (await getBranches(repoConfig, repoName)).map(branch => branch.name)
src/repos.ts:68
Before
68
69
70
71
72
73
74
    const branchNames = await getBranchNames(reposConfig.repos[repoName], repoName)
    for (const branchName of branchNames) {
    const branches = await Promise.all(branchNames.map(async (branchName) => {
      const branchHeadRes = await exec(`git -C ${repoLocation} show-ref refs/heads/${branchName}`)
      return {
        name: branchName,
        fileList: await getFileList(branchName, repoLocation)
After
68
69
70
71
72
73
74
    const branchesToAdd = await getBranches(reposConfig.repos[repoName], repoName)
    for (const branchName of await getBranchNames(reposConfig.repos[repoName], repoName)) {
    const branches = await Promise.all(branchesToAdd.map(async (branch) => {
      const branchHeadRes = await exec(`git -C ${repoLocation} show-ref refs/heads/${branch.name}`)
      const result = {
        name: branch.name,
        fileList: await getFileList(branch.name, repoLocation)
      if (branch.description) { result['description'] = branch.description }
      if (branch.compareTo) { result['compareTo'] = branch.compareTo }
      return result
wiki/projects/branch-globs.md:42
Before
42
After
42

## Jan 18, 2026

*An idea:* allow the object with a glob in it to also specify things like the description
and which parent branch the branch should be compared to.

Multiple branch patterns (let's use the word "pattern" instead of "glob") can match
the same branch -- the more specific one should be used.

E.g.:

```
branchesToPull: [
  {
    pattern: "deploy/**",
    description: "A deployed branch",
  },
  {
    pattern: "deploy/v2**",
    description: "A version 2 deployed branch",
  }
]
```

In the above example, branch `deploy/v1.0.1` should have the description "A deployed
branch," but branch `deploy/v2.0.1` should have the description "A version 2
deployed branch."

Is the "more specific pattern" just always the one that is longer? Try this for now.