A CLI-first git forge.

cli forge git self-hosted

https://gitbay.org

Commit 542b37e732

542b37e7324b23aec449b5fd296735fadffa3cdc

parent: 33f4fa3490

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-24T23:04:09Z

web: repo rows, file table headers, README fixes, branch dropdown

Part of the #10 review round. Repo listings become one row per
repository (title with visibility/archived chips, description, clickable
topic chips linking filtered explore, then default branch · license ·
last updated — license detected from conventional files, date from the
branch tip). The files table gains column headers; the README card
header links to the file's blob page; relative links in rendered
READMEs resolve to blob pages (org/markdown .html exports map back to
their source files) and relative images to raw. The ref chip on tree
and blob pages becomes a no-JS dropdown listing branches with an
all-refs link.
e2e/design_test.go added +66
@@ -0,0 +1,66 @@
1package e2e
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10func TestReadmeRelativeLinks(t *testing.T) {
11 inst := startInstance(t)
12 aliceKey := inst.newKey(t, "alice")
13 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
14 if _, _, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/site"); code != 0 {
15 t.Fatal("repo create failed")
16 }
17 work := t.TempDir()
18 env := inst.gitEnv(aliceKey)
19 mustGit(t, work, env, "clone", inst.sshURL("alice/site"), "w")
20 dir := filepath.Join(work, "w")
21 os.MkdirAll(filepath.Join(dir, "docs"), 0o755)
22 os.MkdirAll(filepath.Join(dir, "img"), 0o755)
23 os.WriteFile(filepath.Join(dir, "README.md"), []byte(
24 "# site\n\n[guide](docs/guide.md) and [export](docs/paper.html) and "+
25 "[abs](https://example.org/x) here\n\n![logo](img/logo.png)\n"), 0o644)
26 os.WriteFile(filepath.Join(dir, "docs", "guide.md"), []byte("# guide\n"), 0o644)
27 os.WriteFile(filepath.Join(dir, "docs", "paper.org"), []byte("* paper\n"), 0o644)
28 os.WriteFile(filepath.Join(dir, "img", "logo.png"), []byte{0x89, 0x50}, 0o644)
29 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
30 mustGit(t, dir, env, "add", ".")
31 mustGit(t, dir, env, "commit", "-q", "-m", "base")
32 mustGit(t, dir, env, "push", "-q", "origin", "main")
33
34 status, body := inst.get(t, "/alice/site")
35 if status != 200 {
36 t.Fatalf("tree: %d", status)
37 }
38 for _, want := range []string{
39 `href="/alice/site/blob/main/docs/guide.md"`, // relative link
40 `href="/alice/site/blob/main/docs/paper.org"`, // .html mapped to .org source
41 `src="/alice/site/raw/main/img/logo.png"`, // relative image via raw
42 `href="https://example.org/x"`, // absolute untouched
43 `href="/alice/site/blob/main/README.md">README.md</a>`, // clickable card header
44 `<th>name</th>`, // file table column headers
45 } {
46 if !strings.Contains(body, want) {
47 t.Errorf("missing %q", want)
48 }
49 }
50 // Branch dropdown lists branches.
51 if !strings.Contains(body, `class="refmenu"`) || !strings.Contains(body, ">all refs") {
52 t.Error("branch dropdown missing")
53 }
54 // Explore rows carry topics, license, and updated date.
55 inst.ssh(t, aliceKey, "", "repo", "topics", "add", "alice/site", "web")
56 os.WriteFile(filepath.Join(dir, "LICENSE"), []byte("Permission to use, copy, modify, and/or distribute this software...\n"), 0o644)
57 mustGit(t, dir, env, "add", ".")
58 mustGit(t, dir, env, "commit", "-q", "-m", "license")
59 mustGit(t, dir, env, "push", "-q", "origin", "main")
60 _, body = inst.get(t, "/explore")
61 for _, want := range []string{`href="/explore?q=web"`, "ISC", "updated 20"} {
62 if !strings.Contains(body, want) {
63 t.Errorf("explore row missing %q", want)
64 }
65 }
66}
internal/gitutil/messages.go +10
@@ -8,6 +8,16 @@ import (
88
99 const zeroSHA = "0000000000000000000000000000000000000000"
1010
11// LastCommitDate returns the committer date (YYYY-MM-DD) of the ref tip,
12// or "" for empty repos.
13func LastCommitDate(dir, ref string) string {
14 out, err := exec.Command("git", "-C", dir, "log", "-1", "--format=%cs", ref).Output()
15 if err != nil {
16 return ""
17 }
18 return strings.TrimSpace(string(out))
19}
20
1121 // HasCommit reports whether sha names a commit object present in dir.
1222 func HasCommit(dir, sha string) bool {
1323 return exec.Command("git", "-C", dir, "cat-file", "-e", sha+"^{commit}").Run() == nil
internal/httpd/readme.go added +108
@@ -0,0 +1,108 @@
1package httpd
2
3import (
4 "html/template"
5 "path"
6 "strings"
7
8 "golang.org/x/net/html"
9 "golang.org/x/net/html/atom"
10
11 "gitbay.org/gitbay/internal/gitutil"
12)
13
14// licenseNames maps content markers to display names, checked in order.
15var licenseChecks = []struct{ marker, name string }{
16 {"MIT License", "MIT"},
17 {"Permission is hereby granted, free of charge", "MIT"},
18 {"Apache License", "Apache-2.0"},
19 {"GNU AFFERO GENERAL PUBLIC LICENSE", "AGPL-3.0"},
20 {"GNU GENERAL PUBLIC LICENSE", "GPL"},
21 {"GNU LESSER GENERAL PUBLIC LICENSE", "LGPL"},
22 {"Mozilla Public License", "MPL-2.0"},
23 {"BSD 3-Clause", "BSD-3-Clause"},
24 {"BSD 2-Clause", "BSD-2-Clause"},
25 {"Redistribution and use in source and binary forms", "BSD"},
26 {"Permission to use, copy, modify, and/or distribute", "ISC"},
27 {"This is free and unencumbered software", "Unlicense"},
28 {"CC0", "CC0"},
29}
30
31// detectLicense reports the repo's license name from a conventional file
32// at the ref root, or "".
33func detectLicense(dir, ref string) string {
34 for _, name := range []string{"LICENSE", "LICENSE.md", "LICENSE.txt", "COPYING", "UNLICENSE"} {
35 raw, err := gitutil.ReadBlob(dir, ref, name, 2048)
36 if err != nil {
37 continue
38 }
39 text := string(raw)
40 for _, c := range licenseChecks {
41 if strings.Contains(text, c.marker) {
42 return c.name
43 }
44 }
45 return "license"
46 }
47 return ""
48}
49
50// rewriteRelativeLinks makes relative hrefs and srcs in rendered repo
51// content resolve on the forge: links go to blob pages, images to raw.
52// go-org exports .org links as .html, so an .html target whose .org (or
53// .md) source exists in the tree maps back to the source file.
54func rewriteRelativeLinks(rendered template.HTML, p repoPage, baseDir string) template.HTML {
55 ctx := &html.Node{Type: html.ElementNode, Data: "div", DataAtom: atom.Div}
56 nodes, err := html.ParseFragment(strings.NewReader(string(rendered)), ctx)
57 if err != nil {
58 return rendered
59 }
60 var walk func(*html.Node)
61 walk = func(n *html.Node) {
62 if n.Type == html.ElementNode {
63 for i, a := range n.Attr {
64 isHref := a.Key == "href" && n.Data == "a"
65 isSrc := a.Key == "src" && (n.Data == "img" || n.Data == "video" || n.Data == "source")
66 if !isHref && !isSrc {
67 continue
68 }
69 v := a.Val
70 if v == "" || strings.Contains(v, "://") || strings.HasPrefix(v, "/") ||
71 strings.HasPrefix(v, "#") || strings.HasPrefix(v, "mailto:") ||
72 strings.HasPrefix(v, "data:") {
73 continue
74 }
75 target := path.Clean(path.Join(baseDir, v))
76 if strings.HasPrefix(target, "..") {
77 continue
78 }
79 if isSrc {
80 n.Attr[i].Val = "/" + p.Repo.Path() + "/raw/" + p.Ref + "/" + target
81 continue
82 }
83 // .html from org/markdown exports maps back to the source.
84 if strings.HasSuffix(target, ".html") {
85 stem := strings.TrimSuffix(target, ".html")
86 for _, ext := range []string{".org", ".md"} {
87 if _, err := gitutil.ReadBlob(p.Dir, p.Ref, stem+ext, 1); err == nil {
88 target = stem + ext
89 break
90 }
91 }
92 }
93 n.Attr[i].Val = "/" + p.Repo.Path() + "/blob/" + p.Ref + "/" + target
94 }
95 }
96 for c := n.FirstChild; c != nil; c = c.NextSibling {
97 walk(c)
98 }
99 }
100 var out strings.Builder
101 for _, n := range nodes {
102 walk(n)
103 if err := html.Render(&out, n); err != nil {
104 return rendered
105 }
106 }
107 return template.HTML(out.String())
108}
internal/httpd/web.go +31 −9
@@ -75,16 +75,28 @@ func (s *Server) notFound(w http.ResponseWriter, r *http.Request) {
7575 buf.WriteTo(w)
7676 }
7777
78// describedRepo pairs a repo with its description for listings.
78// describedRepo pairs a repo with the listing metadata: description,
79// topics, license, and last-updated date.
7980 type describedRepo struct {
8081 store.Repo
81 Desc string
82 Desc string
83 Topics []string
84 License string
85 Updated string
8286 }
8387
8488 func (s *Server) describeAll(repos []store.Repo) []describedRepo {
8589 var out []describedRepo
8690 for _, r := range repos {
87 out = append(out, describedRepo{r, gitutil.ReadDescription(control.RepoDir(s.cfg.Server.Root, r.OwnerName, r.Name))})
91 dir := control.RepoDir(s.cfg.Server.Root, r.OwnerName, r.Name)
92 d := describedRepo{
93 Repo: r,
94 Desc: gitutil.ReadDescription(dir),
95 License: detectLicense(dir, r.DefaultBranch),
96 Updated: gitutil.LastCommitDate(dir, r.DefaultBranch),
97 }
98 d.Topics, _ = s.st.ListTopics(r.ID)
99 out = append(out, d)
88100 }
89101 return out
90102 }
@@ -182,8 +194,7 @@ func (s *Server) filterRepos(q string, repos []describedRepo) []describedRepo {
182194 out = append(out, d)
183195 continue
184196 }
185 topics, _ := s.st.ListTopics(d.ID)
186 for _, t := range topics {
197 for _, t := range d.Topics {
187198 if strings.Contains(t, q) {
188199 out = append(out, d)
189200 break
@@ -342,10 +353,13 @@ func (s *Server) renderTree(w http.ResponseWriter, r *http.Request, p repoPage,
342353 repoPage
343354 Crumbs []crumb
344355 Prefix string
356 DirPath string
357 RefKind string
345358 Entries []gitutil.TreeEntry
359 Branches []gitutil.Ref
346360 ReadmeName string
347361 ReadmeHTML template.HTML
348 }{repoPage: p})
362 }{repoPage: p, RefKind: "tree"})
349363 return
350364 }
351365 entries, err := gitutil.ListTree(p.Dir, p.Ref, dirPath)
@@ -362,18 +376,22 @@ func (s *Server) renderTree(w http.ResponseWriter, r *http.Request, p repoPage,
362376 readmeName := pickReadme(entries)
363377 if readmeName != "" {
364378 if raw, err := gitutil.ReadBlob(p.Dir, p.Ref, prefix+readmeName, maxRenderBytes); err == nil {
365 readmeHTML = renderReadme(readmeName, raw)
379 readmeHTML = rewriteRelativeLinks(renderReadme(readmeName, raw), p, dirPath)
366380 }
367381 }
368382
383 branches, _ := gitutil.Refs(p.Dir, "heads")
369384 s.render(w, "tree.html", struct {
370385 repoPage
371386 Crumbs []crumb
372387 Prefix string
388 DirPath string
389 RefKind string
373390 Entries []gitutil.TreeEntry
391 Branches []gitutil.Ref
374392 ReadmeName string
375393 ReadmeHTML template.HTML
376 }{p, crumbs(p, "tree", dirPath), prefix, entries, readmeName, readmeHTML})
394 }{p, crumbs(p, "tree", dirPath), prefix, dirPath, "tree", entries, branches, readmeName, readmeHTML})
377395 }
378396
379397 func (s *Server) blob(w http.ResponseWriter, r *http.Request) {
@@ -400,15 +418,19 @@ func (s *Server) blob(w http.ResponseWriter, r *http.Request) {
400418 base = cs[len(cs)-1].Name
401419 cs = cs[:len(cs)-1]
402420 }
421 branches, _ := gitutil.Refs(p.Dir, "heads")
403422 s.render(w, "blob.html", struct {
404423 repoPage
405424 Crumbs []crumb
406425 Base string
407426 Path string
427 DirPath string
428 RefKind string
408429 Binary bool
409430 Size int
431 Branches []gitutil.Ref
410432 CodeHTML template.HTML
411 }{p, cs, base, filePath, binary, len(data), codeHTML})
433 }{p, cs, base, filePath, filePath, "blob", binary, len(data), branches, codeHTML})
412434 }
413435
414436 // releases lists tag-anchored releases with notes and assets.
internal/web/static/style.css +54 −28
@@ -201,6 +201,33 @@ nav.tabs a.active { color: var(--fg); border-bottom-color: var(--accent); font-w
201201 color: var(--fg);
202202 }
203203 .pathbar .crumbs strong { color: var(--fg); }
204details.refmenu { position: relative; }
205details.refmenu summary { cursor: pointer; list-style: none; }
206details.refmenu summary::-webkit-details-marker { display: none; }
207details.refmenu summary::after { content: " ▾"; color: var(--muted); }
208details.refmenu .refdrop {
209 position: absolute;
210 z-index: 10;
211 top: calc(100% + 4px);
212 left: 0;
213 min-width: 12rem;
214 max-height: 20rem;
215 overflow-y: auto;
216 background: var(--bg);
217 border: 1px solid var(--line);
218 border-radius: var(--r-md);
219 box-shadow: 0 4px 16px rgba(0,0,0,0.12);
220 padding: var(--sp-1) 0;
221}
222details.refmenu .refdrop a {
223 display: block;
224 padding: var(--sp-1) var(--sp-3);
225 color: var(--fg);
226 font-family: var(--mono);
227 font-size: var(--fs-1);
228}
229details.refmenu .refdrop a:hover { background: var(--surface); text-decoration: none; }
230details.refmenu .refdrop a.allrefs { color: var(--accent); font-family: var(--sans); border-top: 1px solid var(--faint); margin-top: var(--sp-1); }
204231
205232 /* file and refs tables: bordered cards, rows inside */
206233 table.tree, table.refs {
@@ -217,6 +244,17 @@ table.tree td, table.refs td {
217244 }
218245 table.tree tr:last-child td, table.refs tr:last-child td { border-bottom: none; }
219246 table.tree tr:hover td, table.refs tr:hover td { background: var(--surface); }
247table.tree tr.cols th {
248 text-align: left;
249 font-weight: 500;
250 font-size: var(--fs-1);
251 color: var(--muted);
252 padding: var(--sp-1) var(--sp-4);
253 border-bottom: 1px solid var(--line);
254 background: var(--surface);
255}
256table.tree tr.cols:hover th { background: var(--surface); }
257table.tree th.size { text-align: right; }
220258 table.tree td.name { width: 100%; }
221259 table.tree td.name a { color: var(--fg); }
222260 table.tree td.name a:hover { color: var(--accent); }
@@ -281,39 +319,27 @@ code.fullsha { color: var(--muted); overflow-wrap: anywhere; }
281319 svg.icon { vertical-align: -0.125em; }
282320 .lede { font-size: var(--fs-3); margin: var(--sp-2) 0; }
283321
284/* repo listings (index, owner pages): responsive card grid */
285.repogrid {
286 display: grid;
287 grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
288 gap: var(--sp-3);
322/* repo listings (explore, owner pages, dashboard): one row per repo */
323ul.repolist {
324 list-style: none;
289325 margin: var(--sp-3) 0 var(--sp-5);
290}
291.repogrid .empty { color: var(--muted); grid-column: 1 / -1; }
292.repocard {
293 display: flex;
294 flex-direction: column;
295 gap: var(--sp-1);
326 padding: 0;
296327 border: 1px solid var(--line);
297328 border-radius: var(--r-lg);
298 padding: var(--sp-3) var(--sp-4);
299 min-width: 0;
300}
301.repocard p { margin: 0; }
302.repocard .reponame {
303 font-weight: 550;
304 overflow: hidden;
305 text-overflow: ellipsis;
306 white-space: nowrap;
307}
308.repocard .desc {
309 font-size: var(--fs-2);
310 display: -webkit-box;
311 -webkit-line-clamp: 2;
312 -webkit-box-orient: vertical;
313329 overflow: hidden;
314330 }
315.repocard .meta { margin-top: auto; padding-top: var(--sp-1); }
316.repocard .reponame .sep { color: var(--muted); margin: 0 0.1em; }
331ul.repolist li { padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--faint); }
332ul.repolist li:last-child { border-bottom: none; }
333ul.repolist li:hover { background: var(--surface); }
334ul.repolist li.empty { color: var(--muted); }
335ul.repolist p { margin: 0; }
336ul.repolist .reponame { font-size: var(--fs-3); }
337ul.repolist .reponame a { color: var(--fg); }
338ul.repolist .reponame a strong { color: var(--accent); font-weight: 600; }
339ul.repolist .reponame .sep { color: var(--muted); margin: 0 0.1em; }
340ul.repolist .desc { margin-top: 0.1rem; }
341ul.repolist .topics { margin-top: var(--sp-1); }
342ul.repolist .meta { margin-top: var(--sp-1); }
317343
318344 /* owner profile header */
319345 .profilehead {
internal/web/templates/blob.html +1 −1
@@ -2,7 +2,7 @@
22 {{define "content"}}
33 {{template "repoheader" .}}
44 <div class="pathbar">
5 <span class="refchip">{{template "branchicon"}} {{.Ref}}</span>
5 {{template "refmenu" .}}
66 <span class="crumbs"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}">{{.Repo.Name}}</a>/{{range .Crumbs}}<a href="{{.URL}}">{{.Name}}</a>/{{end}}<strong>{{.Base}}</strong></span>
77 <span class="spacer"></span>
88 <span class="actions">{{if not .Binary}}<a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/blame/{{.Ref}}/{{.Path}}">blame</a> · {{end}}<a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/raw/{{.Ref}}/{{.Path}}">raw</a>{{if .Viewer}} · <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/edit/{{.Ref}}/{{.Path}}">edit</a>{{end}}</span>
internal/web/templates/dashboard.html +3 −7
@@ -6,14 +6,10 @@
66 <p class="toolbar">logged in as <a href="/{{.Viewer}}">{{.Viewer}}</a></p>
77 </div>
88 {{if .Pinned}}<h2>pinned</h2>
9<div class="repogrid">
10{{range .Pinned}}<div class="repocard">
11 <p class="reponame"><a href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a>{{if eq .Visibility "private"}} <span class="chip chip-neutral">private</span>{{end}}{{if .Settings.Archived}} <span class="chip chip-stale">archived</span>{{end}}</p>
12 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
13 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p>
14</div>
9<ul class="repolist">
10{{range .Pinned}}{{template "reporow" .}}
1511 {{end}}
16</div>{{end}}
12</ul>{{end}}
1713 <h2>open merge requests <span class="count">{{len .MRs}}</span></h2>
1814 <ul class="issuelist">
1915 {{range .MRs}}<li>
internal/web/templates/explore.html +4 −8
@@ -7,12 +7,8 @@
77 </form>
88 <span class="spacer"></span>
99 </div>
10<div class="repogrid">
11{{range .Repos}}<div class="repocard">
12 <p class="reponame"><a href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a>{{if .Settings.Archived}} <span class="chip chip-stale">archived</span>{{end}}</p>
13 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
14 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p>
15</div>
16{{else}}<p class="empty">no public repositories yet</p>{{end}}
17</div>
10<ul class="repolist">
11{{range .Repos}}{{template "reporow" .}}
12{{else}}<li class="empty">no public repositories yet</li>{{end}}
13</ul>
1814 {{end}}
internal/web/templates/layout.html +15
@@ -45,6 +45,21 @@
4545 </div>
4646 {{end}}
4747
48{{define "reporow"}}<li>
49 <p class="reponame"><a href="/{{.OwnerName}}/{{.Name}}">{{.OwnerName}}<span class="sep">/</span><strong>{{.Name}}</strong></a>{{if eq .Visibility "private"}} <span class="chip chip-neutral">private</span>{{end}}{{if .Settings.Archived}} <span class="chip chip-stale">archived</span>{{end}}</p>
50 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
51 {{if .Topics}}<p class="topics">{{range .Topics}}<a class="chip topic" href="/explore?q={{.}}">{{.}}</a> {{end}}</p>{{end}}
52 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}{{if .License}} · {{.License}}{{end}}{{if .Updated}} · updated {{.Updated}}{{end}}</p>
53</li>{{end}}
54
55{{define "refmenu"}}{{if .Branches}}<details class="refmenu">
56 <summary class="refchip">{{template "branchicon"}} {{.Ref}}</summary>
57 <div class="refdrop">
58 {{range .Branches}}<a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/{{$.RefKind}}/{{.Name}}/{{$.DirPath}}">{{.Name}}</a>
59 {{end}}<a class="allrefs" href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/refs">all refs →</a>
60 </div>
61</details>{{else}}<span class="refchip">{{template "branchicon"}} {{.Ref}}</span>{{end}}{{end}}
62
4863 {{define "branchicon"}}<svg class="icon" width="12" height="12" viewBox="0 0 16 16" aria-hidden="true" fill="currentColor"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628a2.25 2.25 0 0 1-1.5-2.122ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM3.5 3.25a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"/></svg>{{end}}
4964
5065 {{define "sigbadge"}}<span class="badge badge-{{.State}}" title="{{.Fingerprint}}">{{.State}}{{if .Signer}} · {{.Signer}}{{end}}</span>{{end}}
internal/web/templates/owner.html +4 −8
@@ -8,12 +8,8 @@
88 {{if .Members}}<p class="meta">members {{range .Members}}<a class="memberchip" href="/{{.Username}}">{{.Username}} <span class="role">{{.Role}}</span></a> {{end}}</p>{{end}}
99 </section>
1010 <h2>repositories <span class="count">{{len .Repos}}</span></h2>
11<div class="repogrid">
12{{range .Repos}}<div class="repocard">
13 <p class="reponame"><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a>{{if eq .Visibility "private"}} <span class="chip chip-neutral">private</span>{{end}}{{if .Settings.Archived}} <span class="chip chip-stale">archived</span>{{end}}</p>
14 {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
15 <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}</p>
16</div>
17{{else}}<p class="empty">no visible repositories</p>{{end}}
18</div>
11<ul class="repolist">
12{{range .Repos}}{{template "reporow" .}}
13{{else}}<li class="empty">no visible repositories</li>{{end}}
14</ul>
1915 {{end}}
internal/web/templates/tree.html +4 −3
@@ -2,21 +2,22 @@
22 {{define "content"}}
33 {{template "repoheader" .}}
44 <div class="pathbar">
5 <span class="refchip">{{template "branchicon"}} {{.Ref}}</span>
5 {{template "refmenu" .}}
66 <span class="crumbs"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}">{{.Repo.Name}}</a>/{{range .Crumbs}}<a href="{{.URL}}">{{.Name}}</a>/{{end}}</span>
77 <span class="spacer"></span>
88 <span class="clone">clone: <code>git clone {{.CloneURL}}</code></span>
99 </div>
1010 <table class="tree">
11<tr class="cols"><th>name</th><th class="size">size</th><th class="mode">mode</th></tr>
1112 {{range .Entries}}<tr>
1213 {{if eq .Type "tree"}}<td class="name"><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/tree/{{$.Ref}}/{{$.Prefix}}{{.Name}}">{{.Name}}/</a></td><td class="size"></td>
1314 {{else}}<td class="name"><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/blob/{{$.Ref}}/{{$.Prefix}}{{.Name}}">{{.Name}}</a></td><td class="size">{{.Size}}</td>{{end}}
1415 <td class="mode">{{.Mode}}</td>
1516 </tr>
16{{else}}<tr><td class="name empty">this repository is empty — push something:<br><code>git remote add origin {{.CloneURL}}</code></td></tr>{{end}}
17{{else}}<tr><td class="name empty" colspan="3">this repository is empty — push something:<br><code>git remote add origin {{.CloneURL}}</code></td></tr>{{end}}
1718 </table>
1819 {{if .ReadmeHTML}}<section class="readme">
19<div class="cardhead">{{.ReadmeName}}</div>
20<div class="cardhead"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/blob/{{.Ref}}/{{.Prefix}}{{.ReadmeName}}">{{.ReadmeName}}</a></div>
2021 <div class="rendered">{{.ReadmeHTML}}</div>
2122 </section>{{end}}
2223 {{end}}