forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
1package knotserver
2
3import (
4 "compress/gzip"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "errors"
10 "fmt"
11 "log"
12 "net/http"
13 "net/url"
14 "os"
15 "path/filepath"
16 "strconv"
17 "strings"
18
19 securejoin "github.com/cyphar/filepath-securejoin"
20 "github.com/gliderlabs/ssh"
21 "github.com/go-chi/chi/v5"
22 gogit "github.com/go-git/go-git/v5"
23 "github.com/go-git/go-git/v5/plumbing"
24 "github.com/go-git/go-git/v5/plumbing/object"
25 "tangled.sh/tangled.sh/core/knotserver/db"
26 "tangled.sh/tangled.sh/core/knotserver/git"
27 "tangled.sh/tangled.sh/core/types"
28)
29
30func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
31 w.Write([]byte("This is a knot server. More info at https://tangled.sh"))
32}
33
34func (h *Handle) Capabilities(w http.ResponseWriter, r *http.Request) {
35 w.Header().Set("Content-Type", "application/json")
36
37 capabilities := map[string]any{
38 "pull_requests": map[string]any{
39 "patch_submissions": true,
40 "branch_submissions": true,
41 "fork_submissions": true,
42 },
43 }
44
45 jsonData, err := json.Marshal(capabilities)
46 if err != nil {
47 http.Error(w, "Failed to serialize JSON", http.StatusInternalServerError)
48 return
49 }
50
51 w.Write(jsonData)
52}
53
54func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
55 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
56 l := h.l.With("path", path, "handler", "RepoIndex")
57 ref := chi.URLParam(r, "ref")
58 ref, _ = url.PathUnescape(ref)
59
60 gr, err := git.Open(path, ref)
61 if err != nil {
62 log.Println(err)
63 if errors.Is(err, plumbing.ErrReferenceNotFound) {
64 resp := types.RepoIndexResponse{
65 IsEmpty: true,
66 }
67 writeJSON(w, resp)
68 return
69 } else {
70 l.Error("opening repo", "error", err.Error())
71 notFound(w)
72 return
73 }
74 }
75
76 commits, err := gr.Commits()
77 total := len(commits)
78 if err != nil {
79 writeError(w, err.Error(), http.StatusInternalServerError)
80 l.Error("fetching commits", "error", err.Error())
81 return
82 }
83 if len(commits) > 10 {
84 commits = commits[:10]
85 }
86
87 branches, err := gr.Branches()
88 if err != nil {
89 l.Error("getting branches", "error", err.Error())
90 writeError(w, err.Error(), http.StatusInternalServerError)
91 return
92 }
93
94 bs := []types.Branch{}
95 for _, branch := range branches {
96 b := types.Branch{}
97 b.Hash = branch.Hash().String()
98 b.Name = branch.Name().Short()
99 bs = append(bs, b)
100 }
101
102 tags, err := gr.Tags()
103 if err != nil {
104 // Non-fatal, we *should* have at least one branch to show.
105 l.Warn("getting tags", "error", err.Error())
106 }
107
108 rtags := []*types.TagReference{}
109 for _, tag := range tags {
110 tr := types.TagReference{
111 Tag: tag.TagObject(),
112 }
113
114 tr.Reference = types.Reference{
115 Name: tag.Name(),
116 Hash: tag.Hash().String(),
117 }
118
119 if tag.Message() != "" {
120 tr.Message = tag.Message()
121 }
122
123 rtags = append(rtags, &tr)
124 }
125
126 var readmeContent string
127 var readmeFile string
128 for _, readme := range h.c.Repo.Readme {
129 content, _ := gr.FileContent(readme)
130 if len(content) > 0 {
131 readmeContent = string(content)
132 readmeFile = readme
133 }
134 }
135
136 files, err := gr.FileTree("")
137 if err != nil {
138 writeError(w, err.Error(), http.StatusInternalServerError)
139 l.Error("file tree", "error", err.Error())
140 return
141 }
142
143 if ref == "" {
144 mainBranch, err := gr.FindMainBranch()
145 if err != nil {
146 writeError(w, err.Error(), http.StatusInternalServerError)
147 l.Error("finding main branch", "error", err.Error())
148 return
149 }
150 ref = mainBranch
151 }
152
153 resp := types.RepoIndexResponse{
154 IsEmpty: false,
155 Ref: ref,
156 Commits: commits,
157 Description: getDescription(path),
158 Readme: readmeContent,
159 ReadmeFileName: readmeFile,
160 Files: files,
161 Branches: bs,
162 Tags: rtags,
163 TotalCommits: total,
164 }
165
166 writeJSON(w, resp)
167 return
168}
169
170func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
171 treePath := chi.URLParam(r, "*")
172 ref := chi.URLParam(r, "ref")
173 ref, _ = url.PathUnescape(ref)
174
175 l := h.l.With("handler", "RepoTree", "ref", ref, "treePath", treePath)
176
177 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
178 gr, err := git.Open(path, ref)
179 if err != nil {
180 notFound(w)
181 return
182 }
183
184 files, err := gr.FileTree(treePath)
185 if err != nil {
186 writeError(w, err.Error(), http.StatusInternalServerError)
187 l.Error("file tree", "error", err.Error())
188 return
189 }
190
191 resp := types.RepoTreeResponse{
192 Ref: ref,
193 Parent: treePath,
194 Description: getDescription(path),
195 DotDot: filepath.Dir(treePath),
196 Files: files,
197 }
198
199 writeJSON(w, resp)
200 return
201}
202
203func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) {
204 treePath := chi.URLParam(r, "*")
205 ref := chi.URLParam(r, "ref")
206 ref, _ = url.PathUnescape(ref)
207
208 l := h.l.With("handler", "FileContent", "ref", ref, "treePath", treePath)
209
210 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
211 gr, err := git.Open(path, ref)
212 if err != nil {
213 notFound(w)
214 return
215 }
216
217 var isBinaryFile bool = false
218 contents, err := gr.FileContent(treePath)
219 if errors.Is(err, git.ErrBinaryFile) {
220 isBinaryFile = true
221 } else if errors.Is(err, object.ErrFileNotFound) {
222 notFound(w)
223 return
224 } else if err != nil {
225 writeError(w, err.Error(), http.StatusInternalServerError)
226 return
227 }
228
229 bytes := []byte(contents)
230 // safe := string(sanitize(bytes))
231 sizeHint := len(bytes)
232
233 resp := types.RepoBlobResponse{
234 Ref: ref,
235 Contents: string(bytes),
236 Path: treePath,
237 IsBinary: isBinaryFile,
238 SizeHint: uint64(sizeHint),
239 }
240
241 h.showFile(resp, w, l)
242}
243
244func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
245 name := chi.URLParam(r, "name")
246 file := chi.URLParam(r, "file")
247
248 l := h.l.With("handler", "Archive", "name", name, "file", file)
249
250 // TODO: extend this to add more files compression (e.g.: xz)
251 if !strings.HasSuffix(file, ".tar.gz") {
252 notFound(w)
253 return
254 }
255
256 ref := strings.TrimSuffix(file, ".tar.gz")
257
258 // This allows the browser to use a proper name for the file when
259 // downloading
260 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
261 setContentDisposition(w, filename)
262 setGZipMIME(w)
263
264 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
265 gr, err := git.Open(path, ref)
266 if err != nil {
267 notFound(w)
268 return
269 }
270
271 gw := gzip.NewWriter(w)
272 defer gw.Close()
273
274 prefix := fmt.Sprintf("%s-%s", name, ref)
275 err = gr.WriteTar(gw, prefix)
276 if err != nil {
277 // once we start writing to the body we can't report error anymore
278 // so we are only left with printing the error.
279 l.Error("writing tar file", "error", err.Error())
280 return
281 }
282
283 err = gw.Flush()
284 if err != nil {
285 // once we start writing to the body we can't report error anymore
286 // so we are only left with printing the error.
287 l.Error("flushing?", "error", err.Error())
288 return
289 }
290}
291
292func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
293 ref := chi.URLParam(r, "ref")
294 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
295
296 l := h.l.With("handler", "Log", "ref", ref, "path", path)
297
298 gr, err := git.Open(path, ref)
299 if err != nil {
300 notFound(w)
301 return
302 }
303
304 commits, err := gr.Commits()
305 if err != nil {
306 writeError(w, err.Error(), http.StatusInternalServerError)
307 l.Error("fetching commits", "error", err.Error())
308 return
309 }
310
311 // Get page parameters
312 page := 1
313 pageSize := 30
314
315 if pageParam := r.URL.Query().Get("page"); pageParam != "" {
316 if p, err := strconv.Atoi(pageParam); err == nil && p > 0 {
317 page = p
318 }
319 }
320
321 if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" {
322 if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 {
323 pageSize = ps
324 }
325 }
326
327 // Calculate pagination
328 start := (page - 1) * pageSize
329 end := start + pageSize
330 total := len(commits)
331
332 if start >= total {
333 commits = []*object.Commit{}
334 } else {
335 if end > total {
336 end = total
337 }
338 commits = commits[start:end]
339 }
340
341 resp := types.RepoLogResponse{
342 Commits: commits,
343 Ref: ref,
344 Description: getDescription(path),
345 Log: true,
346 Total: total,
347 Page: page,
348 PerPage: pageSize,
349 }
350
351 writeJSON(w, resp)
352 return
353}
354
355func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
356 ref := chi.URLParam(r, "ref")
357 ref, _ = url.PathUnescape(ref)
358
359 l := h.l.With("handler", "Diff", "ref", ref)
360
361 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
362 gr, err := git.Open(path, ref)
363 if err != nil {
364 notFound(w)
365 return
366 }
367
368 diff, err := gr.Diff()
369 if err != nil {
370 writeError(w, err.Error(), http.StatusInternalServerError)
371 l.Error("getting diff", "error", err.Error())
372 return
373 }
374
375 resp := types.RepoCommitResponse{
376 Ref: ref,
377 Diff: diff,
378 }
379
380 writeJSON(w, resp)
381 return
382}
383
384func (h *Handle) Tags(w http.ResponseWriter, r *http.Request) {
385 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
386 l := h.l.With("handler", "Refs")
387
388 gr, err := git.Open(path, "")
389 if err != nil {
390 notFound(w)
391 return
392 }
393
394 tags, err := gr.Tags()
395 if err != nil {
396 // Non-fatal, we *should* have at least one branch to show.
397 l.Warn("getting tags", "error", err.Error())
398 }
399
400 rtags := []*types.TagReference{}
401 for _, tag := range tags {
402 tr := types.TagReference{
403 Tag: tag.TagObject(),
404 }
405
406 tr.Reference = types.Reference{
407 Name: tag.Name(),
408 Hash: tag.Hash().String(),
409 }
410
411 if tag.Message() != "" {
412 tr.Message = tag.Message()
413 }
414
415 rtags = append(rtags, &tr)
416 }
417
418 resp := types.RepoTagsResponse{
419 Tags: rtags,
420 }
421
422 writeJSON(w, resp)
423 return
424}
425
426func (h *Handle) Branches(w http.ResponseWriter, r *http.Request) {
427 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
428 l := h.l.With("handler", "Branches")
429
430 gr, err := git.Open(path, "")
431 if err != nil {
432 notFound(w)
433 return
434 }
435
436 branches, err := gr.Branches()
437 if err != nil {
438 l.Error("getting branches", "error", err.Error())
439 writeError(w, err.Error(), http.StatusInternalServerError)
440 return
441 }
442
443 bs := []types.Branch{}
444 for _, branch := range branches {
445 b := types.Branch{}
446 b.Hash = branch.Hash().String()
447 b.Name = branch.Name().Short()
448 bs = append(bs, b)
449 }
450
451 resp := types.RepoBranchesResponse{
452 Branches: bs,
453 }
454
455 writeJSON(w, resp)
456 return
457}
458
459func (h *Handle) Branch(w http.ResponseWriter, r *http.Request) {
460 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
461 branchName := chi.URLParam(r, "branch")
462 l := h.l.With("handler", "Branch")
463
464 gr, err := git.PlainOpen(path)
465 if err != nil {
466 notFound(w)
467 return
468 }
469
470 ref, err := gr.Branch(branchName)
471 if err != nil {
472 l.Error("getting branches", "error", err.Error())
473 writeError(w, err.Error(), http.StatusInternalServerError)
474 return
475 }
476
477 resp := types.RepoBranchResponse{
478 Branch: types.Branch{
479 Reference: types.Reference{
480 Name: ref.Name().Short(),
481 Hash: ref.Hash().String(),
482 },
483 },
484 }
485
486 writeJSON(w, resp)
487 return
488}
489
490func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
491 l := h.l.With("handler", "Keys")
492
493 switch r.Method {
494 case http.MethodGet:
495 keys, err := h.db.GetAllPublicKeys()
496 if err != nil {
497 writeError(w, err.Error(), http.StatusInternalServerError)
498 l.Error("getting public keys", "error", err.Error())
499 return
500 }
501
502 data := make([]map[string]any, 0)
503 for _, key := range keys {
504 j := key.JSON()
505 data = append(data, j)
506 }
507 writeJSON(w, data)
508 return
509
510 case http.MethodPut:
511 pk := db.PublicKey{}
512 if err := json.NewDecoder(r.Body).Decode(&pk); err != nil {
513 writeError(w, "invalid request body", http.StatusBadRequest)
514 return
515 }
516
517 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key))
518 if err != nil {
519 writeError(w, "invalid pubkey", http.StatusBadRequest)
520 }
521
522 if err := h.db.AddPublicKey(pk); err != nil {
523 writeError(w, err.Error(), http.StatusInternalServerError)
524 l.Error("adding public key", "error", err.Error())
525 return
526 }
527
528 w.WriteHeader(http.StatusNoContent)
529 return
530 }
531}
532
533func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) {
534 l := h.l.With("handler", "NewRepo")
535
536 data := struct {
537 Did string `json:"did"`
538 Name string `json:"name"`
539 DefaultBranch string `json:"default_branch,omitempty"`
540 }{}
541
542 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
543 writeError(w, "invalid request body", http.StatusBadRequest)
544 return
545 }
546
547 if data.DefaultBranch == "" {
548 data.DefaultBranch = h.c.Repo.MainBranch
549 }
550
551 did := data.Did
552 name := data.Name
553 defaultBranch := data.DefaultBranch
554
555 relativeRepoPath := filepath.Join(did, name)
556 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
557 err := git.InitBare(repoPath, defaultBranch)
558 if err != nil {
559 l.Error("initializing bare repo", "error", err.Error())
560 if errors.Is(err, gogit.ErrRepositoryAlreadyExists) {
561 writeError(w, "That repo already exists!", http.StatusConflict)
562 return
563 } else {
564 writeError(w, err.Error(), http.StatusInternalServerError)
565 return
566 }
567 }
568
569 // add perms for this user to access the repo
570 err = h.e.AddRepo(did, ThisServer, relativeRepoPath)
571 if err != nil {
572 l.Error("adding repo permissions", "error", err.Error())
573 writeError(w, err.Error(), http.StatusInternalServerError)
574 return
575 }
576
577 w.WriteHeader(http.StatusNoContent)
578}
579
580func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) {
581 l := h.l.With("handler", "RepoFork")
582
583 data := struct {
584 Did string `json:"did"`
585 Source string `json:"source"`
586 Name string `json:"name,omitempty"`
587 }{}
588
589 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
590 writeError(w, "invalid request body", http.StatusBadRequest)
591 return
592 }
593
594 did := data.Did
595 source := data.Source
596
597 if did == "" || source == "" {
598 l.Error("invalid request body, empty did or name")
599 w.WriteHeader(http.StatusBadRequest)
600 return
601 }
602
603 var name string
604 if data.Name != "" {
605 name = data.Name
606 } else {
607 name = filepath.Base(source)
608 }
609
610 relativeRepoPath := filepath.Join(did, name)
611 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
612
613 err := git.Fork(repoPath, source)
614 if err != nil {
615 l.Error("forking repo", "error", err.Error())
616 writeError(w, err.Error(), http.StatusInternalServerError)
617 return
618 }
619
620 w.WriteHeader(http.StatusNoContent)
621}
622
623func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) {
624 l := h.l.With("handler", "RemoveRepo")
625
626 data := struct {
627 Did string `json:"did"`
628 Name string `json:"name"`
629 }{}
630
631 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
632 writeError(w, "invalid request body", http.StatusBadRequest)
633 return
634 }
635
636 did := data.Did
637 name := data.Name
638
639 if did == "" || name == "" {
640 l.Error("invalid request body, empty did or name")
641 w.WriteHeader(http.StatusBadRequest)
642 return
643 }
644
645 relativeRepoPath := filepath.Join(did, name)
646 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
647 err := os.RemoveAll(repoPath)
648 if err != nil {
649 l.Error("removing repo", "error", err.Error())
650 writeError(w, err.Error(), http.StatusInternalServerError)
651 return
652 }
653
654 w.WriteHeader(http.StatusNoContent)
655
656}
657func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) {
658 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
659
660 data := types.MergeRequest{}
661
662 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
663 writeError(w, err.Error(), http.StatusBadRequest)
664 h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err)
665 return
666 }
667
668 mo := &git.MergeOptions{
669 AuthorName: data.AuthorName,
670 AuthorEmail: data.AuthorEmail,
671 CommitBody: data.CommitBody,
672 CommitMessage: data.CommitMessage,
673 }
674
675 patch := data.Patch
676 branch := data.Branch
677 gr, err := git.Open(path, branch)
678 if err != nil {
679 notFound(w)
680 return
681 }
682 if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil {
683 var mergeErr *git.ErrMerge
684 if errors.As(err, &mergeErr) {
685 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
686 for i, conflict := range mergeErr.Conflicts {
687 conflicts[i] = types.ConflictInfo{
688 Filename: conflict.Filename,
689 Reason: conflict.Reason,
690 }
691 }
692 response := types.MergeCheckResponse{
693 IsConflicted: true,
694 Conflicts: conflicts,
695 Message: mergeErr.Message,
696 }
697 writeConflict(w, response)
698 h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr)
699 } else {
700 writeError(w, err.Error(), http.StatusBadRequest)
701 h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error())
702 }
703 return
704 }
705
706 w.WriteHeader(http.StatusOK)
707}
708
709func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) {
710 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
711
712 var data struct {
713 Patch string `json:"patch"`
714 Branch string `json:"branch"`
715 }
716
717 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
718 writeError(w, err.Error(), http.StatusBadRequest)
719 h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err)
720 return
721 }
722
723 patch := data.Patch
724 branch := data.Branch
725 gr, err := git.Open(path, branch)
726 if err != nil {
727 notFound(w)
728 return
729 }
730
731 err = gr.MergeCheck([]byte(patch), branch)
732 if err == nil {
733 response := types.MergeCheckResponse{
734 IsConflicted: false,
735 }
736 writeJSON(w, response)
737 return
738 }
739
740 var mergeErr *git.ErrMerge
741 if errors.As(err, &mergeErr) {
742 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
743 for i, conflict := range mergeErr.Conflicts {
744 conflicts[i] = types.ConflictInfo{
745 Filename: conflict.Filename,
746 Reason: conflict.Reason,
747 }
748 }
749 response := types.MergeCheckResponse{
750 IsConflicted: true,
751 Conflicts: conflicts,
752 Message: mergeErr.Message,
753 }
754 writeConflict(w, response)
755 h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error())
756 return
757 }
758 writeError(w, err.Error(), http.StatusInternalServerError)
759 h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error())
760}
761
762func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) {
763 rev1 := chi.URLParam(r, "rev1")
764 rev1, _ = url.PathUnescape(rev1)
765
766 rev2 := chi.URLParam(r, "rev2")
767 rev2, _ = url.PathUnescape(rev2)
768
769 l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2)
770
771 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
772 gr, err := git.PlainOpen(path)
773 if err != nil {
774 notFound(w)
775 return
776 }
777
778 commit1, err := gr.ResolveRevision(rev1)
779 if err != nil {
780 l.Error("error resolving revision 1", "msg", err.Error())
781 writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest)
782 return
783 }
784
785 commit2, err := gr.ResolveRevision(rev2)
786 if err != nil {
787 l.Error("error resolving revision 2", "msg", err.Error())
788 writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest)
789 return
790 }
791
792 mergeBase, err := gr.MergeBase(commit1, commit2)
793 if err != nil {
794 l.Error("failed to find merge-base", "msg", err.Error())
795 writeError(w, "failed to calculate diff", http.StatusBadRequest)
796 return
797 }
798
799 difftree, err := gr.DiffTree(mergeBase, commit2)
800 if err != nil {
801 l.Error("error comparing revisions", "msg", err.Error())
802 writeError(w, "error comparing revisions", http.StatusBadRequest)
803 return
804 }
805
806 writeJSON(w, types.RepoDiffTreeResponse{difftree})
807 return
808}
809
810func (h *Handle) NewHiddenRef(w http.ResponseWriter, r *http.Request) {
811 l := h.l.With("handler", "NewHiddenRef")
812
813 forkRef := chi.URLParam(r, "forkRef")
814 remoteRef := chi.URLParam(r, "remoteRef")
815 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
816 gr, err := git.PlainOpen(path)
817 if err != nil {
818 notFound(w)
819 return
820 }
821
822 err = gr.TrackHiddenRemoteRef(forkRef, remoteRef)
823 if err != nil {
824 l.Error("error tracking hidden remote ref", "msg", err.Error())
825 writeError(w, "error tracking hidden remote ref", http.StatusBadRequest)
826 return
827 }
828
829 w.WriteHeader(http.StatusNoContent)
830 return
831}
832
833func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) {
834 l := h.l.With("handler", "AddMember")
835
836 data := struct {
837 Did string `json:"did"`
838 }{}
839
840 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
841 writeError(w, "invalid request body", http.StatusBadRequest)
842 return
843 }
844
845 did := data.Did
846
847 if err := h.db.AddDid(did); err != nil {
848 l.Error("adding did", "error", err.Error())
849 writeError(w, err.Error(), http.StatusInternalServerError)
850 return
851 }
852 h.jc.AddDid(did)
853
854 if err := h.e.AddMember(ThisServer, did); err != nil {
855 l.Error("adding member", "error", err.Error())
856 writeError(w, err.Error(), http.StatusInternalServerError)
857 return
858 }
859
860 if err := h.fetchAndAddKeys(r.Context(), did); err != nil {
861 l.Error("fetching and adding keys", "error", err.Error())
862 writeError(w, err.Error(), http.StatusInternalServerError)
863 return
864 }
865
866 w.WriteHeader(http.StatusNoContent)
867}
868
869func (h *Handle) AddRepoCollaborator(w http.ResponseWriter, r *http.Request) {
870 l := h.l.With("handler", "AddRepoCollaborator")
871
872 data := struct {
873 Did string `json:"did"`
874 }{}
875
876 ownerDid := chi.URLParam(r, "did")
877 repo := chi.URLParam(r, "name")
878
879 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
880 writeError(w, "invalid request body", http.StatusBadRequest)
881 return
882 }
883
884 if err := h.db.AddDid(data.Did); err != nil {
885 l.Error("adding did", "error", err.Error())
886 writeError(w, err.Error(), http.StatusInternalServerError)
887 return
888 }
889 h.jc.AddDid(data.Did)
890
891 repoName, _ := securejoin.SecureJoin(ownerDid, repo)
892 if err := h.e.AddCollaborator(data.Did, ThisServer, repoName); err != nil {
893 l.Error("adding repo collaborator", "error", err.Error())
894 writeError(w, err.Error(), http.StatusInternalServerError)
895 return
896 }
897
898 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil {
899 l.Error("fetching and adding keys", "error", err.Error())
900 writeError(w, err.Error(), http.StatusInternalServerError)
901 return
902 }
903
904 w.WriteHeader(http.StatusNoContent)
905}
906
907func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) {
908 l := h.l.With("handler", "DefaultBranch")
909 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
910
911 gr, err := git.Open(path, "")
912 if err != nil {
913 notFound(w)
914 return
915 }
916
917 branch, err := gr.FindMainBranch()
918 if err != nil {
919 writeError(w, err.Error(), http.StatusInternalServerError)
920 l.Error("getting default branch", "error", err.Error())
921 return
922 }
923
924 writeJSON(w, types.RepoDefaultBranchResponse{
925 Branch: branch,
926 })
927}
928
929func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
930 l := h.l.With("handler", "SetDefaultBranch")
931 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
932
933 data := struct {
934 Branch string `json:"branch"`
935 }{}
936
937 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
938 writeError(w, err.Error(), http.StatusBadRequest)
939 return
940 }
941
942 gr, err := git.Open(path, "")
943 if err != nil {
944 notFound(w)
945 return
946 }
947
948 err = gr.SetDefaultBranch(data.Branch)
949 if err != nil {
950 writeError(w, err.Error(), http.StatusInternalServerError)
951 l.Error("setting default branch", "error", err.Error())
952 return
953 }
954
955 w.WriteHeader(http.StatusNoContent)
956}
957
958func (h *Handle) Init(w http.ResponseWriter, r *http.Request) {
959 l := h.l.With("handler", "Init")
960
961 if h.knotInitialized {
962 writeError(w, "knot already initialized", http.StatusConflict)
963 return
964 }
965
966 data := struct {
967 Did string `json:"did"`
968 }{}
969
970 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
971 l.Error("failed to decode request body", "error", err.Error())
972 writeError(w, "invalid request body", http.StatusBadRequest)
973 return
974 }
975
976 if data.Did == "" {
977 l.Error("empty DID in request", "did", data.Did)
978 writeError(w, "did is empty", http.StatusBadRequest)
979 return
980 }
981
982 if err := h.db.AddDid(data.Did); err != nil {
983 l.Error("failed to add DID", "error", err.Error())
984 writeError(w, err.Error(), http.StatusInternalServerError)
985 return
986 }
987 h.jc.AddDid(data.Did)
988
989 if err := h.e.AddOwner(ThisServer, data.Did); err != nil {
990 l.Error("adding owner", "error", err.Error())
991 writeError(w, err.Error(), http.StatusInternalServerError)
992 return
993 }
994
995 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil {
996 l.Error("fetching and adding keys", "error", err.Error())
997 writeError(w, err.Error(), http.StatusInternalServerError)
998 return
999 }
1000
1001 close(h.init)
1002
1003 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret))
1004 mac.Write([]byte("ok"))
1005 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil)))
1006
1007 w.WriteHeader(http.StatusNoContent)
1008}
1009
1010func (h *Handle) Health(w http.ResponseWriter, r *http.Request) {
1011 w.Write([]byte("ok"))
1012}