From cae29bfa94f697a9e8a08c4277b031065b080ea8 Mon Sep 17 00:00:00 2001
From: tri <tri@thac.loan>
Date: Tue, 30 Sep 2025 18:01:14 +0700
Subject: [PATCH] read repo description from .git/description

---
 Makefile             | 15 ++++++---------
 src/assets/style.css |  4 ++--
 src/git.zig          | 15 +++++++++++++++
 src/main.zig         | 38 ++++++++++++++++++++++++++++----------
 4 files changed, 51 insertions(+), 21 deletions(-)

diff --git a/Makefile b/Makefile
index 107a7ea..714a392 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,17 @@
+ZIGARGS = -Doptimize=ReleaseSafe -Dcpu=baseline
+
+build:
+	zig build $(ZIGARGS)
+
 watch:
 	zig build run --watch -- demo http://localhost:8000
 
-watch2:
-	zig build run --watch -- demo2 http://localhost:8000
-
 # TODO: write a simple zig server instead
 serve:
 	python -m http.server -b localhost -d demo 8000
 
-# TODO: write a simple zig server instead
-serve2:
-	python -m http.server -b localhost -d demo2 8000
-
 test:
 	zig build test --watch
 
-deploy:
-	zig build -Doptimize=ReleaseSafe -Dcpu=baseline
+deploy: build
 	scp zig-out/bin/khoe root@khoe:/usr/local/bin/
diff --git a/src/assets/style.css b/src/assets/style.css
index dcea7d1..9521153 100644
--- a/src/assets/style.css
+++ b/src/assets/style.css
@@ -51,10 +51,10 @@ tbody tr:hover {
 }
 td,
 th {
-  padding: 0.1rem 10px;
+  padding: 0.1rem 1rem;
 }
 table {
-  margin-left: -10px;
+  margin-left: -1rem;
   font-variant-numeric: tabular-nums;
 }
 
diff --git a/src/git.zig b/src/git.zig
index e2bc7e7..a91299e 100644
--- a/src/git.zig
+++ b/src/git.zig
@@ -91,6 +91,21 @@ pub fn getLatestCommit(arena: mem.Allocator, dir: fs.Dir) !?Commit {
     };
 }
 
