the repository which powers this website
-rw-r--r--cgit.mk1
-rw-r--r--cmd.c3
-rw-r--r--themed/base.html11
-rw-r--r--themed/themed.in.css39
-rw-r--r--themed/tree.html197
-rw-r--r--ui-tree.c12
-rw-r--r--ui-tree.h5
7 files changed, 262 insertions, 6 deletions
diff --git a/cgit.mk b/cgit.mk
index e0e29a42..746ddae4 100644
--- a/cgit.mk
+++ b/cgit.mk
@@ -104,6 +104,7 @@ CGIT_THEMED_INPUTS += $(CGIT_PREFIX)themed/commit.html
CGIT_THEMED_INPUTS += $(CGIT_PREFIX)themed/log.html
CGIT_THEMED_INPUTS += $(CGIT_PREFIX)themed/refs.html
CGIT_THEMED_INPUTS += $(CGIT_PREFIX)themed/summary.html
+CGIT_THEMED_INPUTS += $(CGIT_PREFIX)themed/tree.html
CGIT_OBJS := $(addprefix $(CGIT_PREFIX),$(CGIT_OBJ_NAMES))
diff --git a/cmd.c b/cmd.c
index 620a3899..086c812a 100644
--- a/cmd.c
+++ b/cmd.c
@@ -163,7 +163,8 @@ static void tag_fn(void)
static void tree_fn(void)
{
- cgit_print_tree(ctx.qry.oid, ctx.qry.path);
+ //cgit_print_tree(ctx.qry.oid, ctx.qry.path);
+ cgit_print_tree();
}
#define def_cmd(name, want_repo, want_vpath, is_clone) \
diff --git a/themed/base.html b/themed/base.html
index 53865043..fdfa700e 100644
--- a/themed/base.html
+++ b/themed/base.html
@@ -26,9 +26,7 @@
{{ ctx.repo->desc }}
</div>
{% endblock %}
-{% block repo_summary_bar %}
- <nav class="flex text-sm mb-4">
- {# Repo navigation panel #}
+{% block repo_summary_bar_current_branch %}
<a href="{! cgit_shared_repolink_url("refs", NULL, NULL); !}" class="flex gap-x-1.5 py-1.5 px-3 bg-gray-50 border border-gray-300 rounded-md hover:bg-gray-100">
{# Heroicons micro list-bullet #}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4 self-center text-gray-500"><path d="M3 4.75a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM6.25 3a.75.75 0 0 0 0 1.5h7a.75.75 0 0 0 0-1.5h-7ZM6.25 7.25a.75.75 0 0 0 0 1.5h7a.75.75 0 0 0 0-1.5h-7ZM6.25 11.5a.75.75 0 0 0 0 1.5h7a.75.75 0 0 0 0-1.5h-7ZM4 12.25a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM3 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" /></svg>
@@ -36,6 +34,11 @@
{# Heroicons micro chevron-down #}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4 self-center text-gray-500"><path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /></svg>
</a>
+{% endblock %}
+{% block repo_summary_bar %}
+ <nav class="flex text-sm mb-4">
+ {# Repo navigation panel #}
+ {! repo_summary_bar_current_branch(); !}
<a href="{! cgit_shared_repolink_url("log", NULL, NULL); !}" class="flex gap-x-1 py-1.5 px-3 ml-3 rounded-md hover:bg-gray-100">
{# Heroicons micro clock #}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4 self-center text-gray-500"><path fill-rule="evenodd" d="M1 8a7 7 0 1 1 14 0A7 7 0 0 1 1 8Zm7.75-4.25a.75.75 0 0 0-1.5 0V8c0 .414.336.75.75.75h3.25a.75.75 0 0 0 0-1.5h-2.5v-3.5Z" clip-rule="evenodd" /></svg>
@@ -55,6 +58,7 @@
<span class="font-semibold">{{ num_tags|%d }}</span><span class="font-semibold text-gray-500">Tag{% if num_tags != 1 %}s{% endif %}</span>
</a>
<div class="flex-1"></div>
+ <!--
<div class="flex outline-1 outline-gray-300 rounded-lg has-[input:focus-within]:outline-2 has-[input:focus-within]:outline-blue-600">
{# Search box #}
<select class="py-1.5 px-2 text-sm text-gray-500 focus:outline-none">
@@ -69,6 +73,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="text-gray-700 size-4"><path fill-rule="evenodd" d="M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z" clip-rule="evenodd" /></svg>
</button>
</div>
+ -->
<div class="flex relative">
{# Code box and panel #}
<button class="flex gap-x-1.5 py-1.5 px-3 ml-3 bg-blue-500 text-white rounded-md cursor-pointer hover:bg-blue-600 peer">
diff --git a/themed/themed.in.css b/themed/themed.in.css
index 336859db..f82fb5b4 100644
--- a/themed/themed.in.css
+++ b/themed/themed.in.css
@@ -20,6 +20,45 @@
}
}
+.rendered-blob {
+ /* From cgit.css */
+
+ table.blob {
+ margin-top: 0.5em;
+ border-top: solid 1px black;
+ }
+
+ table.blob td.hashes,
+ table.blob td.lines {
+ margin: 0; padding: 0 0 0 0.5em;
+ vertical-align: top;
+ color: black;
+ }
+
+ table.blob td.linenumbers {
+ margin: 0; padding: 0 0.5em 0 0.5em;
+ vertical-align: top;
+ text-align: right;
+ border-right: 1px solid gray;
+ }
+
+ table.blob pre {
+ padding: 0; margin: 0;
+ }
+
+ table.blob td.linenumbers a,
+ table.ssdiff td.lineno a {
+ color: gray;
+ text-align: right;
+ text-decoration: none;
+ }
+
+ table.blob td.linenumbers a:hover,
+ table.ssdiff td.lineno a:hover {
+ color: black;
+ }
+}
+
.diff-panel {
a {
@apply text-blue-500 hover:text-blue-600 hover:underline;
diff --git a/themed/tree.html b/themed/tree.html
new file mode 100644
index 00000000..bd42a0f8
--- /dev/null
+++ b/themed/tree.html
@@ -0,0 +1,197 @@
+{! #include "../ui-tree.h" !}
+
+{% block repo_navigation_breadcrumbs %}
+ <div class="px-3">
+ {# Breadcrumbs #}
+ {# TODO: Make breadcrumbs hyperlinks #}
+ <a href="{! cgit_shared_repolink_url(NULL, NULL, NULL); !}" class="text-blue-500 hover:text-blue-600 hover:underline">{{ ctx.repo->name }}</a> / {{ ctx.qry.path }}
+ </div>
+{% endblock %}
+
+{% block tree_content_directory_header %}
+ {# Header for directory listing #}
+ <nav class="flex text-sm mb-4 items-baseline">
+ {# Repo navigation panel #}
+ {! repo_summary_bar_current_branch(); !}
+ {! repo_navigation_breadcrumbs(); !}
+ </nav>
+ <div class="grid grid-cols-[auto_1fr_auto_auto] border border-gray-300 rounded-md mb-4">
+{% endblock %}
+{% block tree_content_directory_item(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, int child_idx) %}
+ {# Directory listing entry #}
+ {!
+ unsigned long size = 0;
+ if (!S_ISGITLINK(mode)) {
+ oid_object_info(the_repository, oid, &size);
+ }
+
+ struct strbuf fullpath = STRBUF_INIT;
+ strbuf_addf(&fullpath, "%s%s", base->buf, pathname);
+ !}
+ <div class="pl-3 pr-1 py-2{% if child_idx > 0 %} border-t border-gray-300{% endif %}">
+ {% if S_ISDIR(mode) %}
+ {# Heroicons solid folder #}
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-5 text-blue-400 mt-[0.1rem]"><path d="M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z" /></svg>
+ {% else %}
+ {# Heroicons outline document #}
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5 text-gray-500 mt-[0.1rem]"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" /></svg>
+ {% endif %}
+ </div>
+ <div class="pr-3 py-2{% if child_idx > 0 %} border-t border-gray-300{% endif %}"><a href="{! cgit_shared_repolink_url("tree", ctx.qry.head, fullpath.buf); !}" class="hover:text-blue-600 hover:underline">{{ pathname }}</a></div>
+ <div class="pr-3 py-2{% if child_idx > 0 %} border-t border-gray-300{% endif %} text-gray-500 font-mono">{! cgit_print_filemode(mode); !}</div>
+ <div class="pr-3 py-2{% if child_idx > 0 %} border-t border-gray-300{% endif %} text-gray-500 text-end">{% if !S_ISDIR(mode) %}{{ size|%ld }}{% endif %}</div>
+ {! strbuf_release(&fullpath); !}
+{% endblock %}
+{% block tree_content_directory_footer %}
+ {# Footer for directory listing #}
+ </div>
+{% endblock %}
+{% block tree_content_file(const struct object_id *oid, const char *path, const char *basename, const char *rev) %}
+ <nav class="flex text-sm mb-4 items-baseline">
+ {# Repo navigation panel #}
+ {! repo_summary_bar_current_branch(); !}
+ {! repo_navigation_breadcrumbs(); !}
+ <div class="flex-1"></div>
+ <div class="flex">
+ {# File buttons #}
+ <a href="{! cgit_shared_repolink_url("plain", ctx.qry.head, ctx.qry.path); !}" class="text-sm text-gray-500 py-1.5 px-3 bg-gray-50 border border-gray-300 rounded-md hover:bg-gray-100">Raw</a>
+ </div>
+ </nav>
+ {!
+ unsigned long size;
+ enum object_type type = oid_object_info(the_repository, oid, &size);
+ if (type == OBJ_BAD) {
+ die("Bad object name");
+ }
+ char *buf = repo_read_object_file(the_repository, oid, &type, &size);
+ if (!buf) {
+ die("Error reading object");
+ }
+ bool is_binary = buffer_is_binary(buf, size);
+ !}
+ {% if ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size %}
+ <div class="text-red-600">
+ blob size ({{ size / 1024|%ld }}KB) exceeds display size limit ({{ ctx.cfg.max_blob_size|%d }}KB).
+ </div>
+ {% else %}
+ <div class="rendered-blob overflow-x-auto">
+ {!
+ if (is_binary) {
+ cgit_tree_print_binary_buffer(buf, size);
+ } else {
+ cgit_tree_print_text_buffer(basename, buf, size);
+ }
+ !}
+ </div>
+ {% endif %}
+{% endblock %}
+
+{!
+ struct walk_tree_context {
+ char *curr_rev;
+ char *match_path;
+ int state;
+ int directory_child_idx;
+ };
+
+ static int walk_tree(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *cbdata)
+ {
+ struct walk_tree_context *walk_tree_ctx = cbdata;
+
+ if (walk_tree_ctx->state == 0) {
+ // State 0 = Walking recursively to find the target path
+
+ struct strbuf buffer = STRBUF_INIT;
+ strbuf_addbuf(&buffer, base);
+ strbuf_addstr(&buffer, pathname);
+ if (strcmp(walk_tree_ctx->match_path, buffer.buf)) {
+ // Not the target path, so continue to walk the tree
+ return READ_TREE_RECURSIVE;
+ }
+
+ // This is the target path
+ if (S_ISDIR(mode)) {
+ // Target path is a directory - set state to 1 and do one final walk to get contents
+ walk_tree_ctx->state = 1;
+ strbuf_release(&buffer);
+ tree_content_directory_header();
+ return READ_TREE_RECURSIVE;
+ } else {
+ // Target path is a file - set state to 2, display file and exit
+ walk_tree_ctx->state = 2;
+ tree_content_file(oid, buffer.buf, pathname, walk_tree_ctx->curr_rev);
+ strbuf_release(&buffer);
+ return 0;
+ }
+ }
+
+ if (walk_tree_ctx->state == 1) {
+ // State 1 = Target path is a directory, one final walk to get contents
+ // Either a child of the directory of interest, or a child of a parent directory - so must check the path
+
+ struct strbuf buffer = STRBUF_INIT;
+ strbuf_addstr(&buffer, walk_tree_ctx->match_path);
+ strbuf_addstr(&buffer, "/");
+
+ if (!strcmp(buffer.buf, base->buf)) {
+ tree_content_directory_item(oid, base, pathname, mode, walk_tree_ctx->directory_child_idx);
+ walk_tree_ctx->directory_child_idx++;
+ }
+ return 0;
+ }
+
+ return 0; // Should be unreachable
+ }
+!}
+
+{% page cgit_print_tree %}
+{!
+ // Redirect to summary page if no subdirectory
+ if (!ctx.qry.path) { return cgit_print_summary(); }
+!}
+{! page_start(); !}
+{! repo_header(); !}
+ <main class="max-w-[1280px] mx-auto py-4">{# Main content #}
+{! repo_description_panel(); !}
+ {!
+ char *hex = ctx.qry.oid;
+ if (!hex) { hex = ctx.qry.head; }
+
+ struct object_id oid;
+ if (repo_get_oid(the_repository, hex, &oid)) {
+ die("Bad object id");
+ }
+ struct commit *commit = lookup_commit_reference(the_repository, &oid);
+ if (!commit) {
+ die("Bad commit reference");
+ }
+
+ // Prepare to walk the tree recursively to find the path
+ struct pathspec paths = {
+ .nr = 0
+ };
+ struct walk_tree_context walk_tree_ctx = {
+ .curr_rev = xstrdup(hex),
+ .match_path = ctx.qry.path,
+ .state = 0,
+ .directory_child_idx = 0
+ };
+
+ // State 0 = Walking recursively to find the target path
+ // State 1 = Target path is a directory, one final walk to get contents
+ // State 2 = Target path is a file
+
+ read_tree(the_repository, repo_get_commit_tree(the_repository, commit), &paths, walk_tree, &walk_tree_ctx);
+ free(walk_tree_ctx.curr_rev);
+ !}
+ {% if walk_tree_ctx.state == 0 %}
+ <nav class="flex text-sm mb-4 items-baseline">
+ {# Repo navigation panel #}
+ {! repo_summary_bar_current_branch(); !}
+ {! repo_navigation_breadcrumbs(); !}
+ </nav>
+ <div class="text-red-600">File not found</div>
+ {% endif %}
+ </main>
+{! page_end(); !}
+{% endpage %}
diff --git a/ui-tree.c b/ui-tree.c
index 3d8a2eb5..dbce43e2 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -61,6 +61,11 @@ static void print_text_buffer(const char *name, char *buf, unsigned long size)
html("</code></pre></td></tr></table>\n");
}
+void cgit_tree_print_text_buffer(const char *name, char *buf, unsigned long size)
+{
+ print_text_buffer(name, buf, size);
+}
+
#define ROWLEN 32
static void print_binary_buffer(char *buf, unsigned long size)
@@ -86,6 +91,11 @@ static void print_binary_buffer(char *buf, unsigned long size)
html("</table>\n");
}
+void cgit_tree_print_binary_buffer(char *buf, unsigned long size)
+{
+ print_binary_buffer(buf, size);
+}
+
static void print_object(const struct object_id *oid, const char *path, const char *basename, const char *rev)
{
enum object_type type;
@@ -354,7 +364,7 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base,
* rev: the commit pointing at the root tree object
* path: path to tree or blob
*/
-void cgit_print_tree(const char *rev, char *path)
+void _orig_cgit_print_tree(const char *rev, char *path)
{
struct object_id oid;
struct commit *commit;
diff --git a/ui-tree.h b/ui-tree.h
index bbd34e35..943d6313 100644
--- a/ui-tree.h
+++ b/ui-tree.h
@@ -1,6 +1,9 @@
#ifndef UI_TREE_H
#define UI_TREE_H
-extern void cgit_print_tree(const char *rev, char *path);
+extern void cgit_tree_print_binary_buffer(char *buf, unsigned long size);
+extern void cgit_tree_print_text_buffer(const char *name, char *buf, unsigned long size);
+
+extern void cgit_print_tree();
#endif /* UI_TREE_H */