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