Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

1package xrpc 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/knotserver/git" 12 "tangled.org/core/patchutil" 13 "tangled.org/core/rbac" 14 "tangled.org/core/types" 15 xrpcerr "tangled.org/core/xrpc/errors" 16) 17 18func (x *Xrpc) Merge(w http.ResponseWriter, r *http.Request) { 19 l := x.Logger.With("handler", "Merge") 20 fail := func(e xrpcerr.XrpcError) { 21 l.Error("failed", "kind", e.Tag, "error", e.Message) 22 writeError(w, e, http.StatusBadRequest) 23 } 24 25 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) 26 if !ok { 27 fail(xrpcerr.MissingActorDidError) 28 return 29 } 30 31 var data tangled.RepoMerge_Input 32 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 33 fail(xrpcerr.GenericError(err)) 34 return 35 } 36 37 did := data.Did 38 name := data.Name 39 40 if did == "" || name == "" { 41 fail(xrpcerr.GenericError(fmt.Errorf("did and name are required"))) 42 return 43 } 44 45 repoDid, err := x.Db.GetRepoDid(did, name) 46 if err != nil { 47 fail(xrpcerr.RepoNotFoundError) 48 return 49 } 50 repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 51 if err != nil { 52 fail(xrpcerr.RepoNotFoundError) 53 return 54 } 55 56 if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil { 57 l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid) 58 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 59 return 60 } 61 62 gr, err := git.Open(repoPath, data.Branch) 63 if err != nil { 64 fail(xrpcerr.GenericError(fmt.Errorf("failed to open repository: %w", err))) 65 return 66 } 67 if x.Sandbox != nil { 68 gr = gr.WithSandbox(x.Sandbox) 69 } 70 71 mo := git.MergeOptions{} 72 if data.AuthorName != nil { 73 mo.AuthorName = *data.AuthorName 74 } 75 if data.AuthorEmail != nil { 76 mo.AuthorEmail = *data.AuthorEmail 77 } 78 if data.CommitBody != nil { 79 mo.CommitBody = *data.CommitBody 80 } 81 if data.CommitMessage != nil { 82 mo.CommitMessage = *data.CommitMessage 83 } 84 85 mo.CommitterName = x.Config.Git.UserName 86 mo.CommitterEmail = x.Config.Git.UserEmail 87 mo.FormatPatch = patchutil.IsFormatPatch(data.Patch) 88 89 err = gr.MergeWithOptions(data.Patch, data.Branch, mo) 90 if err != nil { 91 var mergeErr *git.ErrMerge 92 if errors.As(err, &mergeErr) { 93 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 94 for i, conflict := range mergeErr.Conflicts { 95 conflicts[i] = types.ConflictInfo{ 96 Filename: conflict.Filename, 97 Reason: conflict.Reason, 98 } 99 } 100 101 conflictErr := xrpcerr.NewXrpcError( 102 xrpcerr.WithTag("MergeConflict"), 103 xrpcerr.WithMessage(fmt.Sprintf("Merge failed due to conflicts: %s", mergeErr.Message)), 104 ) 105 writeError(w, conflictErr, http.StatusConflict) 106 return 107 } else { 108 l.Error("failed to merge", "error", err.Error()) 109 writeError(w, xrpcerr.GitError(err), http.StatusInternalServerError) 110 return 111 } 112 } 113 114 w.WriteHeader(http.StatusOK) 115}