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