+8
-4
cmd/legit/main.go
+8
-4
cmd/legit/main.go
···
3
import (
4
"flag"
5
"fmt"
6
-
"html/template"
7
"log"
8
"net/http"
9
-
"path/filepath"
10
11
"github.com/icyphox/bild/legit/config"
12
"github.com/icyphox/bild/legit/routes"
···
16
var cfg string
17
flag.StringVar(&cfg, "config", "./config.yaml", "path to config file")
18
flag.Parse()
19
20
c, err := config.Read(cfg)
21
if err != nil {
22
log.Fatal(err)
23
}
24
25
-
t := template.Must(template.ParseGlob(filepath.Join(c.Dirs.Templates, "*")))
26
27
-
mux := routes.Handlers(c, t)
28
addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
29
log.Println("starting server on", addr)
30
log.Fatal(http.ListenAndServe(addr, mux))
···
3
import (
4
"flag"
5
"fmt"
6
"log"
7
+
"log/slog"
8
"net/http"
9
+
"os"
10
11
"github.com/icyphox/bild/legit/config"
12
"github.com/icyphox/bild/legit/routes"
···
16
var cfg string
17
flag.StringVar(&cfg, "config", "./config.yaml", "path to config file")
18
flag.Parse()
19
+
20
+
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil)))
21
22
c, err := config.Read(cfg)
23
if err != nil {
24
log.Fatal(err)
25
}
26
27
+
mux, err := routes.Setup(c)
28
+
if err != nil {
29
+
log.Fatal(err)
30
+
}
31
32
addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
33
log.Println("starting server on", addr)
34
log.Fatal(http.ListenAndServe(addr, mux))
+1
config.yaml
+1
config.yaml
+1
go.mod
+1
go.mod
+2
go.sum
+2
go.sum
···
78
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
79
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
80
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
81
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
82
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
83
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
···
78
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
79
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
80
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
81
+
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
82
+
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
83
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
84
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
85
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
+4
-3
legit/config/config.go
+4
-3
legit/config/config.go
+32
legit/db/init.go
+32
legit/db/init.go
···
···
1
+
package db
2
+
3
+
import (
4
+
"database/sql"
5
+
6
+
_ "github.com/mattn/go-sqlite3"
7
+
)
8
+
9
+
type DB struct {
10
+
db *sql.DB
11
+
}
12
+
13
+
func Setup(dbPath string) (*DB, error) {
14
+
db, err := sql.Open("sqlite3", dbPath)
15
+
if err != nil {
16
+
return nil, err
17
+
}
18
+
19
+
_, err = db.Exec(`
20
+
create table if not exists public_keys (
21
+
id integer primary key autoincrement,
22
+
did text not null,
23
+
name text not null,
24
+
key text not null,
25
+
unique(did, name, key))
26
+
`)
27
+
if err != nil {
28
+
return nil, err
29
+
}
30
+
31
+
return &DB{db: db}, nil
32
+
}
+23
legit/db/pubkeys.go
+23
legit/db/pubkeys.go
···
···
1
+
package db
2
+
3
+
func (d *DB) AddPublicKey(did, name, key string) error {
4
+
query := `insert into public_keys (did, name, key) values (?, ?, ?)`
5
+
_, err := d.db.Exec(query, did, name, key)
6
+
return err
7
+
}
8
+
9
+
func (d *DB) RemovePublicKey(did string) error {
10
+
query := `delete from public_keys where did = ?`
11
+
_, err := d.db.Exec(query, did)
12
+
return err
13
+
}
14
+
15
+
func (d *DB) GetPublicKey(did string) (string, error) {
16
+
var key string
17
+
query := `select key from public_keys where did = ?`
18
+
err := d.db.QueryRow(query, did).Scan(&key)
19
+
if err != nil {
20
+
return "", err
21
+
}
22
+
return key, nil
23
+
}
+2
-2
legit/routes/git.go
+2
-2
legit/routes/git.go
···
10
"github.com/icyphox/bild/legit/git/service"
11
)
12
13
-
func (d *deps) InfoRefs(w http.ResponseWriter, r *http.Request) {
14
name := uniqueName(r)
15
name = filepath.Clean(name)
16
···
31
}
32
}
33
34
-
func (d *deps) UploadPack(w http.ResponseWriter, r *http.Request) {
35
name := uniqueName(r)
36
name = filepath.Clean(name)
37
···
10
"github.com/icyphox/bild/legit/git/service"
11
)
12
13
+
func (d *Handle) InfoRefs(w http.ResponseWriter, r *http.Request) {
14
name := uniqueName(r)
15
name = filepath.Clean(name)
16
···
31
}
32
}
33
34
+
func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) {
35
name := uniqueName(r)
36
name = filepath.Clean(name)
37
+41
-20
legit/routes/handler.go
+41
-20
legit/routes/handler.go
···
1
package routes
2
3
import (
4
"html/template"
5
"net/http"
6
7
"github.com/go-chi/chi/v5"
8
"github.com/icyphox/bild/legit/config"
9
)
10
11
// Checks for gitprotocol-http(5) specific smells; if found, passes
12
// the request on to the git http service, else render the web frontend.
13
-
func (d *deps) Multiplex(w http.ResponseWriter, r *http.Request) {
14
path := chi.URLParam(r, "*")
15
16
if r.URL.RawQuery == "service=git-receive-pack" {
···
22
if path == "info/refs" &&
23
r.URL.RawQuery == "service=git-upload-pack" &&
24
r.Method == "GET" {
25
-
d.InfoRefs(w, r)
26
} else if path == "git-upload-pack" && r.Method == "POST" {
27
-
d.UploadPack(w, r)
28
} else if r.Method == "GET" {
29
-
d.RepoIndex(w, r)
30
}
31
}
32
33
-
func Handlers(c *config.Config, t *template.Template) http.Handler {
34
r := chi.NewRouter()
35
-
d := deps{c, t}
36
37
-
r.Get("/login", d.Login)
38
-
r.Get("/static/{file}", d.ServeStatic)
39
40
r.Route("/@{user}", func(r chi.Router) {
41
-
r.Get("/", d.Index)
42
r.Route("/{name}", func(r chi.Router) {
43
-
r.Get("/", d.Multiplex)
44
-
r.Post("/", d.Multiplex)
45
46
r.Route("/tree/{ref}", func(r chi.Router) {
47
-
r.Get("/*", d.RepoTree)
48
})
49
50
r.Route("/blob/{ref}", func(r chi.Router) {
51
-
r.Get("/*", d.FileContent)
52
})
53
54
-
r.Get("/log/{ref}", d.Log)
55
-
r.Get("/archive/{file}", d.Archive)
56
-
r.Get("/commit/{ref}", d.Diff)
57
-
r.Get("/refs/", d.Refs)
58
59
// Catch-all routes
60
-
r.Get("/*", d.Multiplex)
61
-
r.Post("/*", d.Multiplex)
62
})
63
})
64
65
-
return r
66
}
···
1
package routes
2
3
import (
4
+
"fmt"
5
"html/template"
6
"net/http"
7
+
"path/filepath"
8
9
"github.com/go-chi/chi/v5"
10
"github.com/icyphox/bild/legit/config"
11
+
"github.com/icyphox/bild/legit/db"
12
)
13
14
// Checks for gitprotocol-http(5) specific smells; if found, passes
15
// the request on to the git http service, else render the web frontend.
16
+
func (h *Handle) Multiplex(w http.ResponseWriter, r *http.Request) {
17
path := chi.URLParam(r, "*")
18
19
if r.URL.RawQuery == "service=git-receive-pack" {
···
25
if path == "info/refs" &&
26
r.URL.RawQuery == "service=git-upload-pack" &&
27
r.Method == "GET" {
28
+
h.InfoRefs(w, r)
29
} else if path == "git-upload-pack" && r.Method == "POST" {
30
+
h.UploadPack(w, r)
31
} else if r.Method == "GET" {
32
+
h.RepoIndex(w, r)
33
}
34
}
35
36
+
func Setup(c *config.Config) (http.Handler, error) {
37
r := chi.NewRouter()
38
+
t := template.Must(template.ParseGlob(filepath.Join(c.Dirs.Templates, "*")))
39
+
db, err := db.Setup(c.Server.DBPath)
40
+
41
+
if err != nil {
42
+
return nil, fmt.Errorf("failed to setup db: %w", err)
43
+
}
44
+
45
+
h := Handle{
46
+
c: c,
47
+
t: t,
48
+
db: db,
49
+
}
50
+
51
+
r.Get("/login", h.Login)
52
+
r.Get("/static/{file}", h.ServeStatic)
53
54
+
r.Route("/settings", func(r chi.Router) {
55
+
r.Get("/keys", h.Keys)
56
+
r.Put("/keys", h.Keys)
57
+
})
58
59
r.Route("/@{user}", func(r chi.Router) {
60
+
r.Get("/", h.Index)
61
+
62
+
// Repo routes
63
r.Route("/{name}", func(r chi.Router) {
64
+
r.Get("/", h.Multiplex)
65
+
r.Post("/", h.Multiplex)
66
67
r.Route("/tree/{ref}", func(r chi.Router) {
68
+
r.Get("/*", h.RepoTree)
69
})
70
71
r.Route("/blob/{ref}", func(r chi.Router) {
72
+
r.Get("/*", h.FileContent)
73
})
74
75
+
r.Get("/log/{ref}", h.Log)
76
+
r.Get("/archive/{file}", h.Archive)
77
+
r.Get("/commit/{ref}", h.Diff)
78
+
r.Get("/refs/", h.Refs)
79
80
// Catch-all routes
81
+
r.Get("/*", h.Multiplex)
82
+
r.Post("/*", h.Multiplex)
83
})
84
})
85
86
+
return r, nil
87
}
+98
-73
legit/routes/routes.go
+98
-73
legit/routes/routes.go
···
16
"github.com/dustin/go-humanize"
17
"github.com/go-chi/chi/v5"
18
"github.com/icyphox/bild/legit/config"
19
"github.com/icyphox/bild/legit/git"
20
"github.com/russross/blackfriday/v2"
21
)
22
23
-
type deps struct {
24
-
c *config.Config
25
-
t *template.Template
26
}
27
28
-
func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
29
user := chi.URLParam(r, "user")
30
-
path := filepath.Join(d.c.Repo.ScanPath, user)
31
dirs, err := os.ReadDir(path)
32
if err != nil {
33
-
d.Write500(w)
34
log.Printf("reading scan path: %s", err)
35
return
36
}
···
44
45
for _, dir := range dirs {
46
name := dir.Name()
47
-
if !dir.IsDir() || d.isIgnored(name) || d.isUnlisted(name) {
48
continue
49
}
50
···
56
57
c, err := gr.LastCommit()
58
if err != nil {
59
-
d.Write500(w)
60
log.Println(err)
61
return
62
}
···
75
})
76
77
data := make(map[string]interface{})
78
-
data["meta"] = d.c.Meta
79
data["info"] = infos
80
81
-
if err := d.t.ExecuteTemplate(w, "index", data); err != nil {
82
log.Println(err)
83
return
84
}
85
}
86
87
-
func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
88
name := uniqueName(r)
89
-
if d.isIgnored(name) {
90
-
d.Write404(w)
91
return
92
}
93
94
name = filepath.Clean(name)
95
-
path := filepath.Join(d.c.Repo.ScanPath, name)
96
97
fmt.Println(path)
98
gr, err := git.Open(path, "")
99
if err != nil {
100
-
d.Write404(w)
101
return
102
}
103
commits, err := gr.Commits()
104
if err != nil {
105
-
d.Write500(w)
106
log.Println(err)
107
return
108
}
109
110
var readmeContent template.HTML
111
-
for _, readme := range d.c.Repo.Readme {
112
ext := filepath.Ext(readme)
113
content, _ := gr.FileContent(readme)
114
if len(content) > 0 {
···
134
log.Printf("no readme found for %s", name)
135
}
136
137
-
mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
138
if err != nil {
139
-
d.Write500(w)
140
log.Println(err)
141
return
142
}
···
152
data["readme"] = readmeContent
153
data["commits"] = commits
154
data["desc"] = getDescription(path)
155
-
data["servername"] = d.c.Server.Name
156
-
data["meta"] = d.c.Meta
157
data["gomod"] = isGoModule(gr)
158
159
-
if err := d.t.ExecuteTemplate(w, "repo", data); err != nil {
160
log.Println(err)
161
return
162
}
···
164
return
165
}
166
167
-
func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
168
name := uniqueName(r)
169
-
if d.isIgnored(name) {
170
-
d.Write404(w)
171
return
172
}
173
treePath := chi.URLParam(r, "*")
174
ref := chi.URLParam(r, "ref")
175
176
name = filepath.Clean(name)
177
-
path := filepath.Join(d.c.Repo.ScanPath, name)
178
fmt.Println(path)
179
gr, err := git.Open(path, ref)
180
if err != nil {
181
-
d.Write404(w)
182
return
183
}
184
185
files, err := gr.FileTree(treePath)
186
if err != nil {
187
-
d.Write500(w)
188
log.Println(err)
189
return
190
}
···
197
data["desc"] = getDescription(path)
198
data["dotdot"] = filepath.Dir(treePath)
199
200
-
d.listFiles(files, data, w)
201
return
202
}
203
204
-
func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
205
var raw bool
206
if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
207
raw = rawParam
···
209
210
name := uniqueName(r)
211
212
-
if d.isIgnored(name) {
213
-
d.Write404(w)
214
return
215
}
216
treePath := chi.URLParam(r, "*")
217
ref := chi.URLParam(r, "ref")
218
219
name = filepath.Clean(name)
220
-
path := filepath.Join(d.c.Repo.ScanPath, name)
221
gr, err := git.Open(path, ref)
222
if err != nil {
223
-
d.Write404(w)
224
return
225
}
226
227
contents, err := gr.FileContent(treePath)
228
if err != nil {
229
-
d.Write500(w)
230
return
231
}
232
data := make(map[string]any)
···
239
safe := sanitize([]byte(contents))
240
241
if raw {
242
-
d.showRaw(string(safe), w)
243
} else {
244
-
if d.c.Meta.SyntaxHighlight == "" {
245
-
d.showFile(string(safe), data, w)
246
} else {
247
-
d.showFileWithHighlight(treePath, string(safe), data, w)
248
}
249
}
250
}
251
252
-
func (d *deps) Archive(w http.ResponseWriter, r *http.Request) {
253
name := uniqueName(r)
254
-
if d.isIgnored(name) {
255
-
d.Write404(w)
256
return
257
}
258
···
260
261
// TODO: extend this to add more files compression (e.g.: xz)
262
if !strings.HasSuffix(file, ".tar.gz") {
263
-
d.Write404(w)
264
return
265
}
266
···
272
setContentDisposition(w, filename)
273
setGZipMIME(w)
274
275
-
path := filepath.Join(d.c.Repo.ScanPath, name)
276
gr, err := git.Open(path, ref)
277
if err != nil {
278
-
d.Write404(w)
279
return
280
}
281
···
300
}
301
}
302
303
-
func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
304
name := uniqueName(r)
305
-
if d.isIgnored(name) {
306
-
d.Write404(w)
307
return
308
}
309
ref := chi.URLParam(r, "ref")
310
311
-
path := filepath.Join(d.c.Repo.ScanPath, name)
312
gr, err := git.Open(path, ref)
313
if err != nil {
314
-
d.Write404(w)
315
return
316
}
317
318
commits, err := gr.Commits()
319
if err != nil {
320
-
d.Write500(w)
321
log.Println(err)
322
return
323
}
324
325
data := make(map[string]interface{})
326
data["commits"] = commits
327
-
data["meta"] = d.c.Meta
328
data["name"] = name
329
data["displayname"] = getDisplayName(name)
330
data["ref"] = ref
331
data["desc"] = getDescription(path)
332
data["log"] = true
333
334
-
if err := d.t.ExecuteTemplate(w, "log", data); err != nil {
335
log.Println(err)
336
return
337
}
338
}
339
340
-
func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
341
name := uniqueName(r)
342
-
if d.isIgnored(name) {
343
-
d.Write404(w)
344
return
345
}
346
ref := chi.URLParam(r, "ref")
347
348
-
path := filepath.Join(d.c.Repo.ScanPath, name)
349
gr, err := git.Open(path, ref)
350
if err != nil {
351
-
d.Write404(w)
352
return
353
}
354
355
diff, err := gr.Diff()
356
if err != nil {
357
-
d.Write500(w)
358
log.Println(err)
359
return
360
}
···
364
data["commit"] = diff.Commit
365
data["stat"] = diff.Stat
366
data["diff"] = diff.Diff
367
-
data["meta"] = d.c.Meta
368
data["name"] = name
369
data["displayname"] = getDisplayName(name)
370
data["ref"] = ref
371
data["desc"] = getDescription(path)
372
373
-
if err := d.t.ExecuteTemplate(w, "commit", data); err != nil {
374
log.Println(err)
375
return
376
}
377
}
378
379
-
func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
380
name := chi.URLParam(r, "name")
381
-
if d.isIgnored(name) {
382
-
d.Write404(w)
383
return
384
}
385
386
-
path := filepath.Join(d.c.Repo.ScanPath, name)
387
gr, err := git.Open(path, "")
388
if err != nil {
389
-
d.Write404(w)
390
return
391
}
392
···
399
branches, err := gr.Branches()
400
if err != nil {
401
log.Println(err)
402
-
d.Write500(w)
403
return
404
}
405
406
data := make(map[string]interface{})
407
408
-
data["meta"] = d.c.Meta
409
data["name"] = name
410
data["displayname"] = getDisplayName(name)
411
data["branches"] = branches
412
data["tags"] = tags
413
data["desc"] = getDescription(path)
414
415
-
if err := d.t.ExecuteTemplate(w, "refs", data); err != nil {
416
log.Println(err)
417
return
418
}
419
}
420
421
-
func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
422
f := chi.URLParam(r, "file")
423
-
f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))
424
425
http.ServeFile(w, r, f)
426
}
427
428
-
func (d *deps) Login(w http.ResponseWriter, r *http.Request) {
429
-
if err := d.t.ExecuteTemplate(w, "login", nil); err != nil {
430
log.Println(err)
431
return
432
}
433
}
···
16
"github.com/dustin/go-humanize"
17
"github.com/go-chi/chi/v5"
18
"github.com/icyphox/bild/legit/config"
19
+
"github.com/icyphox/bild/legit/db"
20
"github.com/icyphox/bild/legit/git"
21
"github.com/russross/blackfriday/v2"
22
)
23
24
+
type Handle struct {
25
+
c *config.Config
26
+
t *template.Template
27
+
db *db.DB
28
}
29
30
+
func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
31
user := chi.URLParam(r, "user")
32
+
path := filepath.Join(h.c.Repo.ScanPath, user)
33
dirs, err := os.ReadDir(path)
34
if err != nil {
35
+
h.Write500(w)
36
log.Printf("reading scan path: %s", err)
37
return
38
}
···
46
47
for _, dir := range dirs {
48
name := dir.Name()
49
+
if !dir.IsDir() || h.isIgnored(name) || h.isUnlisted(name) {
50
continue
51
}
52
···
58
59
c, err := gr.LastCommit()
60
if err != nil {
61
+
h.Write500(w)
62
log.Println(err)
63
return
64
}
···
77
})
78
79
data := make(map[string]interface{})
80
+
data["meta"] = h.c.Meta
81
data["info"] = infos
82
83
+
if err := h.t.ExecuteTemplate(w, "index", data); err != nil {
84
log.Println(err)
85
return
86
}
87
}
88
89
+
func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
90
name := uniqueName(r)
91
+
if h.isIgnored(name) {
92
+
h.Write404(w)
93
return
94
}
95
96
name = filepath.Clean(name)
97
+
path := filepath.Join(h.c.Repo.ScanPath, name)
98
99
fmt.Println(path)
100
gr, err := git.Open(path, "")
101
if err != nil {
102
+
h.Write404(w)
103
return
104
}
105
commits, err := gr.Commits()
106
if err != nil {
107
+
h.Write500(w)
108
log.Println(err)
109
return
110
}
111
112
var readmeContent template.HTML
113
+
for _, readme := range h.c.Repo.Readme {
114
ext := filepath.Ext(readme)
115
content, _ := gr.FileContent(readme)
116
if len(content) > 0 {
···
136
log.Printf("no readme found for %s", name)
137
}
138
139
+
mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch)
140
if err != nil {
141
+
h.Write500(w)
142
log.Println(err)
143
return
144
}
···
154
data["readme"] = readmeContent
155
data["commits"] = commits
156
data["desc"] = getDescription(path)
157
+
data["servername"] = h.c.Server.Name
158
+
data["meta"] = h.c.Meta
159
data["gomod"] = isGoModule(gr)
160
161
+
if err := h.t.ExecuteTemplate(w, "repo", data); err != nil {
162
log.Println(err)
163
return
164
}
···
166
return
167
}
168
169
+
func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
170
name := uniqueName(r)
171
+
if h.isIgnored(name) {
172
+
h.Write404(w)
173
return
174
}
175
treePath := chi.URLParam(r, "*")
176
ref := chi.URLParam(r, "ref")
177
178
name = filepath.Clean(name)
179
+
path := filepath.Join(h.c.Repo.ScanPath, name)
180
fmt.Println(path)
181
gr, err := git.Open(path, ref)
182
if err != nil {
183
+
h.Write404(w)
184
return
185
}
186
187
files, err := gr.FileTree(treePath)
188
if err != nil {
189
+
h.Write500(w)
190
log.Println(err)
191
return
192
}
···
199
data["desc"] = getDescription(path)
200
data["dotdot"] = filepath.Dir(treePath)
201
202
+
h.listFiles(files, data, w)
203
return
204
}
205
206
+
func (h *Handle) FileContent(w http.ResponseWriter, r *http.Request) {
207
var raw bool
208
if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
209
raw = rawParam
···
211
212
name := uniqueName(r)
213
214
+
if h.isIgnored(name) {
215
+
h.Write404(w)
216
return
217
}
218
treePath := chi.URLParam(r, "*")
219
ref := chi.URLParam(r, "ref")
220
221
name = filepath.Clean(name)
222
+
path := filepath.Join(h.c.Repo.ScanPath, name)
223
gr, err := git.Open(path, ref)
224
if err != nil {
225
+
h.Write404(w)
226
return
227
}
228
229
contents, err := gr.FileContent(treePath)
230
if err != nil {
231
+
h.Write500(w)
232
return
233
}
234
data := make(map[string]any)
···
241
safe := sanitize([]byte(contents))
242
243
if raw {
244
+
h.showRaw(string(safe), w)
245
} else {
246
+
if h.c.Meta.SyntaxHighlight == "" {
247
+
h.showFile(string(safe), data, w)
248
} else {
249
+
h.showFileWithHighlight(treePath, string(safe), data, w)
250
}
251
}
252
}
253
254
+
func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
255
name := uniqueName(r)
256
+
if h.isIgnored(name) {
257
+
h.Write404(w)
258
return
259
}
260
···
262
263
// TODO: extend this to add more files compression (e.g.: xz)
264
if !strings.HasSuffix(file, ".tar.gz") {
265
+
h.Write404(w)
266
return
267
}
268
···
274
setContentDisposition(w, filename)
275
setGZipMIME(w)
276
277
+
path := filepath.Join(h.c.Repo.ScanPath, name)
278
gr, err := git.Open(path, ref)
279
if err != nil {
280
+
h.Write404(w)
281
return
282
}
283
···
302
}
303
}
304
305
+
func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
306
name := uniqueName(r)
307
+
if h.isIgnored(name) {
308
+
h.Write404(w)
309
return
310
}
311
ref := chi.URLParam(r, "ref")
312
313
+
path := filepath.Join(h.c.Repo.ScanPath, name)
314
gr, err := git.Open(path, ref)
315
if err != nil {
316
+
h.Write404(w)
317
return
318
}
319
320
commits, err := gr.Commits()
321
if err != nil {
322
+
h.Write500(w)
323
log.Println(err)
324
return
325
}
326
327
data := make(map[string]interface{})
328
data["commits"] = commits
329
+
data["meta"] = h.c.Meta
330
data["name"] = name
331
data["displayname"] = getDisplayName(name)
332
data["ref"] = ref
333
data["desc"] = getDescription(path)
334
data["log"] = true
335
336
+
if err := h.t.ExecuteTemplate(w, "log", data); err != nil {
337
log.Println(err)
338
return
339
}
340
}
341
342
+
func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
343
name := uniqueName(r)
344
+
if h.isIgnored(name) {
345
+
h.Write404(w)
346
return
347
}
348
ref := chi.URLParam(r, "ref")
349
350
+
path := filepath.Join(h.c.Repo.ScanPath, name)
351
gr, err := git.Open(path, ref)
352
if err != nil {
353
+
h.Write404(w)
354
return
355
}
356
357
diff, err := gr.Diff()
358
if err != nil {
359
+
h.Write500(w)
360
log.Println(err)
361
return
362
}
···
366
data["commit"] = diff.Commit
367
data["stat"] = diff.Stat
368
data["diff"] = diff.Diff
369
+
data["meta"] = h.c.Meta
370
data["name"] = name
371
data["displayname"] = getDisplayName(name)
372
data["ref"] = ref
373
data["desc"] = getDescription(path)
374
375
+
if err := h.t.ExecuteTemplate(w, "commit", data); err != nil {
376
log.Println(err)
377
return
378
}
379
}
380
381
+
func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) {
382
name := chi.URLParam(r, "name")
383
+
if h.isIgnored(name) {
384
+
h.Write404(w)
385
return
386
}
387
388
+
path := filepath.Join(h.c.Repo.ScanPath, name)
389
gr, err := git.Open(path, "")
390
if err != nil {
391
+
h.Write404(w)
392
return
393
}
394
···
401
branches, err := gr.Branches()
402
if err != nil {
403
log.Println(err)
404
+
h.Write500(w)
405
return
406
}
407
408
data := make(map[string]interface{})
409
410
+
data["meta"] = h.c.Meta
411
data["name"] = name
412
data["displayname"] = getDisplayName(name)
413
data["branches"] = branches
414
data["tags"] = tags
415
data["desc"] = getDescription(path)
416
417
+
if err := h.t.ExecuteTemplate(w, "refs", data); err != nil {
418
log.Println(err)
419
return
420
}
421
}
422
423
+
func (h *Handle) ServeStatic(w http.ResponseWriter, r *http.Request) {
424
f := chi.URLParam(r, "file")
425
+
f = filepath.Clean(filepath.Join(h.c.Dirs.Static, f))
426
427
http.ServeFile(w, r, f)
428
}
429
430
+
func (h *Handle) Login(w http.ResponseWriter, r *http.Request) {
431
+
if err := h.t.ExecuteTemplate(w, "login", nil); err != nil {
432
log.Println(err)
433
return
434
}
435
}
436
+
437
+
func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
438
+
switch r.Method {
439
+
case http.MethodGet:
440
+
// TODO: fetch keys from db
441
+
if err := h.t.ExecuteTemplate(w, "keys", nil); err != nil {
442
+
log.Println(err)
443
+
return
444
+
}
445
+
case http.MethodPut:
446
+
key := r.FormValue("key")
447
+
name := r.FormValue("name")
448
+
// TODO: add did here
449
+
if err := h.db.AddPublicKey("did:ashtntnashtx", name, key); err != nil {
450
+
h.WriteOOBNotice(w, "keys", "Failed to add key")
451
+
log.Printf("adding public key: %s", err)
452
+
return
453
+
}
454
+
455
+
h.WriteOOBNotice(w, "keys", "Key added!")
456
+
return
457
+
}
458
+
}
+19
-14
legit/routes/template.go
+19
-14
legit/routes/template.go
···
2
3
import (
4
"bytes"
5
"html/template"
6
"io"
7
"log"
···
15
"github.com/icyphox/bild/legit/git"
16
)
17
18
-
func (d *deps) Write404(w http.ResponseWriter) {
19
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
20
-
t := template.Must(template.ParseGlob(tpath))
21
w.WriteHeader(404)
22
-
if err := t.ExecuteTemplate(w, "404", nil); err != nil {
23
log.Printf("404 template: %s", err)
24
}
25
}
26
27
-
func (d *deps) Write500(w http.ResponseWriter) {
28
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
29
-
t := template.Must(template.ParseGlob(tpath))
30
w.WriteHeader(500)
31
-
if err := t.ExecuteTemplate(w, "500", nil); err != nil {
32
log.Printf("500 template: %s", err)
33
}
34
}
35
36
-
func (d *deps) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) {
37
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
38
t := template.Must(template.ParseGlob(tpath))
39
40
data["files"] = files
41
-
data["meta"] = d.c.Meta
42
43
if err := t.ExecuteTemplate(w, "tree", data); err != nil {
44
log.Println(err)
···
72
}
73
}
74
75
-
func (d *deps) showFileWithHighlight(name, content string, data map[string]any, w http.ResponseWriter) {
76
tpath := filepath.Join(d.c.Dirs.Templates, "*")
77
t := template.Must(template.ParseGlob(tpath))
78
···
114
}
115
}
116
117
-
func (d *deps) showFile(content string, data map[string]any, w http.ResponseWriter) {
118
tpath := filepath.Join(d.c.Dirs.Templates, "*")
119
t := template.Must(template.ParseGlob(tpath))
120
···
142
}
143
}
144
145
-
func (d *deps) showRaw(content string, w http.ResponseWriter) {
146
w.WriteHeader(http.StatusOK)
147
w.Header().Set("Content-Type", "text/plain")
148
w.Write([]byte(content))
···
2
3
import (
4
"bytes"
5
+
"fmt"
6
"html/template"
7
"io"
8
"log"
···
16
"github.com/icyphox/bild/legit/git"
17
)
18
19
+
func (h *Handle) Write404(w http.ResponseWriter) {
20
w.WriteHeader(404)
21
+
if err := h.t.ExecuteTemplate(w, "404", nil); err != nil {
22
log.Printf("404 template: %s", err)
23
}
24
}
25
26
+
func (h *Handle) Write500(w http.ResponseWriter) {
27
w.WriteHeader(500)
28
+
if err := h.t.ExecuteTemplate(w, "500", nil); err != nil {
29
log.Printf("500 template: %s", err)
30
}
31
}
32
33
+
func (h *Handle) WriteOOBNotice(w http.ResponseWriter, id, msg string) {
34
+
html := fmt.Sprintf(`<span id="%s" hx-swap-oob="innerHTML">%s</span>`, id, msg)
35
+
36
+
w.Header().Set("Content-Type", "text/html")
37
+
w.WriteHeader(http.StatusOK)
38
+
w.Write([]byte(html))
39
+
}
40
+
41
+
func (h *Handle) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) {
42
+
tpath := filepath.Join(h.c.Dirs.Templates, "*")
43
t := template.Must(template.ParseGlob(tpath))
44
45
data["files"] = files
46
+
data["meta"] = h.c.Meta
47
48
if err := t.ExecuteTemplate(w, "tree", data); err != nil {
49
log.Println(err)
···
77
}
78
}
79
80
+
func (d *Handle) showFileWithHighlight(name, content string, data map[string]any, w http.ResponseWriter) {
81
tpath := filepath.Join(d.c.Dirs.Templates, "*")
82
t := template.Must(template.ParseGlob(tpath))
83
···
119
}
120
}
121
122
+
func (d *Handle) showFile(content string, data map[string]any, w http.ResponseWriter) {
123
tpath := filepath.Join(d.c.Dirs.Templates, "*")
124
t := template.Must(template.ParseGlob(tpath))
125
···
147
}
148
}
149
150
+
func (d *Handle) showRaw(content string, w http.ResponseWriter) {
151
w.WriteHeader(http.StatusOK)
152
w.Header().Set("Content-Type", "text/plain")
153
w.Write([]byte(content))
+6
-6
legit/routes/util.go
+6
-6
legit/routes/util.go
···
43
return
44
}
45
46
-
func (d *deps) isUnlisted(name string) bool {
47
-
for _, i := range d.c.Repo.Unlisted {
48
if name == i {
49
return true
50
}
···
53
return false
54
}
55
56
-
func (d *deps) isIgnored(name string) bool {
57
-
for _, i := range d.c.Repo.Ignore {
58
if name == i {
59
return true
60
}
···
69
Category string
70
}
71
72
-
func (d *deps) getAllRepos() ([]repoInfo, error) {
73
repos := []repoInfo{}
74
max := strings.Count(d.c.Repo.ScanPath, string(os.PathSeparator)) + 2
75
···
113
return repos, err
114
}
115
116
-
func (d *deps) category(path string) string {
117
return strings.TrimPrefix(filepath.Dir(strings.TrimPrefix(path, d.c.Repo.ScanPath)), string(os.PathSeparator))
118
}
119
···
43
return
44
}
45
46
+
func (h *Handle) isUnlisted(name string) bool {
47
+
for _, i := range h.c.Repo.Unlisted {
48
if name == i {
49
return true
50
}
···
53
return false
54
}
55
56
+
func (h *Handle) isIgnored(name string) bool {
57
+
for _, i := range h.c.Repo.Ignore {
58
if name == i {
59
return true
60
}
···
69
Category string
70
}
71
72
+
func (d *Handle) getAllRepos() ([]repoInfo, error) {
73
repos := []repoInfo{}
74
max := strings.Count(d.c.Repo.ScanPath, string(os.PathSeparator)) + 2
75
···
113
return repos, err
114
}
115
116
+
func (d *Handle) category(path string) string {
117
return strings.TrimPrefix(filepath.Dir(strings.TrimPrefix(path, d.c.Repo.ScanPath)), string(os.PathSeparator))
118
}
119
+177
-167
legit/static/style.css
+177
-167
legit/static/style.css
···
1
:root {
2
-
--white: #fff;
3
-
--light: #f4f4f4;
4
-
--cyan: #509c93;
5
-
--light-gray: #eee;
6
-
--medium-gray: #ddd;
7
-
--gray: #6a6a6a;
8
-
--dark: #444;
9
-
--darker: #222;
10
11
-
--sans-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto", "Segoe UI", sans-serif;
12
-
--display-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto", "Segoe UI", sans-serif;
13
-
--mono-font: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', 'Roboto Mono', Menlo, Consolas, monospace;
14
}
15
16
@media (prefers-color-scheme: dark) {
17
-
:root {
18
-
color-scheme: dark light;
19
-
--light: #181818;
20
-
--cyan: #76c7c0;
21
-
--light-gray: #333;
22
-
--medium-gray: #444;
23
-
--gray: #aaa;
24
-
--dark: #ddd;
25
-
--darker: #f4f4f4;
26
-
}
27
}
28
29
html {
30
-
background: var(--white);
31
-
-webkit-text-size-adjust: none;
32
-
font-family: var(--sans-font);
33
-
font-weight: 380;
34
}
35
36
pre {
37
-
font-family: var(--mono-font);
38
}
39
40
::selection {
41
-
background: var(--medium-gray);
42
-
opacity: 0.3;
43
}
44
45
* {
46
-
box-sizing: border-box;
47
-
padding: 0;
48
-
margin: 0;
49
}
50
51
body {
52
-
max-width: 1000px;
53
-
padding: 0 13px;
54
-
margin: 40px auto;
55
}
56
57
-
main, footer {
58
-
font-size: 1rem;
59
-
padding: 0;
60
-
line-height: 160%;
61
}
62
63
-
header h1, h2, h3 {
64
-
font-family: var(--display-font);
65
}
66
67
h2 {
68
-
font-weight: 400;
69
}
70
71
strong {
72
-
font-weight: 500;
73
}
74
75
main h1 {
76
-
padding: 10px 0 10px 0;
77
}
78
79
main h2 {
80
-
font-size: 18px;
81
}
82
83
-
main h2, h3 {
84
-
padding: 20px 0 15px 0;
85
}
86
87
nav {
88
-
padding: 0.4rem 0 1.5rem 0;
89
}
90
91
nav ul {
92
-
padding: 0;
93
-
margin: 0;
94
-
list-style: none;
95
-
padding-bottom: 20px;
96
}
97
98
nav ul li {
99
-
padding-right: 10px;
100
-
display: inline-block;
101
}
102
103
a {
104
-
margin: 0;
105
-
padding: 0;
106
-
box-sizing: border-box;
107
-
text-decoration: none;
108
-
word-wrap: break-word;
109
}
110
111
a {
112
-
color: var(--darker);
113
-
border-bottom: 1.5px solid var(--medium-gray);
114
}
115
116
a:hover {
117
-
border-bottom: 1.5px solid var(--gray);
118
}
119
120
.index {
121
-
padding-top: 2em;
122
-
display: grid;
123
-
grid-template-columns: 6em 1fr minmax(0, 7em);
124
-
grid-row-gap: 0.5em;
125
-
min-width: 0;
126
}
127
128
.clone-url {
129
-
padding-top: 2rem;
130
}
131
132
.clone-url pre {
133
-
color: var(--dark);
134
-
white-space: pre-wrap;
135
}
136
137
.desc {
138
-
font-weight: normal;
139
-
color: var(--gray);
140
-
font-style: italic;
141
}
142
143
.tree {
144
-
display: grid;
145
-
grid-template-columns: 10ch auto 1fr;
146
-
grid-row-gap: 0.5em;
147
-
grid-column-gap: 1em;
148
-
min-width: 0;
149
}
150
151
.log {
152
-
display: grid;
153
-
grid-template-columns: 20rem minmax(0, 1fr);
154
-
grid-row-gap: 0.8em;
155
-
grid-column-gap: 8rem;
156
-
margin-bottom: 2em;
157
-
padding-bottom: 1em;
158
-
border-bottom: 1.5px solid var(--medium-gray);
159
}
160
161
.log pre {
162
-
white-space: pre-wrap;
163
}
164
165
-
.mode, .size {
166
-
font-family: var(--mono-font);
167
}
168
.size {
169
-
text-align: right;
170
}
171
172
.readme pre {
173
-
white-space: pre-wrap;
174
-
overflow-x: auto;
175
}
176
177
.readme {
178
-
background: var(--light-gray);
179
-
padding: 0.5rem;
180
}
181
182
.readme ul {
183
-
padding: revert;
184
}
185
186
.readme img {
187
-
max-width: 100%;
188
}
189
190
.diff {
191
-
margin: 1rem 0 1rem 0;
192
-
padding: 1rem 0 1rem 0;
193
-
border-bottom: 1.5px solid var(--medium-gray);
194
}
195
196
.diff pre {
197
-
overflow: scroll;
198
}
199
200
.diff-stat {
201
-
padding: 1rem 0 1rem 0;
202
}
203
204
-
.commit-hash, .commit-email {
205
-
font-family: var(--mono-font);
206
}
207
208
.commit-email:before {
209
-
content: '<';
210
}
211
212
.commit-email:after {
213
-
content: '>';
214
}
215
216
.commit {
217
-
margin-bottom: 1rem;
218
}
219
220
.commit pre {
221
-
padding-bottom: 1rem;
222
-
white-space: pre-wrap;
223
}
224
225
.diff-stat ul li {
226
-
list-style: none;
227
-
padding-left: 0.5em;
228
}
229
230
.diff-add {
231
-
color: green;
232
}
233
234
.diff-del {
235
-
color: red;
236
}
237
238
.diff-noop {
239
-
color: var(--gray);
240
}
241
242
.ref {
243
-
font-family: var(--sans-font);
244
-
font-size: 14px;
245
-
color: var(--gray);
246
-
display: inline-block;
247
-
padding-top: 0.7em;
248
}
249
250
.refs pre {
251
-
white-space: pre-wrap;
252
-
padding-bottom: 0.5rem;
253
}
254
255
.refs strong {
256
-
padding-right: 1em;
257
}
258
259
.line-numbers {
260
-
white-space: pre-line;
261
-
-moz-user-select: -moz-none;
262
-
-khtml-user-select: none;
263
-
-webkit-user-select: none;
264
-
-o-user-select: none;
265
-
user-select: none;
266
-
display: flex;
267
-
float: left;
268
-
flex-direction: column;
269
-
margin-right: 1ch;
270
}
271
272
.file-wrapper {
273
-
display: flex;
274
-
flex-direction: row;
275
-
grid-template-columns: 1rem minmax(0, 1fr);
276
-
gap: 1rem;
277
-
padding: 0.5rem;
278
-
background: var(--light-gray);
279
-
overflow-x: auto;
280
}
281
282
.chroma-file-wrapper {
283
-
display: flex;
284
-
flex-direction: row;
285
-
grid-template-columns: 1rem minmax(0, 1fr);
286
-
overflow-x: auto;
287
}
288
289
.file-content {
290
-
background: var(--light-gray);
291
-
overflow-y: hidden;
292
-
overflow-x: auto;
293
}
294
295
.diff-type {
296
-
color: var(--gray);
297
}
298
299
.commit-info {
300
-
color: var(--gray);
301
-
padding-bottom: 1.5rem;
302
-
font-size: 0.85rem;
303
}
304
305
@media (max-width: 600px) {
306
-
.index {
307
-
grid-row-gap: 0.8em;
308
-
}
309
310
-
.log {
311
-
grid-template-columns: 1fr;
312
-
grid-row-gap: 0em;
313
-
}
314
315
-
.index {
316
-
grid-template-columns: 1fr;
317
-
grid-row-gap: 0em;
318
-
}
319
320
-
.index-name:not(:first-child) {
321
-
padding-top: 1.5rem;
322
-
}
323
324
-
.commit-info:not(:last-child) {
325
-
padding-bottom: 1.5rem;
326
-
}
327
328
-
pre {
329
-
font-size: 0.8rem;
330
-
}
331
}
···
1
:root {
2
+
--white: #fff;
3
+
--light: #f4f4f4;
4
+
--cyan: #509c93;
5
+
--light-gray: #eee;
6
+
--medium-gray: #ddd;
7
+
--gray: #6a6a6a;
8
+
--dark: #444;
9
+
--darker: #222;
10
11
+
--sans-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto",
12
+
"Segoe UI", sans-serif;
13
+
--display-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto",
14
+
"Segoe UI", sans-serif;
15
+
--mono-font: "SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono",
16
+
"Roboto Mono", Menlo, Consolas, monospace;
17
}
18
19
@media (prefers-color-scheme: dark) {
20
+
:root {
21
+
color-scheme: dark light;
22
+
--white: #000;
23
+
--light: #181818;
24
+
--cyan: #76c7c0;
25
+
--light-gray: #333;
26
+
--medium-gray: #444;
27
+
--gray: #aaa;
28
+
--dark: #ddd;
29
+
--darker: #f4f4f4;
30
+
}
31
}
32
33
html {
34
+
background: var(--white);
35
+
-webkit-text-size-adjust: none;
36
+
font-family: var(--sans-font);
37
+
font-weight: 380;
38
}
39
40
pre {
41
+
font-family: var(--mono-font);
42
}
43
44
::selection {
45
+
background: var(--medium-gray);
46
+
opacity: 0.3;
47
}
48
49
* {
50
+
box-sizing: border-box;
51
+
padding: 0;
52
+
margin: 0;
53
}
54
55
body {
56
+
max-width: 1000px;
57
+
padding: 0 13px;
58
+
margin: 40px auto;
59
}
60
61
+
main,
62
+
footer {
63
+
font-size: 1rem;
64
+
padding: 0;
65
+
line-height: 160%;
66
}
67
68
+
header h1,
69
+
h2,
70
+
h3 {
71
+
font-family: var(--display-font);
72
}
73
74
h2 {
75
+
font-weight: 400;
76
}
77
78
strong {
79
+
font-weight: 500;
80
}
81
82
main h1 {
83
+
padding: 10px 0 10px 0;
84
}
85
86
main h2 {
87
+
font-size: 18px;
88
}
89
90
+
main h2,
91
+
h3 {
92
+
padding: 20px 0 15px 0;
93
}
94
95
nav {
96
+
padding: 0.4rem 0 1.5rem 0;
97
}
98
99
nav ul {
100
+
padding: 0;
101
+
margin: 0;
102
+
list-style: none;
103
+
padding-bottom: 20px;
104
}
105
106
nav ul li {
107
+
padding-right: 10px;
108
+
display: inline-block;
109
}
110
111
a {
112
+
margin: 0;
113
+
padding: 0;
114
+
box-sizing: border-box;
115
+
text-decoration: none;
116
+
word-wrap: break-word;
117
}
118
119
a {
120
+
color: var(--darker);
121
+
border-bottom: 1.5px solid var(--medium-gray);
122
}
123
124
a:hover {
125
+
border-bottom: 1.5px solid var(--gray);
126
}
127
128
.index {
129
+
padding-top: 2em;
130
+
display: grid;
131
+
grid-template-columns: 6em 1fr minmax(0, 7em);
132
+
grid-row-gap: 0.5em;
133
+
min-width: 0;
134
}
135
136
.clone-url {
137
+
padding-top: 2rem;
138
}
139
140
.clone-url pre {
141
+
color: var(--dark);
142
+
white-space: pre-wrap;
143
}
144
145
.desc {
146
+
font-weight: normal;
147
+
color: var(--gray);
148
+
font-style: italic;
149
}
150
151
.tree {
152
+
display: grid;
153
+
grid-template-columns: 10ch auto 1fr;
154
+
grid-row-gap: 0.5em;
155
+
grid-column-gap: 1em;
156
+
min-width: 0;
157
}
158
159
.log {
160
+
display: grid;
161
+
grid-template-columns: 20rem minmax(0, 1fr);
162
+
grid-row-gap: 0.8em;
163
+
grid-column-gap: 8rem;
164
+
margin-bottom: 2em;
165
+
padding-bottom: 1em;
166
+
border-bottom: 1.5px solid var(--medium-gray);
167
}
168
169
.log pre {
170
+
white-space: pre-wrap;
171
}
172
173
+
.mode,
174
+
.size {
175
+
font-family: var(--mono-font);
176
}
177
.size {
178
+
text-align: right;
179
}
180
181
.readme pre {
182
+
white-space: pre-wrap;
183
+
overflow-x: auto;
184
}
185
186
.readme {
187
+
background: var(--light-gray);
188
+
padding: 0.5rem;
189
}
190
191
.readme ul {
192
+
padding: revert;
193
}
194
195
.readme img {
196
+
max-width: 100%;
197
}
198
199
.diff {
200
+
margin: 1rem 0 1rem 0;
201
+
padding: 1rem 0 1rem 0;
202
+
border-bottom: 1.5px solid var(--medium-gray);
203
}
204
205
.diff pre {
206
+
overflow: scroll;
207
}
208
209
.diff-stat {
210
+
padding: 1rem 0 1rem 0;
211
}
212
213
+
.commit-hash,
214
+
.commit-email {
215
+
font-family: var(--mono-font);
216
}
217
218
.commit-email:before {
219
+
content: "<";
220
}
221
222
.commit-email:after {
223
+
content: ">";
224
}
225
226
.commit {
227
+
margin-bottom: 1rem;
228
}
229
230
.commit pre {
231
+
padding-bottom: 1rem;
232
+
white-space: pre-wrap;
233
}
234
235
.diff-stat ul li {
236
+
list-style: none;
237
+
padding-left: 0.5em;
238
}
239
240
.diff-add {
241
+
color: green;
242
}
243
244
.diff-del {
245
+
color: red;
246
}
247
248
.diff-noop {
249
+
color: var(--gray);
250
}
251
252
.ref {
253
+
font-family: var(--sans-font);
254
+
font-size: 14px;
255
+
color: var(--gray);
256
+
display: inline-block;
257
+
padding-top: 0.7em;
258
}
259
260
.refs pre {
261
+
white-space: pre-wrap;
262
+
padding-bottom: 0.5rem;
263
}
264
265
.refs strong {
266
+
padding-right: 1em;
267
}
268
269
.line-numbers {
270
+
white-space: pre-line;
271
+
-moz-user-select: -moz-none;
272
+
-khtml-user-select: none;
273
+
-webkit-user-select: none;
274
+
-o-user-select: none;
275
+
user-select: none;
276
+
display: flex;
277
+
float: left;
278
+
flex-direction: column;
279
+
margin-right: 1ch;
280
}
281
282
.file-wrapper {
283
+
display: flex;
284
+
flex-direction: row;
285
+
grid-template-columns: 1rem minmax(0, 1fr);
286
+
gap: 1rem;
287
+
padding: 0.5rem;
288
+
background: var(--light-gray);
289
+
overflow-x: auto;
290
}
291
292
.chroma-file-wrapper {
293
+
display: flex;
294
+
flex-direction: row;
295
+
grid-template-columns: 1rem minmax(0, 1fr);
296
+
overflow-x: auto;
297
}
298
299
.file-content {
300
+
background: var(--light-gray);
301
+
overflow-y: hidden;
302
+
overflow-x: auto;
303
}
304
305
.diff-type {
306
+
color: var(--gray);
307
}
308
309
.commit-info {
310
+
color: var(--gray);
311
+
padding-bottom: 1.5rem;
312
+
font-size: 0.85rem;
313
}
314
315
@media (max-width: 600px) {
316
+
.index {
317
+
grid-row-gap: 0.8em;
318
+
}
319
320
+
.log {
321
+
grid-template-columns: 1fr;
322
+
grid-row-gap: 0em;
323
+
}
324
325
+
.index {
326
+
grid-template-columns: 1fr;
327
+
grid-row-gap: 0em;
328
+
}
329
330
+
.index-name:not(:first-child) {
331
+
padding-top: 1.5rem;
332
+
}
333
334
+
.commit-info:not(:last-child) {
335
+
padding-bottom: 1.5rem;
336
+
}
337
338
+
pre {
339
+
font-size: 0.8rem;
340
+
}
341
}
+22
-15
legit/templates/head.html
+22
-15
legit/templates/head.html
···
1
{{ define "head" }}
2
-
<head>
3
-
<meta charset="utf-8">
4
-
<meta name="viewport" content="width=device-width, initial-scale=1">
5
-
<link rel="stylesheet" href="/static/style.css" type="text/css">
6
-
<link rel="icon" type="image/png" size="32x32" href="/static/legit.png">
7
{{ if .parent }}
8
-
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .parent }}/</title>
9
10
{{ else if .path }}
11
-
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .path }}</title>
12
{{ else if .files }}
13
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }})</title>
14
{{ else if .commit }}
15
<title>{{ .meta.Title }} — {{ .name }}: {{ .commit.This }}</title>
16
{{ else if .branches }}
17
<title>{{ .meta.Title }} — {{ .name }}: refs</title>
18
-
{{ else if .commits }}
19
-
{{ if .log }}
20
<title>{{ .meta.Title }} — {{ .name }}: log</title>
21
{{ else }}
22
<title>{{ .meta.Title }} — {{ .name }}</title>
23
-
{{ end }}
24
-
{{ else }}
25
<title>{{ .meta.Title }}</title>
26
-
{{ end }}
27
-
{{ if and .servername .gomod }}
28
-
<meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}">
29
{{ end }}
30
<!-- other meta tags here -->
31
-
</head>
32
{{ end }}
···
1
{{ define "head" }}
2
+
<head>
3
+
<meta charset="utf-8" />
4
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
5
+
<link rel="stylesheet" href="/static/style.css" type="text/css" />
6
+
<link rel="icon" type="image/png" size="32x32" href="/static/legit.png" />
7
+
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
8
+
<meta name="htmx-config" content='{"selfRequestsOnly":false}' />
9
+
10
{{ if .parent }}
11
+
<title>
12
+
{{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .parent }}/
13
+
</title>
14
15
{{ else if .path }}
16
+
<title>
17
+
{{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .path }}
18
+
</title>
19
{{ else if .files }}
20
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }})</title>
21
{{ else if .commit }}
22
<title>{{ .meta.Title }} — {{ .name }}: {{ .commit.This }}</title>
23
{{ else if .branches }}
24
<title>{{ .meta.Title }} — {{ .name }}: refs</title>
25
+
{{ else if .commits }} {{ if .log }}
26
<title>{{ .meta.Title }} — {{ .name }}: log</title>
27
{{ else }}
28
<title>{{ .meta.Title }} — {{ .name }}</title>
29
+
{{ end }} {{ else }}
30
<title>{{ .meta.Title }}</title>
31
+
{{ end }} {{ if and .servername .gomod }}
32
+
<meta
33
+
name="go-import"
34
+
content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}"
35
+
/>
36
{{ end }}
37
<!-- other meta tags here -->
38
+
</head>
39
{{ end }}
+35
legit/templates/keys.html
+35
legit/templates/keys.html
···
···
1
+
{{ define "keys" }}
2
+
<html>
3
+
{{ template "head" . }}
4
+
5
+
<body>
6
+
<main>
7
+
<form>
8
+
<p>
9
+
Give your key a name and paste your
10
+
<strong>public</strong> key here. This is what you'll use to
11
+
push to your Git repository.
12
+
</p>
13
+
<div id="keys"></div>
14
+
<div>
15
+
<input
16
+
type="text"
17
+
id="name"
18
+
name="name"
19
+
placeholder="my laptop"
20
+
/>
21
+
<input
22
+
type="text"
23
+
id="public_key"
24
+
name="key"
25
+
placeholder="ssh-ed25519 AAABBBHUNTER2..."
26
+
/>
27
+
</div>
28
+
<button hx-put="/settings/keys" hx-swap="none" type="submit">
29
+
Submit
30
+
</button>
31
+
</form>
32
+
</main>
33
+
</body>
34
+
</html>
35
+
{{ end }}
+2
-2
legit/templates/login.html
+2
-2
legit/templates/login.html