Monorepo for Tangled
0

Configure Feed

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

core / appview / pulls / merge.go
4.5 kB 152 lines
1package pulls 2 3import ( 4 "fmt" 5 "net/http" 6 "time" 7 8 "tangled.org/core/api/tangled" 9 "tangled.org/core/appview/db" 10 "tangled.org/core/appview/models" 11 "tangled.org/core/appview/oauth" 12 "tangled.org/core/appview/reporesolver" 13 "tangled.org/core/orm" 14 "tangled.org/core/xrpc/xrpcclient" 15 16 "github.com/bluesky-social/indigo/atproto/syntax" 17) 18 19func (s *Pulls) MergePull(w http.ResponseWriter, r *http.Request) { 20 l := s.logger.With("handler", "MergePull") 21 22 user := s.oauth.GetMultiAccountUser(r) 23 if user == nil { 24 l.Error("nil user") 25 s.pages.Notice(w, "pull-action-error", "You must be logged in to merge this pull.") 26 return 27 } 28 l = l.With("user", user.Did) 29 30 f, err := s.repoResolver.Resolve(r) 31 if err != nil { 32 l.Error("failed to resolve repo", "err", err) 33 s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") 34 return 35 } 36 l = l.With("repo_at", f.RepoAt().String()) 37 38 pull, ok := r.Context().Value("pull").(*models.Pull) 39 if !ok { 40 l.Error("failed to get pull") 41 s.pages.Notice(w, "pull-action-error", "Failed to merge patch. Try again later.") 42 return 43 } 44 l = l.With("pull_id", pull.PullId, "target_branch", pull.TargetBranch) 45 46 stack, ok := r.Context().Value("stack").(models.Stack) 47 if !ok { 48 l.Error("failed to get stack") 49 s.pages.Notice(w, "pull-action-error", "Failed to merge patch. Try again later.") 50 return 51 } 52 53 // combine patches of substack 54 subStack := stack.Below(pull) 55 // collect the portion of the stack that is mergeable 56 pullsToMerge := subStack.Mergeable() 57 l = l.With("pulls_to_merge", len(pullsToMerge)) 58 59 patch := pullsToMerge.CombinedPatch() 60 61 ident, err := s.idResolver.ResolveIdent(r.Context(), pull.OwnerDid) 62 if err != nil { 63 l.Error("failed to resolve identity", "err", err, "owner_did", pull.OwnerDid) 64 w.WriteHeader(http.StatusNotFound) 65 return 66 } 67 68 email, err := db.GetPrimaryEmail(s.db, pull.OwnerDid) 69 if err != nil { 70 l.Warn("failed to get primary email", "err", err, "owner_did", pull.OwnerDid) 71 } 72 73 authorName := ident.Handle.String() 74 mergeInput := &tangled.RepoMerge_Input{ 75 Did: f.Did, 76 Name: f.Name, 77 Repo: f.RepoDidPtr(), 78 Branch: pull.TargetBranch, 79 Patch: patch, 80 CommitMessage: &pull.Title, 81 AuthorName: &authorName, 82 } 83 84 if pull.Body != "" { 85 mergeInput.CommitBody = &pull.Body 86 } 87 88 if email.Address != "" { 89 mergeInput.AuthorEmail = &email.Address 90 } 91 92 client, err := s.oauth.ServiceClient( 93 r, 94 oauth.WithService(f.Knot), 95 oauth.WithLxm(tangled.RepoMergeNSID), 96 oauth.WithDev(s.config.Core.Dev), 97 oauth.WithTimeout(time.Second*20), // merge is quite slow on large repos, like witchsky 98 ) 99 if err != nil { 100 l.Error("failed to connect to knot server", "err", err, "knot", f.Knot) 101 s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") 102 return 103 } 104 105 err = tangled.RepoMerge(r.Context(), client, mergeInput) 106 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 107 s.logger.Error("failed to merge", "xrpcerr", xrpcerr, "err", err) 108 s.pages.Notice(w, "pull-action-error", xrpcerr.Error()) 109 return 110 } 111 112 var atUris []syntax.ATURI 113 for _, p := range pullsToMerge { 114 atUris = append(atUris, p.AtUri()) 115 p.State = models.PullMerged 116 } 117 118 if err := s.writePullStatusRecords(r, user.Did, atUris, models.StateMerged); err != nil { 119 l.Error("failed to write pull status records after merge", "err", err) 120 } 121 122 tx, err := s.db.Begin() 123 if err != nil { 124 l.Error("failed to start transaction", "err", err) 125 s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") 126 return 127 } 128 defer tx.Rollback() 129 130 err = db.MergePulls(tx, orm.FilterEq("repo_did", string(f.RepoDid)), orm.FilterIn("at_uri", atUris)) 131 if err != nil { 132 l.Error("failed to update pull request status in database", "err", err) 133 s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") 134 return 135 } 136 137 err = tx.Commit() 138 if err != nil { 139 // TODO: this is unsound, we should also revert the merge from the knotserver here 140 l.Error("failed to commit merge transaction", "err", err) 141 s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") 142 return 143 } 144 145 // notify about the pull merge 146 for _, p := range pullsToMerge { 147 s.notifier.NewPullState(r.Context(), syntax.DID(user.Did), p) 148 } 149 150 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 151 s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId)) 152}