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}