3 files changed,
86 insertions(+),
32 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-03-03 19:29:41 +0200
Authored at:
2026-03-02 23:38:56 +0200
Change ID:
vvswoqyoytpyyluymkykwzqsvnvvkrzs
Parent:
3d3163c
M
internal/git/repo.go
路路路 186 186 return newCommit(c), nil 187 187 } 188 188 189 +func (g *Repo) lastCommitForFile(filepath string) (*Commit, error) { 190 + iter, err := g.r.Log(&git.LogOptions{ 191 + From: g.h, 192 + Order: git.LogOrderCommitterTime, 193 + }) 194 + if err != nil { 195 + return nil, fmt.Errorf("failed to log: %w", err) 196 + } 197 + defer iter.Close() 198 + 199 + var prevHash plumbing.Hash 200 + var result *object.Commit 201 + err = iter.ForEach(func(com *object.Commit) error { 202 + tree, terr := com.Tree() 203 + if terr != nil { 204 + return terr 205 + } 206 + 207 + var hash plumbing.Hash 208 + entry, eerr := tree.FindEntry(filepath) 209 + if eerr == nil { 210 + hash = entry.Hash 211 + } else { 212 + file, ferr := tree.File(filepath) 213 + if ferr != nil { 214 + return storer.ErrStop 215 + } 216 + hash = file.Hash 217 + } 218 + 219 + if hash != prevHash { 220 + result = com 221 + prevHash = hash 222 + } 223 + return nil 224 + }) 225 + 226 + if !errors.Is(err, storer.ErrStop) && err != nil { 227 + return nil, fmt.Errorf("failed to walk commits: %w", err) 228 + } 229 + if result == nil { 230 + return nil, fmt.Errorf("no commits found for path: %s", filepath) 231 + } 232 + return newCommit(result), nil 233 +} 234 + 189 235 type Branch struct { 190 236 Name string 191 237 LastUpdate time.Time
M
internal/git/tree.go
路路路 5 5 "fmt" 6 6 "io" 7 7 "mime" 8 + "path" 8 9 "path/filepath" 9 10 "strings" 10 11 路路路 12 13 ) 13 14 14 15 type NiceTree struct { 15 - Name string 16 - Mode string 17 - Size int64 18 - IsFile bool 19 - IsSubtree bool 16 + IsFile bool 17 + Name string 18 + Commit *Commit 19 + Mode string 20 + Size int64 20 21 } 21 22 22 -func makeNiceTree(t *object.Tree) []NiceTree { 23 - nts := []NiceTree{} 23 +func (g *Repo) makeNiceTree(t *object.Tree, parent string) []NiceTree { 24 + var nts []NiceTree 24 25 for _, e := range t.Entries { 25 26 mode, _ := e.Mode.ToOSFileMode() 26 27 sz, _ := t.Size(e.Name) 28 + 29 + // TODO: this should be cached, its pretty expensive 30 + lc, _ := g.lastCommitForFile(path.Join(parent, e.Name)) 27 31 nts = append(nts, NiceTree{ 28 32 Name: e.Name, 29 33 Mode: mode.String(), 30 34 IsFile: e.Mode.IsFile(), 35 + Commit: lc, 31 36 Size: sz, 32 37 }) 33 38 } 路路路 40 45 return nil, fmt.Errorf("commit object: %w", err) 41 46 } 42 47 43 - files := []NiceTree{} 44 48 tree, err := c.Tree() 45 49 if err != nil { 46 50 return nil, fmt.Errorf("file tree: %w", err) 47 51 } 48 52 53 + var files []NiceTree 49 54 if path == "" { 50 - files = makeNiceTree(tree) 55 + files = g.makeNiceTree(tree, path) 51 56 } else { 52 57 o, err := tree.FindEntry(path) 53 58 if err != nil { 路路路 59 64 if err != nil { 60 65 return nil, err 61 66 } 62 - files = makeNiceTree(subtree) 67 + files = g.makeNiceTree(subtree, path) 63 68 } 64 69 } 65 70 路路路 117 122 isBin, _ := file.IsBinary() 118 123 mimeType := mime.TypeByExtension(filepath.Ext(path)) 119 124 if mimeType == "" { 125 + mimeType = "text/plain" 120 126 if isBin { 121 127 mimeType = "application/octet-stream" 122 - } else { 123 - mimeType = "text/plain" 124 128 } 125 129 } 126 130
M
web/templates/repo_tree.html
路路路 14 14 <table class="table tree"> 15 15 <thead> 16 16 <tr class="nohover"> 17 - <th class="mode nowrap">mode</th> 18 - <th class="size nowrap">size</th> 19 - <th class="fill">name</th> 17 + <th class="nowrap">name</th> 18 + <th class="fill">last commit</th> 19 + <th class="nowrap">last update</th> 20 20 </tr> 21 21 </thead> 22 22 <tbody> 23 23 {{ if $parent }} 24 24 <tr> 25 - <td class="mode nowrap"></td> 26 - <td class="size nowrap"></td> 27 - <td class="fill"><a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .P.DotDot }}">..</a></td> 25 + <td class="nowrap"><a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .P.DotDot }}">..</a></td> 26 + <th class="fill"></th> 27 + <th class="nowrap"></th> 28 28 </tr> 29 29 {{ end }} 30 30 31 31 {{ range .P.Tree }} 32 32 {{ if not .IsFile }} 33 33 <tr> 34 - <td class="mode nowrap">{{ .Mode }}</td> 35 - <td class="size nowrap">{{ .Size }}</td> 34 + <td class="nowrap"> 35 + {{ if $parent }}<a href="/{{ $name}}/tree/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a> 36 + {{ else }}<a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}/</a>{{ end }} 37 + </td> 36 38 <td class="fill"> 37 - {{ if $parent }} 38 - <a href="/{{ $name}}/tree/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a> 39 - {{ else }} 40 - <a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}/</a> 41 - {{ end }} 39 + <a href="/{{ $name }}/commit/{{ .Commit.Hash }}">{{ commitSummary .Commit.Message }}</a> 40 + </td> 41 + <td class="age nowrap"> 42 + <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ humanizeRelTime .Commit.Committed }}</a> 43 + <span class="tooltip" role="tooltip">{{ humanizeTime .Commit.Committed }}</span> 42 44 </td> 43 45 </tr> 44 46 {{ end }} 路路路 47 49 {{ range .P.Tree }} 48 50 {{ if .IsFile }} 49 51 <tr> 50 - <td class="mode nowrap">{{ .Mode }}</td> 51 - <td class="size nowrap">{{ .Size }}</td> 52 + <td class="nowrap"> 53 + {{ if $parent }}<a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a> 54 + {{ else }}<a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}</a>{{ end }} 55 + </td> 52 56 <td class="fill"> 53 - {{ if $parent }} 54 - <a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a> 55 - {{ else }} 56 - <a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}</a> 57 - {{ end }} 57 + <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ commitSummary .Commit.Message }}</a> 58 + </td> 59 + <td class="age nowrap"> 60 + <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ humanizeRelTime .Commit.Committed }}</a> 61 + <span class="tooltip" role="tooltip">{{ humanizeTime .Commit.Committed }}</span> 58 62 </td> 59 63 </tr> 60 64 {{ end }}