1package xrpc
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "net/http"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 securejoin "github.com/cyphar/filepath-securejoin"
11 "tangled.sh/tangled.sh/core/api/tangled"
12 "tangled.sh/tangled.sh/core/knotserver/git"
13 "tangled.sh/tangled.sh/core/rbac"
14 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
15)
16
17func (x *Xrpc) MergeCheck(w http.ResponseWriter, r *http.Request) {
18 l := x.Logger.With("handler", "MergeCheck")
19 fail := func(e xrpcerr.XrpcError) {
20 l.Error("failed", "kind", e.Tag, "error", e.Message)
21 writeError(w, e, http.StatusBadRequest)
22 }
23
24 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
25 if !ok {
26 fail(xrpcerr.MissingActorDidError)
27 return
28 }
29
30 isMember, err := x.Enforcer.IsKnotMember(actorDid.String(), rbac.ThisServer)
31 if err != nil {
32 fail(xrpcerr.GenericError(err))
33 return
34 }
35 if !isMember {
36 fail(xrpcerr.AccessControlError(actorDid.String()))
37 return
38 }
39
40 var data tangled.RepoMergeCheck_Input
41 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
42 fail(xrpcerr.GenericError(err))
43 return
44 }
45
46 did := data.Did
47 name := data.Name
48
49 if did == "" || name == "" {
50 fail(xrpcerr.GenericError(fmt.Errorf("did and name are required")))
51 return
52 }
53
54 relativeRepoPath, err := securejoin.SecureJoin(did, name)
55 if err != nil {
56 fail(xrpcerr.GenericError(err))
57 return
58 }
59
60 repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
61 if err != nil {
62 fail(xrpcerr.GenericError(err))
63 return
64 }
65
66 gr, err := git.Open(repoPath, data.Branch)
67 if err != nil {
68 fail(xrpcerr.GenericError(fmt.Errorf("failed to open repository: %w", err)))
69 return
70 }
71
72 err = gr.MergeCheck([]byte(data.Patch), data.Branch)
73
74 response := tangled.RepoMergeCheck_Output{
75 Is_conflicted: false,
76 }
77
78 if err != nil {
79 var mergeErr *git.ErrMerge
80 if errors.As(err, &mergeErr) {
81 response.Is_conflicted = true
82
83 conflicts := make([]*tangled.RepoMergeCheck_ConflictInfo, len(mergeErr.Conflicts))
84 for i, conflict := range mergeErr.Conflicts {
85 conflicts[i] = &tangled.RepoMergeCheck_ConflictInfo{
86 Filename: conflict.Filename,
87 Reason: conflict.Reason,
88 }
89 }
90 response.Conflicts = conflicts
91
92 if mergeErr.Message != "" {
93 response.Message = &mergeErr.Message
94 }
95 } else {
96 response.Is_conflicted = true
97 errMsg := err.Error()
98 response.Error = &errMsg
99 }
100 }
101
102 w.Header().Set("Content-Type", "application/json")
103 w.WriteHeader(http.StatusOK)
104 json.NewEncoder(w).Encode(response)
105}