Commit 1790a024c2
1790a024c2586d46bfb1b57f69adc529e7f78282
parent: 27e5426f8f
Verified · cmc
cmc <hello@cleberg.net> · 2026-08-26T01:51:02Z
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.
e2e/design_test.go
+13
| @@ -57,6 +57,19 @@ func TestReadmeRelativeLinks(t *testing.T) { |
| 57 | 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 | 73 | // Branch dropdown lists branches. |
| 61 | 74 | if !strings.Contains(body, `class="refmenu"`) || !strings.Contains(body, ">All refs") { |
| 62 | 75 | t.Error("branch dropdown missing") |
internal/httpd/web.go
+7
| @@ -13,6 +13,7 @@ import ( |
| 13 | 13 | "net/http" |
| 14 | 14 | "path" |
| 15 | 15 | "regexp" |
| 16 | "sort" |
| 16 | 17 | "strconv" |
| 17 | 18 | "strings" |
| 18 | 19 | "time" |
| @@ -433,6 +434,12 @@ func (s *Server) renderTree(w http.ResponseWriter, r *http.Request, p repoPage, |
| 433 | 434 | s.notFound(w, r) |
| 434 | 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 | 443 | prefix := "" |
| 437 | 444 | if dirPath != "" { |
| 438 | 445 | prefix = dirPath + "/" |