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