Commit c1ecc59d1b

c1ecc59d1bba0dda41ae3412fbc4d8ccd5c77be1

parent: e5973a00dd

Verified · cmc ci/build: success ci/test: success ci/vuln: success

cmc <hello@cleberg.net> · 2026-09-03 05:52 UTC

web: directory crumbs on blob and blame pages link to the tree

crumbs built every component's URL with the page's own kind, so on a
file page the parent directories linked to /blob/<ref>/<dir>, which is a
404. Only the leaf is a blob or blame page; the rest are directories.

Closes #103

Layout: unified · split

internal/httpd/crumbs_test.go added +34
@@ -0,0 +1,34 @@
1package httpd
2
3import (
4 "testing"
5
6 "gitbay.org/gitbay/internal/store"
7)
8
9// TestCrumbsLinkDirectoriesToTree: on a blob or blame page the parent
10// crumbs are directories, so they link to the tree; only the leaf keeps the
11// page's kind. They used to carry the page's kind throughout, and
12// /blob/<ref>/<dir> is a 404 (#103).
13func TestCrumbsLinkDirectoriesToTree(t *testing.T) {
14 p := repoPage{Repo: store.Repo{OwnerName: "krz", Name: "gitbay"}, Ref: "main"}
15 for _, kind := range []string{"blob", "blame", "tree"} {
16 cs := crumbs(p, kind, "internal/control/control.go")
17 want := []string{
18 "/krz/gitbay/tree/main/internal",
19 "/krz/gitbay/tree/main/internal/control",
20 "/krz/gitbay/" + kind + "/main/internal/control/control.go",
21 }
22 if len(cs) != len(want) {
23 t.Fatalf("%s: %d crumbs, want %d", kind, len(cs), len(want))
24 }
25 for i, c := range cs {
26 if c.URL != want[i] {
27 t.Errorf("%s crumb %d: %s, want %s", kind, i, c.URL, want[i])
28 }
29 }
30 }
31 if cs := crumbs(p, "tree", ""); len(cs) != 0 {
32 t.Errorf("root: %d crumbs, want none", len(cs))
33 }
34}
internal/httpd/web.go +10 −3
@@ -337,16 +337,23 @@ type crumb struct {
337 URL string 337 URL string
338} 338}
339 339
340// crumbs builds one crumb per path component. Every component but the
341// last is a directory and links to the tree; only the leaf is a page of
342// the given kind.
340func crumbs(p repoPage, kind, filePath string) []crumb { 343func crumbs(p repoPage, kind, filePath string) []crumb {
341 var cs []crumb 344 var cs []crumb
342 base := "/" + p.Repo.Path() + "/" + kind + "/" + p.Ref + "/" 345 parts := strings.Split(strings.Trim(filePath, "/"), "/")
343 acc := "" 346 acc := ""
344 for _, part := range strings.Split(filePath, "/") { 347 for i, part := range parts {
345 if part == "" { 348 if part == "" {
346 continue 349 continue
347 } 350 }
348 acc = path.Join(acc, part) 351 acc = path.Join(acc, part)
349 cs = append(cs, crumb{Name: part, URL: base + acc}) 352 k := "tree"
353 if i == len(parts)-1 {
354 k = kind
355 }
356 cs = append(cs, crumb{Name: part, URL: "/" + p.Repo.Path() + "/" + k + "/" + p.Ref + "/" + acc})
350 } 357 }
351 return cs 358 return cs
352} 359}