forked from tangled.org/core
this repo has no description
at master 25 kB view raw
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}