This repository has no description
1package pulls
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "strconv"
8
9 "tangled.org/core/api/tangled"
10 "tangled.org/core/appview/db"
11 "tangled.org/core/appview/models"
12 "tangled.org/core/appview/pages"
13 "tangled.org/core/xrpc/xrpcclient"
14
15 "github.com/bluesky-social/indigo/atproto/syntax"
16 "github.com/go-chi/chi/v5"
17)
18
19// htmx fragment
20func (s *Pulls) PullActions(w http.ResponseWriter, r *http.Request) {
21 l := s.logger.With("handler", "PullActions")
22
23 switch r.Method {
24 case http.MethodGet:
25 user := s.oauth.GetMultiAccountUser(r)
26 if user != nil {
27 l = l.With("user", user.Did)
28 }
29
30 f, err := s.repoResolver.Resolve(r)
31 if err != nil {
32 l.Error("failed to get repo and knot", "err", err)
33 return
34 }
35
36 pull, ok := r.Context().Value("pull").(*models.Pull)
37 if !ok {
38 l.Error("failed to get pull")
39 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
40 return
41 }
42 l = l.With("pull_id", pull.PullId, "pull_owner", pull.OwnerDid)
43
44 versionNumber, err := strconv.Atoi(chi.URLParam(r, "version"))
45 if err != nil {
46 versionNumber = pull.LatestVersionNumber()
47 }
48 if versionNumber >= len(pull.Versions) {
49 http.Error(w, "bad round id", http.StatusBadRequest)
50 l.Error("failed to parse round id", "err", err, "round_number", versionNumber)
51 return
52 }
53
54 isLastVersion := versionNumber == pull.LatestVersionNumber()
55
56 var workflowsChanged bool
57 var changedWorkflows []string
58 hasPipeline := false
59 if isLastVersion && f.Spindle != "" {
60 pipelines := fetchPipelines(r.Context(), l, f, []string{pull.LatestSha()})
61 _, hasPipeline = pipelines[pull.LatestSha()]
62
63 if pull.IsForkBased() && !hasPipeline {
64 latest := pull.LatestVersion()
65 changedWorkflows, err = func(ctx context.Context) ([]string, error) {
66 base, err := s.resolveRev(ctx, pull.RepoDid, pull.TargetBranch)
67 if err != nil {
68 return nil, fmt.Errorf("failed to resolve target branch: %w", err)
69 }
70 return s.changedWorkflowFiles(ctx, pull.RepoDid, base, pull.SourceRepo, latest.Head)
71 }(r.Context())
72 if err != nil {
73 l.Error("failed to inspect latest round's patch for workflow changes", "err", err)
74 }
75 workflowsChanged = len(changedWorkflows) > 0
76 }
77 }
78
79 // only the last round's buttons and banners use merge/resubmit checks
80 var mergeCheckParams pages.MergeCheckParams
81 var resubmitResult = pages.Unknown
82 if isLastVersion {
83 mergeCheckParams = s.composeMergeCheck(r.Context(), f, pull.TargetBranch, pull.SourceRepo, pull.LatestVersion().Head)
84 if user != nil && syntax.DID(user.Did) == pull.OwnerDid {
85 resubmitResult = s.resubmitCheck(r.Context(), pull)
86 }
87 }
88
89 s.pages.PullActionsFragment(w, pages.PullActionsParams{
90 BaseParams: pages.BaseParamsFromContext(r.Context()),
91 RepoInfo: s.repoResolver.GetRepoInfo(r, user),
92 Pull: pull,
93 RoundNumber: versionNumber,
94 MergeCheck: mergeCheckParams,
95 ResubmitCheck: resubmitResult,
96 BranchDeleteStatus: s.branchDeleteStatus(r, f, pull),
97
98 WorkflowsChanged: workflowsChanged,
99 ChangedWorkflowFiles: changedWorkflows,
100 HasPipeline: hasPipeline,
101 })
102 return
103 }
104}
105
106func (s *Pulls) branchDeleteStatus(r *http.Request, repo *models.Repo, pull *models.Pull) *pages.BranchDeleteStatus {
107 if pull.State != models.PullMerged || pull.SourceBranch == nil {
108 return nil
109 }
110
111 user := s.oauth.GetMultiAccountUser(r)
112 if user == nil {
113 return nil
114 }
115
116 branch := *pull.SourceBranch
117
118 if syntax.DID(repo.RepoDid) != pull.SourceRepo {
119 var err error
120 repo, err = db.GetRepoByDid(s.db, pull.SourceRepo.String())
121 if err != nil {
122 return nil
123 }
124 }
125
126 // user can only delete branch if they are a collaborator in the repo that the branch belongs to
127 if !s.acl.HasRepoPermission(r.Context(), repo, user.Did, "repo:push") {
128 return nil
129 }
130
131 xrpcc := s.knotMirrorXRPC
132 resp, err := tangled.GitTempGetBranch(r.Context(), xrpcc, branch, repo.RepoDid)
133 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
134 s.logger.Error("failed to get branch", "xrpcerr", xrpcerr, "err", err)
135 return nil
136 }
137
138 return &pages.BranchDeleteStatus{
139 Repo: repo,
140 Branch: resp.Name,
141 }
142}
143
144func (s *Pulls) resubmitCheck(ctx context.Context, pull *models.Pull) pages.ResubmitResult {
145 if pull.State == models.PullMerged || pull.State == models.PullAbandoned || pull.SourceBranch == nil {
146 return pages.Unknown
147 }
148
149 sourceSha, err := s.resolveRev(ctx, pull.SourceRepo, *pull.SourceBranch)
150 if err != nil {
151 s.logger.Error("failed to resolve source branch", "err", err, "pull_id", pull.PullId, "branch", *pull.SourceBranch)
152 return pages.Unknown
153 }
154
155 if pull.LatestVersion().Head != sourceSha {
156 return pages.ShouldResubmit
157 }
158
159 return pages.ShouldNotResubmit
160}