Commit 69bd667

Nick Faro committed on
Link files to their latest commits
commit 69bd66774ce3c83fd351be4f17e6b5769c438c99 parent 7bcc324
5 changed files +71−7
ModifiedREADME.md +1−0
@@ -92,6 +92,7 @@ content-addressed packfiles are served as immutable objects.
92 The generated browser includes: 92 The generated browser includes:
93 93
94 - repository and directory trees; 94 - repository and directory trees;
95 - per-file latest commit messages linked into branch history;
95 - rendered GitHub-flavored Markdown README files; 96 - rendered GitHub-flavored Markdown README files;
96 - syntax highlighting and linked line numbers; 97 - syntax highlighting and linked line numbers;
97 - raw file URLs and image previews; 98 - raw file URLs and image previews;
Modifiedinternal/site/site.go +65−6
@@ -68,12 +68,14 @@ type Commit struct {
68 } 68 }
69 69
70 type TreeEntry struct { 70 type TreeEntry struct {
71 Mode string 71 Mode string
72 Type string 72 Type string
73 OID string 73 OID string
74 Size int64 74 Size int64
75 Name string 75 Name string
76 URL string 76 URL string
77 CommitMessage string
78 CommitURL string
77 } 79 }
78 80
79 type Crumb struct { 81 type Crumb struct {
@@ -326,6 +328,9 @@ func (g *Generator) treePage(ctx context.Context, branch, oid, dir string, summa
326 entries[i].URL = routeURL(g.BaseURL, path.Join("blob", branch, fullPath)) 328 entries[i].URL = routeURL(g.BaseURL, path.Join("blob", branch, fullPath))
327 } 329 }
328 } 330 }
331 if err := g.populateEntryCommits(ctx, oid, dir, entries, summary.CommitsURL); err != nil {
332 return Page{}, err
333 }
329 data := g.pageData("tree", g.RepoName, branch, summary) 334 data := g.pageData("tree", g.RepoName, branch, summary)
330 data.Path = dir 335 data.Path = dir
331 data.Breadcrumbs = g.breadcrumbs(branch, dir) 336 data.Breadcrumbs = g.breadcrumbs(branch, dir)
@@ -353,6 +358,60 @@ func (g *Generator) treePage(ctx context.Context, branch, oid, dir string, summa
353 return Page{Route: path.Join("tree", branch, dir), Body: body, ContentType: "text/html; charset=utf-8", Cache: siteCache}, nil 358 return Page{Route: path.Join("tree", branch, dir), Body: body, ContentType: "text/html; charset=utf-8", Cache: siteCache}, nil
354 } 359 }
355 360
361 func (g *Generator) populateEntryCommits(ctx context.Context, oid, dir string, entries []TreeEntry, commitsURL string) error {
362 if len(entries) == 0 {
363 return nil
364 }
365 ctx, cancel := context.WithCancel(ctx)
366 defer cancel()
367 workers := min(8, len(entries))
368 jobs := make(chan int)
369 var wait sync.WaitGroup
370 var once sync.Once
371 var firstErr error
372 for range workers {
373 wait.Add(1)
374 go func() {
375 defer wait.Done()
376 for index := range jobs {
377 commit, err := g.entryCommit(ctx, oid, path.Join(dir, entries[index].Name))
378 if err != nil {
379 once.Do(func() {
380 firstErr = err
381 cancel()
382 })
383 continue
384 }
385 entries[index].CommitMessage = commit.Message
386 entries[index].CommitURL = commitsURL + "#" + commit.OID
387 }
388 }()
389 }
390 sendJobs:
391 for index := range entries {
392 select {
393 case jobs <- index:
394 case <-ctx.Done():
395 break sendJobs
396 }
397 }
398 close(jobs)
399 wait.Wait()
400 return firstErr
401 }
402
403 func (g *Generator) entryCommit(ctx context.Context, oid, filePath string) (Commit, error) {
404 out, err := g.Git.Run(ctx, "--literal-pathspecs", "log", "-1", "--format=%H%x00%s", oid, "--", filePath)
405 if err != nil {
406 return Commit{}, err
407 }
408 fields := strings.Split(strings.TrimSpace(string(out)), "\x00")
409 if len(fields) != 2 || fields[0] == "" {
410 return Commit{}, fmt.Errorf("could not find the latest commit for %s", filePath)
411 }
412 return Commit{OID: fields[0], Message: fields[1]}, nil
413 }
414
356 func (g *Generator) blobPages(ctx context.Context, branch, oid string, entry TreeEntry, summary branchSummary) ([]Page, error) { 415 func (g *Generator) blobPages(ctx context.Context, branch, oid string, entry TreeEntry, summary branchSummary) ([]Page, error) {
357 var content []byte 416 var content []byte
358 var err error 417 var err error
Modifiedinternal/site/site_test.go +3−0
@@ -56,6 +56,7 @@ func TestFullAndIncrementalBuild(t *testing.T) {
56 assertPage(t, full, "tree/main/src", "main.go") 56 assertPage(t, full, "tree/main/src", "main.go")
57 assertPage(t, full, "tree/main/src", `<div class="tree-row">`) 57 assertPage(t, full, "tree/main/src", `<div class="tree-row">`)
58 assertPageDoesNotContain(t, full, "tree/main/src", `<a class="tree-row"`) 58 assertPageDoesNotContain(t, full, "tree/main/src", `<a class="tree-row"`)
59 assertPage(t, full, "tree/main/src", `commits/main#`+first+`" title="initial">initial</a>`)
59 assertPage(t, full, "commits/main", "Commit history") 60 assertPage(t, full, "commits/main", "Commit history")
60 assertPage(t, full, "commits/main", "gravatar.com/avatar") 61 assertPage(t, full, "commits/main", "gravatar.com/avatar")
61 assertPage(t, full, ".dumbforge/files/main.json", `"path":"src/main.go"`) 62 assertPage(t, full, ".dumbforge/files/main.json", `"path":"src/main.go"`)
@@ -76,6 +77,8 @@ func TestFullAndIncrementalBuild(t *testing.T) {
76 } 77 }
77 assertPage(t, incremental, "blob/main/src/main.go", "changed") 78 assertPage(t, incremental, "blob/main/src/main.go", "changed")
78 assertPage(t, incremental, "tree/main/src", "change one file") 79 assertPage(t, incremental, "tree/main/src", "change one file")
80 assertPage(t, incremental, "tree/main/src", `commits/main#`+second+`" title="change one file">change one file</a>`)
81 assertPage(t, incremental, "", `commits/main#`+first+`" title="initial">initial</a>`)
79 assertPage(t, incremental, "commits/main", "change one file") 82 assertPage(t, incremental, "commits/main", "change one file")
80 } 83 }
81 84
Modifiedinternal/site/style.go +1−0
@@ -113,6 +113,7 @@ button, input { font: inherit; }
113 .file-name { color: var(--text); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } 113 .file-name { color: var(--text); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
114 .file-name:hover { text-decoration: underline; } 114 .file-name:hover { text-decoration: underline; }
115 .row-message { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } 115 .row-message { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
116 .row-message:hover { color: var(--blue); text-decoration: underline; }
116 .file-size { color: var(--muted); text-align: right; font-size: 12px; white-space: nowrap; } 117 .file-size { color: var(--muted); text-align: right; font-size: 12px; white-space: nowrap; }
117 .parent-row { grid-template-columns: 24px 1fr 1fr 75px; } 118 .parent-row { grid-template-columns: 24px 1fr 1fr 75px; }
118 .readme-card { margin-top: 18px; } 119 .readme-card { margin-top: 18px; }
Modifiedinternal/site/template.go +1−1
@@ -478,7 +478,7 @@ const pageTemplate = `<!doctype html>
478 {{range .Entries}}<div class="tree-row"> 478 {{range .Entries}}<div class="tree-row">
479 {{if eq .Type "tree"}}<span class="file-icon folder">{{icon "folder"}}</span>{{else}}<span class="file-icon">{{icon "file"}}</span>{{end}} 479 {{if eq .Type "tree"}}<span class="file-icon folder">{{icon "folder"}}</span>{{else}}<span class="file-icon">{{icon "file"}}</span>{{end}}
480 <a class="file-name" href="{{.URL}}">{{.Name}}</a> 480 <a class="file-name" href="{{.URL}}">{{.Name}}</a>
481 <span class="row-message">{{$.Commit.Message}}</span> 481 <a class="row-message" href="{{.CommitURL}}" title="{{.CommitMessage}}">{{.CommitMessage}}</a>
482 <span class="file-size">{{humanSize .Size}}</span> 482 <span class="file-size">{{humanSize .Size}}</span>
483 </div>{{end}} 483 </div>{{end}}
484 </div> 484 </div>