forked from tangled.org/core
this repo has no description
at knot-xrpc 3.0 kB view raw
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/patchutil" 14 "tangled.sh/tangled.sh/core/rbac" 15 "tangled.sh/tangled.sh/core/types" 16 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors" 17) 18 19func (x *Xrpc) Merge(w http.ResponseWriter, r *http.Request) { 20 l := x.Logger.With("handler", "Merge") 21 fail := func(e xrpcerr.XrpcError) { 22 l.Error("failed", "kind", e.Tag, "error", e.Message) 23 writeError(w, e, http.StatusBadRequest) 24 } 25 26 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) 27 if !ok { 28 fail(xrpcerr.MissingActorDidError) 29 return 30 } 31 32 var data tangled.RepoMerge_Input 33 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 34 fail(xrpcerr.GenericError(err)) 35 return 36 } 37 38 did := data.Did 39 name := data.Name 40 41 if did == "" || name == "" { 42 fail(xrpcerr.GenericError(fmt.Errorf("did and name are required"))) 43 return 44 } 45 46 relativeRepoPath, err := securejoin.SecureJoin(did, name) 47 if err != nil { 48 fail(xrpcerr.GenericError(err)) 49 return 50 } 51 52 if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil { 53 l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath) 54 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 55 return 56 } 57 58 repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath) 59 if err != nil { 60 fail(xrpcerr.GenericError(err)) 61 return 62 } 63 64 gr, err := git.Open(repoPath, data.Branch) 65 if err != nil { 66 fail(xrpcerr.GenericError(fmt.Errorf("failed to open repository: %w", err))) 67 return 68 } 69 70 mo := &git.MergeOptions{} 71 if data.AuthorName != nil { 72 mo.AuthorName = *data.AuthorName 73 } 74 if data.AuthorEmail != nil { 75 mo.AuthorEmail = *data.AuthorEmail 76 } 77 if data.CommitBody != nil { 78 mo.CommitBody = *data.CommitBody 79 } 80 if data.CommitMessage != nil { 81 mo.CommitMessage = *data.CommitMessage 82 } 83 84 mo.FormatPatch = patchutil.IsFormatPatch(data.Patch) 85 86 err = gr.MergeWithOptions([]byte(data.Patch), data.Branch, mo) 87 if err != nil { 88 var mergeErr *git.ErrMerge 89 if errors.As(err, &mergeErr) { 90 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 91 for i, conflict := range mergeErr.Conflicts { 92 conflicts[i] = types.ConflictInfo{ 93 Filename: conflict.Filename, 94 Reason: conflict.Reason, 95 } 96 } 97 98 conflictErr := xrpcerr.NewXrpcError( 99 xrpcerr.WithTag("MergeConflict"), 100 xrpcerr.WithMessage(fmt.Sprintf("Merge failed due to conflicts: %s", mergeErr.Message)), 101 ) 102 writeError(w, conflictErr, http.StatusConflict) 103 return 104 } else { 105 l.Error("failed to merge", "error", err.Error()) 106 writeError(w, xrpcerr.GitError(err), http.StatusInternalServerError) 107 return 108 } 109 } 110 111 w.WriteHeader(http.StatusOK) 112}