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