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) BlobRaw(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", "BlobRaw", "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 contents, err := gr.RawContent(treePath) 212 if err != nil { 213 writeError(w, err.Error(), http.StatusBadRequest) 214 l.Error("file content", "error", err.Error()) 215 return 216 } 217 218 mimeType := http.DetectContentType(contents) 219 220 // exception for svg 221 if strings.HasPrefix(mimeType, "text/xml") && filepath.Ext(treePath) == ".svg" { 222 mimeType = "image/svg+xml" 223 } 224 225 if !strings.HasPrefix(mimeType, "image/") && !strings.HasPrefix(mimeType, "video/") { 226 l.Error("attempted to serve non-image/video file", "mimetype", mimeType) 227 writeError(w, "only image and video files can be accessed directly", http.StatusForbidden) 228 return 229 } 230 231 w.Header().Set("Cache-Control", "public, max-age=86400") // cache for 24 hours 232 w.Header().Set("ETag", fmt.Sprintf("%x", sha256.Sum256(contents))) 233 w.Header().Set("Content-Type", mimeType) 234 w.Write(contents) 235} 236 237func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) { 238 treePath := chi.URLParam(r, "*") 239 ref := chi.URLParam(r, "ref") 240 ref, _ = url.PathUnescape(ref) 241 242 l := h.l.With("handler", "Blob", "ref", ref, "treePath", treePath) 243 244 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 245 gr, err := git.Open(path, ref) 246 if err != nil { 247 notFound(w) 248 return 249 } 250 251 var isBinaryFile bool = false 252 contents, err := gr.FileContent(treePath) 253 if errors.Is(err, git.ErrBinaryFile) { 254 isBinaryFile = true 255 } else if errors.Is(err, object.ErrFileNotFound) { 256 notFound(w) 257 return 258 } else if err != nil { 259 writeError(w, err.Error(), http.StatusInternalServerError) 260 return 261 } 262 263 bytes := []byte(contents) 264 // safe := string(sanitize(bytes)) 265 sizeHint := len(bytes) 266 267 resp := types.RepoBlobResponse{ 268 Ref: ref, 269 Contents: string(bytes), 270 Path: treePath, 271 IsBinary: isBinaryFile, 272 SizeHint: uint64(sizeHint), 273 } 274 275 h.showFile(resp, w, l) 276} 277 278func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { 279 name := chi.URLParam(r, "name") 280 file := chi.URLParam(r, "file") 281 282 l := h.l.With("handler", "Archive", "name", name, "file", file) 283 284 // TODO: extend this to add more files compression (e.g.: xz) 285 if !strings.HasSuffix(file, ".tar.gz") { 286 notFound(w) 287 return 288 } 289 290 ref := strings.TrimSuffix(file, ".tar.gz") 291 292 // This allows the browser to use a proper name for the file when 293 // downloading 294 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref) 295 setContentDisposition(w, filename) 296 setGZipMIME(w) 297 298 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 299 gr, err := git.Open(path, ref) 300 if err != nil { 301 notFound(w) 302 return 303 } 304 305 gw := gzip.NewWriter(w) 306 defer gw.Close() 307 308 prefix := fmt.Sprintf("%s-%s", name, ref) 309 err = gr.WriteTar(gw, prefix) 310 if err != nil { 311 // once we start writing to the body we can't report error anymore 312 // so we are only left with printing the error. 313 l.Error("writing tar file", "error", err.Error()) 314 return 315 } 316 317 err = gw.Flush() 318 if err != nil { 319 // once we start writing to the body we can't report error anymore 320 // so we are only left with printing the error. 321 l.Error("flushing?", "error", err.Error()) 322 return 323 } 324} 325 326func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { 327 ref := chi.URLParam(r, "ref") 328 ref, _ = url.PathUnescape(ref) 329 330 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 331 332 l := h.l.With("handler", "Log", "ref", ref, "path", path) 333 334 gr, err := git.Open(path, ref) 335 if err != nil { 336 notFound(w) 337 return 338 } 339 340 commits, err := gr.Commits() 341 if err != nil { 342 writeError(w, err.Error(), http.StatusInternalServerError) 343 l.Error("fetching commits", "error", err.Error()) 344 return 345 } 346 347 // Get page parameters 348 page := 1 349 pageSize := 30 350 351 if pageParam := r.URL.Query().Get("page"); pageParam != "" { 352 if p, err := strconv.Atoi(pageParam); err == nil && p > 0 { 353 page = p 354 } 355 } 356 357 if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" { 358 if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 { 359 pageSize = ps 360 } 361 } 362 363 // Calculate pagination 364 start := (page - 1) * pageSize 365 end := start + pageSize 366 total := len(commits) 367 368 if start >= total { 369 commits = []*object.Commit{} 370 } else { 371 if end > total { 372 end = total 373 } 374 commits = commits[start:end] 375 } 376 377 resp := types.RepoLogResponse{ 378 Commits: commits, 379 Ref: ref, 380 Description: getDescription(path), 381 Log: true, 382 Total: total, 383 Page: page, 384 PerPage: pageSize, 385 } 386 387 writeJSON(w, resp) 388 return 389} 390 391func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) { 392 ref := chi.URLParam(r, "ref") 393 ref, _ = url.PathUnescape(ref) 394 395 l := h.l.With("handler", "Diff", "ref", ref) 396 397 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 398 gr, err := git.Open(path, ref) 399 if err != nil { 400 notFound(w) 401 return 402 } 403 404 diff, err := gr.Diff() 405 if err != nil { 406 writeError(w, err.Error(), http.StatusInternalServerError) 407 l.Error("getting diff", "error", err.Error()) 408 return 409 } 410 411 resp := types.RepoCommitResponse{ 412 Ref: ref, 413 Diff: diff, 414 } 415 416 writeJSON(w, resp) 417 return 418} 419 420func (h *Handle) Tags(w http.ResponseWriter, r *http.Request) { 421 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 422 l := h.l.With("handler", "Refs") 423 424 gr, err := git.Open(path, "") 425 if err != nil { 426 notFound(w) 427 return 428 } 429 430 tags, err := gr.Tags() 431 if err != nil { 432 // Non-fatal, we *should* have at least one branch to show. 433 l.Warn("getting tags", "error", err.Error()) 434 } 435 436 rtags := []*types.TagReference{} 437 for _, tag := range tags { 438 tr := types.TagReference{ 439 Tag: tag.TagObject(), 440 } 441 442 tr.Reference = types.Reference{ 443 Name: tag.Name(), 444 Hash: tag.Hash().String(), 445 } 446 447 if tag.Message() != "" { 448 tr.Message = tag.Message() 449 } 450 451 rtags = append(rtags, &tr) 452 } 453 454 resp := types.RepoTagsResponse{ 455 Tags: rtags, 456 } 457 458 writeJSON(w, resp) 459 return 460} 461 462func (h *Handle) Branches(w http.ResponseWriter, r *http.Request) { 463 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 464 l := h.l.With("handler", "Branches") 465 466 gr, err := git.Open(path, "") 467 if err != nil { 468 notFound(w) 469 return 470 } 471 472 branches, err := gr.Branches() 473 if err != nil { 474 l.Error("getting branches", "error", err.Error()) 475 writeError(w, err.Error(), http.StatusInternalServerError) 476 return 477 } 478 479 resp := types.RepoBranchesResponse{ 480 Branches: branches, 481 } 482 483 writeJSON(w, resp) 484 return 485} 486 487func (h *Handle) Branch(w http.ResponseWriter, r *http.Request) { 488 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 489 branchName := chi.URLParam(r, "branch") 490 branchName, _ = url.PathUnescape(branchName) 491 492 l := h.l.With("handler", "Branch") 493 494 gr, err := git.PlainOpen(path) 495 if err != nil { 496 notFound(w) 497 return 498 } 499 500 ref, err := gr.Branch(branchName) 501 if err != nil { 502 l.Error("getting branch", "error", err.Error()) 503 writeError(w, err.Error(), http.StatusInternalServerError) 504 return 505 } 506 507 commit, err := gr.Commit(ref.Hash()) 508 if err != nil { 509 l.Error("getting commit object", "error", err.Error()) 510 writeError(w, err.Error(), http.StatusInternalServerError) 511 return 512 } 513 514 defaultBranch, err := gr.FindMainBranch() 515 isDefault := false 516 if err != nil { 517 l.Error("getting default branch", "error", err.Error()) 518 // do not quit though 519 } else if defaultBranch == branchName { 520 isDefault = true 521 } 522 523 resp := types.RepoBranchResponse{ 524 Branch: types.Branch{ 525 Reference: types.Reference{ 526 Name: ref.Name().Short(), 527 Hash: ref.Hash().String(), 528 }, 529 Commit: commit, 530 IsDefault: isDefault, 531 }, 532 } 533 534 writeJSON(w, resp) 535 return 536} 537 538func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 539 l := h.l.With("handler", "Keys") 540 541 switch r.Method { 542 case http.MethodGet: 543 keys, err := h.db.GetAllPublicKeys() 544 if err != nil { 545 writeError(w, err.Error(), http.StatusInternalServerError) 546 l.Error("getting public keys", "error", err.Error()) 547 return 548 } 549 550 data := make([]map[string]any, 0) 551 for _, key := range keys { 552 j := key.JSON() 553 data = append(data, j) 554 } 555 writeJSON(w, data) 556 return 557 558 case http.MethodPut: 559 pk := db.PublicKey{} 560 if err := json.NewDecoder(r.Body).Decode(&pk); err != nil { 561 writeError(w, "invalid request body", http.StatusBadRequest) 562 return 563 } 564 565 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key)) 566 if err != nil { 567 writeError(w, "invalid pubkey", http.StatusBadRequest) 568 } 569 570 if err := h.db.AddPublicKey(pk); err != nil { 571 writeError(w, err.Error(), http.StatusInternalServerError) 572 l.Error("adding public key", "error", err.Error()) 573 return 574 } 575 576 w.WriteHeader(http.StatusNoContent) 577 return 578 } 579} 580 581func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) { 582 l := h.l.With("handler", "NewRepo") 583 584 data := struct { 585 Did string `json:"did"` 586 Name string `json:"name"` 587 DefaultBranch string `json:"default_branch,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 if data.DefaultBranch == "" { 596 data.DefaultBranch = h.c.Repo.MainBranch 597 } 598 599 did := data.Did 600 name := data.Name 601 defaultBranch := data.DefaultBranch 602 603 relativeRepoPath := filepath.Join(did, name) 604 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 605 err := git.InitBare(repoPath, defaultBranch) 606 if err != nil { 607 l.Error("initializing bare repo", "error", err.Error()) 608 if errors.Is(err, gogit.ErrRepositoryAlreadyExists) { 609 writeError(w, "That repo already exists!", http.StatusConflict) 610 return 611 } else { 612 writeError(w, err.Error(), http.StatusInternalServerError) 613 return 614 } 615 } 616 617 // add perms for this user to access the repo 618 err = h.e.AddRepo(did, ThisServer, relativeRepoPath) 619 if err != nil { 620 l.Error("adding repo permissions", "error", err.Error()) 621 writeError(w, err.Error(), http.StatusInternalServerError) 622 return 623 } 624 625 w.WriteHeader(http.StatusNoContent) 626} 627 628func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) { 629 l := h.l.With("handler", "RepoFork") 630 631 data := struct { 632 Did string `json:"did"` 633 Source string `json:"source"` 634 Name string `json:"name,omitempty"` 635 }{} 636 637 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 638 writeError(w, "invalid request body", http.StatusBadRequest) 639 return 640 } 641 642 did := data.Did 643 source := data.Source 644 645 if did == "" || source == "" { 646 l.Error("invalid request body, empty did or name") 647 w.WriteHeader(http.StatusBadRequest) 648 return 649 } 650 651 var name string 652 if data.Name != "" { 653 name = data.Name 654 } else { 655 name = filepath.Base(source) 656 } 657 658 relativeRepoPath := filepath.Join(did, name) 659 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 660 661 err := git.Fork(repoPath, source) 662 if err != nil { 663 l.Error("forking repo", "error", err.Error()) 664 writeError(w, err.Error(), http.StatusInternalServerError) 665 return 666 } 667 668 // add perms for this user to access the repo 669 err = h.e.AddRepo(did, ThisServer, relativeRepoPath) 670 if err != nil { 671 l.Error("adding repo permissions", "error", err.Error()) 672 writeError(w, err.Error(), http.StatusInternalServerError) 673 return 674 } 675 676 w.WriteHeader(http.StatusNoContent) 677} 678 679func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) { 680 l := h.l.With("handler", "RemoveRepo") 681 682 data := struct { 683 Did string `json:"did"` 684 Name string `json:"name"` 685 }{} 686 687 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 688 writeError(w, "invalid request body", http.StatusBadRequest) 689 return 690 } 691 692 did := data.Did 693 name := data.Name 694 695 if did == "" || name == "" { 696 l.Error("invalid request body, empty did or name") 697 w.WriteHeader(http.StatusBadRequest) 698 return 699 } 700 701 relativeRepoPath := filepath.Join(did, name) 702 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 703 err := os.RemoveAll(repoPath) 704 if err != nil { 705 l.Error("removing repo", "error", err.Error()) 706 writeError(w, err.Error(), http.StatusInternalServerError) 707 return 708 } 709 710 w.WriteHeader(http.StatusNoContent) 711 712} 713func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { 714 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 715 716 data := types.MergeRequest{} 717 718 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 719 writeError(w, err.Error(), http.StatusBadRequest) 720 h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err) 721 return 722 } 723 724 mo := &git.MergeOptions{ 725 AuthorName: data.AuthorName, 726 AuthorEmail: data.AuthorEmail, 727 CommitBody: data.CommitBody, 728 CommitMessage: data.CommitMessage, 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 mo.FormatPatch = patchutil.IsFormatPatch(patch) 740 741 if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil { 742 var mergeErr *git.ErrMerge 743 if errors.As(err, &mergeErr) { 744 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 745 for i, conflict := range mergeErr.Conflicts { 746 conflicts[i] = types.ConflictInfo{ 747 Filename: conflict.Filename, 748 Reason: conflict.Reason, 749 } 750 } 751 response := types.MergeCheckResponse{ 752 IsConflicted: true, 753 Conflicts: conflicts, 754 Message: mergeErr.Message, 755 } 756 writeConflict(w, response) 757 h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr) 758 } else { 759 writeError(w, err.Error(), http.StatusBadRequest) 760 h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error()) 761 } 762 return 763 } 764 765 w.WriteHeader(http.StatusOK) 766} 767 768func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) { 769 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 770 771 var data struct { 772 Patch string `json:"patch"` 773 Branch string `json:"branch"` 774 } 775 776 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 777 writeError(w, err.Error(), http.StatusBadRequest) 778 h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err) 779 return 780 } 781 782 patch := data.Patch 783 branch := data.Branch 784 gr, err := git.Open(path, branch) 785 if err != nil { 786 notFound(w) 787 return 788 } 789 790 err = gr.MergeCheck([]byte(patch), branch) 791 if err == nil { 792 response := types.MergeCheckResponse{ 793 IsConflicted: false, 794 } 795 writeJSON(w, response) 796 return 797 } 798 799 var mergeErr *git.ErrMerge 800 if errors.As(err, &mergeErr) { 801 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 802 for i, conflict := range mergeErr.Conflicts { 803 conflicts[i] = types.ConflictInfo{ 804 Filename: conflict.Filename, 805 Reason: conflict.Reason, 806 } 807 } 808 response := types.MergeCheckResponse{ 809 IsConflicted: true, 810 Conflicts: conflicts, 811 Message: mergeErr.Message, 812 } 813 writeConflict(w, response) 814 h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error()) 815 return 816 } 817 writeError(w, err.Error(), http.StatusInternalServerError) 818 h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error()) 819} 820 821func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) { 822 rev1 := chi.URLParam(r, "rev1") 823 rev1, _ = url.PathUnescape(rev1) 824 825 rev2 := chi.URLParam(r, "rev2") 826 rev2, _ = url.PathUnescape(rev2) 827 828 l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2) 829 830 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 831 gr, err := git.PlainOpen(path) 832 if err != nil { 833 notFound(w) 834 return 835 } 836 837 commit1, err := gr.ResolveRevision(rev1) 838 if err != nil { 839 l.Error("error resolving revision 1", "msg", err.Error()) 840 writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest) 841 return 842 } 843 844 commit2, err := gr.ResolveRevision(rev2) 845 if err != nil { 846 l.Error("error resolving revision 2", "msg", err.Error()) 847 writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest) 848 return 849 } 850 851 mergeBase, err := gr.MergeBase(commit1, commit2) 852 if err != nil { 853 l.Error("failed to find merge-base", "msg", err.Error()) 854 writeError(w, "failed to calculate diff", http.StatusBadRequest) 855 return 856 } 857 858 rawPatch, formatPatch, err := gr.FormatPatch(mergeBase, commit2) 859 if err != nil { 860 l.Error("error comparing revisions", "msg", err.Error()) 861 writeError(w, "error comparing revisions", http.StatusBadRequest) 862 return 863 } 864 865 writeJSON(w, types.RepoFormatPatchResponse{ 866 Rev1: commit1.Hash.String(), 867 Rev2: commit2.Hash.String(), 868 FormatPatch: formatPatch, 869 Patch: rawPatch, 870 }) 871 return 872} 873 874func (h *Handle) NewHiddenRef(w http.ResponseWriter, r *http.Request) { 875 l := h.l.With("handler", "NewHiddenRef") 876 877 forkRef := chi.URLParam(r, "forkRef") 878 forkRef, _ = url.PathUnescape(forkRef) 879 880 remoteRef := chi.URLParam(r, "remoteRef") 881 remoteRef, _ = url.PathUnescape(remoteRef) 882 883 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 884 gr, err := git.PlainOpen(path) 885 if err != nil { 886 notFound(w) 887 return 888 } 889 890 err = gr.TrackHiddenRemoteRef(forkRef, remoteRef) 891 if err != nil { 892 l.Error("error tracking hidden remote ref", "msg", err.Error()) 893 writeError(w, "error tracking hidden remote ref", http.StatusBadRequest) 894 return 895 } 896 897 w.WriteHeader(http.StatusNoContent) 898 return 899} 900 901func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) { 902 l := h.l.With("handler", "AddMember") 903 904 data := struct { 905 Did string `json:"did"` 906 }{} 907 908 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 909 writeError(w, "invalid request body", http.StatusBadRequest) 910 return 911 } 912 913 did := data.Did 914 915 if err := h.db.AddDid(did); err != nil { 916 l.Error("adding did", "error", err.Error()) 917 writeError(w, err.Error(), http.StatusInternalServerError) 918 return 919 } 920 h.jc.AddDid(did) 921 922 if err := h.e.AddMember(ThisServer, did); err != nil { 923 l.Error("adding member", "error", err.Error()) 924 writeError(w, err.Error(), http.StatusInternalServerError) 925 return 926 } 927 928 if err := h.fetchAndAddKeys(r.Context(), did); err != nil { 929 l.Error("fetching and adding keys", "error", err.Error()) 930 writeError(w, err.Error(), http.StatusInternalServerError) 931 return 932 } 933 934 w.WriteHeader(http.StatusNoContent) 935} 936 937func (h *Handle) AddRepoCollaborator(w http.ResponseWriter, r *http.Request) { 938 l := h.l.With("handler", "AddRepoCollaborator") 939 940 data := struct { 941 Did string `json:"did"` 942 }{} 943 944 ownerDid := chi.URLParam(r, "did") 945 repo := chi.URLParam(r, "name") 946 947 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 948 writeError(w, "invalid request body", http.StatusBadRequest) 949 return 950 } 951 952 if err := h.db.AddDid(data.Did); err != nil { 953 l.Error("adding did", "error", err.Error()) 954 writeError(w, err.Error(), http.StatusInternalServerError) 955 return 956 } 957 h.jc.AddDid(data.Did) 958 959 repoName, _ := securejoin.SecureJoin(ownerDid, repo) 960 if err := h.e.AddCollaborator(data.Did, ThisServer, repoName); err != nil { 961 l.Error("adding repo collaborator", "error", err.Error()) 962 writeError(w, err.Error(), http.StatusInternalServerError) 963 return 964 } 965 966 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 967 l.Error("fetching and adding keys", "error", err.Error()) 968 writeError(w, err.Error(), http.StatusInternalServerError) 969 return 970 } 971 972 w.WriteHeader(http.StatusNoContent) 973} 974 975func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) { 976 l := h.l.With("handler", "DefaultBranch") 977 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 978 979 gr, err := git.Open(path, "") 980 if err != nil { 981 notFound(w) 982 return 983 } 984 985 branch, err := gr.FindMainBranch() 986 if err != nil { 987 writeError(w, err.Error(), http.StatusInternalServerError) 988 l.Error("getting default branch", "error", err.Error()) 989 return 990 } 991 992 writeJSON(w, types.RepoDefaultBranchResponse{ 993 Branch: branch, 994 }) 995} 996 997func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 998 l := h.l.With("handler", "SetDefaultBranch") 999 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 1000 1001 data := struct { 1002 Branch string `json:"branch"` 1003 }{} 1004 1005 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 1006 writeError(w, err.Error(), http.StatusBadRequest) 1007 return 1008 } 1009 1010 gr, err := git.Open(path, "") 1011 if err != nil { 1012 notFound(w) 1013 return 1014 } 1015 1016 err = gr.SetDefaultBranch(data.Branch) 1017 if err != nil { 1018 writeError(w, err.Error(), http.StatusInternalServerError) 1019 l.Error("setting default branch", "error", err.Error()) 1020 return 1021 } 1022 1023 w.WriteHeader(http.StatusNoContent) 1024} 1025 1026func (h *Handle) Init(w http.ResponseWriter, r *http.Request) { 1027 l := h.l.With("handler", "Init") 1028 1029 if h.knotInitialized { 1030 writeError(w, "knot already initialized", http.StatusConflict) 1031 return 1032 } 1033 1034 data := struct { 1035 Did string `json:"did"` 1036 }{} 1037 1038 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 1039 l.Error("failed to decode request body", "error", err.Error()) 1040 writeError(w, "invalid request body", http.StatusBadRequest) 1041 return 1042 } 1043 1044 if data.Did == "" { 1045 l.Error("empty DID in request", "did", data.Did) 1046 writeError(w, "did is empty", http.StatusBadRequest) 1047 return 1048 } 1049 1050 if err := h.db.AddDid(data.Did); err != nil { 1051 l.Error("failed to add DID", "error", err.Error()) 1052 writeError(w, err.Error(), http.StatusInternalServerError) 1053 return 1054 } 1055 h.jc.AddDid(data.Did) 1056 1057 if err := h.e.AddOwner(ThisServer, data.Did); err != nil { 1058 l.Error("adding owner", "error", err.Error()) 1059 writeError(w, err.Error(), http.StatusInternalServerError) 1060 return 1061 } 1062 1063 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 1064 l.Error("fetching and adding keys", "error", err.Error()) 1065 writeError(w, err.Error(), http.StatusInternalServerError) 1066 return 1067 } 1068 1069 close(h.init) 1070 1071 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret)) 1072 mac.Write([]byte("ok")) 1073 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil))) 1074 1075 w.WriteHeader(http.StatusNoContent) 1076} 1077 1078func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 1079 w.Write([]byte("ok")) 1080}