From 7a470a36b217fa4e0a5a4707a27f49a9d2d50791 Mon Sep 17 00:00:00 2001
From: tri <tri@thac.loan>
Date: Sat, 4 Oct 2025 14:49:46 +0700
Subject: [PATCH] write blob pages to /objects/<hash>/index.html

So that it matches commit pages url scheme too.
---
 src/main.zig | 259 +++++++++++++++++++++++++--------------------------
 1 file changed, 127 insertions(+), 132 deletions(-)

diff --git a/src/main.zig b/src/main.zig
index c67d5ac..e05e18a 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -200,7 +200,7 @@ pub fn writeHomePage(arena: mem.Allocator, dir: fs.Dir, repos: []RepoSummary) !v
                 else
                     try html.escapeAlloc(arena, repo.description), // 2
                 repo.commit_count, // 3
-                repo.last_commit_msg, // 4
+                try html.escapeAlloc(arena, repo.last_commit_msg), // 4
                 repo.last_commit_time, // 5
             },
         );
@@ -303,22 +303,20 @@ pub fn writeRepoPage(
 
     var treeWalker = try git.walkTree(arena, args.in_repo_dir);
     while (try treeWalker.next()) |src_file| {
-        const file_name = try std.fmt.allocPrint(arena, "{s}.html", .{src_file.hash});
-
         try writer.print(
             \\<tr>
-            \\  <td><a href="{2s}/{3s}">{0s}</a></td>
-            \\  <td style="opacity:0.4">{1s}</td>
+            \\  <td><a href="{0s}/{1s}/">{2s}</a></td>
+            \\  <td style="opacity:0.4">{3s}</td>
             \\</tr>
             \\
         , .{
+            constants.web_objects_path,
+            src_file.hash,
             try html.escapeAlloc(arena, src_file.path),
             try utils.humanReadableSize(arena, src_file.size),
-            constants.web_objects_path,
-            file_name,
         });
 
-        try writeFilePage(args, objects_dir, src_file, file_name);
+        try writeBlobPage(args, objects_dir, src_file);
     }
 
     try writer.writeAll(
@@ -616,143 +614,140 @@ pub fn writeCommitPage(
     try writer.flush();
 }
 
-pub fn writeFilePage(
+pub fn writeBlobPage(
     args: *const RepoArgs,
-    out_dir: fs.Dir,
+    objects_dir: fs.Dir,
     src_file: git.File,
-    file_name: []const u8,
 ) !void {
     const arena = args.arena;
 
-    _ = out_dir.statFile(file_name) catch |err| {
-        switch (err) {
-            error.FileNotFound => {
-                const object_type = try git.objectType(arena, args.in_repo_dir, src_file.hash);
-                if (object_type != .blob) {
-                    return;
-                }
+    var out_dir = try objects_dir.makeOpenPath(src_file.hash, .{});
+    defer out_dir.close();
 
-                println("    writing {s}: {s}/{s}", .{
-                    args.repo_name,
-                    constants.web_objects_path,
-                    file_name,
-                });
-                var file = try out_dir.createFile(file_name, .{});
-                errdefer out_dir.deleteFile(file_name) catch {};
-                defer file.close();
-
-                var buf: [4096]u8 = undefined;
-                var file_writer = file.writer(&buf);
-                const writer = &file_writer.interface;
-
-                const file_label = try std.fmt.allocPrint(
-                    arena,
-                    "{s}: {s}",
-                    .{ src_file.hash[0..10], src_file.path },
-                );
-
-                try templates.base_start(arena, writer, .{
-                    .title = try std.fmt.allocPrint(
-                        arena,
-                        "{s} - {s}",
-                        .{ file_label, args.repo_name },
-                    ),
-                    .breadcrumbs = &.{ .{
-                        .href = "/",
-                        .text = "repos",
-                    }, .{
-                        .href = "../",
-                        .text = args.repo_name,
-                    }, .{
-                        .text = file_label,
-                    } },
-                });
+    const index_html = "index.html";
+
+    const should_skip = blk: {
+        _ = out_dir.statFile(index_html) catch |err| {
+            switch (err) {
+                error.FileNotFound => break :blk false,
+                else => return err,
+            }
+        };
+        break :blk true;
+    };
+    if (should_skip) return;
+
+    println("    writing {s} blob: {s}", .{ args.repo_name, src_file.hash });
+
+    const object_type = try git.objectType(arena, args.in_repo_dir, src_file.hash);
+    if (object_type != .blob) {
+        return;
+    }
+
+    var file = try out_dir.createFile(index_html, .{});
+    errdefer out_dir.deleteFile(index_html) catch {};
+    defer file.close();
+
+    var buf: [4096]u8 = undefined;
+    var file_writer = file.writer(&buf);
+    const writer = &file_writer.interface;
+
+    const file_label = try std.fmt.allocPrint(
+        arena,
+        "{s}: {s}",
+        .{ src_file.hash[0..10], src_file.path },
+    );
+
+    try templates.base_start(arena, writer, .{
+        .title = try std.fmt.allocPrint(
+            arena,
+            "{s} - {s}",
+            .{ file_label, args.repo_name },
+        ),
+        .breadcrumbs = &.{ .{
+            .href = "/",
+            .text = "repos",
+        }, .{
+            .href = "../../",
+            .text = args.repo_name,
+        }, .{
+            .text = file_label,
+        } },
+    });
+
+    try writer.print(
+        \\<p>size: {0s}</p>
+    , .{
+        try utils.humanReadableSize(arena, src_file.size),
+    });
+
+    try writer.writeAll(
+        \\<pre class="blob-content">
+        \\
+    );
 
+    // This wastefully reads the whole file into memory.
+    // TODO: Try to implement a git.catFile() that only reads the
+    // first n bytes.
+    const file_content = try git.catFile(
+        arena,
+        args.in_repo_dir,
+        src_file.hash,
+    );
+    const blob_type = utils.blobType(src_file.path);
+
+    if (file_content.len <= 1024 * 1024 * 16 and
+        (blob_type == .image or blob_type == .video))
+    {
+        const src_file_name =
+            if (mem.lastIndexOfScalar(u8, src_file.path, '/')) |slash_idx|
+                src_file.path[slash_idx + 1 ..]
+            else
+                src_file.path;
+
+        // write the blob's actual content next to its html page
+        var blob_file = try out_dir.createFile(src_file_name, .{});
+        errdefer out_dir.deleteFile(src_file_name) catch {};
+        defer blob_file.close();
+        try blob_file.writeAll(file_content);
+
+        // write <img> markup in blob page
+        const escaped_file_name = try html.escapeAlloc(arena, src_file_name);
+
+        switch (blob_type) {
+            .image => {
                 try writer.print(
-                    \\<p>size: {0s}</p>
+                    \\<img src="{0s}" alt="{0s}">
+                    \\
                 , .{
-                    try utils.humanReadableSize(arena, src_file.size),
+                    escaped_file_name,
                 });
-
-                try writer.writeAll(
-                    \\<pre class="blob-content">
-                    \\
-                );
-
-                // This wastefully reads the whole file into memory.
-                // TODO: Try to implement a git.catFile() that only reads the
-                // first n bytes.
-                const file_content = try git.catFile(
-                    arena,
-                    args.in_repo_dir,
-                    src_file.hash,
-                );
-                const blob_type = utils.blobType(src_file.path);
-
-                if (file_content.len <= 1024 * 1024 * 16 and
-                    (blob_type == .image or blob_type == .video))
-                {
-                    const src_file_name =
-                        if (mem.lastIndexOfScalar(u8, src_file.path, '/')) |slash_idx|
-                            src_file.path[slash_idx + 1 ..]
-                        else
-                            src_file.path;
-
-                    const blob_file_name = try std.fmt.allocPrint(
-                        arena,
-                        "{s}.{s}",
-                        .{ src_file.hash, src_file_name },
-                    );
-
-                    // write the blob's actual content next to its html page
-                    var blob_file = try out_dir.createFile(blob_file_name, .{});
-                    errdefer out_dir.deleteFile(blob_file_name) catch {};
-                    defer blob_file.close();
-                    try blob_file.writeAll(file_content);
-
-                    // write <img> markup in blob page
-                    const escaped_file_name = try html.escapeAlloc(arena, src_file_name);
-
-                    switch (blob_type) {
-                        .image => {
-                            try writer.print(
-                                \\<img src="{0s}.{1s}" alt="{1s}">
-                                \\
-                            , .{
-                                src_file.hash,
-                                escaped_file_name,
-                            });
-                        },
-                        .video => {
-                            try writer.print(
-                                \\<video src="{0s}.{1s}" controls></video>
-                                \\
-                            , .{
-                                src_file.hash,
-                                escaped_file_name,
-                            });
-                        },
-                        else => return error.UnexpectedBlobType,
-                    }
-                } else if (git.isBinary(file_content)) {
-                    try writer.writeAll(
-                        \\<span style="opacity: 0.5">(binary data)</span>
-                        \\
-                    );
-                } else {
-                    try html.escape(writer, file_content);
-                }
-
-                try writer.writeAll(
-                    \\</pre>
+            },
+            .video => {
+                try writer.print(
+                    \\<video src="{0s}" controls></video>
                     \\
-                );
-
-                try writer.flush();
+                , .{
+                    escaped_file_name,
+                });
             },
-            else => return err,
+            else => return error.UnexpectedBlobType,
         }
-    };
+    } else if (git.isBinary(file_content)) {
+        try writer.writeAll(
+            \\<span style="opacity: 0.5">(binary data)</span>
+            \\
+        );
+    } else {
+        try html.escape(writer, file_content);
+    }
+
+    try writer.writeAll(
+        \\</pre>
+        \\
+    );
+
+    try writer.flush();
 }
 
 test "all" {
-- 
2.47.3

