This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / pulls / interdiff.go
3.5 kB 120 lines
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}