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