3 files changed,
86 insertions(+),
32 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-03-03 19:29:41 +0200
Change ID:
vvswoqyoytpyyluymkykwzqsvnvvkrzs
Parent:
3d3163c
M
internal/git/repo.go
@@ -186,6 +186,52 @@
return newCommit(c), nil } +func (g *Repo) lastCommitForFile(filepath string) (*Commit, error) { + iter, err := g.r.Log(&git.LogOptions{ + From: g.h, + Order: git.LogOrderCommitterTime, + }) + if err != nil { + return nil, fmt.Errorf("failed to log: %w", err) + } + defer iter.Close() + + var prevHash plumbing.Hash + var result *object.Commit + err = iter.ForEach(func(com *object.Commit) error { + tree, terr := com.Tree() + if terr != nil { + return terr + } + + var hash plumbing.Hash + entry, eerr := tree.FindEntry(filepath) + if eerr == nil { + hash = entry.Hash + } else { + file, ferr := tree.File(filepath) + if ferr != nil { + return storer.ErrStop + } + hash = file.Hash + } + + if hash != prevHash { + result = com + prevHash = hash + } + return nil + }) + + if !errors.Is(err, storer.ErrStop) && err != nil { + return nil, fmt.Errorf("failed to walk commits: %w", err) + } + if result == nil { + return nil, fmt.Errorf("no commits found for path: %s", filepath) + } + return newCommit(result), nil +} + type Branch struct { Name string LastUpdate time.Time
M
internal/git/tree.go
@@ -5,6 +5,7 @@ "errors"
"fmt" "io" "mime" + "path" "path/filepath" "strings"@@ -12,22 +13,26 @@ "github.com/go-git/go-git/v5/plumbing/object"
) type NiceTree struct { - Name string - Mode string - Size int64 - IsFile bool - IsSubtree bool + IsFile bool + Name string + Commit *Commit + Mode string + Size int64 } -func makeNiceTree(t *object.Tree) []NiceTree { - nts := []NiceTree{} +func (g *Repo) makeNiceTree(t *object.Tree, parent string) []NiceTree { + var nts []NiceTree for _, e := range t.Entries { mode, _ := e.Mode.ToOSFileMode() sz, _ := t.Size(e.Name) + + // TODO: this should be cached, its pretty expensive + lc, _ := g.lastCommitForFile(path.Join(parent, e.Name)) nts = append(nts, NiceTree{ Name: e.Name, Mode: mode.String(), IsFile: e.Mode.IsFile(), + Commit: lc, Size: sz, }) }@@ -40,14 +45,14 @@ if err != nil {
return nil, fmt.Errorf("commit object: %w", err) } - files := []NiceTree{} tree, err := c.Tree() if err != nil { return nil, fmt.Errorf("file tree: %w", err) } + var files []NiceTree if path == "" { - files = makeNiceTree(tree) + files = g.makeNiceTree(tree, path) } else { o, err := tree.FindEntry(path) if err != nil {@@ -59,7 +64,7 @@ subtree, err := tree.Tree(path)
if err != nil { return nil, err } - files = makeNiceTree(subtree) + files = g.makeNiceTree(subtree, path) } }@@ -117,10 +122,9 @@
isBin, _ := file.IsBinary() mimeType := mime.TypeByExtension(filepath.Ext(path)) if mimeType == "" { + mimeType = "text/plain" if isBin { mimeType = "application/octet-stream" - } else { - mimeType = "text/plain" } }
M
web/templates/repo_tree.html
@@ -14,31 +14,33 @@
<table class="table tree"> <thead> <tr class="nohover"> - <th class="mode nowrap">mode</th> - <th class="size nowrap">size</th> - <th class="fill">name</th> + <th class="nowrap">name</th> + <th class="fill">last commit</th> + <th class="nowrap">last update</th> </tr> </thead> <tbody> {{ if $parent }} <tr> - <td class="mode nowrap"></td> - <td class="size nowrap"></td> - <td class="fill"><a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .P.DotDot }}">..</a></td> + <td class="nowrap"><a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .P.DotDot }}">..</a></td> + <th class="fill"></th> + <th class="nowrap"></th> </tr> {{ end }} {{ range .P.Tree }} {{ if not .IsFile }} <tr> - <td class="mode nowrap">{{ .Mode }}</td> - <td class="size nowrap">{{ .Size }}</td> + <td class="nowrap"> + {{ if $parent }}<a href="/{{ $name}}/tree/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a> + {{ else }}<a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}/</a>{{ end }} + </td> <td class="fill"> - {{ if $parent }} - <a href="/{{ $name}}/tree/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a> - {{ else }} - <a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}/</a> - {{ end }} + <a href="/{{ $name }}/commit/{{ .Commit.Hash }}">{{ commitSummary .Commit.Message }}</a> + </td> + <td class="age nowrap"> + <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ humanizeRelTime .Commit.Committed }}</a> + <span class="tooltip" role="tooltip">{{ humanizeTime .Commit.Committed }}</span> </td> </tr> {{ end }}@@ -47,14 +49,16 @@
{{ range .P.Tree }} {{ if .IsFile }} <tr> - <td class="mode nowrap">{{ .Mode }}</td> - <td class="size nowrap">{{ .Size }}</td> + <td class="nowrap"> + {{ if $parent }}<a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a> + {{ else }}<a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}</a>{{ end }} + </td> <td class="fill"> - {{ if $parent }} - <a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a> - {{ else }} - <a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}</a> - {{ end }} + <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ commitSummary .Commit.Message }}</a> + </td> + <td class="age nowrap"> + <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ humanizeRelTime .Commit.Committed }}</a> + <span class="tooltip" role="tooltip">{{ humanizeTime .Commit.Committed }}</span> </td> </tr> {{ end }}