Commit 1790a024c2

1790a024c2586d46bfb1b57f69adc529e7f78282

parent: 27e5426f8f

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-26 01:51 UTC

web: sort directories ahead of files in the tree listing

git's tree order interleaves them, but a listing is scanned by shape
before name. Stable, so each group keeps the ordering git gave it, and
scoped to the web listing: the wiki page list and the CLI's template scan
read the same call and should not shift.

Layout: unified · split

e2e/design_test.go +13
@@ -57,6 +57,19 @@ func TestReadmeRelativeLinks(t *testing.T) {
57 t.Errorf("missing %q", want) 57 t.Errorf("missing %q", want)
58 } 58 }
59 } 59 }
60 // Directories sort ahead of files, whatever git's own tree order was:
61 // docs/ and img/ precede LICENSE and README.md despite sorting after
62 // them byte-wise.
63 for _, dir := range []string{"docs", "img"} {
64 d := strings.Index(body, `/tree/main/`+dir+`">`)
65 f := strings.Index(body, `/blob/main/README.md">`)
66 if d < 0 || f < 0 {
67 t.Fatalf("listing missing %s/ or README.md", dir)
68 }
69 if d > f {
70 t.Errorf("%s/ listed after README.md; directories should come first", dir)
71 }
72 }
60 // Branch dropdown lists branches. 73 // Branch dropdown lists branches.
61 if !strings.Contains(body, `class="refmenu"`) || !strings.Contains(body, ">All refs") { 74 if !strings.Contains(body, `class="refmenu"`) || !strings.Contains(body, ">All refs") {
62 t.Error("branch dropdown missing") 75 t.Error("branch dropdown missing")
internal/httpd/web.go +7
@@ -13,6 +13,7 @@ import (
13 "net/http" 13 "net/http"
14 "path" 14 "path"
15 "regexp" 15 "regexp"
16 "sort"
16 "strconv" 17 "strconv"
17 "strings" 18 "strings"
18 "time" 19 "time"
@@ -433,6 +434,12 @@ func (s *Server) renderTree(w http.ResponseWriter, r *http.Request, p repoPage,
433 s.notFound(w, r) 434 s.notFound(w, r)
434 return 435 return
435 } 436 }
437 // Directories first. git's tree order interleaves them with files, but
438 // a listing is scanned by shape before name. Stable, so each group
439 // keeps the ordering git gave it.
440 sort.SliceStable(entries, func(i, j int) bool {
441 return entries[i].Type == "tree" && entries[j].Type != "tree"
442 })
436 prefix := "" 443 prefix := ""
437 if dirPath != "" { 444 if dirPath != "" {
438 prefix = dirPath + "/" 445 prefix = dirPath + "/"