Add branches

Sat Apr 12 2025

Tucker McKnight

Previously, all pages were inside of a repository. Now, there
is a repository that can contain many branches. The branches
are what actually have a darcs repo in them.

The URL for all pages should now be:
/repos/reponame/branches/branchname/page.html

Flattened data (like flatPatches and flatFiles) now has the
branch name included in it -- there were not major changes
needed there.

The paginated patches object needs to sort into pages by matching
on the repo name and the branch name.

The main repos object now has the repository name nested under it,
and the branch name(s) underneath that, and then the files and patches
nested underneath the branch name.

Anything that was previously getting information out of that repo object
will now need to look under the branch name, too. (E.g. getFileContents will need
to know the branch name in order to get the file contents.)
  addfile ./_data/branches.js

eae6f68caf35f1258e33fe13e09b32104b229dc5

darcs pull https://repos.tuckerm.us/repos/eleventy-darcs/branches/main -h eae6f68caf35f1258e33fe13e09b32104b229dc5
_data/branches.js:1
Before
After
import repos from './repos.js'

export default async () => {
  const result = await repos()
  return Object.keys(result).flatMap((repoName) => {
    return Object.keys(result[repoName]).map((branchName) => {
      return {
        branchName,
        repoName,
      }
    })
  })
}
_data/darcsConfig.js:3
Before
export default () => {
  return {
    baseUrl: darcsConfig.baseUrl,
  }
}
After
export default darcsConfig
_data/flatFiles.js:6
Before
    return result[repoName].files.map((file) => {
        return {
          file,
          repoName,
        }
      
After
    return Object.keys(result[repoName]).flatMap((branchName) => {
      return result[repoName][branchName].files.map((file) => {
        return {
          file,
          branchName,
          repoName,
        }
      })
_data/flatPatches.js:6
Before
    return result[repoName].patches.map((patch) => {
        return {
          patch,
          repoName,
        }
      
After
    return Object.keys(result[repoName]).flatMap((branchName) => {
      return result[repoName][branchName].patches.map((patch) => {
        return {
          patch,
          branchName,
          repoName,
        }
      })
_data/paginatedPatches.js:11
Before
      return page.repoName === patch.repoName
        && page.patches.length <= patchesPerPage
      
After
      return (
        page.repoName === patch.repoName
        && page.branchName == patch.branchName
        && page.patches.length <= patchesPerPage
      )
_data/paginatedPatches.js:19
Before
      const pageNumber = paginatedPatches.filter(page => page.repoName === patch.repoName ).length + 1
After
      const pageNumber = paginatedPatches.filter(page => (page.repoName === patch.repoName && page.branchName === patch.branchName)).length + 1
_data/paginatedPatches.js:22
Before
After
        branchName: patch.branchName,
_data/repos.js:13
Before
      const repoLocation = darcsConfig.repos[repoName].location
        const filesRes = await exec(`(cd ${repoLocation}; darcs show files)`)
        const files = filesRes.stdout.split("\n").filter(item => item.length > 0 && item != ".")

        const patches = new Map()
        const totalPatchesCountRes = await exec(`(cd ${repoLocation}; darcs log --count)`)
        const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
        let hunkRegex = RegExp(/^ *hunk /)

        // Get 100 patches at a time and parse those
        for (let i = 1; i <= totalPatchesCount; i = i + 100) {
          let patchesSubset = await exec(`(cd ${repoLocation}; darcs log --index=${i}-${i+100} -v)`)
          patchesSubset = patchesSubset.stdout.split("\n")
          do {
            const nextPatchStart = patchesSubset.findIndex((line, index) => {
              return index > 0 && line.startsWith("patch ")
            })
            const currentPatch = patchesSubset.slice(0, nextPatchStart - 1)
            patchesSubset = patchesSubset.slice(nextPatchStart)

            const hash = currentPatch[0].replace("patch ", "").trim()
            const author = currentPatch[1].replace("Author: ", "").trim()
            const date = currentPatch[2].replace("Date: ", "").trim()
            const name = currentPatch[3].replace("  * ", "").trim()
            const diffStart = currentPatch.findIndex((line) => {
              return line.match(hunkRegex)
            })
            const description = currentPatch.slice(5, diffStart).map(str => str.replace("  ", "")).join("\n").trim()

            const diffs = getDiffsFromPatchText(currentPatch.slice(diffStart).map(str => str.trimStart()).join("\n"))
            patches.set(hash, {
              name,
              description,
              author,
              date,
              hash,
              diffs,
            })
          } while (patchesSubset.length > 1)
        }

        const info = {
          files,
          patches: [...patches.values()],
        }

        return [repoName, info]
      
After
      const branchNames = Object.keys(darcsConfig.repos[repoName].branches)
      const branches = await Promise.all(branchNames.map(async (branchName) => {
        const repoLocation = darcsConfig.repos[repoName].branches[branchName].location
        const filesRes = await exec(`(cd ${repoLocation}; darcs show files)`)
        const files = filesRes.stdout.split("\n").filter(item => item.length > 0 && item != ".")

        const patches = new Map()
        const totalPatchesCountRes = await exec(`(cd ${repoLocation}; darcs log --count)`)
        const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
        let hunkRegex = RegExp(/^ *hunk /)

        // Get 100 patches at a time and parse those
        for (let i = 1; i <= totalPatchesCount; i = i + 100) {
          let patchesSubset = await exec(`(cd ${repoLocation}; darcs log --index=${i}-${i+100} -v)`)
          patchesSubset = patchesSubset.stdout.split("\n")
          do {
            const nextPatchStart = patchesSubset.findIndex((line, index) => {
              return index > 0 && line.startsWith("patch ")
            })
            const currentPatch = patchesSubset.slice(0, nextPatchStart - 1)
            patchesSubset = patchesSubset.slice(nextPatchStart)

            const hash = currentPatch[0].replace("patch ", "").trim()
            const author = currentPatch[1].replace("Author: ", "").trim()
            const date = currentPatch[2].replace("Date: ", "").trim()
            const name = currentPatch[3].replace("  * ", "").trim()
            const diffStart = currentPatch.findIndex((line) => {
              return line.match(hunkRegex)
            })
            const description = currentPatch.slice(5, diffStart).map(str => str.replace("  ", "")).join("\n").trim()

            const diffs = getDiffsFromPatchText(currentPatch.slice(diffStart).map(str => str.trimStart()).join("\n"))
            patches.set(hash, {
              name,
              description,
              author,
              date,
              hash,
              diffs,
            })
          } while (patchesSubset.length > 1)
        }

        const info = {
          files,
          patches: [...patches.values()],
        }

        return [branchName, info]
      }))

      const branchObject = {}
      branches.forEach((branch) => {
        branchObject[branch[0]] = branch[1]
      })
      return [repoName, branchObject]
_data/repos.js:70
Before
After

    const reposObject = {}
    repos.forEach((repo) => {
      reposObject[repo[0]] = repo[1]
    })

    repos = reposObject
_data/repos.js:79
Before
  const returnMe = {}
  repos.forEach((repo) => {
    returnMe[repo[0]] = repo[1]
  })

  return returnMe
After
  return repos
_includes/main.njk:2
Before
After
<html>
_includes/main.njk:56
Before
                  <a href="/" class="text-decoration-none">Darcs Repositories</a>{% if nav.repoName %}<span class="text-secondary mx-2">&gt;</span><a href="/repos/{{nav.repoName }}" class="text-decoration-none">eleventy-darcs</a >{% endif %}
After
                  <a href="/" class="text-decoration-none">Darcs Repositories</a>{% if nav.repoName %}<span class="text-secondary mx-2">&gt;</span><a href="/repos/{{nav.repoName | slugify}}/branches/{{darcsConfig.repos[nav.repoName].defaultBranch | slugify}}" class="text-decoration-none">{{nav.repoName}}</a>{% endif %}{% if nav.branchName %}<span class="text-secondary mx-2&quot;>&gt;&lt;/span><a class=&quot;text-decoration-none" href="/repos/{{nav.repoName | slugify}}/branches/{{nav.branchName | slugify}}"&gt;{{nav.branchName}}</a>{% endif %}
_includes/main.njk:71
Before
                  <a class="nav-link {% if navTab == "landing" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}">Landing Page</a>
After
                  <a class="nav-link {% if navTab == "landing" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/branches/{{nav.branchName}}">Landing Page</a>
_includes/main.njk:74
Before
                  <a class="nav-link {% if navTab == "files" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/files">Files</a>
After
                  <a class="nav-link {% if navTab == "files" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/branches/{{nav.branchName}}/files">Files</a>
_includes/main.njk:77
Before
                  <a class="nav-link {% if navTab == "patches" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/patches/page1">Patches</a>
After
                  <a class="nav-link {% if navTab == "patches" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/branches/{{nav.branchName}}/patches/page1">Patches</a>
darcsconfig.js:3
Before
"eleventy-darcs": { // <-- Edit this to be the name of your local repository
      location: "./", // &lt;-- Make this the relative path to that repository.
          // You might want a "../" at the beginning to go up //     one directory. 
After
    // Change this "eleventy-darcs" key to be the name of your project
    "Eleventy Darcs": {
      defaultBranch: 'eleventy-darcs-main',
      branches: {
        &#39;eleventy-darcs-main': {
          // Set the 'location' to be the relative path to that repository.
          // You might want a "../" at the beginning to go up one level.
          location: "./",
        },
      },

      // Add things to "languageExtensions" to tell the syntax highlighter what to do
      // with file extensions that it might not know by default. "njk": "html" means
      // to highlight a .njk file like a .html file.
darcsconfig.js:21
Before
//  "another-repository": {
//    location: "./another/relative/path"
//  },
After
darcsconfig.js:22
Before
baseUrl : "https://repos.tuckerm.us", // <-- Change this to your deployed domain
                                    //     if this site is public.
After

  // Change the baseUrl to be your real domain name when when deploying this publicly.
  baseUrl: "https://repos.tuckerm.us",
eleventy.config.js:24
Before
passthroughs[`${darcsConfig.repos[repoName].location}/_darcs`] = `repos/${repoName}/_darcs`
    
After
    Object.keys(darcsConfig.repos[repoName].branches).forEach((branchName) => {
      passthroughs[`${darcsConfig.repos[repoName].branches[branchName].location}/_darcs`] = `repos/${eleventyConfig.getFilter("slugify")(repoName)}/branches/${branchName}/_darcs`
    })
eleventy.config.js:43
Before
  eleventyConfig.addAsyncFilter("getReadMe", async (repoName) => {
    const res = await exec(`(cd ${darcsConfig.repos[repoName].location}; darcs show contents README.md)`)
After
  eleventyConfig.addAsyncFilter("getReadMe", async (repoName, branchName) => {
    const res = await exec(`(cd ${darcsConfig.repos[repoName].branches[branchName].location}; darcs show contents README.md)`)
eleventy.config.js:56
Before
  eleventyConfig.addAsyncFilter("isDirectory", async(repo, filename) => {
After
  eleventyConfig.addAsyncFilter("isDirectory", async(repo, branch, filename) => {
eleventy.config.js:58
Before
      const fileInfo = await fs.stat(`${darcsConfig.repos[repo].location}/${filename}`)
After
      const fileInfo = await fs.stat(`${darcsConfig.repos[repo].branches[branch].location}/${filename}`)
eleventy.config.js:68
Before
  eleventyConfig.addFilter("getDirectoryContents", (repo, dirPath) => {
    return repos[repo].files.filter(file => file.startsWith(dirPath) && file !== dirPath)
After
  eleventyConfig.addFilter("getDirectoryContents", (repo, branch, dirPath) => {
    return repos[repo][branch].files.filter(file => file.startsWith(dirPath) && file !== dirPath)
eleventy.config.js:76
Before
  eleventyConfig.addAsyncFilter("getFileContents", async (repo, filename) => {
    const res = await exec(`(cd ${darcsConfig.repos[repo].location}; darcs show contents ${filename})`)
After
  eleventyConfig.addAsyncFilter("getFileContents", async (repo, branch, filename) => {
    const res = await exec(`(cd ${darcsConfig.repos[repo].branches[branch].location}; darcs show contents ${filename})`)
feed.njk:3
Before
  data: repos
After
  data: branches
feed.njk:5
Before
  alias: repo
permalink: "repos/{{repo | slugify }}/patches.xml"
After
  alias: branch
permalink: "repos/{{branch.repoName | slugify }}/branches/{{branch.branchName}}/patches.xml"
feed.njk:12
Before
  <title>Latest patches in {{repo}}</title>
  <link href="{{ ('/repos/' + (repo | slugify) + '/patches.xml')| htmlBaseUrl(darcsConfig.baseUrl) }}" rel="self" />
  <link href="{{ ('/repos/' + (repo | slugify) )| htmlBaseUrl(darcsConfig.baseUrl) }}" />
  {% set lastPatch = repos[repo].patches | last %}
After
  <title>Latest patches in {{branch.branchName}}</title>
  <link href="{{ ('/repos/' + (branch.repoName | slugify) + '/branches/' + (branch.branchName | slugify) + '/patches.xml')| htmlBaseUrl(darcsConfig.baseUrl) }}" rel="self" />
  <link href="{{ ('/repos/' + (branch.repoName | slugify) + '/branches/' + (branch.branchName | slugify)) | htmlBaseUrl(darcsConfig.baseUrl) }}" />
  {% set lastPatch = repos[branch.repoName][branch.branchName].patches | last %}
feed.njk:19
Before
    <name>{{repo}} contributors</name>
After
    <name>{{branch.repoName}} contributors</name>
feed.njk:21
Before
  {%- for patch in repos[repo].patches | reverse %}
  {%- set absolutePostUrl %}{{ ('/repos/' + repo + '/patches/' + patch.hash) | htmlBaseUrl(darcsConfig.baseUrl) }}{% endset %}
After
  {%- for patch in repos[branch.repoName][branch.branchName].patches | reverse %}
  {%- set absolutePostUrl %}{{ ('/repos/' + (branch.repoName | slugify) + '/branches/' + (branch.branchName | slugify) + '/patches/' + patch.hash) | htmlBaseUrl(darcsConfig.baseUrl) }}{% endset %}
file.11tydata.js:6
Before
After
      },
      branchName: (data) => {
        return data.fileInfo.branchName
file.njk:6
Before
permalink: "repos/{{fileInfo.repoName | slugify}}/files/{{fileInfo.file}}.html"
After
permalink: "repos/{{fileInfo.repoName | slugify}}/branches/{{fileInfo.branchName}}/files/{{fileInfo.file}}.html"
file.njk:11
Before
{% if fileInfo.repoName | isDirectory(fileInfo.file) %}
After
{% if fileInfo.repoName | isDirectory(fileInfo.branchName, fileInfo.file) %}
file.njk:13
Before
{% set dirs = fileInfo.repoName | getDirectoryContents(fileInfo.file) %}
After
{% set dirs = fileInfo.repoName | getDirectoryContents(fileInfo.branchName, fileInfo.file) %}
file.njk:15
Before
  <li><a href="/repos/{{fileInfo.repoName | slugify}}/files/{{dir}}.html">{{fileInfo.file | getRelativePath(dir)}}</a></li>
After
  <li><a href="/repos/{{fileInfo.repoName | slugify}}/branches/{{fileInfo.branchName | slugify}}/files/{{dir}}.html">{{fileInfo.file | getRelativePath(dir)}}</a></li>
file.njk:19
Before
<pre class=&quot;code"&gt;<code class="language-{{fileInfo.file | languageExtension(fileInfo.repoName)}}">{{ fileInfo.repoName | getFileContents(fileInfo.file) }}</code></pre>
After
<pre><code class="line-numbers language-{{fileInfo.file | languageExtension(fileInfo.repoName)}}">{{ fileInfo.repoName | getFileContents(fileInfo.branchName, fileInfo.file) }}</code></pre>
files.11tydata.js:5
Before
        return data.repo
After
        return data.branchInfo.repoName
      },
      branchName: (data) => {
        return data.branchInfo.branchName
files.njk:3
Before
  data: repos
After
  data: branches
files.njk:5
Before
  alias: repo
permalink: "repos/{{repo | slugify}}/files/"
After
  alias: branchInfo
permalink: "repos/{{branchInfo.repoName | slugify}}/branches/{{branchInfo.branchName}}/files/"
files.njk:10
Before
{% set files = repos[repo].files | topLevelFilesOnly %}
After
{% set files = repos[branchInfo.repoName][branchInfo.branchName].files | topLevelFilesOnly %}
files.njk:12
Before
  <li><a href="/repos/{{repo | slugify}}/files/{{file}}.html">{{file}}</a></li>
After
  <li><a href="/repos/{{branchInfo.repoName | slugify}}/branches/{{branchInfo.branchName}}/files/{{file}}.html">{{file}}</a></li>
index.njk:3
Before
  <li><a href="/repos/{{repoName | slugify}}">{{repoName}}</a></li>
After
  <li><a href="/repos/{{repoName | slugify}}/branches/{{darcsConfig.repos[repoName].defaultBranch}}">{{repoName}}</a></li>
patch.11tydata.js:6
Before
After
      },
      branchName: (data) => {
        return data.patchInfo.branchName
patch.njk:6
Before
permalink: "repos/{{patchInfo.repoName | slugify}}/patches/{{patchInfo.patch.hash}}/"
After
permalink: "repos/{{patchInfo.repoName | slugify}}/branches/{{patchInfo.branchName | slugify}}/patches/{{patchInfo.patch.hash}}/"
patch.njk:24
Before
          {% set url = [darcsConfig.baseUrl, "/repos/", patchInfo.repoName | slugify] | join | url %}
After
          {% set url = [darcsConfig.baseUrl, "/repos/", patchInfo.repoName | slugify, "/branches/", patchInfo.branchName | slugify] | join | url %}
patch.njk:37
Before
      <span class="font-monospace fw-bold"><a href="/repos/{{patchInfo.repoName }}/files/{{ hunk.file }}.html">{{ hunk.file }}:{{ hunk.lineNumber }}</a></span>
After
      <span class="font-monospace fw-bold"><a href="/repos/{{patchInfo.repoName | slugify}}/branches/{{patchInfo.branchName | slugify}}/files/{{ hunk.file }}.html">{{ hunk.file }}:{{ hunk.lineNumber }}</a></span>
patch.njk:41
Before
          <pre class=&#39;line-numbers&#39;data-start="{{hunk.lineNumber}}"><code data-type="before" class="language-{{hunk.file | languageExtension(patchInfo.repoName)}}">{{hunk.previousText | safe}}</code></pre>
After
          <pre&gt;&lt;code data-start="{{hunk.lineNumber}}" data-type="before" class="line-numbers language-{{hunk.file | languageExtension(patchInfo.repoName)}}">{{hunk.previousText | safe}}</code></pre>
patch.njk:45
Before
          <pre class=&#39;line-numbers&#39;data-start="{{hunk.lineNumber}}"><code data-type="after" class="language-{{hunk.file | languageExtension(patchInfo.repoName)}}">{{ hunk.afterText | safe}}</code></pre>
After
          <pre&gt;<code data-type=&quot;after" data-start="{{hunk.lineNumber}}" class="line-numbers language-{{hunk.file | languageExtension(patchInfo.repoName)}}">{{ hunk.afterText | safe}}</code></pre>
patches.11tydata.js:6
Before
After
      },
      branchName: (data) => {
        return data.patchPage.branchName
patches.njk:6
Before
permalink: "repos/{{patchPage.repoName | slugify}}/patches/page{{patchPage.pageNumber}}/"
After
permalink: "repos/{{patchPage.repoName | slugify}}/branches/{{patchPage.branchName}}/patches/page{{patchPage.pageNumber}}/"
patches.njk:13
Before
      <a class="page-link {% if pageObj.pageNumber == patchPage.pageNumber %}active{% endif %}" href="/repos/{{patchPage.repoName | slugify}}/patches/page{{pageObj.pageNumber}}">{{ pageObj.pageNumber }}</a>
After
      <a class="page-link {% if pageObj.pageNumber == patchPage.pageNumber %}active{% endif %}" href="/repos/{{patchPage.repoName | slugify}}/branches/{{patchPage.branchName}}/patches/page{{pageObj.pageNumber}}">{{ pageObj.pageNumber }}</a>
patches.njk:23
Before
        <span class="patch-name"><a href="/repos/{{patchPage.repoName | slugify}}/patches/{{patch.hash}}">{{patch.name}}</a></span>
After
        <span class="patch-name"><a href="/repos/{{patchPage.repoName | slugify}}/branches/{{patchPage.branchName | slugify}}/patches/{{patch.hash}}">{{patch.name}}</a></span>
repo.11tydata.js:5
Before
        return data.repo
After
        return data.branch.repoName
      },
      branchName: (data) => {
        return data.branch.branchName
repo.njk:3
Before
  data: repos
After
  data: branches
repo.njk:5
Before
  alias: repo
permalink: "repos/{{repo | slugify}}/"
After
  alias: branch
permalink: "repos/{{branch.repoName | slugify}}/branches/{{branch.branchName}}/"
repo.njk:11
Before
    {{ repo | getReadMe| renderContent("md") | safe }}
After
    {{ branch.repoName | getReadMe(branch.branchName) | renderContent("md") | safe }}
repo.njk:25
Before
                {% set url = [darcsConfig.baseUrl, "/repos/", repo | slugify] | join | url %}
After
                {% set url = [darcsConfig.baseUrl, "/repos/", branch.repoName | slugify, "/branches/", branch.branchName | slugify] | join | url %}
repo.njk:41
Before
            <a href="/repos/{{ repo }}/patches.xml" class="initialism">RSS<i class="bi bi-rss-fill ms-2" style="color: orange;"></i></a>
After
            <a href="/repos/{{ branch.repoName | slugify }}/branches/{{ branch.branchName | slugify }}/patches.xml" class="initialism">RSS<i class="bi bi-rss-fill ms-2" style="color: orange;"></i></a>
repo.njk:44
Before
        {% for patch in repos[repo].patches | batch(3) | first %}
After
        {% for patch in repos[branch.repoName][branch.branchName].patches | batch(3) | first %}