This repository has no description
1package pulls
2
3import (
4 "errors"
5 "fmt"
6 "html/template"
7 "io"
8 "net/http"
9 "strings"
10
11 "github.com/bluesky-social/indigo/atproto/syntax"
12 "golang.org/x/sync/errgroup"
13 "tangled.org/core/appview/pages"
14 gitmirrorv1 "tangled.org/core/gitmirror/proto/gen"
15)
16
17// htmx fragment. render interdiff between changes
18func (s *Pulls) PullInterdiffFragment(w http.ResponseWriter, r *http.Request) {
19 l := s.logger.With("handler", "PullInterdiffFragment")
20 ctx := r.Context()
21 var (
22 repoRaw = r.URL.Query().Get("repo") // source repo DID
23 base1 = r.URL.Query().Get("base1") // base1 commit ID
24 head1 = r.URL.Query().Get("head1") // head1 commit ID
25 base2 = r.URL.Query().Get("base2") // base2 commit ID
26 head2 = r.URL.Query().Get("head2") // head2 commit ID
27 unified = r.URL.Query().Get("view") == "unified"
28 )
29 repo, err := syntax.ParseDID(repoRaw)
30 if err != nil {
31 http.Error(w, "invalid repo DID", http.StatusBadRequest)
32 return
33 }
34 l.Debug("interdiff", "base1", base1, "head1", head1, "base2", base2, "head2", head2)
35
36 var params pages.PullDiffFragmentParams
37 params.DiffUrl = r.URL.Path
38 params.Unified = unified
39 defer func() {
40 s.pages.PullDiffFragment(w, params)
41 }()
42
43 stream, err := s.gitmirror.Interdiff(ctx, &gitmirrorv1.InterdiffRequest{
44 Repo: repo.String(),
45 FromBase: []byte(base1),
46 FromHead: []byte(head1),
47 ToBase: []byte(base2),
48 ToHead: []byte(head2),
49 })
50 if err != nil {
51 l.Error("failed to interdiff", "err", err)
52 params.ErrorMsg = "Failed to interdiff. Try again later."
53 return
54 }
55 var files []*fileDiff
56 for {
57 fd, err := stream.Recv()
58 if errors.Is(err, io.EOF) {
59 break
60 }
61 if err != nil {
62 l.Error("failed to drain interdiff response", "err", err)
63 params.ErrorMsg = "Failed to interdiff. Try again later."
64 return
65 }
66 files = append(files, &fileDiff{diff: fd})
67 }
68
69 g, gctx := errgroup.WithContext(ctx)
70 for _, f := range files {
71 g.Go(func() error {
72 lhs, rhs := f.diff.GetLhsSrc(), f.diff.GetRhsSrc()
73 // Binary/submodule files have no line content to fetch.
74 if isBinaryOrSubmodule(lhs) || isBinaryOrSubmodule(rhs) {
75 return nil
76 }
77
78 headBlob, err := s.getBlob(gctx, syntax.DID(repo), rhs.GetOid())
79 if err != nil {
80 return err
81 }
82
83 if f.diff.LhsSrc.Content != nil {
84 for line := range strings.SplitSeq(strings.TrimSuffix(string(f.diff.LhsSrc.Content), "\n"), "\n") {
85 f.baseLines = append(f.baseLines, template.HTML(fmt.Sprintf("<div><span>%s</span></div>", template.HTMLEscapeString(line))))
86 }
87 }
88
89 for line := range strings.SplitSeq(strings.TrimSuffix(string(headBlob), "\n"), "\n") {
90 f.headLines = append(f.headLines, template.HTML(fmt.Sprintf("<div><span>%s</span></div>", template.HTMLEscapeString(line))))
91 }
92 return nil
93 })
94 }
95 if err := g.Wait(); err != nil {
96 l.Error("failed to prepare interdiff UI", "err", err)
97 params.ErrorMsg = "Failed to render interdiff. Try again later."
98 return
99 }
100 for _, f := range files {
101 df := pages.DiffFile{Path: f.diff.GetRhsSrc().GetPath()}
102
103 if isBinaryOrSubmodule(f.diff.GetLhsSrc()) || isBinaryOrSubmodule(f.diff.GetRhsSrc()) {
104 df.Note = "binary or submodule"
105 params.Files = append(params.Files, df)
106 continue
107 }
108
109 for _, h := range buildHunks(f.baseLines, f.headLines, f.diff.Hunks) {
110 var dh pages.DiffHunk
111 if unified {
112 dh.Lines = buildUnifiedLines(h, f.baseLines, f.headLines)
113 } else {
114 dh.Rows = buildSplitRows(h, f.baseLines, f.headLines)
115 }
116 df.Hunks = append(df.Hunks, dh)
117 }
118 params.Files = append(params.Files, df)
119 }
120}