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