This repository has no description
1package pulls
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "strconv"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "github.com/bluesky-social/indigo/lex/util"
11 indigoxrpc "github.com/bluesky-social/indigo/xrpc"
12 "github.com/go-chi/chi/v5"
13 "golang.org/x/sync/errgroup"
14 "tangled.org/core/api/tangled"
15 "tangled.org/core/appview/models"
16 "tangled.org/core/appview/pages"
17 "tangled.org/core/types"
18)
19
20// NOTE: parsing object in middleware is bad pattern
21// you will have to check if object exist in context "just in case"
22// so it's better to make helper function that can read the url pattern instead.
23
24
25// A -- B -- C
26// (master) (pr/123/0)
27//
28// A -- B -- C
29// \ (pr/123/0)
30// `-- D <- B' <- C'
31// (master) (pr/123/1)
32
33// 1. rebase B<-C to D
34// 2. compare tree of C and D
35
36// PullInterDiff is router for /pulls/{pull}/{version}..{version}/{change}
37//
38// Examples:
39// - /pulls/123/0..2/all
40// - /pulls/123/0..2/nrpytyzw
41func (s *Pulls) PullInterDiff(w http.ResponseWriter, r *http.Request) {
42 l := s.logger.With("handler", "PullRound")
43 ctx := r.Context()
44
45 pull, ok := r.Context().Value("pull").(*models.Pull)
46 if !ok {
47 l.Error("failed to get pull")
48 s.pages.Error500(w)
49 return
50 }
51
52 var (
53 version1 = 0
54 version2 = 0
55 changeId = chi.URLParam(r, "*")
56 )
57 if changeId == "all" {
58 changeId = ""
59 }
60
61 // defer render
62 var params pages.PullInterdiffParams
63 params.Pull = pull
64 params.Version1 = version1
65 params.Version2 = version2
66 params.ChangeId = changeId
67 defer s.pages.PullInterdiff(w, params)
68
69 // 1. resolve target branch -> (branch, commit)
70 xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url}
71 branch, err := tangled.GitTempGetBranch(ctx, xrpcc, pull.TargetBranch, pull.RepoDid.String())
72 if err != nil {
73 panic("unimplemented")
74 }
75
76 base := branch.Hash
77 head1 := "" // pull.Versions[version1].Head
78 head2 := "" // pull.Versions[version2].Head
79
80 // 1. log commits from base..head1 and base..head2
81 var commits1, commits2 []types.Commit
82 g, gctx := errgroup.WithContext(ctx)
83 g.Go(func() error {
84 commits1, err = getTempListCommits(gctx, xrpcc, pull.SourceRepoDid(), base, head1)
85 return err
86 })
87 g.Go(func() error {
88 commits2, err = getTempListCommits(gctx, xrpcc, pull.SourceRepoDid(), base, head2)
89 return err
90 })
91 if err := g.Wait(); err != nil {
92 params.ErrorMsg = "something something"
93 panic("unimplemented")
94 }
95
96 if changeId != "" {
97 // interdiff by change-id
98 var old, new *types.Commit
99 for _, commit := range commits1 {
100 if commit.ChangeId == changeId {
101 old = &commit
102 break
103 }
104 }
105 for _, commit := range commits2 {
106 if commit.ChangeId == changeId {
107 new = &commit
108 break
109 }
110 }
111 _, _ = old, new
112 panic("unimplemented")
113 } else {
114 // interdiff of two commit ranges
115 panic("unimplemented")
116 }
117}
118
119// PullDiff is router for /pulls/{pull}/{version}/{commit}..{commit}
120//
121// Examples:
122// - /pulls/123/latest
123// - /pulls/123/2/head
124// - /pulls/123/2/base..head
125// - /pulls/123/2/a53ab251e..d8add468c
126// - /pulls/123/2/d8add468c
127func (s *Pulls) PullDiff(w http.ResponseWriter, r *http.Request) {
128 l := s.logger.With("handler", "PullRound")
129 ctx := r.Context()
130
131 pull, ok := r.Context().Value("pull").(*models.Pull)
132 if !ok {
133 l.Error("failed to get pull")
134 s.pages.Error500(w)
135 return
136 }
137
138 var err error
139
140 var version int
141 var versionRaw = chi.URLParam(r, "version")
142 if versionRaw == "latest" {
143 version = pull.LastRoundNumber()
144 } else {
145 version, err = strconv.Atoi(versionRaw)
146 if err != nil {
147 // invalid version number. redirect
148 http.Redirect(w, r,
149 fmt.Sprintf("/%s/pulls/%d/latest", pull.Repo.RepoIdentifier(), pull.ID),
150 http.StatusSeeOther,
151 )
152 return
153 }
154 }
155
156 var range_ = chi.URLParam(r, "*")
157 base, head, err := parseRevRange(range_)
158 if err != nil {
159 http.Redirect(w, r,
160 fmt.Sprintf("/%s/pulls/%d/%s", pull.Repo.RepoIdentifier(), pull.ID, versionRaw),
161 http.StatusSeeOther,
162 )
163 return
164 }
165
166 // defer render
167 var params pages.PullDiffParams
168 params.Pull = pull
169 params.Version = version
170 defer s.pages.PullDiff(w, params)
171
172 // 1. resolve target branch -> (branch, commit)
173 xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url}
174 branch, err := tangled.GitTempGetBranch(ctx, xrpcc, pull.TargetBranch, pull.RepoDid.String())
175 if err != nil {
176 l.Warn("Failed to resolve target branch", "branch", pull.TargetBranch, "err", err)
177 params.ErrorMsg = fmt.Sprintf("Failed to resolve target branch %q", pull.TargetBranch)
178 return
179 }
180
181 if base == "base" {
182 base = branch.Hash
183 }
184 if head == "head" {
185 head = pull.HEAD()
186 }
187
188 sourceRepoDid := pull.SourceRepoDid()
189
190 // 2. list diverged commits using knotmirror (BASE..HEAD) -> ([]commit)
191 // - knotmirror needs on-demand fetch implementation for this
192 commits, err := getTempListCommits(ctx, xrpcc, sourceRepoDid, base, head)
193 if err != nil {
194 panic("unimplemented")
195 }
196
197 // 3. list every commits in UI. They will be lazy-loaded
198 params.Commits = commits
199}
200
201// htmx fragment. render diff between commits
202func (s *Pulls) PullDiffFragment(w http.ResponseWriter, r *http.Request) {
203 // var (
204 // base = r.URL.Query().Get("base") // base commit ID
205 // head = r.URL.Query().Get("head") // head commit ID
206 // unified = r.URL.Query().Get("view") == "unified"
207 // )
208
209 // 1. get commit object
210 // 2. get diff between parent..commit (knotmirror), parse that diff
211 // 3. fetch each file entries (& run syntax highlight) <- skip this part for stage 1. we will do this at stage 2.
212 // 4. render diff
213}
214
215// htmx fragment. render interdiff between changes
216func (s *Pulls) PullInterdiffFragment(w http.ResponseWriter, r *http.Request) {
217 // var (
218 // base1 = r.URL.Query().Get("base1") // base1 commit ID
219 // base2 = r.URL.Query().Get("base2") // base2 commit ID
220 // head1 = r.URL.Query().Get("head1") // head1 commit ID
221 // head2 = r.URL.Query().Get("head2") // head1 commit ID
222 // unified = r.URL.Query().Get("view") == "unified"
223 // )
224
225 // 1. compute interdiff. (knotmirror)
226 // 2. return rich diff data. (knotmirror)
227 // 3. load old/new blobs & run syntax highlight
228 // 4. render diff
229}
230
231// parseRevRange parses <head>..<base> string.
232// base and head will default to "base" and "head" when omitted.
233func parseRevRange(range_ string) (base string, head string, err error) {
234 panic("unimplemented")
235}
236
237// parseVersionRange parses <version>..<version> string.
238// Each versions will default to "base" and "latest" when omitted.
239func parseVersionRange(range_ string) (base string, head string, err error) {
240 panic("unimplemented")
241}
242
243func getTempListCommits(ctx context.Context, xrpcc util.LexClient, repo syntax.DID, base, head string) ([]types.Commit, error) {
244 panic("unimplemented")
245 // raw, err := tangled.GitTempListCommits(ctx, xrpcc, "", 1000, head, repo.String())
246 // if err != nil {
247 // return nil, err
248 // }
249 //
250 // var xrpcResp types.RepoLogResponse
251 // if err := json.Unmarshal(raw, &xrpcResp); err != nil {
252 // return nil, fmt.Errorf("failed to decode XRPC response: %w", err)
253 // }
254 //
255 // return xrpcResp.Commits, nil
256}
257
258// htmx fragment. render interdiff between commits
259func (s *Pulls) PullInterDiffFragment(w http.ResponseWriter, r *http.Request) {
260 panic("unimplemented")
261}
262
263// gitmirror
264// - git.ListCommitsSinceMergeBase(repo, base, head)
265// - git.Diff(repo, base, head, mode)
266// - git.Interdiff(repo,
267
268// for interdiff, we want: from{start,end}, to{start,end}
269// 1. squash from.start ~ from.end into one commit
270// 2. rebase that commit to to.start.parent()
271// 3. diff from_squashed.tree and to.end.tree
272
273// we want git log BASE..HEAD (only commits in HEAD) diverged=false
274// and git diff BASE...HEAD (changes from HEAD since merge-base) absolute=false
275
276// commands.go:56 picks comparison type:
277// - diff BASE..HEAD (COMPARISON_TYPE_ONLY_IN_HEAD) = direct
278// - diff BASE...HEAD (COMPARISON_TYPE_INTERSECTION) = merge-base. Server resolves merge-base via g.MergeBase() first (diff.go:43) then diffs.
279// we want second one. we should compute merge-base first.
280// we can have