From eefbbb39c047419e11797ce415b8208c5bc67d69 Mon Sep 17 00:00:00 2001
From: tri <tri@thac.loan>
Date: Mon, 29 Sep 2025 08:36:59 +0700
Subject: [PATCH] more robust git dir detection logic

---
 Makefile     |  7 +++++++
 src/git.zig  | 21 +++++++-------------
 src/main.zig | 54 +++++++++++++++++++++++++++++++++++++---------------
 3 files changed, 53 insertions(+), 29 deletions(-)

diff --git a/Makefile b/Makefile
index 2f8c180..d2bc10e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,16 @@
 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
diff --git a/src/git.zig b/src/git.zig
index f9fc639..ae16e32 100644
--- a/src/git.zig
+++ b/src/git.zig
@@ -2,20 +2,13 @@ const std = @import("std");
 const mem = std.mem;
 const fs = std.fs;
 
-pub fn isGitDir(name: []const u8, dir: fs.Dir) !bool {
-    if (mem.endsWith(u8, name, ".git")) {
-        return true;
-    }
-    const stat = dir.statFile(".git") catch |err| {
-        switch (err) {
-            error.FileNotFound => return false,
-            else => return err,
-        }
-    };
-    if (stat.kind == .directory) {
-        return true;
-    }
-    return false;
+pub fn findGitDir(arena: mem.Allocator, dir: fs.Dir) ![]const u8 {
+    var proc = try std.process.Child.run(.{
+        .allocator = arena,
+        .cwd_dir = dir,
+        .argv = &.{ "git", "rev-parse", "--git-dir" },
+    });
+    return mem.trimEnd(u8, proc.stdout, "\n");
 }
 
 pub fn updateServerInfo(gpa: mem.Allocator, dir: fs.Dir) !void {
diff --git a/src/main.zig b/src/main.zig
index d6ddce8..23bae57 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,5 +1,6 @@
 const std = @import("std");
 const fs = std.fs;
+const mem = std.mem;
 const println = @import("utils.zig").println;
 const git = @import("git.zig");
 
@@ -54,7 +55,16 @@ pub fn main() !u8 {
         var repo_dir = try target_dir.openDir(entry.name, .{});
         defer repo_dir.close();
 
-        if (!try git.isGitDir(entry.name, repo_dir)) {
+        const git_dir_path = try git.findGitDir(repo_arena, repo_dir);
+        if (!(
+            // check if entry is actually a git repo
+            mem.eql(u8, git_dir_path, ".") // bare repo
+            or mem.eql(u8, git_dir_path, ".git") // normal repo
+        )) {
+            // Note: git dir could also return a full path, which means the
+            // current dir is NOT a git repo, but one of its ancestors is.
+            // We're not interested in such cases either.
+            println("* {s} is not a git repo", .{entry.name});
             continue;
         }
 
@@ -72,14 +82,15 @@ pub fn main() !u8 {
             .last_commit_time = if (commits.len == 0) "" else try arena.dupe(u8, commits[0].time),
         });
 
-        try writeRepoPage(
-            repo_arena,
-            site_url,
-            entry.name,
-            target_dir,
-            repo_dir,
-            commits,
-        );
+        try writeRepoPage(.{
+            .arena = repo_arena,
+            .site_url = site_url,
+            .repo_name = entry.name,
+            .target_dir = target_dir,
+            .in_repo_dir = repo_dir,
+            .commits = commits,
+            .git_dir_path = git_dir_path,
+        });
 
         // TODO: write repo's commits
     }
@@ -182,15 +193,25 @@ pub fn writeHomePage(dir: fs.Dir, repos: []RepoSummary) !void {
     try writer.interface.flush();
 }
 
-// TODO: write repo's index
-pub fn writeRepoPage(
+const RepoArgs = struct {
     arena: std.mem.Allocator,
     site_url: [*:0]const u8,
     repo_name: []const u8,
     target_dir: fs.Dir,
     in_repo_dir: fs.Dir,
     commits: []git.Commit,
-) !void {
+    git_dir_path: []const u8,
+};
+
+pub fn writeRepoPage(args: RepoArgs) !void {
+    const arena = args.arena;
+    const site_url = args.site_url;
+    const repo_name = args.repo_name;
+    const target_dir = args.target_dir;
+    const in_repo_dir = args.in_repo_dir;
+    const commits = args.commits;
+    const git_dir_path = args.git_dir_path;
+
     var buf: [1024]u8 = undefined;
     var out_repo_dir = try target_dir.makeOpenPath(
         try std.fmt.bufPrint(&buf, "{s}/{s}", .{ web_prefix, repo_name }),
@@ -203,6 +224,9 @@ pub fn writeRepoPage(
         // TODO write readme content to repo index page, probably in a <details>
     }
 
+    const git_dir = try git.findGitDir(arena, in_repo_dir);
+    _ = git_dir;
+
     var file = try out_repo_dir.createFile("index.html", .{});
     defer file.close();
 
@@ -226,7 +250,7 @@ pub fn writeRepoPage(
         \\    </header>
         \\
         \\    <p>
-        \\      clone command: <code>git clone {2s}/{3s}/</code><br>
+        \\      clone command: <code>git clone {2s}/{3s}</code><br>
         \\      commits: <b>{1d}</b><br>
         \\    </p>
         \\
@@ -245,10 +269,10 @@ pub fn writeRepoPage(
         repo_name,
         commits.len,
         site_url,
-        if (std.mem.endsWith(u8, repo_name, ".git"))
+        if (mem.eql(u8, git_dir_path, "."))
             repo_name
         else
-            try std.fmt.bufPrint(&buf, "{s}/.git", .{repo_name}),
+            try std.fmt.bufPrint(&buf, "{s}/{s}", .{ repo_name, git_dir_path }),
     });
 
     for (commits) |cmt| {
-- 
2.47.3

