+32
-24
appview/pages/templates/repo/index.html
+32
-24
appview/pages/templates/repo/index.html
···
41
41
{{ range .Files }}
42
42
{{ if not .IsFile }}
43
43
<div class="{{ $containerstyle }}">
44
-
<a
45
-
href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}/{{ .Name }}"
46
-
class="{{ $linkstyle }}"
47
-
>
48
-
<div class="flex items-center gap-2">
49
-
<i
50
-
class="w-3 h-3 fill-current"
51
-
data-lucide="folder"
52
-
></i
53
-
>{{ .Name }}/
54
-
</div>
55
-
</a>
44
+
<div class="flex justify-between items-center">
45
+
<a
46
+
href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}/{{ .Name }}"
47
+
class="{{ $linkstyle }}"
48
+
>
49
+
<div class="flex items-center gap-2">
50
+
<i
51
+
class="w-3 h-3 fill-current"
52
+
data-lucide="folder"
53
+
></i
54
+
>{{ .Name }}
55
+
</div>
56
+
</a>
57
+
58
+
<time class="text-xs text-gray-500">{{ timeFmt .LastCommit.Author.When }}</time>
59
+
</div>
56
60
</div>
57
61
{{ end }}
58
62
{{ end }}
···
60
64
{{ range .Files }}
61
65
{{ if .IsFile }}
62
66
<div class="{{ $containerstyle }}">
63
-
<a
64
-
href="/{{ $.RepoInfo.FullName }}/blob/{{ $.Ref }}/{{ .Name }}"
65
-
class="{{ $linkstyle }}"
66
-
>
67
-
<div class="flex items-center gap-2">
68
-
<i
69
-
class="w-3 h-3"
70
-
data-lucide="file"
71
-
></i
72
-
>{{ .Name }}
73
-
</div>
74
-
</a>
67
+
<div class="flex justify-between items-center">
68
+
<a
69
+
href="/{{ $.RepoInfo.FullName }}/blob/{{ $.Ref }}/{{ .Name }}"
70
+
class="{{ $linkstyle }}"
71
+
>
72
+
<div class="flex items-center gap-2">
73
+
<i
74
+
class="w-3 h-3"
75
+
data-lucide="file"
76
+
></i
77
+
>{{ .Name }}
78
+
</div>
79
+
</a>
80
+
81
+
<time class="text-xs text-gray-500">{{ timeFmt .LastCommit.Author.When }}</time>
82
+
</div>
75
83
</div>
76
84
{{ end }}
77
85
{{ end }}
+18
-10
appview/pages/templates/repo/tree.html
+18
-10
appview/pages/templates/repo/tree.html
···
13
13
{{ range .Files }}
14
14
{{ if not .IsFile }}
15
15
<div class="{{ $containerstyle }}">
16
-
<a href="/{{ $.BaseTreeLink }}/{{ .Name }}" class="{{ $linkstyle }}">
17
-
<div class="flex items-center gap-2">
18
-
<i class="w-3 h-3 fill-current" data-lucide="folder"></i>{{ .Name }}/
16
+
<div class="flex justify-between items-center">
17
+
<a href="/{{ $.BaseTreeLink }}/{{ .Name }}" class="{{ $linkstyle }}">
18
+
<div class="flex items-center gap-2">
19
+
<i class="w-3 h-3 fill-current" data-lucide="folder"></i>{{ .Name }}
20
+
</div>
21
+
</a>
19
22
</div>
20
-
</a>
23
+
<time class="text-xs text-gray-500">{{ timeFmt .LastCommit.Author.When }}</time>
21
24
</div>
22
25
{{ end }}
23
26
{{ end }}
24
27
25
28
{{ range .Files }}
26
29
{{ if .IsFile }}
27
-
<div class="{{ $containerstyle }}">
28
-
<a href="/{{ $.BaseBlobLink }}/{{ .Name }}" class="{{ $linkstyle }}">
29
-
<div class="flex items-center gap-2">
30
-
<i class="w-3 h-3" data-lucide="file"></i>{{ .Name }}
31
-
</div>
32
-
</a>
30
+
<div class="{{ $containerstyle }}">
31
+
32
+
<div class="flex justify-between items-center">
33
+
<a href="/{{ $.BaseBlobLink }}/{{ .Name }}" class="{{ $linkstyle }}">
34
+
<div class="flex items-center gap-2">
35
+
<i class="w-3 h-3" data-lucide="file"></i>{{ .Name }}
36
+
</div>
37
+
</a>
38
+
39
+
<time class="text-xs text-gray-500">{{ timeFmt .LastCommit.Author.When }}</time>
33
40
</div>
41
+
</div>
34
42
{{ end }}
35
43
{{ end }}
36
44
</div>
+8
-2
appview/state/state.go
+8
-2
appview/state/state.go
···
519
519
s.pages.Notice(w, "repo", "Failed to create repository on knot server.")
520
520
return
521
521
}
522
-
if resp.StatusCode != http.StatusNoContent {
523
-
s.pages.Notice(w, "repo", fmt.Sprintf("Server returned unexpected status: %d", resp.StatusCode))
522
+
523
+
switch resp.StatusCode {
524
+
case http.StatusConflict:
525
+
s.pages.Notice(w, "repo", "A repository with that name already exists.")
524
526
return
527
+
case http.StatusInternalServerError:
528
+
s.pages.Notice(w, "repo", "Failed to create repository on knot. Try again later.")
529
+
case http.StatusNoContent:
530
+
// continue
525
531
}
526
532
527
533
// add to local db
+1
-1
input.css
+1
-1
input.css
+26
-17
knotserver/git/git.go
+26
-17
knotserver/git/git.go
···
2
2
3
3
import (
4
4
"archive/tar"
5
+
"bytes"
5
6
"fmt"
6
7
"io"
7
8
"io/fs"
9
+
"os/exec"
8
10
"path"
9
11
"sort"
12
+
"strings"
10
13
"sync"
11
14
"time"
12
15
···
35
38
)
36
39
37
40
type GitRepo struct {
38
-
r *git.Repository
39
-
h plumbing.Hash
41
+
path string
42
+
r *git.Repository
43
+
h plumbing.Hash
40
44
}
41
45
42
46
type TagList struct {
···
102
106
103
107
func Open(path string, ref string) (*GitRepo, error) {
104
108
var err error
105
-
g := GitRepo{}
109
+
g := GitRepo{path: path}
106
110
g.r, err = git.PlainOpen(path)
107
111
if err != nil {
108
112
return nil, fmt.Errorf("opening %s: %w", path, err)
···
295
299
return nil
296
300
}
297
301
298
-
func (g *GitRepo) LastCommitTime(filePath string) (*object.Commit, error) {
302
+
func (g *GitRepo) LastCommitForPath(path string) (*object.Commit, error) {
299
303
cacheMu.RLock()
300
-
if commit, exists := commitCache.Get(filePath); exists {
304
+
if commit, found := commitCache.Get(path); found {
301
305
cacheMu.RUnlock()
302
306
return commit.(*object.Commit), nil
303
307
}
304
308
cacheMu.RUnlock()
305
309
306
-
commitIter, err := g.r.Log(&git.LogOptions{
307
-
From: g.h,
308
-
PathFilter: func(s string) bool {
309
-
return s == filePath
310
-
},
311
-
Order: git.LogOrderCommitterTime,
312
-
})
310
+
cmd := exec.Command("git", "-C", g.path, "log", "-1", "--format=%H", "--", path)
311
+
312
+
var out bytes.Buffer
313
+
cmd.Stdout = &out
314
+
cmd.Stderr = &out
315
+
316
+
if err := cmd.Run(); err != nil {
317
+
return nil, fmt.Errorf("failed to get commit hash: %w", err)
318
+
}
313
319
314
-
if err != nil {
315
-
return nil, fmt.Errorf("failed to get commit log for %s: %w", filePath, err)
320
+
commitHash := strings.TrimSpace(out.String())
321
+
if commitHash == "" {
322
+
return nil, fmt.Errorf("no commits found for path: %s", path)
316
323
}
317
324
318
-
commit, err := commitIter.Next()
325
+
hash := plumbing.NewHash(commitHash)
326
+
327
+
commit, err := g.r.CommitObject(hash)
319
328
if err != nil {
320
-
return nil, fmt.Errorf("no commit found for %s", filePath)
329
+
return nil, err
321
330
}
322
331
323
332
cacheMu.Lock()
324
-
commitCache.Set(filePath, commit, 1)
333
+
commitCache.Set(path, commit, 1)
325
334
cacheMu.Unlock()
326
335
327
336
return commit, nil
+11
-4
knotserver/git/tree.go
+11
-4
knotserver/git/tree.go
···
20
20
}
21
21
22
22
if path == "" {
23
-
files = g.makeNiceTree(tree)
23
+
files = g.makeNiceTree(tree, "")
24
24
} else {
25
25
o, err := tree.FindEntry(path)
26
26
if err != nil {
···
33
33
return nil, err
34
34
}
35
35
36
-
files = g.makeNiceTree(subtree)
36
+
files = g.makeNiceTree(subtree, path)
37
37
}
38
38
}
39
39
40
40
return files, nil
41
41
}
42
42
43
-
func (g *GitRepo) makeNiceTree(t *object.Tree) []types.NiceTree {
43
+
func (g *GitRepo) makeNiceTree(t *object.Tree, parent string) []types.NiceTree {
44
44
nts := []types.NiceTree{}
45
45
46
46
for _, e := range t.Entries {
47
47
mode, _ := e.Mode.ToOSFileMode()
48
48
sz, _ := t.Size(e.Name)
49
49
50
-
lastCommit, err := g.LastCommitTime(e.Name)
50
+
var fpath string
51
+
if parent != "" {
52
+
fpath = fmt.Sprintf("%s/%s", parent, e.Name)
53
+
} else {
54
+
fpath = e.Name
55
+
}
56
+
lastCommit, err := g.LastCommitForPath(fpath)
51
57
if err != nil {
58
+
fmt.Println("error getting last commit time:", err)
52
59
continue
53
60
}
54
61
+8
-2
knotserver/routes.go
+8
-2
knotserver/routes.go
···
17
17
securejoin "github.com/cyphar/filepath-securejoin"
18
18
"github.com/gliderlabs/ssh"
19
19
"github.com/go-chi/chi/v5"
20
+
gogit "github.com/go-git/go-git/v5"
20
21
"github.com/go-git/go-git/v5/plumbing"
21
22
"github.com/go-git/go-git/v5/plumbing/object"
22
23
"github.com/russross/blackfriday/v2"
···
504
505
err := git.InitBare(repoPath)
505
506
if err != nil {
506
507
l.Error("initializing bare repo", "error", err.Error())
507
-
writeError(w, err.Error(), http.StatusInternalServerError)
508
-
return
508
+
if errors.Is(err, gogit.ErrRepositoryAlreadyExists) {
509
+
writeError(w, "That repo already exists!", http.StatusConflict)
510
+
return
511
+
} else {
512
+
writeError(w, err.Error(), http.StatusInternalServerError)
513
+
return
514
+
}
509
515
}
510
516
511
517
// add perms for this user to access the repo