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.Did, 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 rp.pages.RepoCompareNew(w, pages.RepoCompareNewParams{
92 LoggedInUser: user,
93 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
94 Branches: branches,
95 Tags: tags.Tags,
96 Base: base,
97 Head: head,
98 })
99}
100
101func (rp *Repo) Compare(w http.ResponseWriter, r *http.Request) {
102 l := rp.logger.With("handler", "RepoCompare")
103
104 user := rp.oauth.GetUser(r)
105 f, err := rp.repoResolver.Resolve(r)
106 if err != nil {
107 l.Error("failed to get repo and knot", "err", err)
108 return
109 }
110
111 var diffOpts types.DiffOpts
112 if d := r.URL.Query().Get("diff"); d == "split" {
113 diffOpts.Split = true
114 }
115
116 // if user is navigating to one of
117 // /compare/{base}...{head}
118 // /compare/{base}/{head}
119 var base, head string
120 rest := chi.URLParam(r, "*")
121
122 var parts []string
123 if strings.Contains(rest, "...") {
124 parts = strings.SplitN(rest, "...", 2)
125 } else if strings.Contains(rest, "/") {
126 parts = strings.SplitN(rest, "/", 2)
127 }
128
129 if len(parts) == 2 {
130 base = parts[0]
131 head = parts[1]
132 }
133
134 base, _ = url.PathUnescape(base)
135 head, _ = url.PathUnescape(head)
136
137 if base == "" || head == "" {
138 l.Error("invalid comparison")
139 rp.pages.Error404(w)
140 return
141 }
142
143 scheme := "http"
144 if !rp.config.Core.Dev {
145 scheme = "https"
146 }
147 host := fmt.Sprintf("%s://%s", scheme, f.Knot)
148 xrpcc := &indigoxrpc.Client{
149 Host: host,
150 }
151
152 repo := fmt.Sprintf("%s/%s", f.Did, f.Name)
153
154 branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
155 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
156 l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
157 rp.pages.Error503(w)
158 return
159 }
160
161 var branches types.RepoBranchesResponse
162 if err := json.Unmarshal(branchBytes, &branches); err != nil {
163 l.Error("failed to decode XRPC branches response", "err", err)
164 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
165 return
166 }
167
168 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
169 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
170 l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
171 rp.pages.Error503(w)
172 return
173 }
174
175 var tags types.RepoTagsResponse
176 if err := json.Unmarshal(tagBytes, &tags); err != nil {
177 l.Error("failed to decode XRPC tags response", "err", err)
178 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
179 return
180 }
181
182 compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head)
183 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
184 l.Error("failed to call XRPC repo.compare", "err", xrpcerr)
185 rp.pages.Error503(w)
186 return
187 }
188
189 var formatPatch types.RepoFormatPatchResponse
190 if err := json.Unmarshal(compareBytes, &formatPatch); err != nil {
191 l.Error("failed to decode XRPC compare response", "err", err)
192 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
193 return
194 }
195
196 var diff types.NiceDiff
197 if formatPatch.CombinedPatchRaw != "" {
198 diff = patchutil.AsNiceDiff(formatPatch.CombinedPatchRaw, base)
199 } else {
200 diff = patchutil.AsNiceDiff(formatPatch.FormatPatchRaw, base)
201 }
202
203 rp.pages.RepoCompare(w, pages.RepoCompareParams{
204 LoggedInUser: user,
205 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
206 Branches: branches.Branches,
207 Tags: tags.Tags,
208 Base: base,
209 Head: head,
210 Diff: &diff,
211 DiffOpts: diffOpts,
212 })
213
214}