forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
this repo has no description
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}