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/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}