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