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}