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