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