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 "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 branchName, _ = url.PathUnescape(branchName) 465 466 l := h.l.With("handler", "Branch") 467 468 gr, err := git.PlainOpen(path) 469 if err != nil { 470 notFound(w) 471 return 472 } 473 474 ref, err := gr.Branch(branchName) 475 if err != nil { 476 l.Error("getting branches", "error", err.Error()) 477 writeError(w, err.Error(), http.StatusInternalServerError) 478 return 479 } 480 481 resp := types.RepoBranchResponse{ 482 Branch: types.Branch{ 483 Reference: types.Reference{ 484 Name: ref.Name().Short(), 485 Hash: ref.Hash().String(), 486 }, 487 }, 488 } 489 490 writeJSON(w, resp) 491 return 492} 493 494func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 495 l := h.l.With("handler", "Keys") 496 497 switch r.Method { 498 case http.MethodGet: 499 keys, err := h.db.GetAllPublicKeys() 500 if err != nil { 501 writeError(w, err.Error(), http.StatusInternalServerError) 502 l.Error("getting public keys", "error", err.Error()) 503 return 504 } 505 506 data := make([]map[string]any, 0) 507 for _, key := range keys { 508 j := key.JSON() 509 data = append(data, j) 510 } 511 writeJSON(w, data) 512 return 513 514 case http.MethodPut: 515 pk := db.PublicKey{} 516 if err := json.NewDecoder(r.Body).Decode(&pk); err != nil { 517 writeError(w, "invalid request body", http.StatusBadRequest) 518 return 519 } 520 521 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key)) 522 if err != nil { 523 writeError(w, "invalid pubkey", http.StatusBadRequest) 524 } 525 526 if err := h.db.AddPublicKey(pk); err != nil { 527 writeError(w, err.Error(), http.StatusInternalServerError) 528 l.Error("adding public key", "error", err.Error()) 529 return 530 } 531 532 w.WriteHeader(http.StatusNoContent) 533 return 534 } 535} 536 537func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) { 538 l := h.l.With("handler", "NewRepo") 539 540 data := struct { 541 Did string `json:"did"` 542 Name string `json:"name"` 543 DefaultBranch string `json:"default_branch,omitempty"` 544 }{} 545 546 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 547 writeError(w, "invalid request body", http.StatusBadRequest) 548 return 549 } 550 551 if data.DefaultBranch == "" { 552 data.DefaultBranch = h.c.Repo.MainBranch 553 } 554 555 did := data.Did 556 name := data.Name 557 defaultBranch := data.DefaultBranch 558 559 relativeRepoPath := filepath.Join(did, name) 560 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 561 err := git.InitBare(repoPath, defaultBranch) 562 if err != nil { 563 l.Error("initializing bare repo", "error", err.Error()) 564 if errors.Is(err, gogit.ErrRepositoryAlreadyExists) { 565 writeError(w, "That repo already exists!", http.StatusConflict) 566 return 567 } else { 568 writeError(w, err.Error(), http.StatusInternalServerError) 569 return 570 } 571 } 572 573 // add perms for this user to access the repo 574 err = h.e.AddRepo(did, ThisServer, relativeRepoPath) 575 if err != nil { 576 l.Error("adding repo permissions", "error", err.Error()) 577 writeError(w, err.Error(), http.StatusInternalServerError) 578 return 579 } 580 581 w.WriteHeader(http.StatusNoContent) 582} 583 584func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) { 585 l := h.l.With("handler", "RepoFork") 586 587 data := struct { 588 Did string `json:"did"` 589 Source string `json:"source"` 590 Name string `json:"name,omitempty"` 591 }{} 592 593 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 594 writeError(w, "invalid request body", http.StatusBadRequest) 595 return 596 } 597 598 did := data.Did 599 source := data.Source 600 601 if did == "" || source == "" { 602 l.Error("invalid request body, empty did or name") 603 w.WriteHeader(http.StatusBadRequest) 604 return 605 } 606 607 var name string 608 if data.Name != "" { 609 name = data.Name 610 } else { 611 name = filepath.Base(source) 612 } 613 614 relativeRepoPath := filepath.Join(did, name) 615 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 616 617 err := git.Fork(repoPath, source) 618 if err != nil { 619 l.Error("forking repo", "error", err.Error()) 620 writeError(w, err.Error(), http.StatusInternalServerError) 621 return 622 } 623 624 // add perms for this user to access the repo 625 err = h.e.AddRepo(did, ThisServer, relativeRepoPath) 626 if err != nil { 627 l.Error("adding repo permissions", "error", err.Error()) 628 writeError(w, err.Error(), http.StatusInternalServerError) 629 return 630 } 631 632 w.WriteHeader(http.StatusNoContent) 633} 634 635func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) { 636 l := h.l.With("handler", "RemoveRepo") 637 638 data := struct { 639 Did string `json:"did"` 640 Name string `json:"name"` 641 }{} 642 643 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 644 writeError(w, "invalid request body", http.StatusBadRequest) 645 return 646 } 647 648 did := data.Did 649 name := data.Name 650 651 if did == "" || name == "" { 652 l.Error("invalid request body, empty did or name") 653 w.WriteHeader(http.StatusBadRequest) 654 return 655 } 656 657 relativeRepoPath := filepath.Join(did, name) 658 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 659 err := os.RemoveAll(repoPath) 660 if err != nil { 661 l.Error("removing repo", "error", err.Error()) 662 writeError(w, err.Error(), http.StatusInternalServerError) 663 return 664 } 665 666 w.WriteHeader(http.StatusNoContent) 667 668} 669func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { 670 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 671 672 data := types.MergeRequest{} 673 674 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 675 writeError(w, err.Error(), http.StatusBadRequest) 676 h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err) 677 return 678 } 679 680 mo := &git.MergeOptions{ 681 AuthorName: data.AuthorName, 682 AuthorEmail: data.AuthorEmail, 683 CommitBody: data.CommitBody, 684 CommitMessage: data.CommitMessage, 685 } 686 687 patch := data.Patch 688 branch := data.Branch 689 gr, err := git.Open(path, branch) 690 if err != nil { 691 notFound(w) 692 return 693 } 694 695 mo.FormatPatch = patchutil.IsFormatPatch(patch) 696 697 if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil { 698 var mergeErr *git.ErrMerge 699 if errors.As(err, &mergeErr) { 700 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 701 for i, conflict := range mergeErr.Conflicts { 702 conflicts[i] = types.ConflictInfo{ 703 Filename: conflict.Filename, 704 Reason: conflict.Reason, 705 } 706 } 707 response := types.MergeCheckResponse{ 708 IsConflicted: true, 709 Conflicts: conflicts, 710 Message: mergeErr.Message, 711 } 712 writeConflict(w, response) 713 h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr) 714 } else { 715 writeError(w, err.Error(), http.StatusBadRequest) 716 h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error()) 717 } 718 return 719 } 720 721 w.WriteHeader(http.StatusOK) 722} 723 724func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) { 725 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 726 727 var data struct { 728 Patch string `json:"patch"` 729 Branch string `json:"branch"` 730 } 731 732 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 733 writeError(w, err.Error(), http.StatusBadRequest) 734 h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err) 735 return 736 } 737 738 patch := data.Patch 739 branch := data.Branch 740 gr, err := git.Open(path, branch) 741 if err != nil { 742 notFound(w) 743 return 744 } 745 746 err = gr.MergeCheck([]byte(patch), branch) 747 if err == nil { 748 response := types.MergeCheckResponse{ 749 IsConflicted: false, 750 } 751 writeJSON(w, response) 752 return 753 } 754 755 var mergeErr *git.ErrMerge 756 if errors.As(err, &mergeErr) { 757 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 758 for i, conflict := range mergeErr.Conflicts { 759 conflicts[i] = types.ConflictInfo{ 760 Filename: conflict.Filename, 761 Reason: conflict.Reason, 762 } 763 } 764 response := types.MergeCheckResponse{ 765 IsConflicted: true, 766 Conflicts: conflicts, 767 Message: mergeErr.Message, 768 } 769 writeConflict(w, response) 770 h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error()) 771 return 772 } 773 writeError(w, err.Error(), http.StatusInternalServerError) 774 h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error()) 775} 776 777func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) { 778 rev1 := chi.URLParam(r, "rev1") 779 rev1, _ = url.PathUnescape(rev1) 780 781 rev2 := chi.URLParam(r, "rev2") 782 rev2, _ = url.PathUnescape(rev2) 783 784 l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2) 785 786 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 787 gr, err := git.PlainOpen(path) 788 if err != nil { 789 notFound(w) 790 return 791 } 792 793 commit1, err := gr.ResolveRevision(rev1) 794 if err != nil { 795 l.Error("error resolving revision 1", "msg", err.Error()) 796 writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest) 797 return 798 } 799 800 commit2, err := gr.ResolveRevision(rev2) 801 if err != nil { 802 l.Error("error resolving revision 2", "msg", err.Error()) 803 writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest) 804 return 805 } 806 807 mergeBase, err := gr.MergeBase(commit1, commit2) 808 if err != nil { 809 l.Error("failed to find merge-base", "msg", err.Error()) 810 writeError(w, "failed to calculate diff", http.StatusBadRequest) 811 return 812 } 813 814 rawPatch, formatPatch, err := gr.FormatPatch(mergeBase, commit2) 815 if err != nil { 816 l.Error("error comparing revisions", "msg", err.Error()) 817 writeError(w, "error comparing revisions", http.StatusBadRequest) 818 return 819 } 820 821 writeJSON(w, types.RepoFormatPatchResponse{ 822 Rev1: commit1.Hash.String(), 823 Rev2: commit2.Hash.String(), 824 FormatPatch: formatPatch, 825 Patch: rawPatch, 826 }) 827 return 828} 829 830func (h *Handle) NewHiddenRef(w http.ResponseWriter, r *http.Request) { 831 l := h.l.With("handler", "NewHiddenRef") 832 833 forkRef := chi.URLParam(r, "forkRef") 834 forkRef, _ = url.PathUnescape(forkRef) 835 836 remoteRef := chi.URLParam(r, "remoteRef") 837 remoteRef, _ = url.PathUnescape(remoteRef) 838 839 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 840 gr, err := git.PlainOpen(path) 841 if err != nil { 842 notFound(w) 843 return 844 } 845 846 err = gr.TrackHiddenRemoteRef(forkRef, remoteRef) 847 if err != nil { 848 l.Error("error tracking hidden remote ref", "msg", err.Error()) 849 writeError(w, "error tracking hidden remote ref", http.StatusBadRequest) 850 return 851 } 852 853 w.WriteHeader(http.StatusNoContent) 854 return 855} 856 857func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) { 858 l := h.l.With("handler", "AddMember") 859 860 data := struct { 861 Did string `json:"did"` 862 }{} 863 864 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 865 writeError(w, "invalid request body", http.StatusBadRequest) 866 return 867 } 868 869 did := data.Did 870 871 if err := h.db.AddDid(did); err != nil { 872 l.Error("adding did", "error", err.Error()) 873 writeError(w, err.Error(), http.StatusInternalServerError) 874 return 875 } 876 h.jc.AddDid(did) 877 878 if err := h.e.AddMember(ThisServer, did); err != nil { 879 l.Error("adding member", "error", err.Error()) 880 writeError(w, err.Error(), http.StatusInternalServerError) 881 return 882 } 883 884 if err := h.fetchAndAddKeys(r.Context(), did); err != nil { 885 l.Error("fetching and adding keys", "error", err.Error()) 886 writeError(w, err.Error(), http.StatusInternalServerError) 887 return 888 } 889 890 w.WriteHeader(http.StatusNoContent) 891} 892 893func (h *Handle) AddRepoCollaborator(w http.ResponseWriter, r *http.Request) { 894 l := h.l.With("handler", "AddRepoCollaborator") 895 896 data := struct { 897 Did string `json:"did"` 898 }{} 899 900 ownerDid := chi.URLParam(r, "did") 901 repo := chi.URLParam(r, "name") 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 if err := h.db.AddDid(data.Did); err != nil { 909 l.Error("adding did", "error", err.Error()) 910 writeError(w, err.Error(), http.StatusInternalServerError) 911 return 912 } 913 h.jc.AddDid(data.Did) 914 915 repoName, _ := securejoin.SecureJoin(ownerDid, repo) 916 if err := h.e.AddCollaborator(data.Did, ThisServer, repoName); err != nil { 917 l.Error("adding repo collaborator", "error", err.Error()) 918 writeError(w, err.Error(), http.StatusInternalServerError) 919 return 920 } 921 922 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 923 l.Error("fetching and adding keys", "error", err.Error()) 924 writeError(w, err.Error(), http.StatusInternalServerError) 925 return 926 } 927 928 w.WriteHeader(http.StatusNoContent) 929} 930 931func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) { 932 l := h.l.With("handler", "DefaultBranch") 933 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 934 935 gr, err := git.Open(path, "") 936 if err != nil { 937 notFound(w) 938 return 939 } 940 941 branch, err := gr.FindMainBranch() 942 if err != nil { 943 writeError(w, err.Error(), http.StatusInternalServerError) 944 l.Error("getting default branch", "error", err.Error()) 945 return 946 } 947 948 writeJSON(w, types.RepoDefaultBranchResponse{ 949 Branch: branch, 950 }) 951} 952 953func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 954 l := h.l.With("handler", "SetDefaultBranch") 955 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 956 957 data := struct { 958 Branch string `json:"branch"` 959 }{} 960 961 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 962 writeError(w, err.Error(), http.StatusBadRequest) 963 return 964 } 965 966 gr, err := git.Open(path, "") 967 if err != nil { 968 notFound(w) 969 return 970 } 971 972 err = gr.SetDefaultBranch(data.Branch) 973 if err != nil { 974 writeError(w, err.Error(), http.StatusInternalServerError) 975 l.Error("setting default branch", "error", err.Error()) 976 return 977 } 978 979 w.WriteHeader(http.StatusNoContent) 980} 981 982func (h *Handle) Init(w http.ResponseWriter, r *http.Request) { 983 l := h.l.With("handler", "Init") 984 985 if h.knotInitialized { 986 writeError(w, "knot already initialized", http.StatusConflict) 987 return 988 } 989 990 data := struct { 991 Did string `json:"did"` 992 }{} 993 994 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 995 l.Error("failed to decode request body", "error", err.Error()) 996 writeError(w, "invalid request body", http.StatusBadRequest) 997 return 998 } 999 1000 if data.Did == "" { 1001 l.Error("empty DID in request", "did", data.Did) 1002 writeError(w, "did is empty", http.StatusBadRequest) 1003 return 1004 } 1005 1006 if err := h.db.AddDid(data.Did); err != nil { 1007 l.Error("failed to add DID", "error", err.Error()) 1008 writeError(w, err.Error(), http.StatusInternalServerError) 1009 return 1010 } 1011 h.jc.AddDid(data.Did) 1012 1013 if err := h.e.AddOwner(ThisServer, data.Did); err != nil { 1014 l.Error("adding owner", "error", err.Error()) 1015 writeError(w, err.Error(), http.StatusInternalServerError) 1016 return 1017 } 1018 1019 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 1020 l.Error("fetching and adding keys", "error", err.Error()) 1021 writeError(w, err.Error(), http.StatusInternalServerError) 1022 return 1023 } 1024 1025 close(h.init) 1026 1027 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret)) 1028 mac.Write([]byte("ok")) 1029 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil))) 1030 1031 w.WriteHeader(http.StatusNoContent) 1032} 1033 1034func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 1035 w.Write([]byte("ok")) 1036}