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 branchName, _ = url.PathUnescape(branchName)
465
466 l := h.l.With("handler", "Branch")
467
468 gr, err := git.PlainOpen(path)
469 if err != nil {
470 notFound(w)
471 return
472 }
473
474 ref, err := gr.Branch(branchName)
475 if err != nil {
476 l.Error("getting branches", "error", err.Error())
477 writeError(w, err.Error(), http.StatusInternalServerError)
478 return
479 }
480
481 resp := types.RepoBranchResponse{
482 Branch: types.Branch{
483 Reference: types.Reference{
484 Name: ref.Name().Short(),
485 Hash: ref.Hash().String(),
486 },
487 },
488 }
489
490 writeJSON(w, resp)
491 return
492}
493
494func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
495 l := h.l.With("handler", "Keys")
496
497 switch r.Method {
498 case http.MethodGet:
499 keys, err := h.db.GetAllPublicKeys()
500 if err != nil {
501 writeError(w, err.Error(), http.StatusInternalServerError)
502 l.Error("getting public keys", "error", err.Error())
503 return
504 }
505
506 data := make([]map[string]any, 0)
507 for _, key := range keys {
508 j := key.JSON()
509 data = append(data, j)
510 }
511 writeJSON(w, data)
512 return
513
514 case http.MethodPut:
515 pk := db.PublicKey{}
516 if err := json.NewDecoder(r.Body).Decode(&pk); err != nil {
517 writeError(w, "invalid request body", http.StatusBadRequest)
518 return
519 }
520
521 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key))
522 if err != nil {
523 writeError(w, "invalid pubkey", http.StatusBadRequest)
524 }
525
526 if err := h.db.AddPublicKey(pk); err != nil {
527 writeError(w, err.Error(), http.StatusInternalServerError)
528 l.Error("adding public key", "error", err.Error())
529 return
530 }
531
532 w.WriteHeader(http.StatusNoContent)
533 return
534 }
535}
536
537func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) {
538 l := h.l.With("handler", "NewRepo")
539
540 data := struct {
541 Did string `json:"did"`
542 Name string `json:"name"`
543 DefaultBranch string `json:"default_branch,omitempty"`
544 }{}
545
546 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
547 writeError(w, "invalid request body", http.StatusBadRequest)
548 return
549 }
550
551 if data.DefaultBranch == "" {
552 data.DefaultBranch = h.c.Repo.MainBranch
553 }
554
555 did := data.Did
556 name := data.Name
557 defaultBranch := data.DefaultBranch
558
559 relativeRepoPath := filepath.Join(did, name)
560 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
561 err := git.InitBare(repoPath, defaultBranch)
562 if err != nil {
563 l.Error("initializing bare repo", "error", err.Error())
564 if errors.Is(err, gogit.ErrRepositoryAlreadyExists) {
565 writeError(w, "That repo already exists!", http.StatusConflict)
566 return
567 } else {
568 writeError(w, err.Error(), http.StatusInternalServerError)
569 return
570 }
571 }
572
573 // add perms for this user to access the repo
574 err = h.e.AddRepo(did, ThisServer, relativeRepoPath)
575 if err != nil {
576 l.Error("adding repo permissions", "error", err.Error())
577 writeError(w, err.Error(), http.StatusInternalServerError)
578 return
579 }
580
581 w.WriteHeader(http.StatusNoContent)
582}
583
584func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) {
585 l := h.l.With("handler", "RepoFork")
586
587 data := struct {
588 Did string `json:"did"`
589 Source string `json:"source"`
590 Name string `json:"name,omitempty"`
591 }{}
592
593 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
594 writeError(w, "invalid request body", http.StatusBadRequest)
595 return
596 }
597
598 did := data.Did
599 source := data.Source
600
601 if did == "" || source == "" {
602 l.Error("invalid request body, empty did or name")
603 w.WriteHeader(http.StatusBadRequest)
604 return
605 }
606
607 var name string
608 if data.Name != "" {
609 name = data.Name
610 } else {
611 name = filepath.Base(source)
612 }
613
614 relativeRepoPath := filepath.Join(did, name)
615 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
616
617 err := git.Fork(repoPath, source)
618 if err != nil {
619 l.Error("forking repo", "error", err.Error())
620 writeError(w, err.Error(), http.StatusInternalServerError)
621 return
622 }
623
624 // add perms for this user to access the repo
625 err = h.e.AddRepo(did, ThisServer, relativeRepoPath)
626 if err != nil {
627 l.Error("adding repo permissions", "error", err.Error())
628 writeError(w, err.Error(), http.StatusInternalServerError)
629 return
630 }
631
632 w.WriteHeader(http.StatusNoContent)
633}
634
635func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) {
636 l := h.l.With("handler", "RemoveRepo")
637
638 data := struct {
639 Did string `json:"did"`
640 Name string `json:"name"`
641 }{}
642
643 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
644 writeError(w, "invalid request body", http.StatusBadRequest)
645 return
646 }
647
648 did := data.Did
649 name := data.Name
650
651 if did == "" || name == "" {
652 l.Error("invalid request body, empty did or name")
653 w.WriteHeader(http.StatusBadRequest)
654 return
655 }
656
657 relativeRepoPath := filepath.Join(did, name)
658 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
659 err := os.RemoveAll(repoPath)
660 if err != nil {
661 l.Error("removing repo", "error", err.Error())
662 writeError(w, err.Error(), http.StatusInternalServerError)
663 return
664 }
665
666 w.WriteHeader(http.StatusNoContent)
667
668}
669func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) {
670 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
671
672 data := types.MergeRequest{}
673
674 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
675 writeError(w, err.Error(), http.StatusBadRequest)
676 h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err)
677 return
678 }
679
680 mo := &git.MergeOptions{
681 AuthorName: data.AuthorName,
682 AuthorEmail: data.AuthorEmail,
683 CommitBody: data.CommitBody,
684 CommitMessage: data.CommitMessage,
685 }
686
687 patch := data.Patch
688 branch := data.Branch
689 gr, err := git.Open(path, branch)
690 if err != nil {
691 notFound(w)
692 return
693 }
694
695 mo.FormatPatch = patchutil.IsFormatPatch(patch)
696
697 if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil {
698 var mergeErr *git.ErrMerge
699 if errors.As(err, &mergeErr) {
700 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
701 for i, conflict := range mergeErr.Conflicts {
702 conflicts[i] = types.ConflictInfo{
703 Filename: conflict.Filename,
704 Reason: conflict.Reason,
705 }
706 }
707 response := types.MergeCheckResponse{
708 IsConflicted: true,
709 Conflicts: conflicts,
710 Message: mergeErr.Message,
711 }
712 writeConflict(w, response)
713 h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr)
714 } else {
715 writeError(w, err.Error(), http.StatusBadRequest)
716 h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error())
717 }
718 return
719 }
720
721 w.WriteHeader(http.StatusOK)
722}
723
724func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) {
725 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
726
727 var data struct {
728 Patch string `json:"patch"`
729 Branch string `json:"branch"`
730 }
731
732 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
733 writeError(w, err.Error(), http.StatusBadRequest)
734 h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err)
735 return
736 }
737
738 patch := data.Patch
739 branch := data.Branch
740 gr, err := git.Open(path, branch)
741 if err != nil {
742 notFound(w)
743 return
744 }
745
746 err = gr.MergeCheck([]byte(patch), branch)
747 if err == nil {
748 response := types.MergeCheckResponse{
749 IsConflicted: false,
750 }
751 writeJSON(w, response)
752 return
753 }
754
755 var mergeErr *git.ErrMerge
756 if errors.As(err, &mergeErr) {
757 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
758 for i, conflict := range mergeErr.Conflicts {
759 conflicts[i] = types.ConflictInfo{
760 Filename: conflict.Filename,
761 Reason: conflict.Reason,
762 }
763 }
764 response := types.MergeCheckResponse{
765 IsConflicted: true,
766 Conflicts: conflicts,
767 Message: mergeErr.Message,
768 }
769 writeConflict(w, response)
770 h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error())
771 return
772 }
773 writeError(w, err.Error(), http.StatusInternalServerError)
774 h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error())
775}
776
777func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) {
778 rev1 := chi.URLParam(r, "rev1")
779 rev1, _ = url.PathUnescape(rev1)
780
781 rev2 := chi.URLParam(r, "rev2")
782 rev2, _ = url.PathUnescape(rev2)
783
784 l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2)
785
786 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
787 gr, err := git.PlainOpen(path)
788 if err != nil {
789 notFound(w)
790 return
791 }
792
793 commit1, err := gr.ResolveRevision(rev1)
794 if err != nil {
795 l.Error("error resolving revision 1", "msg", err.Error())
796 writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest)
797 return
798 }
799
800 commit2, err := gr.ResolveRevision(rev2)
801 if err != nil {
802 l.Error("error resolving revision 2", "msg", err.Error())
803 writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest)
804 return
805 }
806
807 mergeBase, err := gr.MergeBase(commit1, commit2)
808 if err != nil {
809 l.Error("failed to find merge-base", "msg", err.Error())
810 writeError(w, "failed to calculate diff", http.StatusBadRequest)
811 return
812 }
813
814 rawPatch, formatPatch, err := gr.FormatPatch(mergeBase, commit2)
815 if err != nil {
816 l.Error("error comparing revisions", "msg", err.Error())
817 writeError(w, "error comparing revisions", http.StatusBadRequest)
818 return
819 }
820
821 writeJSON(w, types.RepoFormatPatchResponse{
822 Rev1: commit1.Hash.String(),
823 Rev2: commit2.Hash.String(),
824 FormatPatch: formatPatch,
825 Patch: rawPatch,
826 })
827 return
828}
829
830func (h *Handle) NewHiddenRef(w http.ResponseWriter, r *http.Request) {
831 l := h.l.With("handler", "NewHiddenRef")
832
833 forkRef := chi.URLParam(r, "forkRef")
834 forkRef, _ = url.PathUnescape(forkRef)
835
836 remoteRef := chi.URLParam(r, "remoteRef")
837 remoteRef, _ = url.PathUnescape(remoteRef)
838
839 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
840 gr, err := git.PlainOpen(path)
841 if err != nil {
842 notFound(w)
843 return
844 }
845
846 err = gr.TrackHiddenRemoteRef(forkRef, remoteRef)
847 if err != nil {
848 l.Error("error tracking hidden remote ref", "msg", err.Error())
849 writeError(w, "error tracking hidden remote ref", http.StatusBadRequest)
850 return
851 }
852
853 w.WriteHeader(http.StatusNoContent)
854 return
855}
856
857func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) {
858 l := h.l.With("handler", "AddMember")
859
860 data := struct {
861 Did string `json:"did"`
862 }{}
863
864 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
865 writeError(w, "invalid request body", http.StatusBadRequest)
866 return
867 }
868
869 did := data.Did
870
871 if err := h.db.AddDid(did); err != nil {
872 l.Error("adding did", "error", err.Error())
873 writeError(w, err.Error(), http.StatusInternalServerError)
874 return
875 }
876 h.jc.AddDid(did)
877
878 if err := h.e.AddMember(ThisServer, did); err != nil {
879 l.Error("adding member", "error", err.Error())
880 writeError(w, err.Error(), http.StatusInternalServerError)
881 return
882 }
883
884 if err := h.fetchAndAddKeys(r.Context(), did); err != nil {
885 l.Error("fetching and adding keys", "error", err.Error())
886 writeError(w, err.Error(), http.StatusInternalServerError)
887 return
888 }
889
890 w.WriteHeader(http.StatusNoContent)
891}
892
893func (h *Handle) AddRepoCollaborator(w http.ResponseWriter, r *http.Request) {
894 l := h.l.With("handler", "AddRepoCollaborator")
895
896 data := struct {
897 Did string `json:"did"`
898 }{}
899
900 ownerDid := chi.URLParam(r, "did")
901 repo := chi.URLParam(r, "name")
902
903 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
904 writeError(w, "invalid request body", http.StatusBadRequest)
905 return
906 }
907
908 if err := h.db.AddDid(data.Did); err != nil {
909 l.Error("adding did", "error", err.Error())
910 writeError(w, err.Error(), http.StatusInternalServerError)
911 return
912 }
913 h.jc.AddDid(data.Did)
914
915 repoName, _ := securejoin.SecureJoin(ownerDid, repo)
916 if err := h.e.AddCollaborator(data.Did, ThisServer, repoName); err != nil {
917 l.Error("adding repo collaborator", "error", err.Error())
918 writeError(w, err.Error(), http.StatusInternalServerError)
919 return
920 }
921
922 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil {
923 l.Error("fetching and adding keys", "error", err.Error())
924 writeError(w, err.Error(), http.StatusInternalServerError)
925 return
926 }
927
928 w.WriteHeader(http.StatusNoContent)
929}
930
931func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) {
932 l := h.l.With("handler", "DefaultBranch")
933 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
934
935 gr, err := git.Open(path, "")
936 if err != nil {
937 notFound(w)
938 return
939 }
940
941 branch, err := gr.FindMainBranch()
942 if err != nil {
943 writeError(w, err.Error(), http.StatusInternalServerError)
944 l.Error("getting default branch", "error", err.Error())
945 return
946 }
947
948 writeJSON(w, types.RepoDefaultBranchResponse{
949 Branch: branch,
950 })
951}
952
953func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
954 l := h.l.With("handler", "SetDefaultBranch")
955 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
956
957 data := struct {
958 Branch string `json:"branch"`
959 }{}
960
961 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
962 writeError(w, err.Error(), http.StatusBadRequest)
963 return
964 }
965
966 gr, err := git.Open(path, "")
967 if err != nil {
968 notFound(w)
969 return
970 }
971
972 err = gr.SetDefaultBranch(data.Branch)
973 if err != nil {
974 writeError(w, err.Error(), http.StatusInternalServerError)
975 l.Error("setting default branch", "error", err.Error())
976 return
977 }
978
979 w.WriteHeader(http.StatusNoContent)
980}
981
982func (h *Handle) Init(w http.ResponseWriter, r *http.Request) {
983 l := h.l.With("handler", "Init")
984
985 if h.knotInitialized {
986 writeError(w, "knot already initialized", http.StatusConflict)
987 return
988 }
989
990 data := struct {
991 Did string `json:"did"`
992 }{}
993
994 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
995 l.Error("failed to decode request body", "error", err.Error())
996 writeError(w, "invalid request body", http.StatusBadRequest)
997 return
998 }
999
1000 if data.Did == "" {
1001 l.Error("empty DID in request", "did", data.Did)
1002 writeError(w, "did is empty", http.StatusBadRequest)
1003 return
1004 }
1005
1006 if err := h.db.AddDid(data.Did); err != nil {
1007 l.Error("failed to add DID", "error", err.Error())
1008 writeError(w, err.Error(), http.StatusInternalServerError)
1009 return
1010 }
1011 h.jc.AddDid(data.Did)
1012
1013 if err := h.e.AddOwner(ThisServer, data.Did); err != nil {
1014 l.Error("adding owner", "error", err.Error())
1015 writeError(w, err.Error(), http.StatusInternalServerError)
1016 return
1017 }
1018
1019 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil {
1020 l.Error("fetching and adding keys", "error", err.Error())
1021 writeError(w, err.Error(), http.StatusInternalServerError)
1022 return
1023 }
1024
1025 close(h.init)
1026
1027 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret))
1028 mac.Write([]byte("ok"))
1029 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil)))
1030
1031 w.WriteHeader(http.StatusNoContent)
1032}
1033
1034func (h *Handle) Health(w http.ResponseWriter, r *http.Request) {
1035 w.Write([]byte("ok"))
1036}