forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package repo
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "net/url"
8 "strings"
9
10 "tangled.org/core/api/tangled"
11 "tangled.org/core/appview/pages"
12 xrpcclient "tangled.org/core/appview/xrpcclient"
13 "tangled.org/core/patchutil"
14 "tangled.org/core/types"
15
16 indigoxrpc "github.com/bluesky-social/indigo/xrpc"
17 "github.com/go-chi/chi/v5"
18)
19
20func (rp *Repo) CompareNew(w http.ResponseWriter, r *http.Request) {
21 l := rp.logger.With("handler", "RepoCompareNew")
22
23 user := rp.oauth.GetUser(r)
24 f, err := rp.repoResolver.Resolve(r)
25 if err != nil {
26 l.Error("failed to get repo and knot", "err", err)
27 return
28 }
29
30 scheme := "http"
31 if !rp.config.Core.Dev {
32 scheme = "https"
33 }
34 host := fmt.Sprintf("%s://%s", scheme, f.Knot)
35 xrpcc := &indigoxrpc.Client{
36 Host: host,
37 }
38
39 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
40 branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
41 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
42 l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
43 rp.pages.Error503(w)
44 return
45 }
46
47 var branchResult types.RepoBranchesResponse
48 if err := json.Unmarshal(branchBytes, &branchResult); err != nil {
49 l.Error("failed to decode XRPC branches response", "err", err)
50 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
51 return
52 }
53 branches := branchResult.Branches
54
55 sortBranches(branches)
56
57 var defaultBranch string
58 for _, b := range branches {
59 if b.IsDefault {
60 defaultBranch = b.Name
61 }
62 }
63
64 base := defaultBranch
65 head := defaultBranch
66
67 params := r.URL.Query()
68 queryBase := params.Get("base")
69 queryHead := params.Get("head")
70 if queryBase != "" {
71 base = queryBase
72 }
73 if queryHead != "" {
74 head = queryHead
75 }
76
77 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
78 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
79 l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
80 rp.pages.Error503(w)
81 return
82 }
83
84 var tags types.RepoTagsResponse
85 if err := json.Unmarshal(tagBytes, &tags); err != nil {
86 l.Error("failed to decode XRPC tags response", "err", err)
87 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
88 return
89 }
90
91 repoinfo := f.RepoInfo(user)
92
93 rp.pages.RepoCompareNew(w, pages.RepoCompareNewParams{
94 LoggedInUser: user,
95 RepoInfo: repoinfo,
96 Branches: branches,
97 Tags: tags.Tags,
98 Base: base,
99 Head: head,
100 })
101}
102
103func (rp *Repo) Compare(w http.ResponseWriter, r *http.Request) {
104 l := rp.logger.With("handler", "RepoCompare")
105
106 user := rp.oauth.GetUser(r)
107 f, err := rp.repoResolver.Resolve(r)
108 if err != nil {
109 l.Error("failed to get repo and knot", "err", err)
110 return
111 }
112
113 var diffOpts types.DiffOpts
114 if d := r.URL.Query().Get("diff"); d == "split" {
115 diffOpts.Split = true
116 }
117
118 // if user is navigating to one of
119 // /compare/{base}...{head}
120 // /compare/{base}/{head}
121 var base, head string
122 rest := chi.URLParam(r, "*")
123
124 var parts []string
125 if strings.Contains(rest, "...") {
126 parts = strings.SplitN(rest, "...", 2)
127 } else if strings.Contains(rest, "/") {
128 parts = strings.SplitN(rest, "/", 2)
129 }
130
131 if len(parts) == 2 {
132 base = parts[0]
133 head = parts[1]
134 }
135
136 base, _ = url.PathUnescape(base)
137 head, _ = url.PathUnescape(head)
138
139 if base == "" || head == "" {
140 l.Error("invalid comparison")
141 rp.pages.Error404(w)
142 return
143 }
144
145 scheme := "http"
146 if !rp.config.Core.Dev {
147 scheme = "https"
148 }
149 host := fmt.Sprintf("%s://%s", scheme, f.Knot)
150 xrpcc := &indigoxrpc.Client{
151 Host: host,
152 }
153
154 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
155
156 branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
157 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
158 l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
159 rp.pages.Error503(w)
160 return
161 }
162
163 var branches types.RepoBranchesResponse
164 if err := json.Unmarshal(branchBytes, &branches); err != nil {
165 l.Error("failed to decode XRPC branches response", "err", err)
166 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
167 return
168 }
169
170 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
171 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
172 l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
173 rp.pages.Error503(w)
174 return
175 }
176
177 var tags types.RepoTagsResponse
178 if err := json.Unmarshal(tagBytes, &tags); err != nil {
179 l.Error("failed to decode XRPC tags response", "err", err)
180 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
181 return
182 }
183
184 compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head)
185 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
186 l.Error("failed to call XRPC repo.compare", "err", xrpcerr)
187 rp.pages.Error503(w)
188 return
189 }
190
191 var formatPatch types.RepoFormatPatchResponse
192 if err := json.Unmarshal(compareBytes, &formatPatch); err != nil {
193 l.Error("failed to decode XRPC compare response", "err", err)
194 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
195 return
196 }
197
198 var diff types.NiceDiff
199 if formatPatch.CombinedPatchRaw != "" {
200 diff = patchutil.AsNiceDiff(formatPatch.CombinedPatchRaw, base)
201 } else {
202 diff = patchutil.AsNiceDiff(formatPatch.FormatPatchRaw, base)
203 }
204
205 repoinfo := f.RepoInfo(user)
206
207 rp.pages.RepoCompare(w, pages.RepoCompareParams{
208 LoggedInUser: user,
209 RepoInfo: repoinfo,
210 Branches: branches.Branches,
211 Tags: tags.Tags,
212 Base: base,
213 Head: head,
214 Diff: &diff,
215 DiffOpts: diffOpts,
216 })
217
218}