+pub fn getDescription(arena: mem.Allocator, git_dir: fs.Dir) ![]const u8 {
+    const description = git_dir.readFileAlloc(
+        "description",
+        arena,
+        .limited(4096),
+    ) catch |err| {
+        switch (err) {
+            error.FileNotFound => return "",
+            else => return err,
+        }
+    };
+    if (mem.startsWith(u8, description, "Unnamed repository;")) return "";
+    return description;
+}
+
 /// If found, return the exact readme filename.
 pub fn findReadme(arena: mem.Allocator, dir: fs.Dir) !?[]const u8 {
     var proc = try std.process.Child.run(.{
diff --git a/src/main.zig b/src/main.zig
index 228bb7e..8ede4ba 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -12,7 +12,7 @@ const assets_path = web_path ++ "/_khoe-hang";
 
 const Mode = union(enum) {
     all: void,
-    single_repo: []const u8,
+    single_repo: [*:0]const u8,
 };
 
 pub fn main() !u8 {
@@ -92,8 +92,14 @@ pub fn main() !u8 {
         const commits = try git.getCommits(repo_arena, repo_dir);
         println("Found repo {s}: {d} commits", .{ entry.name, commits.len });
 
+        var git_dir = try repo_dir.openDir(git_dir_path, .{});
+        defer git_dir.close();
+
+        const repo_description = try git.getDescription(arena, git_dir);
+
         try repo_summaries.append(arena, .{
             .name = try arena.dupe(u8, entry.name),
+            .description = repo_description,
             .commit_count = commits.len,
             .last_commit_msg = if (commits.len == 0) "" else try arena.dupe(u8, commits[0].subject),
             .last_commit_time = if (commits.len == 0) "" else try arena.dupe(u8, commits[0].time),
@@ -103,6 +109,7 @@ pub fn main() !u8 {
             .arena = repo_arena,
             .site_url = site_url,
             .repo_name = entry.name,
+            .description = repo_description,
             .target_dir = target_dir,
             .in_repo_dir = repo_dir,
             .commits = commits,
@@ -114,7 +121,7 @@ pub fn main() !u8 {
 
     std.mem.sortUnstable(RepoSummary, repo_summaries.items, {}, RepoSummary.lessThan);
 
-    try writeHomePage(target_dir, repo_summaries.items);
+    try writeHomePage(arena, target_dir, repo_summaries.items);
 
     var assets_dir = try target_dir.makeOpenPath(assets_path, .{});
     inline for (.{ "style.css", "script.js" }) |asset_name| {
@@ -129,6 +136,7 @@ pub fn main() !u8 {
 
 const RepoSummary = struct {
     name: []const u8,
+    description: []const u8 = "",
     commit_count: usize,
     last_commit_time: []const u8,
     last_commit_msg: []const u8,
@@ -143,7 +151,7 @@ const RepoSummary = struct {
 
 // TODO: decide on some sort of templating system
 // FIXME: no html escape is being done
-pub fn writeHomePage(dir: fs.Dir, repos: []RepoSummary) !void {
+pub fn writeHomePage(arena: mem.Allocator, dir: fs.Dir, repos: []RepoSummary) !void {
     var file = try dir.createFile("index.html", .{});
     defer file.close();
 
@@ -171,6 +179,7 @@ pub fn writeHomePage(dir: fs.Dir, repos: []RepoSummary) !void {
         \\        <thead>
         \\          <tr>
         \\            <th>name</th>
+        \\            <th>description</th>
         \\            <th>commits</th>
         \\            <th>last commit</th>
         \\            <th>last modified</th>
@@ -184,18 +193,23 @@ pub fn writeHomePage(dir: fs.Dir, repos: []RepoSummary) !void {
         try writer.interface.print(
             \\<tr>
             \\  <td><a href="/{0s}/{1s}/">{1s}</a></td>
-            \\  <td>{2d}</td>
-            \\  <td>{3s}</td>
-            \\  <td><time class="relative" datetime="{4s}" title="{4s}">{4s}</time></td>
+            \\  <td>{2s}</td>
+            \\  <td>{3d}</td>
+            \\  <td>{4s}</td>
+            \\  <td><time class="relative" datetime="{5s}" title="{5s}">{5s}</time></td>
             \\</tr>
             \\
         ,
             .{
                 web_path, // 0
-                repo.name, // 1
-                repo.commit_count, // 2
-                repo.last_commit_msg, // 3
-                repo.last_commit_time, // 4
+                try html.escapeAlloc(arena, repo.name), // 1
+                if (repo.description.len == 0)
+                    "-"
+                else
+                    try html.escapeAlloc(arena, repo.description), // 2
+                repo.commit_count, // 3
+                repo.last_commit_msg, // 4
+                repo.last_commit_time, // 5
             },
         );
     }
@@ -215,6 +229,7 @@ const RepoArgs = struct {
     arena: std.mem.Allocator,
     site_url: [*:0]const u8,
     repo_name: []const u8,
+    description: []const u8,
     target_dir: fs.Dir,
     in_repo_dir: fs.Dir,
     commits: []git.Commit,
@@ -225,6 +240,7 @@ pub fn writeRepoPage(args: RepoArgs) !void {
     const arena = args.arena;
     const site_url = args.site_url;
     const repo_name = args.repo_name;
+    const description = args.description;
     const target_dir = args.target_dir;
     const in_repo_dir = args.in_repo_dir;
     const commits = args.commits;
@@ -261,6 +277,7 @@ pub fn writeRepoPage(args: RepoArgs) !void {
         \\        /<a href="/">repos</a>/<h1>{0s}</h1>/
         \\      </div>
         \\    </header>
+        \\    <p>{4s}</p>
         \\
         \\    <p>
         \\      clone: <code>git clone {2s}/{3s}</code><br>
@@ -275,6 +292,7 @@ pub fn writeRepoPage(args: RepoArgs) !void {
             repo_name
         else
             try std.fmt.bufPrint(&buf, "{s}/{s}", .{ repo_name, git_dir_path }),
+        try html.escapeAlloc(arena, description),
     });
 
     // If there's a readme file, include it in repo index:
-- 
2.47.3

