Monorepo for Tangled tangled.org
781
fork

Configure Feed

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

at master 142 lines 3.6 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 "tangled.org/core/api/tangled" 11 "tangled.org/core/knotserver/db" 12 "tangled.org/core/knotserver/git" 13 "tangled.org/core/patchutil" 14 "tangled.org/core/rbac" 15 "tangled.org/core/tid" 16 "tangled.org/core/types" 17 xrpcerr "tangled.org/core/xrpc/errors" 18) 19 20func (x *Xrpc) Merge(w http.ResponseWriter, r *http.Request) { 21 l := x.Logger.With("handler", "Merge") 22 fail := func(e xrpcerr.XrpcError) { 23 l.Error("failed", "kind", e.Tag, "error", e.Message) 24 writeError(w, e, http.StatusBadRequest) 25 } 26 27 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) 28 if !ok { 29 fail(xrpcerr.MissingActorDidError) 30 return 31 } 32 33 var data tangled.RepoMerge_Input 34 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 35 fail(xrpcerr.GenericError(err)) 36 return 37 } 38 39 did := data.Did 40 name := data.Name 41 42 if did == "" || name == "" { 43 fail(xrpcerr.GenericError(fmt.Errorf("did and name are required"))) 44 return 45 } 46 47 repoDid, err := x.Db.GetRepoDid(did, name) 48 if err != nil { 49 fail(xrpcerr.RepoNotFoundError) 50 return 51 } 52 repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid) 53 if err != nil { 54 fail(xrpcerr.RepoNotFoundError) 55 return 56 } 57 58 if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil { 59 l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid) 60 writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 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.CommitterName = x.Config.Git.UserName 85 mo.CommitterEmail = x.Config.Git.UserEmail 86 mo.FormatPatch = patchutil.IsFormatPatch(data.Patch) 87 88 err = gr.MergeWithOptions(data.Patch, data.Branch, mo) 89 if err != nil { 90 var mergeErr *git.ErrMerge 91 if errors.As(err, &mergeErr) { 92 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 93 for i, conflict := range mergeErr.Conflicts { 94 conflicts[i] = types.ConflictInfo{ 95 Filename: conflict.Filename, 96 Reason: conflict.Reason, 97 } 98 } 99 100 conflictErr := xrpcerr.NewXrpcError( 101 xrpcerr.WithTag("MergeConflict"), 102 xrpcerr.WithMessage(fmt.Sprintf("Merge failed due to conflicts: %s", mergeErr.Message)), 103 ) 104 writeError(w, conflictErr, http.StatusConflict) 105 return 106 } else { 107 l.Error("failed to merge", "error", err.Error()) 108 writeError(w, xrpcerr.GitError(err), http.StatusInternalServerError) 109 return 110 } 111 } 112 113 oldSha := gr.Hash() 114 if err := gr.Refresh(); err != nil { 115 l.Error("failed to refresh", "error", err) 116 } 117 newSha := gr.Hash() 118 119 go func() { 120 refUpdate := tangled.GitRefUpdate{ 121 RepoDid: &repoDid, 122 OwnerDid: &data.Did, 123 RepoName: data.Name, 124 Ref: data.Branch, 125 OldSha: oldSha.String(), 126 NewSha: newSha.String(), 127 CommitterDid: actorDid.String(), 128 } 129 eventJson, err := json.Marshal(refUpdate) 130 if err != nil { 131 return 132 } 133 134 x.Db.InsertEvent(db.Event{ 135 Rkey: tid.TID(), 136 Nsid: tangled.GitRefUpdateNSID, 137 EventJson: string(eventJson), 138 }, x.Notifier) 139 }() 140 141 w.WriteHeader(http.StatusOK) 142}