Commit ba94ae5190871fc07a7e490f90e3b2a3f0984e4b Parent: 45e5fe4230364612e439938a4067b955ce1ba1b1 Author: Matt Colligan <mcol@posteo.net> Date: 2026-08-30 23:53:30 +0100 Committer: GitHub <noreply@github.com> Committed: 2026-08-30 23:53:30 +0100 Generate pages for nested files, not just top-level ones (#33) * Expose a genuinely flattened file listing (blobs) Both "tree" and "tree_recursive" (at the top-level package scope, and the per-file scope on a directory entry) are misleadingly named - neither actually recurses. "tree" is filtered to top-level entries; "tree_recursive" is the exact same unfiltered top-level list (a no-op filter, since top-level entries never contain a path separator); and per-file `tree`/`tree_recursive` both just return one level of a directory's immediate children (treeFileGetTreeRecursive literally just returns `fs` unchanged). There was no existing way to get a real flat, full-depth listing of a repo's files. Add `flattenFiles` (Types.hs), which actually recurses into FolderContents and collects only file/blob leaves, and expose it as a new top-level `blobs` field in Repositories.hs's `package` scope. Left the existing `tree`/`tree_recursive` fields untouched, since other templates (and the readme/license lookup, which searches `tree`) may depend on their current, if misleadingly-named, behavior. Each entry's `path` in the flattened list is already the full path relative to the repo root - the tree walk in Repositories.hs accumulates this via prependParent as it descends, regardless of how deep a file is nested. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RfZTyHvxLyVp4yaWmNvhN * Actually generate file pages for nested files, not just top-level ones processRepo' only ever called the file-page generator on the top-level tree (in both the force and non-force/getUpdatedFiles branches), never on anything reachable through a directory's FolderContents. So gitja has never actually written a file.html-equivalent page for anything nested in a subdirectory, independent of the "blobs" field or any template - the page itself was never generated, regardless of what a template linked to. Flatten tree with flattenFiles before both generation paths, so a file's depth no longer determines whether it gets a page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RfZTyHvxLyVp4yaWmNvhN * Drop comment Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RfZTyHvxLyVp4yaWmNvhN * Move flattenFiles into Repositories.hs Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RfZTyHvxLyVp4yaWmNvhN * Drop tree_recursive - it never actually recursed Confirmed by reading the code: at both scopes (top-level package, and per-file on a directory entry), tree_recursive was identical to tree. Top-level tree's filter (notElem pathSeparator ...) was a no-op, since the list it filters only ever contains top-level entries in the first place - nested entries live inside each directory's own FolderContents, never flattened into it. Per-file, treeFileGetTreeRecursive just returned `fs` unchanged - no recursion despite the name. The docs described tree_recursive as "a list of *all* of the repository's/directory's contents", which was never actually true. "tree" is git's own name for a tree object's entries (a git tree is not itself recursive - that's exactly what it means for git to have separate tree objects per directory), so keeping that name for "this tree's entries" is accurate, not a simplification. Dropped the now-pointless filter/atTop logic along with tree_recursive, and updated DOCUMENTATION.md's Scopes/Attributes tables to match - "blobs" is documented there too, since it existed but wasn't previously listed. This doesn't add per-tree-entry pages for every object type a tree can contain (blob/tree/commit-as-submodule) - just removes a redundant, inaccurately-documented field. That's real future work, not this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RfZTyHvxLyVp4yaWmNvhN * Reinstate tree_recursive, properly recursive this time blobs (leaves only) and tree_recursive (every entry, blobs and trees alike, at any depth) are different things - collapsing tree_recursive into blobs lost that distinction. Add flattenTree (Types.hs), the tree-and-blob equivalent of flattenFiles - keeps every entry including directories themselves as it descends, equivalent to `git ls-tree -r -t`. Used for both the top-level "tree_recursive" (package scope, Repositories.hs) and the per-file "tree_recursive" on a directory entry (treeAsLookup, Types.hs). flattenTree lives in Types.hs rather than alongside flattenFiles in Repositories.hs, since it's needed by treeAsLookup there too and Repositories.hs already depends on Types.hs (the reverse would be a cycle). Updated DOCUMENTATION.md's Scopes/Attributes tables to describe both tree_recursive and blobs accurately. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RfZTyHvxLyVp4yaWmNvhN * Add a per-tree page category, and rename file -> blob Third special repo/ template, alongside commit.html and the newly-renamed blob.html: tree.html, generated once per tree (directory) object at any depth, mirroring how blob.html/commit.html already work per blob/commit. Uses flattenTrees (Repositories.hs) to walk every tree object including itself, and treeHref for its output path. Unlike blobs, a tree's own path never appears in a commit diff, so there's no cheap way to tell whether a tree needs regenerating from newCommits alone - tree pages are always regenerated on every run rather than incrementally, which is simpler and correct rather than risking the exact kind of silent staleness bug the blob-generation fix earlier addressed. "file" -> "blob" throughout (envFileTemplate/fileT/fileHref/fileDir -> envBlobTemplate/blobT/blobHref/blobDir, output moves from file/ to blob/), matching git's own object vocabulary and freeing up the name "file" from colliding with anything tree-related. Updated the three bundled templates (base/docs/stagit) so this doesn't silently break them: file.html -> blob.html (dropping their now-truly- unreachable is_directory branches, since blobs are never directories), the existing once-per-repo file-listing tree.html -> files.html (freeing the name for the new per-object template), and a real tree.html per-object page for each, built from what those old is_directory branches were already trying to do. Every href referencing the old file/ output directory is updated to blob/, and files.html/tree.html's child links now route to tree/ or blob/ depending on the entry's own type. Updated DOCUMENTATION.md's folder structure, scopes and attributes tables to describe blob/tree/commit as the three special per-object templates, and the blob/tree attribute table entries as shared (most attributes apply to both, a few are n/a on one side). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RfZTyHvxLyVp4yaWmNvhN * Fix test.sh and rename special templates to foreach.<scope>.html Fixes the CI failure on the previous commit: test.sh's golden-file test still expected output under gitja/file/... (the pre-rename output directory), and test/templates/repo/file.html was never updated to match the blob rename. Renamed that test template to foreach.blob.html (see below), updated its dumped "file"/"scope: file" labels to "blob"/"scope: blob" (the values themselves - paths, hrefs, contents - are unchanged, since it's the same underlying object under a renamed scope), moved test/expected/gitja/file/ to test/expected/gitja/blob/ with matching label updates, and fixed test.sh's TESTS array paths. Also rename the three special per-object templates to foreach.blob.html/foreach.commit.html/foreach.tree.html (from blob.html/commit.html/tree.html), across the bundled templates, the test template, and Env.hs's lookups. Makes it visually obvious in a directory listing which templates in repo/ run once per repo vs. once per object - previously "blob.html"/"commit.html"/"tree.html" looked like just three more arbitrarily-named repo templates alongside index.html/refs.html/etc, despite being the special-cased ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RfZTyHvxLyVp4yaWmNvhN * Rename TreeFile to TreeEntry and tree_recursive to entries, add trees field TreeEntry better reflects that these represent any tree object (blob, tree, or submodule), and entries/trees give the flattened lists clearer, distinct names. --------- Co-authored-by: Claude <noreply@anthropic.com>
test/test.sh Modified
@@ -36,10 +36,10 @@ "static/a_nice_file" # Static folder at top level "gitja/index.html" # Repo scope "gitja/commit/0292014748caae952bbc8dd6225680d83c0a5135.html" # A commit - "gitja/file/test.templates.style.css.html" # A plain text file + "gitja/blob/test.templates.style.css.html" # A plain text file "so_called_binary_file" # Static file at top level that git considers binary - "gitja/file/test.templates.so_called_binary_file.html" # The binary file - "gitja/file/github.FUNDING.yml.html" # HREF drops leading period + "gitja/blob/test.templates.so_called_binary_file.html" # The binary file + "gitja/blob/github.FUNDING.yml.html" # HREF drops leading period "gitja/log.html" # Symbolic link inside repo/ "gitja/static/another_file" # Static folder inside repo/ )
test/templates/repo/foreach.commit.html Added
@@ -0,0 +1,19 @@ +{% extends "../title.html.include" -%} +{%- block title -%}{{ commit }}{%- endblock -%} + +{% block body %} +scope: commit +commit: {{ commit }} +commit.id: {{ commit.id }} +commit.title: {{ commit.title }} +commit.body: {{ commit.body }} +commit.message: {{ commit.message }} +commit.author: {{ commit.author }} +commit.committer: {{ commit.committer }} +commit.author_email: {{ commit.author_email }} +commit.committer_email: {{ commit.committer_email }} +commit.authored: {{ commit.authored }} +commit.committed: {{ commit.committed }} +commit.encoding: {{ commit.encoding }} +commit.parent: {{ commit.parent }} +{% endblock %}
test/templates/repo/foreach.blob.html Added
@@ -0,0 +1,28 @@ +{% extends "../title.html.include" -%} +{%- block title -%}{{ blob }}{%- endblock -%} + +{% block body %} +scope: blob +blob: {{ blob }} +blob.path: {{ blob.path }} +blob.href: {{ blob.href }} +blob.contents: {% if blob.is_binary %}(binary) +{% else %}{{ blob.contents }}{% endif %} +blob.mode: {{ blob.mode }} +blob.mode_octal: {{ blob.mode_octal }} +blob.mode_symbolic: {{ blob.mode_symbolic }} +if blob.is_directory then "directory" else "file": + {%- if blob.is_directory %} "directory"{% else %} "file"{% endif %} + + +blob scope also should have data from repo scope available: +host: {{ host }} +repos: {{ repositories }} +name: {{ name }} +description: {{ description }} +commits: (skipped) +tree: (skipped) +tags: {{ length(filter(tags, (t) -> t.name == "0.2.0")) }} +readme.path: {{ readme.path }} +license.path: {{ license.path }} +{% endblock %}
test/templates/repo/file.html Deleted
@@ -1,28 +0,0 @@ -{% extends "../title.html.include" -%} -{%- block title -%}{{ file }}{%- endblock -%} - -{% block body %} -scope: file -file: {{ file }} -file.path: {{ file.path }} -file.href: {{ file.href }} -file.contents: {% if file.is_binary %}(binary) -{% else %}{{ file.contents }}{% endif %} -file.mode: {{ file.mode }} -file.mode_octal: {{ file.mode_octal }} -file.mode_symbolic: {{ file.mode_symbolic }} -if file.is_directory then "directory" else "file": - {%- if file.is_directory %} "directory"{% else %} "file"{% endif %} - - -file scope also should have data from repo scope available: -host: {{ host }} -repos: {{ repositories }} -name: {{ name }} -description: {{ description }} -commits: (skipped) -tree: (skipped) -tags: {{ length(filter(tags, (t) -> t.name == "0.2.0")) }} -readme.path: {{ readme.path }} -license.path: {{ license.path }} -{% endblock %}
test/templates/repo/commit.html Deleted
@@ -1,19 +0,0 @@ -{% extends "../title.html.include" -%} -{%- block title -%}{{ commit }}{%- endblock -%} - -{% block body %} -scope: commit -commit: {{ commit }} -commit.id: {{ commit.id }} -commit.title: {{ commit.title }} -commit.body: {{ commit.body }} -commit.message: {{ commit.message }} -commit.author: {{ commit.author }} -commit.committer: {{ commit.committer }} -commit.author_email: {{ commit.author_email }} -commit.committer_email: {{ commit.committer_email }} -commit.authored: {{ commit.authored }} -commit.committed: {{ commit.committed }} -commit.encoding: {{ commit.encoding }} -commit.parent: {{ commit.parent }} -{% endblock %}
test/expected/gitja/file/test.templates.style.css.html Deleted
@@ -1,21 +0,0 @@ -test/templates/style.css -scope: file -file: test/templates/style.css -file.path: test/templates/style.css -file.href: test.templates.style.css.html -file.contents: html -file.mode: Plain -file.mode_octal: 00644 -file.mode_symbolic: -rw-r--r-- -if file.is_directory then "directory" else "file": "file" - -file scope also should have data from repo scope available: -host: https://github.com/m-col/gitja -repos: gitja -name: gitja -description: 🐙 Templated web page generator for your git repositories -commits: (skipped) -tree: (skipped) -tags: 1 -readme.path: README.rst -license.path: LICENSE
test/expected/gitja/file/test.templates.so_called_binary_file.html Deleted
@@ -1,21 +0,0 @@ -test/templates/so_called_binary_file -scope: file -file: test/templates/so_called_binary_file -file.path: test/templates/so_called_binary_file -file.href: test.templates.so_called_binary_file.html -file.contents: (binary) -file.mode: Plain -file.mode_octal: 00644 -file.mode_symbolic: -rw-r--r-- -if file.is_directory then "directory" else "file": "file" - -file scope also should have data from repo scope available: -host: https://github.com/m-col/gitja -repos: gitja -name: gitja -description: 🐙 Templated web page generator for your git repositories -commits: (skipped) -tree: (skipped) -tags: 1 -readme.path: README.rst -license.path: LICENSE
test/expected/gitja/file/github.FUNDING.yml.html Deleted
@@ -1,22 +0,0 @@ -.github/FUNDING.yml -scope: file -file: .github/FUNDING.yml -file.path: .github/FUNDING.yml -file.href: github.FUNDING.yml.html -file.contents: github: ["m-col"] -custom: ["https://liberapay.com/mcol"] -file.mode: Plain -file.mode_octal: 00644 -file.mode_symbolic: -rw-r--r-- -if file.is_directory then "directory" else "file": "file" - -file scope also should have data from repo scope available: -host: https://github.com/m-col/gitja -repos: gitja -name: gitja -description: 🐙 Templated web page generator for your git repositories -commits: (skipped) -tree: (skipped) -tags: 1 -readme.path: README.rst -license.path: LICENSE
test/expected/gitja/blob/test.templates.style.css.html Added
@@ -0,0 +1,21 @@ +test/templates/style.css +scope: blob +blob: test/templates/style.css +blob.path: test/templates/style.css +blob.href: test.templates.style.css.html +blob.contents: html +blob.mode: Plain +blob.mode_octal: 00644 +blob.mode_symbolic: -rw-r--r-- +if blob.is_directory then "directory" else "file": "file" + +blob scope also should have data from repo scope available: +host: https://github.com/m-col/gitja +repos: gitja +name: gitja +description: 🐙 Templated web page generator for your git repositories +commits: (skipped) +tree: (skipped) +tags: 1 +readme.path: README.rst +license.path: LICENSE
test/expected/gitja/blob/test.templates.so_called_binary_file.html Added
@@ -0,0 +1,21 @@ +test/templates/so_called_binary_file +scope: blob +blob: test/templates/so_called_binary_file +blob.path: test/templates/so_called_binary_file +blob.href: test.templates.so_called_binary_file.html +blob.contents: (binary) +blob.mode: Plain +blob.mode_octal: 00644 +blob.mode_symbolic: -rw-r--r-- +if blob.is_directory then "directory" else "file": "file" + +blob scope also should have data from repo scope available: +host: https://github.com/m-col/gitja +repos: gitja +name: gitja +description: 🐙 Templated web page generator for your git repositories +commits: (skipped) +tree: (skipped) +tags: 1 +readme.path: README.rst +license.path: LICENSE
test/expected/gitja/blob/github.FUNDING.yml.html Added
@@ -0,0 +1,22 @@ +.github/FUNDING.yml +scope: blob +blob: .github/FUNDING.yml +blob.path: .github/FUNDING.yml +blob.href: github.FUNDING.yml.html +blob.contents: github: ["m-col"] +custom: ["https://liberapay.com/mcol"] +blob.mode: Plain +blob.mode_octal: 00644 +blob.mode_symbolic: -rw-r--r-- +if blob.is_directory then "directory" else "file": "file" + +blob scope also should have data from repo scope available: +host: https://github.com/m-col/gitja +repos: gitja +name: gitja +description: 🐙 Templated web page generator for your git repositories +commits: (skipped) +tree: (skipped) +tags: 1 +readme.path: README.rst +license.path: LICENSE
templates/stagit/repo/tree.html Deleted
@@ -1,29 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title -%} -{{ name }} - target -{%- endblock -%} - -{% block body %} -{% include "repo-table-header.html.include" %} - -<div id="content"> - <table id="files"> - <thead> - <tr> - <td><b>Mode</b></td> - <td><b>Name</b></td> - </tr> - </thead> - - <tbody> - {% for file in tree %} - <tr> - <td>{{ file.mode_symbolic }}</td> - <td><a href="{{ host }}/{{ name }}/file/{{ file.href }}">{{ file.path }}</a></td> - </tr> - {% endfor %} - </tbody> - </table> -</div> -{% endblock %}
templates/stagit/repo/repo-table-header.html.include Modified
@@ -16,9 +16,9 @@ <tr><td><span class="desc">{{ description }}</span></td></tr> <tr class="url"><td>git clone <a href="{{ host }}/{{ name }}">{{ host }}/{{ name }}</a></td></tr> <tr><td> - <a href="{{ host }}/{{ name }}/tree.html">Files</a> | <a href="{{ host }}/{{ name }}/refs.html">Refs</a> - {% if readme %} | <a href="{{ host }}/{{ name }}/file/{{ readme.href }}">Readme</a>{% endif %} - {% if license %} | <a href="{{ host }}/{{ name }}/file/{{ license.href }}">License</a>{% endif %} + <a href="{{ host }}/{{ name }}/files.html">Files</a> | <a href="{{ host }}/{{ name }}/refs.html">Refs</a> + {% if readme %} | <a href="{{ host }}/{{ name }}/blob/{{ readme.href }}">Readme</a>{% endif %} + {% if license %} | <a href="{{ host }}/{{ name }}/blob/{{ license.href }}">License</a>{% endif %} </td></tr> </table> <hr>
templates/stagit/repo/foreach.tree.html Added
@@ -0,0 +1,35 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ name }} - {{ tree }} +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <p>{{ tree.mode_symbolic }} {{ tree }}</p><hr> + + <table id="files"> + <thead> + <tr> + <td><b>Mode</b></td> + <td><b>Name</b></td> + </tr> + </thead> + + <tbody> + {% for child in tree.tree %} + <tr> + <td>{{ child.mode_symbolic }}</td> + {% if child.is_directory %} + <td><a href="{{ host }}/{{ name }}/tree/{{ child.href }}">{{ child.path }}</a></td> + {% else %} + <td><a href="{{ host }}/{{ name }}/blob/{{ child.href }}">{{ child.path }}</a></td> + {% endif %} + </tr> + {% endfor %} + </tbody> + </table> +</div> +{% endblock %}
templates/stagit/repo/foreach.commit.html Added
@@ -0,0 +1,46 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ name }} - {{ commit.title }} +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <pre><b>Commit</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.id }}.html">{{ commit.id }}</a> +<b>Parent:</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.parent }}.html">{{ commit.parent }}</a> +<b>Author:</b> {{ commit.author }} <<a href="mailto:{{ commit.author_email }}">{{ commit.author_email }}</a>> +<b>Date:</b> {{ commit.authored }} +<b>Committer:</b> {{ commit.committer }} <<a href="mailto:{{ commit.committer_email }}">{{ commit.committer_email }}</a>> +<b>Committed:</b> {{ commit.committed }} + +{{ commit.message }} +</pre> + +{%- for diff in commit.diffs -%} +<div class="diff"> + <p> + <b>{{ diff.new_file }}</b> + <span class="status">{{ diff.status }}</span> + </p> + + {% if diff.newfile != diff.oldfile %} + <p>Renamed from {{ diff.old_file }}</p> + {% endif %} + + {%- for hunk in diff.hunks -%} + {{ hunk.header }} + + <pre> + {%- for line in hunk.lines -%} + <span class="{{ line.class }}">{{ line.text }}</span> + {%- endfor -%} + </pre> + + {%- endfor -%} +</div> +{%- endfor -%} + +</div> +{% endblock %}
templates/stagit/repo/foreach.blob.html Added
@@ -0,0 +1,21 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ name }} - {{ blob }} +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <p>{{ blob.mode_symbolic }} {{ blob }}</p><hr> + + {% if blob.is_binary %} + (File is binary) + {% else %} +<pre id="blob">{% for line in split(blob.contents, '\n') %} +<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%7v", loop.index) }}</a> {{ line }} +{% endfor %}</pre> + {% endif %} +</div> +{% endblock %}
templates/stagit/repo/files.html Added
@@ -0,0 +1,33 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ name }} - files +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <table id="files"> + <thead> + <tr> + <td><b>Mode</b></td> + <td><b>Name</b></td> + </tr> + </thead> + + <tbody> + {% for file in tree %} + <tr> + <td>{{ file.mode_symbolic }}</td> + {% if file.is_directory %} + <td><a href="{{ host }}/{{ name }}/tree/{{ file.href }}">{{ file.path }}</a></td> + {% else %} + <td><a href="{{ host }}/{{ name }}/blob/{{ file.href }}">{{ file.path }}</a></td> + {% endif %} + </tr> + {% endfor %} + </tbody> + </table> +</div> +{% endblock %}
templates/stagit/repo/file.html Deleted
@@ -1,41 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title -%} -{{ name }} - {{ file }} -{%- endblock -%} - -{% block body %} -{% include "repo-table-header.html.include" %} - -<div id="content"> - <p>{{ file.mode_symbolic }} {{ file }}</p><hr> - -{% if file.is_directory %} - <table id="files"> - <thead> - <tr> - <td><b>Mode</b></td> - <td><b>Name</b></td> - </tr> - </thead> - - <tbody> - {% for child in file.contents %} - <tr> - <td>{{ child.mode_symbolic }}</td> - <td><a href="{{ host }}/{{ name }}/file/{{ child.href }}">{{ child.path }}</a></td> - </tr> - {% endfor %} - </tbody> - </table> -{% else %} - {% if file.is_binary %} - (File is binary) - {% else %} -<pre id="blob">{% for line in split(file.contents, '\n') %} -<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%7v", loop.index) }}</a> {{ line }} -{% endfor %}</pre> - {% endif %} -{% endif %} -</div> -{% endblock %}
templates/stagit/repo/commit.html Deleted
@@ -1,46 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title -%} -{{ name }} - {{ commit.title }} -{%- endblock -%} - -{% block body %} -{% include "repo-table-header.html.include" %} - -<div id="content"> - <pre><b>Commit</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.id }}.html">{{ commit.id }}</a> -<b>Parent:</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.parent }}.html">{{ commit.parent }}</a> -<b>Author:</b> {{ commit.author }} <<a href="mailto:{{ commit.author_email }}">{{ commit.author_email }}</a>> -<b>Date:</b> {{ commit.authored }} -<b>Committer:</b> {{ commit.committer }} <<a href="mailto:{{ commit.committer_email }}">{{ commit.committer_email }}</a>> -<b>Committed:</b> {{ commit.committed }} - -{{ commit.message }} -</pre> - -{%- for diff in commit.diffs -%} -<div class="diff"> - <p> - <b>{{ diff.new_file }}</b> - <span class="status">{{ diff.status }}</span> - </p> - - {% if diff.newfile != diff.oldfile %} - <p>Renamed from {{ diff.old_file }}</p> - {% endif %} - - {%- for hunk in diff.hunks -%} - {{ hunk.header }} - - <pre> - {%- for line in hunk.lines -%} - <span class="{{ line.class }}">{{ line.text }}</span> - {%- endfor -%} - </pre> - - {%- endfor -%} -</div> -{%- endfor -%} - -</div> -{% endblock %}
templates/docs/template.html.include Modified
@@ -23,10 +23,10 @@ <span id="links"> <a href="{{ host }}">Home</a> | <a href="{{ host }}/{{ name }}/log.html">Log</a> | - <a href="{{ host }}/{{ name }}/tree.html">Files</a> | + <a href="{{ host }}/{{ name }}/files.html">Files</a> | <a href="{{ host }}/{{ name }}/refs.html">Refs</a> | - <a href="{{ host }}/{{ name }}/file/{{ readme.href }}">Readme</a> | - <a href="{{ host }}/{{ name }}/file/{{ license.href }}">License</a> + <a href="{{ host }}/{{ name }}/blob/{{ readme.href }}">Readme</a> | + <a href="{{ host }}/{{ name }}/blob/{{ license.href }}">License</a> </span> <p id="tag">
templates/docs/repo/tree.html Deleted
@@ -1,23 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title %}tree{% endblock -%} - -{% block body %} -<table id="files"> - <thead> - <tr> - <td><b>Mode</b></td> - <td><b>Name</b></td> - </tr> - </thead> - - <tbody> - {% for file in tree %} - <tr> - <td>{{ file.mode_symbolic }}</td> - <td><a href="{{ host }}/{{ name }}/file/{{ file.href }}">{{ file.path }}</a></td> - </tr> - {% endfor %} - </tbody> -</table> -{% endblock %}
templates/docs/repo/foreach.tree.html Added
@@ -0,0 +1,29 @@ +{% extends "../template.html.include" %} + +{%- block title %}{{ tree }}{% endblock -%} + +{% block body %} +<p>{{ tree.mode_symbolic }} {{ tree }}</p><hr> + +<table id="files"> + <thead> + <tr> + <td><b>Mode</b></td> + <td><b>Name</b></td> + </tr> + </thead> + + <tbody> + {% for child in tree.tree %} + <tr> + <td>{{ child.mode_symbolic }}</td> + {% if child.is_directory %} + <td><a href="{{ host }}/{{ name }}/tree/{{ child.href }}">{{ child.path }}</a></td> + {% else %} + <td><a href="{{ host }}/{{ name }}/blob/{{ child.href }}">{{ child.path }}</a></td> + {% endif %} + </tr> + {% endfor %} + </tbody> +</table> +{% endblock %}
templates/docs/repo/foreach.commit.html Added
@@ -0,0 +1,40 @@ +{% extends "../template.html.include" %} + +{%- block title %}{{ commit.title }}{% endblock -%} + +{% block body %} +<pre><b>Commit</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.id }}.html">{{ commit.id }}</a> +<b>Parent:</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.parent }}.html">{{ commit.parent }}</a> +<b>Author:</b> {{ commit.author }} <<a href="mailto:{{ commit.author_email }}">{{ commit.author_email }}</a>> +<b>Date:</b> {{ commit.authored }} +<b>Committer:</b> {{ commit.committer }} <<a href="mailto:{{ commit.committer_email }}">{{ commit.committer_email }}</a>> +<b>Committed:</b> {{ commit.committed }} + +{{ commit.message }} +</pre> + +{%- for diff in commit.diffs -%} +<div class="diff"> + <p> + <b>{{ diff.new_file }}</b> + <span class="status">{{ diff.status }}</span> + </p> + + {% if diff.newfile != diff.oldfile %} + <p>Renamed from {{ diff.old_file }}</p> + {% endif %} + + {%- for hunk in diff.hunks -%} + {{ hunk.header }} + + <pre> + {%- for line in hunk.lines -%} + <span class="{{ line.class }}">{{ line.text }}</span> + {%- endfor -%} + </pre> + + {%- endfor -%} +</div> +{%- endfor -%} + +{% endblock %}
templates/docs/repo/foreach.blob.html Added
@@ -0,0 +1,15 @@ +{% extends "../template.html.include" %} + +{%- block title %}{{ blob }}{% endblock -%} + +{% block body %} +<p>{{ blob.mode_symbolic }} {{ blob }}</p><hr> + +{% if blob.is_binary %} +(File is binary) +{% else %} +<pre id="blob">{% for line in split(blob.contents, '\n') %} +<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%5v", loop.index) }}</a> {{ line }} +{% endfor %}</pre> +{% endif %} +{% endblock %}
templates/docs/repo/files.html Added
@@ -0,0 +1,27 @@ +{% extends "../template.html.include" %} + +{%- block title %}tree{% endblock -%} + +{% block body %} +<table id="files"> + <thead> + <tr> + <td><b>Mode</b></td> + <td><b>Name</b></td> + </tr> + </thead> + + <tbody> + {% for file in tree %} + <tr> + <td>{{ file.mode_symbolic }}</td> + {% if file.is_directory %} + <td><a href="{{ host }}/{{ name }}/tree/{{ file.href }}">{{ file.path }}</a></td> + {% else %} + <td><a href="{{ host }}/{{ name }}/blob/{{ file.href }}">{{ file.path }}</a></td> + {% endif %} + </tr> + {% endfor %} + </tbody> +</table> +{% endblock %}
templates/docs/repo/file.html Deleted
@@ -1,47 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title %}{{ file }}{% endblock -%} - -{% block body %} -<p>{{ file.mode_symbolic }} {{ file }}</p><hr> - -{% if file.is_directory %} -<table id="files"> - <thead> - <tr> - <td><b>Mode</b></td> - <td><b>Name</b></td> - </tr> - </thead> - - <tbody> - {% for child in file.tree %} - <tr> - <td>{{ child.mode_symbolic }}</td> - <td><a href="{{ host }}/{{ name }}/file/{{ child.href }}">{{ child.path }}</a></td> - </tr> - {% endfor %} - </tbody> -</table> - - {%- if length(file.contents) == 1 -%} - {%- set child = file.contents[0] -%} - {% if child.is_binary %} - (Child is binary) - {% else %} -<pre id="blob">{% for line in split(child.contents, '\n') %} -<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%5v", loop.index) }}</a> {{ line }} -{% endfor %}</pre> - {% endif %} - {%- endif -%} - -{% else %} - {% if file.is_binary %} - (File is binary) - {% else %} -<pre id="blob">{% for line in split(file.contents, '\n') %} -<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%5v", loop.index) }}</a> {{ line }} -{% endfor %}</pre> - {% endif %} -{% endif %} -{% endblock %}
templates/docs/repo/commit.html Deleted
@@ -1,40 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title %}{{ commit.title }}{% endblock -%} - -{% block body %} -<pre><b>Commit</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.id }}.html">{{ commit.id }}</a> -<b>Parent:</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.parent }}.html">{{ commit.parent }}</a> -<b>Author:</b> {{ commit.author }} <<a href="mailto:{{ commit.author_email }}">{{ commit.author_email }}</a>> -<b>Date:</b> {{ commit.authored }} -<b>Committer:</b> {{ commit.committer }} <<a href="mailto:{{ commit.committer_email }}">{{ commit.committer_email }}</a>> -<b>Committed:</b> {{ commit.committed }} - -{{ commit.message }} -</pre> - -{%- for diff in commit.diffs -%} -<div class="diff"> - <p> - <b>{{ diff.new_file }}</b> - <span class="status">{{ diff.status }}</span> - </p> - - {% if diff.newfile != diff.oldfile %} - <p>Renamed from {{ diff.old_file }}</p> - {% endif %} - - {%- for hunk in diff.hunks -%} - {{ hunk.header }} - - <pre> - {%- for line in hunk.lines -%} - <span class="{{ line.class }}">{{ line.text }}</span> - {%- endfor -%} - </pre> - - {%- endfor -%} -</div> -{%- endfor -%} - -{% endblock %}
templates/base/template.html.include Modified
@@ -19,13 +19,13 @@ <span id="clone"><b>git clone <a href="{{ host }}/{{ name }}">{{ host }}/{{ name }}</a></b></span> <div class="links"> <a href="{{ host }}/{{ name }}/index.html">Log</a> | - <a href="{{ host }}/{{ name }}/tree.html">Files</a> | + <a href="{{ host }}/{{ name }}/files.html">Files</a> | <a href="{{ host }}/{{ name }}/refs.html">Refs</a> {% if readme %} - | <a href="{{ host }}/{{ name }}/file/{{ readme.href }}">{{ readme.name }}</a> + | <a href="{{ host }}/{{ name }}/blob/{{ readme.href }}">{{ readme.name }}</a> {% endif %} {% if license %} - | <a href="{{ host }}/{{ name }}/file/{{ license.href }}">{{ license.name }}</a> + | <a href="{{ host }}/{{ name }}/blob/{{ license.href }}">{{ license.name }}</a> {% endif %} </div> </p>
templates/base/repo/tree.html Deleted
@@ -1,23 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title %}{{ name }} - Tree{% endblock -%} - -{% block content %} -<table> - <thead> - <tr> - <td><b>Mode</b></td> - <td><b>Name</b></td> - </tr> - </thead> - - <tbody> - {% for file in tree %} - <tr> - <td>{{ file.mode_symbolic }}</td> - <td><a href="{{ host }}/{{ name }}/file/{{ file.href }}">{{ file.path }}</a></td> - </tr> - {% endfor %} - </tbody> -</table> -{% endblock %}
templates/base/repo/foreach.tree.html Added
@@ -0,0 +1,29 @@ +{% extends "../template.html.include" %} + +{%- block title %}{{ name }} - {{ tree }}{% endblock %} + +{%- block content %} +<p>{{ tree.mode_symbolic }} {{ tree }}</p><hr> + +<table> + <thead> + <tr> + <td><b>Mode</b></td> + <td><b>Name</b></td> + </tr> + </thead> + + <tbody> + {% for child in tree.tree %} + <tr> + <td>{{ child.mode_symbolic }}</td> + {% if child.is_directory %} + <td><a href="{{ host }}/{{ name }}/tree/{{ child.href }}">{{ child.path }}</a></td> + {% else %} + <td><a href="{{ host }}/{{ name }}/blob/{{ child.href }}">{{ child.path }}</a></td> + {% endif %} + </tr> + {% endfor %} + </tbody> +</table> +{% endblock %}
templates/base/repo/foreach.commit.html Added
@@ -0,0 +1,40 @@ +{% extends "../template.html.include" %} + +{%- block title %}{{ name }} - {{ commit.title }}{% endblock %} + +{%- block content %} +<pre><b>Commit</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.id }}.html">{{ commit.id }}</a> +<b>Parent:</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.parent }}.html">{{ commit.parent }}</a> +<b>Author:</b> {{ commit.author }} <<a href="mailto:{{ commit.author_email }}">{{ commit.author_email }}</a>> +<b>Date:</b> {{ commit.authored }} +<b>Committer:</b> {{ commit.committer }} <<a href="mailto:{{ commit.committer_email }}">{{ commit.committer_email }}</a>> +<b>Committed:</b> {{ commit.committed }} + +{{ commit.message }} +</pre> + +{%- for diff in commit.diffs -%} +<div class="diff"> + <p> + <b>{{ diff.new_file }}</b> + <span class="status">{{ diff.status }}</span> + </p> + + {% if diff.newfile != diff.oldfile %} + <p>Renamed from {{ diff.old_file }}</p> + {% endif %} + + {%- for hunk in diff.hunks -%} + {{ hunk.header }} + + <pre> + {%- for line in hunk.lines -%} + <span class="{{ line.class }}">{{ line.text }}</span> + {%- endfor -%} + </pre> + + {%- endfor -%} +</div> +{%- endfor -%} + +{% endblock %}
templates/base/repo/foreach.blob.html Added
@@ -0,0 +1,15 @@ +{% extends "../template.html.include" %} + +{%- block title %}{{ name }} - {{ blob }}{% endblock %} + +{%- block content %} +<p>{{ blob.mode_symbolic }} {{ blob }}</p><hr> + +{% if blob.is_binary %} +(File is binary) +{% else %} +<pre>{% for line in split(blob.contents, '\n') %} +<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%5v", loop.index) }}</a> {{ line }} +{% endfor %}</pre> +{% endif %} +{% endblock %}
templates/base/repo/files.html Added
@@ -0,0 +1,27 @@ +{% extends "../template.html.include" %} + +{%- block title %}{{ name }} - Tree{% endblock -%} + +{% block content %} +<table> + <thead> + <tr> + <td><b>Mode</b></td> + <td><b>Name</b></td> + </tr> + </thead> + + <tbody> + {% for file in tree %} + <tr> + <td>{{ file.mode_symbolic }}</td> + {% if file.is_directory %} + <td><a href="{{ host }}/{{ name }}/tree/{{ file.href }}">{{ file.path }}</a></td> + {% else %} + <td><a href="{{ host }}/{{ name }}/blob/{{ file.href }}">{{ file.path }}</a></td> + {% endif %} + </tr> + {% endfor %} + </tbody> +</table> +{% endblock %}
templates/base/repo/file.html Deleted
@@ -1,35 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title %}{{ name }} - {{ file }}{% endblock %} - -{%- block content %} -<p>{{ file.mode_symbolic }} {{ file }}</p><hr> - -{% if file.is_directory %} -<table> - <thead> - <tr> - <td><b>Mode</b></td> - <td><b>Name</b></td> - </tr> - </thead> - - <tbody> - {% for child in file.tree %} - <tr> - <td>{{ child.mode_symbolic }}</td> - <td><a href="{{ host }}/{{ name }}/file/{{ child.href }}">{{ child.path }}</a></td> - </tr> - {% endfor %} - </tbody> -</table> -{% else %} - {% if file.is_binary %} - (File is binary) - {% else %} -<pre>{% for line in split(file.contents, '\n') %} -<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%5v", loop.index) }}</a> {{ line }} -{% endfor %}</pre> -{% endif %} -{% endif %} -{% endblock %}
templates/base/repo/commit.html Deleted
@@ -1,40 +0,0 @@ -{% extends "../template.html.include" %} - -{%- block title %}{{ name }} - {{ commit.title }}{% endblock %} - -{%- block content %} -<pre><b>Commit</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.id }}.html">{{ commit.id }}</a> -<b>Parent:</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.parent }}.html">{{ commit.parent }}</a> -<b>Author:</b> {{ commit.author }} <<a href="mailto:{{ commit.author_email }}">{{ commit.author_email }}</a>> -<b>Date:</b> {{ commit.authored }} -<b>Committer:</b> {{ commit.committer }} <<a href="mailto:{{ commit.committer_email }}">{{ commit.committer_email }}</a>> -<b>Committed:</b> {{ commit.committed }} - -{{ commit.message }} -</pre> - -{%- for diff in commit.diffs -%} -<div class="diff"> - <p> - <b>{{ diff.new_file }}</b> - <span class="status">{{ diff.status }}</span> - </p> - - {% if diff.newfile != diff.oldfile %} - <p>Renamed from {{ diff.old_file }}</p> - {% endif %} - - {%- for hunk in diff.hunks -%} - {{ hunk.header }} - - <pre> - {%- for line in hunk.lines -%} - <span class="{{ line.class }}">{{ line.text }}</span> - {%- endfor -%} - </pre> - - {%- endfor -%} -</div> -{%- endfor -%} - -{% endblock %}
src/Types.hs Modified
@@ -14,7 +14,7 @@ import Control.Monad.Trans.Reader (ReaderT) import Data.ByteString (ByteString) import Data.Default (def) -import Data.Maybe (fromMaybe, listToMaybe) +import Data.Maybe (listToMaybe) import Data.Tagged (untag) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8With) @@ -214,15 +214,16 @@ {- Next we have some data used to represent a repository's tree and the different kinds of -objects contained therein. +objects contained therein. A TreeEntry is any single entry found in a git tree object - +a blob, a nested tree, or a commit (submodule reference). -} -data TreeFile = TreeFile - { treeFilePath :: T.Text - , treeFileContents :: TreeFileContents - , treeFileMode :: TreeEntryMode +data TreeEntry = TreeEntry + { treeEntryPath :: T.Text + , treeEntryContents :: TreeEntryContents + , treeEntryMode :: TreeEntryMode } -data TreeFileContents = BinaryContents | FileContents ByteString | FolderContents [TreeFile] +data TreeEntryContents = BinaryContents | FileContents ByteString | FolderContents [TreeEntry] data TreeEntryMode = ModeDirectory | ModePlain | ModeExecutable | ModeSymlink | ModeSubmodule deriving stock (Show) @@ -240,7 +241,7 @@ {- This -} -getBlobContents :: Git.BlobOid LgRepo -> ReaderT LgRepo IO TreeFileContents +getBlobContents :: Git.BlobOid LgRepo -> ReaderT LgRepo IO TreeEntryContents getBlobContents oid = do repo <- Git.getRepository blobPtr <- liftIO mallocForeignPtr @@ -259,59 +260,63 @@ GVal implementations for data definitions above, allowing commits to be rendered in Ginger templates. -} -instance ToGVal m TreeFile where - toGVal :: TreeFile -> GVal m - toGVal treefile = +instance ToGVal m TreeEntry where + toGVal :: TreeEntry -> GVal m + toGVal treeentry = def - { asHtml = html . treeFilePath $ treefile - , asText = treeFilePath treefile - , asLookup = Just . treeAsLookup $ treefile + { asHtml = html . treeEntryPath $ treeentry + , asText = treeEntryPath treeentry + , asLookup = Just . treeAsLookup $ treeentry , asBoolean = True -- Used for conditionally checking readme/license template variables. } -instance ToGVal m TreeFileContents where - toGVal :: TreeFileContents -> GVal m +instance ToGVal m TreeEntryContents where + toGVal :: TreeEntryContents -> GVal m toGVal BinaryContents = def toGVal (FileContents bytestring) = toGVal bytestring - toGVal (FolderContents treeFiles) = + toGVal (FolderContents treeEntries) = def - { asHtml = html . T.pack . show . fmap treeFilePath $ treeFiles - , asText = T.pack . show . fmap treeFilePath $ treeFiles - , asList = Just . fmap toGVal $ treeFiles + { asHtml = html . T.pack . show . fmap treeEntryPath $ treeEntries + , asText = T.pack . show . fmap treeEntryPath $ treeEntries + , asList = Just . fmap toGVal $ treeEntries } -treeAsLookup :: TreeFile -> T.Text -> Maybe (GVal m) -treeAsLookup treefile = \case - "path" -> Just . toGVal . treeFilePath $ treefile - "name" -> Just . toGVal . FP.takeFileName . T.unpack . treeFilePath $ treefile - "href" -> Just . toGVal . treePathToHref $ treefile - "contents" -> Just . toGVal . treeFileContents $ treefile - "tree" -> Just . toGVal . treeFileGetTree (treeFilePath treefile) . treeFileContents $ treefile - "tree_recursive" -> Just . toGVal . treeFileGetTreeRecursive . treeFileContents $ treefile - "mode" -> Just . toGVal . drop 4 . show . treeFileMode $ treefile - "mode_octal" -> Just . toGVal . modeToOctal . treeFileMode $ treefile - "mode_symbolic" -> Just . toGVal . modeToSymbolic . treeFileMode $ treefile - "is_binary" -> Just . toGVal . treeFileIsBinary $ treefile - "is_directory" -> Just . toGVal . treeFileIsDirectory $ treefile +{- +Recursively descend into a tree, keeping every entry along the way - blobs and trees +alike - as a single flat list. Equivalent to `git ls-tree -r -t`. +-} +flattenTree :: TreeEntry -> [TreeEntry] +flattenTree treeentry = case treeEntryContents treeentry of + FolderContents entries -> treeentry : concatMap flattenTree entries + _ -> [treeentry] + +treeAsLookup :: TreeEntry -> T.Text -> Maybe (GVal m) +treeAsLookup treeentry = \case + "path" -> Just . toGVal . treeEntryPath $ treeentry + "name" -> Just . toGVal . FP.takeFileName . T.unpack . treeEntryPath $ treeentry + "href" -> Just . toGVal . treePathToHref $ treeentry + "contents" -> Just . toGVal . treeEntryContents $ treeentry + "tree" -> Just . toGVal . treeEntryGetTree . treeEntryContents $ treeentry + "entries" -> Just . toGVal . concatMap flattenTree . treeEntryGetTree . treeEntryContents $ treeentry + "mode" -> Just . toGVal . drop 4 . show . treeEntryMode $ treeentry + "mode_octal" -> Just . toGVal . modeToOctal . treeEntryMode $ treeentry + "mode_symbolic" -> Just . toGVal . modeToSymbolic . treeEntryMode $ treeentry + "is_binary" -> Just . toGVal . treeEntryIsBinary $ treeentry + "is_directory" -> Just . toGVal . treeEntryIsDirectory $ treeentry _ -> Nothing where - treeFileGetTree :: T.Text -> TreeFileContents -> [TreeFile] - treeFileGetTree parent (FolderContents fs) = filter atTop fs - where - atTop = notElem FP.pathSeparator . drop 1 . T.unpack . fromMaybe "" . T.stripPrefix parent . treeFilePath - treeFileGetTree _ _ = [] - - treeFileGetTreeRecursive :: TreeFileContents -> [TreeFile] - treeFileGetTreeRecursive (FolderContents fs) = fs - treeFileGetTreeRecursive _ = [] - - treeFileIsBinary :: TreeFile -> Bool - treeFileIsBinary treefile' = case treeFileContents treefile' of + -- The entries of this tree object - i.e. this directory's immediate children. + treeEntryGetTree :: TreeEntryContents -> [TreeEntry] + treeEntryGetTree (FolderContents fs) = fs + treeEntryGetTree _ = [] + + treeEntryIsBinary :: TreeEntry -> Bool + treeEntryIsBinary treeentry' = case treeEntryContents treeentry' of BinaryContents -> True _ -> False - treeFileIsDirectory :: TreeFile -> Bool - treeFileIsDirectory treefile' = case treeFileContents treefile' of + treeEntryIsDirectory :: TreeEntry -> Bool + treeEntryIsDirectory treeentry' = case treeEntryContents treeentry' of FolderContents _ -> True _ -> False @@ -330,10 +335,10 @@ modeToSymbolic ModeSubmodule = "git-module" {- -Get the name of a tree file path's HTML file. Leading periods are dropped. +Get the name of a tree entry path's HTML file. Leading periods are dropped. -} -treePathToHref :: TreeFile -> T.Text -treePathToHref = T.dropWhile (== '.') . flip T.append ".html" . T.replace "/" "." . treeFilePath +treePathToHref :: TreeEntry -> T.Text +treePathToHref = T.dropWhile (== '.') . flip T.append ".html" . T.replace "/" "." . treeEntryPath {- Data to store information about references: tags and branches.
src/Repositories.hs Modified
@@ -132,9 +132,11 @@ withRunInIO \runInIO -> do -- Create the destination folders -- commitDir <- (directory </>) <$> parseRelDir "commit" - fileDir <- (directory </>) <$> parseRelDir "file" + blobDir <- (directory </>) <$> parseRelDir "blob" + treeDir <- (directory </>) <$> parseRelDir "tree" ensureDir commitDir - ensureDir fileDir + ensureDir blobDir + ensureDir treeDir -- Check which commits are new since the last run -- newCommits <- getUpdates commitDir commits @@ -149,12 +151,21 @@ whenJust (envCommitTemplate env) \commitT -> do mapM_ (gen force commitT "commit" commitDir commitHref) newCommits - whenJust (envFileTemplate env) \fileT -> do + whenJust (envBlobTemplate env) \blobT -> do + let allBlobs = concatMap flattenFiles tree if force - then mapM_ (gen True fileT "file" fileDir fileHref) tree + then mapM_ (gen True blobT "blob" blobDir blobHref) allBlobs else - let updatedFiles = getUpdatedFiles tree newCommits - in mapM_ (gen True fileT "file" fileDir fileHref) updatedFiles + let updatedBlobs = getUpdatedFiles allBlobs newCommits + in mapM_ (gen True blobT "blob" blobDir blobHref) updatedBlobs + + whenJust (envTreeTemplate env) \treeT -> do + -- A tree's own path never appears in a commit's diff (only + -- the blobs within it do), so unlike blobs, staleness can't + -- be judged by newCommits/getUpdatedFiles - always + -- regenerate every tree page. + let allTrees = concatMap flattenTrees tree + mapM_ (gen True treeT "tree" treeDir treeHref) allTrees -- Copy any static files/folders into the output directory -- envRepoCopyStatics env directory @@ -174,7 +185,7 @@ Path Rel Dir -> T.Text -> [Commit] -> - [TreeFile] -> + [TreeEntry] -> HashMap.HashMap T.Text (GVal RunRepo) package env repos name description commits tree = HashMap.fromList @@ -183,16 +194,18 @@ , ("name", toGVal . T.pack . init . toFilePath $ name) , ("description", toGVal description) , ("commits", toGVal commits) - , ("tree", toGVal . filter (notElem FP.pathSeparator . T.unpack . treeFilePath) $ tree) - , ("tree_recursive", toGVal tree) + , ("tree", toGVal tree) + , ("entries", toGVal . concatMap flattenTree $ tree) + , ("blobs", toGVal . concatMap flattenFiles $ tree) + , ("trees", toGVal . concatMap flattenTrees $ tree) , ("readme", toGVal . findFile "readme" $ tree) , ("license", toGVal . findFile "license" $ tree) ] where -- Find a file in the tree starting with the specified prefix. The prefix is looked -- for on the full path, so will only find files in the top level directory. - findFile :: T.Text -> [TreeFile] -> Maybe TreeFile - findFile prefix = find (T.isPrefixOf prefix . T.toLower . treeFilePath) + findFile :: T.Text -> [TreeEntry] -> Maybe TreeEntry + findFile prefix = find (T.isPrefixOf prefix . T.toLower . treeEntryPath) {- Collect commit history up to a head. @@ -298,17 +311,17 @@ Collect tree information for the given commit. Recurses on directories to list their contents. -} -getTree :: Git.CommitOid LgRepo -> ReaderT LgRepo IO [TreeFile] +getTree :: Git.CommitOid LgRepo -> ReaderT LgRepo IO [TreeEntry] getTree = getTree' "" 0 . Git.commitTree <=< Git.lookupCommit where - getTree' :: Git.TreeFilePath -> Int -> Git.TreeOid LgRepo -> ReaderT LgRepo IO [TreeFile] + getTree' :: Git.TreeFilePath -> Int -> Git.TreeOid LgRepo -> ReaderT LgRepo IO [TreeEntry] getTree' parent count toid = do one <- Git.lookupTree toid entries <- Git.listTreeEntries one let entries' = fmap (prependParent parent) entries contents <- mapM (\x -> getEntryContents x (count + 1)) entries' modes <- mapM (getEntryModes . snd) entries' - return $ zipWith3 TreeFile (fmap treePaths entries') contents modes + return $ zipWith3 TreeEntry (fmap treePaths entries') contents modes prependParent :: Git.TreeFilePath -> @@ -317,7 +330,7 @@ prependParent "" pathentry = pathentry prependParent parent (path, entry) = (mconcat [parent, "/", path], entry) - getEntryContents :: (Git.TreeFilePath, Git.TreeEntry LgRepo) -> Int -> ReaderT LgRepo IO TreeFileContents + getEntryContents :: (Git.TreeFilePath, Git.TreeEntry LgRepo) -> Int -> ReaderT LgRepo IO TreeEntryContents getEntryContents (_, Git.BlobEntry oid _) _ = getBlobContents oid getEntryContents (path, Git.TreeEntry oid) count = FolderContents <$> getTree' path count oid getEntryContents (_, Git.CommitEntry oid) _ = return . FileContents . B.fromString . show . untag $ oid @@ -372,10 +385,20 @@ (return []) ((x :) <$> go xs) -getUpdatedFiles :: [TreeFile] -> [Commit] -> [TreeFile] +flattenFiles :: TreeEntry -> [TreeEntry] +flattenFiles treeentry = case treeEntryContents treeentry of + FolderContents files -> concatMap flattenFiles files + _ -> [treeentry] + +flattenTrees :: TreeEntry -> [TreeEntry] +flattenTrees treeentry = case treeEntryContents treeentry of + FolderContents files -> treeentry : concatMap flattenTrees files + _ -> [] + +getUpdatedFiles :: [TreeEntry] -> [Commit] -> [TreeEntry] getUpdatedFiles [] _ = [] getUpdatedFiles _ [] = [] -getUpdatedFiles files commits = filter ((`elem` updated) . treeFilePath) files +getUpdatedFiles files commits = filter ((`elem` updated) . treeEntryPath) files where updated :: [T.Text] updated = fmap (bsToText . diffNewFile) . concatMap commitDiffs $ commits @@ -425,8 +448,11 @@ commitHref :: Commit -> FilePath commitHref = (++ ".html") . commitHash -fileHref :: TreeFile -> FilePath -fileHref = T.unpack . treePathToHref +blobHref :: TreeEntry -> FilePath +blobHref = T.unpack . treePathToHref + +treeHref :: TreeEntry -> FilePath +treeHref = T.unpack . treePathToHref {- With a dictionary of preloaded values and a function to access additional data, create a
src/Env.hs Modified
@@ -68,7 +68,8 @@ { envConfig :: Config , envIndexTemplates :: [Template] , envCommitTemplate :: Maybe Template - , envFileTemplate :: Maybe Template + , envBlobTemplate :: Maybe Template + , envTreeTemplate :: Maybe Template , envRepoTemplates :: [Template] , envOutput :: Path Abs Dir , envRepos :: [Path Abs Dir] @@ -104,16 +105,17 @@ -- Load files from template directory indexT <- collectTemplates files - commitT <- findTemplate "commit.html" filesRepo - fileT <- findTemplate "file.html" filesRepo + commitT <- findTemplate "foreach.commit.html" filesRepo + blobT <- findTemplate "foreach.blob.html" filesRepo + treeT <- findTemplate "foreach.tree.html" filesRepo repoT <- collectTemplates - . filter (flip notElem ["commit.html", "file.html"] . toFilePath . filename) + . filter (flip notElem ["foreach.commit.html", "foreach.blob.html", "foreach.tree.html"] . toFilePath . filename) $ filesRepo -- Exit early if we didn't find any templates when - ( all null [indexT, repoT] && all isNothing [commitT, fileT] + ( all null [indexT, repoT] && all isNothing [commitT, blobT, treeT] ) $ die "No templates were found." @@ -123,7 +125,8 @@ { envConfig = config , envIndexTemplates = indexT , envCommitTemplate = commitT - , envFileTemplate = fileT + , envBlobTemplate = blobT + , envTreeTemplate = treeT , envRepoTemplates = repoT , envOutput = output , envRepos = repos
DOCUMENTATION.md Modified
@@ -67,7 +67,7 @@ otherwise the ginger docs can be very helpful to see what is supported. Templates are a folder containing a number of ginger template files. There are -4 "scopes", each making available a unique set of variables storing information +5 "scopes", each making available a unique set of variables storing information about the git repositories. Each template file has access to a single one of these scopes. The structure of the template folder determines the scopes of the files contained therein. @@ -82,8 +82,9 @@ repo/ inside_this_folder.html two_names_are_special.html - file.html - commit.html + foreach.blob.html + foreach.commit.html + foreach.tree.html The top-level folder, here `template`, is that which is specified in the config file. @@ -98,9 +99,14 @@ information pertaining to a single git repository. The template files contained within this folder are parsed and output once per git repository. -The exceptions to this are the two special template files with the names -"file.html" and "commit.html". These have access to the *file scope* and -*commit scope* respectively, and are parsed and output once per file or commit. +The exceptions to this are the three special template files with the names +"foreach.blob.html", "foreach.commit.html" and "foreach.tree.html" - named to +make clear that, unlike everything else in "repo/", these are parsed and +output once *per* blob, commit or tree found anywhere in the repository, at +any depth, rather than once per repository. These have access to the *blob +scope*, *commit scope* and *tree scope* respectively (a submodule reference +is currently treated as a blob whose content is its target commit hash, +rather than as its own scope). The resulting folder structure found in `output` will look like this (if `repos` only contains gitja): @@ -112,7 +118,7 @@ gitja/ inside_this_folder.html two_names_are_special.html - file/ + blob/ LICENSE.html Makefile.html ... @@ -120,6 +126,9 @@ 0a18f38bb5c398bd192a6268281fc6abefaedd63.html 0a7601059956d9c4d395f5d08e8cf48a515d080f.html ... + tree/ + src.html + ... ... ### Static files @@ -145,16 +154,20 @@ | | name | The repository name, taken from its folder name. | | | description | The repository's description (see below). | | | commits | A list of the repository's commits. | -| | tree | A list of the top-level folder's contents. | -| | tree\_recursive | A list of *all* of the repository's contents. | +| | tree | A list of the repository root tree's entries. | +| | entries | A flat list of *every* entry (blob or tree) at any depth in the repository, like `git ls-tree -r -t`. | +| | blobs | A flat list of *every* blob (file) in the repository. | +| | trees | A flat list of *every* tree (directory) at any depth in the repository. | | | tags | A list of the refs corresponding to tags. | | | branches | A list of the refs corresponding to branches. | | | readme | The repository's readme file, if it has one. | | | license | The repository's license file, if it has one. | -| File | | *In addition to the variables from the Repo scope...* | -| | file | A single file. | +| Blob | | *In addition to the variables from the Repo scope...* | +| | blob | A single blob (file). | | Commit | | *In addition to the variables from the Repo scope...* | | | commit | A single commit. | +| Tree | | *In addition to the variables from the Repo scope...* | +| | tree | A single tree (directory) - shadows the repo-scope `tree`, since a tree page is itself scoped to one tree. | As in [Jinja](https://jinja.palletsprojects.com), a list can be accessed with indexing, and attributes can be accessed using a dot notation. For example, a @@ -170,17 +183,17 @@ | | description | The repository's description (see below). | | | head | The current git commit. | | | updated | The time when the current commit was committed. | -| file | path | The path the file relative to the repository root. | -| | name | The name of the file. | -| | href | The name of the HTML file for this file. | -| | contents | The file's contents. | +| blob/tree | path | The path relative to the repository root. | +| | name | The name of the blob or tree. | +| | href | The name of the HTML file for this blob or tree. | +| | contents | The blob's contents (n/a for a tree). | | | mode | Directory, Plain, Executable, Symlink or Submodule. | | | mode\_octal | Mode in octal form e.g. "00644" for plain files. | | | mode\_symbolic | Mode in symbolic form e.g. ""-rw-r--r--" for plain files.| | | is\_directory | A boolean, useful for ginger conditionals. | | | is\_binary | A boolean, tells you if the contents can be rendered. | -| | tree | A list of a directory's direct contents. | -| | tree\_recursive | A list of *all* of a directory's contents. | +| | tree | This tree's direct contents (n/a for a blob). | +| | entries | A flat list of *every* entry (blob or tree) at any depth under this tree (n/a for a blob). | | ref | name | The tag or branch name. | | | commit | The commit pointed to by the tag or branch. | | commit | id | The SHA of the given commit. |