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 base := chi.URLParam(r, "base")
122 head := chi.URLParam(r, "head")
123 if base == "" && head == "" {
124 rest := chi.URLParam(r, "*") // master...feature/xyz
125 parts := strings.SplitN(rest, "...", 2)
126 if len(parts) == 2 {
127 base = parts[0]
128 head = parts[1]
129 }
130 }
131
132 base, _ = url.PathUnescape(base)
133 head, _ = url.PathUnescape(head)
134
135 if base == "" || head == "" {
136 l.Error("invalid comparison")
137 rp.pages.Error404(w)
138 return
139 }
140
141 scheme := "http"
142 if !rp.config.Core.Dev {
143 scheme = "https"
144 }
145 host := fmt.Sprintf("%s://%s", scheme, f.Knot)
146 xrpcc := &indigoxrpc.Client{
147 Host: host,
148 }
149
150 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
151
152 branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
153 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
154 l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
155 rp.pages.Error503(w)
156 return
157 }
158
159 var branches types.RepoBranchesResponse
160 if err := json.Unmarshal(branchBytes, &branches); err != nil {
161 l.Error("failed to decode XRPC branches response", "err", err)
162 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
163 return
164 }
165
166 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
167 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
168 l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
169 rp.pages.Error503(w)
170 return
171 }
172
173 var tags types.RepoTagsResponse
174 if err := json.Unmarshal(tagBytes, &tags); err != nil {
175 l.Error("failed to decode XRPC tags response", "err", err)
176 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
177 return
178 }
179
180 compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head)
181 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
182 l.Error("failed to call XRPC repo.compare", "err", xrpcerr)
183 rp.pages.Error503(w)
184 return
185 }
186
187 var formatPatch types.RepoFormatPatchResponse
188 if err := json.Unmarshal(compareBytes, &formatPatch); err != nil {
189 l.Error("failed to decode XRPC compare response", "err", err)
190 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
191 return
192 }
193
194 var diff types.NiceDiff
195 if formatPatch.CombinedPatchRaw != "" {
196 diff = patchutil.AsNiceDiff(formatPatch.CombinedPatchRaw, base)
197 } else {
198 diff = patchutil.AsNiceDiff(formatPatch.FormatPatchRaw, base)
199 }
200
201 repoinfo := f.RepoInfo(user)
202
203 rp.pages.RepoCompare(w, pages.RepoCompareParams{
204 LoggedInUser: user,
205 RepoInfo: repoinfo,
206 Branches: branches.Branches,
207 Tags: tags.Tags,
208 Base: base,
209 Head: head,
210 Diff: &diff,
211 DiffOpts: diffOpts,
212 })
213
214}