1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
|
diff --git a/Makefile b/Makefile
index 71c83020cf95e0962da6589020c5d062a425f06b..21ee118cb3dc98d8e01eded11c9dc0210749f92e 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@
.PHONY: dev
dev:
THEME_DIR=./themes REPO_PATH=./repos LOG_LEVEL=debug \
- $(GO) run github.com/cosmtrek/air@latest \
+ $(GO) run github.com/cosmtrek/air@v1.49.0 \
-build.cmd="make gogit" \
-build.bin="./gogit" \
-build.exclude_regex="(repos/|Makefile)" \
diff --git a/go.mod b/go.mod
index cbb298f89d79559a6f9aad5535d73e27d5698269..31bee2916da28a786354c638e0c8fb55ed602707 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ github.com/haya14busa/go-versionsort v0.0.0-20190326014014-5b3a40d6070a
github.com/rjeczalik/notify v0.9.3
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e
github.com/valyala/fasthttp v1.50.0
+ golang.org/x/mod v0.14.0
)
require (
@@ -35,7 +36,6 @@ github.com/stretchr/testify v1.8.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.14.0 // indirect
- golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/tools v0.13.0 // indirect
diff --git a/go.sum b/go.sum
index 6c523d85a03ec9636328ff0aff413bba8ef5342b..3130ac9926db2e902403b2f8830bb2ee6bd93b91 100644
--- a/go.sum
+++ b/go.sum
@@ -105,6 +105,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
diff --git a/internal/handler/404.go b/internal/handler/404.go
index b24c736277d80ba58c984803b4225abbcbcaae70..640941000b99dc7dd63230b85cc13ce2d8a4d750 100644
--- a/internal/handler/404.go
+++ b/internal/handler/404.go
@@ -17,6 +17,8 @@ status = fasthttp.StatusNotFound
log.Warn(err.Error(), "path", ctx.Path())
case errors.Is(err, ErrNotImplemented):
status = fasthttp.StatusNotImplemented
+ case errors.Is(err, ErrNoOriginPath):
+ status = fasthttp.StatusServiceUnavailable
default:
log.Error("internal server error", "error", err.Error())
}
@@ -28,4 +30,5 @@
var (
ErrNotFound = errors.New("page not found")
ErrNotImplemented = errors.New("not yet implemented")
+ ErrNoOriginPath = errors.New("missing upstream repo")
)
diff --git a/internal/handler/go-mod.go b/internal/handler/go-mod.go
new file mode 100644
index 0000000000000000000000000000000000000000..9a97cb790da5d6b80ce6004d8831c5beb46499e8
--- /dev/null
+++ b/internal/handler/go-mod.go
@@ -0,0 +1,18 @@
+package handler
+
+import (
+ "fmt"
+
+ "git.sr.ht/~mpldr/gogit/internal/types"
+ "github.com/valyala/fasthttp"
+)
+
+func goGetHandler(repo *types.Repo) fasthttp.RequestHandler {
+ return func(ctx *fasthttp.RequestCtx) {
+ ctx.SetContentType("text/html; charset=utf-8")
+ module := repo.GoMod[string(ctx.Path())]
+ fmt.Fprintf(ctx, goGetTmpl, module, "", module)
+ }
+}
+
+const goGetTmpl = `<html><head><meta name="go-import" content="%s git %s"><title>Moving to %s…</title></head></html>`
diff --git a/internal/handler/gomod.go b/internal/handler/gomod.go
new file mode 100644
index 0000000000000000000000000000000000000000..2cc48f40dcc126cf1ba06d41b4078aa99b4ba922
--- /dev/null
+++ b/internal/handler/gomod.go
@@ -0,0 +1,25 @@
+package handler
+
+import (
+ "fmt"
+
+ "git.sr.ht/~mpldr/gogit/internal/types"
+ "github.com/valyala/fasthttp"
+)
+
+func (h *Handler) GoModuleHandler(ctx *fasthttp.RequestCtx, repo *types.Repo) {
+ origin := repo.Origin()
+ if origin == "" {
+ errorHandler(ctx, ErrNoOriginPath)
+ return
+ }
+
+ ctx.SetContentType("text/html; charset=utf-8")
+ _, _ = fmt.Fprintf(ctx, `<html>
+ <head>
+ <title>%s</title>
+ <meta name="go-import" content="%s git %s">
+ </head>
+ <body></body>
+</html>`, repo.Name, repo.GoMod[string(ctx.Path())], origin)
+}
diff --git a/internal/handler/repo.go b/internal/handler/repo.go
index 32d9e39a463e6ba0d188490e48792128f96ab7b0..f40835edf189322908758ee12f78f098b400d4d4 100644
--- a/internal/handler/repo.go
+++ b/internal/handler/repo.go
@@ -14,6 +14,16 @@ log := conman.GetLogger(ctx)
operation := strings.TrimPrefix(string(ctx.Path()), repo.Path)
operation = strings.SplitN(strings.TrimPrefix(operation, "/"), "/", 2)[0]
log.Debug("handling repository request", "repo", repo.Name, "operation", operation)
+
+ if ctx.URI().QueryArgs().Has("go-get") && len(repo.GoMod) > 0 {
+ if repo.GoMod[string(ctx.Path())] != "" {
+ goGetHandler(repo)(ctx)
+ return
+ }
+ errorHandler(ctx, ErrNotFound)
+ return
+ }
+
switch operation {
case "":
templates.Overview(repo)(ctx)
diff --git a/internal/handler/serve-http.go b/internal/handler/serve-http.go
index 8160e47d7a0da94ea61afb6d6daf5ebfca4e3947..0a86257b90f95436abc20a7a05db14a5feb3fe73 100644
--- a/internal/handler/serve-http.go
+++ b/internal/handler/serve-http.go
@@ -2,6 +2,7 @@ package handler
import (
"log/slog"
+ "path/filepath"
"strings"
"git.sr.ht/~mpldr/gogit/internal/conman"
@@ -30,12 +31,29 @@ })(ctx)
return
}
+ if ctx.URI().QueryArgs().GetBool("go-get") {
+ g := h.GoModulePath(string(ctx.Host()), string(ctx.Path()))
+ if g != nil {
+ h.GoModuleHandler(ctx, g)
+ return
+ }
+ }
+
repo := h.lookupProject(string(ctx.Path()))
if repo == nil {
errorHandler(ctx, ErrNotFound)
return
}
h.repoHandler(ctx, repo)
+}
+
+func (h *Handler) GoModulePath(host, path string) *types.Repo {
+ for _, r := range h.repolist {
+ if filepath.Join(host, path) == r.GoMod[path] {
+ return r
+ }
+ }
+ return nil
}
func (h *Handler) lookupProject(path string) *types.Repo {
diff --git a/internal/repo-monitor/module-finder.go b/internal/repo-monitor/module-finder.go
new file mode 100644
index 0000000000000000000000000000000000000000..c58f5f6fde4edea4935be78a26f75015d6b05a01
--- /dev/null
+++ b/internal/repo-monitor/module-finder.go
@@ -0,0 +1,44 @@
+package repomonitor
+
+import (
+ "log/slog"
+ "os"
+ "path/filepath"
+
+ "git.sr.ht/~mpldr/gogit/internal/types"
+ "golang.org/x/mod/modfile"
+)
+
+func findModules(path string, prefix string, repo *types.Repo) {
+ err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return nil
+ }
+
+ if info.IsDir() {
+ return nil
+ }
+
+ if info.Name() != "go.mod" {
+ return nil
+ }
+ slog.Debug("found go.mod", "path", path, "prefix", prefix)
+
+ modFile, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ modpath := modfile.ModulePath(modFile)
+ if modpath == "" {
+ slog.Warn("failed to parse module path", "path", path)
+ return nil
+ }
+ repo.GoMod[prefix] = modpath
+ return nil
+ })
+ slog.Debug("module search completed", "repo", repo.Name, "modules", repo.GoMod)
+ if err != nil {
+ slog.Error("failed to search for modules", "repo", repo.Name, "error", err)
+ }
+}
diff --git a/internal/repo-monitor/repo.go b/internal/repo-monitor/repo.go
index 2a8bd8a78f77653d43badd6e15c39dd3a157d51a..125819a1ddf4582f31ffee1915e3ab41467a64b8 100644
--- a/internal/repo-monitor/repo.go
+++ b/internal/repo-monitor/repo.go
@@ -4,14 +4,27 @@ import (
"log/slog"
"os"
"path/filepath"
+ "strconv"
"strings"
"git.sr.ht/~mpldr/gogit/internal/types"
+
"github.com/go-git/go-git/v5"
+ "golang.org/x/mod/modfile"
)
func ListRepos(paths string) []*types.Repo {
var repos []*types.Repo
+
+ scanModules := false
+ if s, ok := os.LookupEnv("SCAN_MODULES"); ok {
+ var err error
+ scanModules, err = strconv.ParseBool(s)
+ if err != nil {
+ slog.Warn("failed to parse SCAN_MODULES", "error", err)
+ }
+ }
+
slog.Debug("scanning for repositories", "path", paths)
err := filepath.Walk(paths, func(path string, info os.FileInfo, err error) error {
if err != nil {
@@ -33,9 +46,24 @@ if !strings.HasPrefix(prefix, "/") {
prefix = "/" + prefix
}
- slog.Debug("found repository", "web_path", path, "prefix", prefix)
r := types.NewRepo(filepath.Base(prefix), prefix, repo)
+ gomod, err := os.ReadFile(filepath.Join(path, "go.mod"))
+ if err == nil {
+ r.GoMod[path] = modfile.ModulePath(gomod)
+ }
+
+ slog.Debug(
+ "found repository",
+ "web_path", path,
+ "prefix", prefix,
+ "go_module", r.GoMod,
+ )
+
repos = append(repos, r)
+
+ if scanModules {
+ findModules(path, prefix, r)
+ }
return filepath.SkipDir
})
diff --git a/internal/types/repo.go b/internal/types/repo.go
index b8d9190b1f0d37355a8ccf847c3cb1229a37a26c..a1f6165f38454615fbcc4684eb9cc9de0d72583d 100644
--- a/internal/types/repo.go
+++ b/internal/types/repo.go
@@ -1,6 +1,7 @@
package types
import (
+ "fmt"
"io"
"log/slog"
"path/filepath"
@@ -18,6 +19,8 @@ type Repo struct {
Name string
Path string
Category string
+ GoMod map[string]string
+ Remote string
repo *git.Repository
}
@@ -27,6 +30,7 @@ category = strings.TrimPrefix(category, "/")
return &Repo{
Name: name,
Path: path,
+ GoMod: make(map[string]string),
Category: category,
repo: repo,
}
@@ -148,3 +152,27 @@ return nil
}
return NewTag(tagObj, r)
}
+
+func (r *Repo) Origin() string {
+ // TODO: add config inside git-Repo
+ origin, _ := r.repo.Remote("origin")
+ if origin != nil {
+ return origin.Config().URLs[0]
+ }
+
+ return ""
+}
+
+func (r *Repo) WriteRefs(w io.Writer) error {
+ refs, err := r.repo.References()
+ if err != nil {
+ return fmt.Errorf("failed to get refs: %w", err)
+ }
+ err = refs.ForEach(func(ref *plumbing.Reference) error {
+ if ref.Type() == plumbing.HashReference {
+ fmt.Fprintln(w, ref)
+ }
+ return nil
+ })
+ return err
+}
|