Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).

patchutil,types: implement DiffRenderer interface for all diffs

types.NiceDiff and patchutil.Interdiff now implement the new interface.
this allows us to remove the differing rendering logic necessary to
present each kind of diff.

authored by oppi.li and committed by tangled.org d33579d7 32468e92

+175 -59
+2 -2
appview/pulls/opengraph.go
··· 18 18 "tangled.org/core/types" 19 19 ) 20 20 21 - func (s *Pulls) drawPullSummaryCard(pull *models.Pull, repo *models.Repo, commentCount int, diffStats types.DiffStat, filesChanged int) (*ogcard.Card, error) { 21 + func (s *Pulls) drawPullSummaryCard(pull *models.Pull, repo *models.Repo, commentCount int, diffStats types.DiffFileStat, filesChanged int) (*ogcard.Card, error) { 22 22 width, height := ogcard.DefaultSize() 23 23 mainCard, err := ogcard.NewCard(width, height) 24 24 if err != nil { ··· 284 284 commentCount := len(comments) 285 285 286 286 // Calculate diff stats from latest submission using patchutil 287 - var diffStats types.DiffStat 287 + var diffStats types.DiffFileStat 288 288 filesChanged := 0 289 289 if len(pull.Submissions) > 0 { 290 290 latestSubmission := pull.Submissions[len(pull.Submissions)-1]
+3 -8
knotserver/git/diff.go
··· 64 64 65 65 for _, tf := range d.TextFragments { 66 66 ndiff.TextFragments = append(ndiff.TextFragments, *tf) 67 - for _, l := range tf.Lines { 68 - switch l.Op { 69 - case gitdiff.OpAdd: 70 - nd.Stat.Insertions += 1 71 - case gitdiff.OpDelete: 72 - nd.Stat.Deletions += 1 73 - } 74 - } 67 + nd.Stat.Insertions += tf.LinesAdded 68 + nd.Stat.Deletions += tf.LinesDeleted 75 69 } 76 70 77 71 nd.Diff = append(nd.Diff, ndiff) 78 72 } 79 73 74 + nd.Stat.FilesChanged += len(diffs) 80 75 nd.Commit.FromGoGitCommit(c) 81 76 82 77 return &nd, nil
+66 -10
patchutil/interdiff.go
··· 5 5 "strings" 6 6 7 7 "github.com/bluekeyes/go-gitdiff/gitdiff" 8 + "tangled.org/core/appview/filetree" 8 9 "tangled.org/core/types" 9 10 ) 10 11 ··· 13 12 Files []*InterdiffFile 14 13 } 15 14 16 - func (i *InterdiffResult) AffectedFiles() []string { 17 - files := make([]string, len(i.Files)) 18 - for _, f := range i.Files { 19 - files = append(files, f.Name) 15 + func (i *InterdiffResult) Stats() types.DiffStat { 16 + var ins, del int64 17 + for _, s := range i.ChangedFiles() { 18 + stat := s.Stats() 19 + ins += stat.Insertions 20 + del += stat.Deletions 20 21 } 21 - return files 22 + return types.DiffStat{ 23 + Insertions: ins, 24 + Deletions: del, 25 + FilesChanged: len(i.Files), 26 + } 27 + } 28 + 29 + func (i *InterdiffResult) ChangedFiles() []types.DiffFileRenderer { 30 + drs := make([]types.DiffFileRenderer, len(i.Files)) 31 + for i, s := range i.Files { 32 + drs[i] = s 33 + } 34 + return drs 35 + } 36 + 37 + func (i *InterdiffResult) FileTree() *filetree.FileTreeNode { 38 + fs := make([]string, len(i.Files)) 39 + for i, s := range i.Files { 40 + fs[i] = s.Name 41 + } 42 + return filetree.FileTree(fs) 22 43 } 23 44 24 45 func (i *InterdiffResult) String() string { ··· 59 36 Status InterdiffFileStatus 60 37 } 61 38 62 - func (s *InterdiffFile) Split() *types.SplitDiff { 39 + func (s *InterdiffFile) Id() string { 40 + return s.Name 41 + } 42 + 43 + func (s *InterdiffFile) Split() types.SplitDiff { 63 44 fragments := make([]types.SplitFragment, len(s.TextFragments)) 64 45 65 46 for i, fragment := range s.TextFragments { ··· 76 49 } 77 50 } 78 51 79 - return &types.SplitDiff{ 52 + return types.SplitDiff{ 80 53 Name: s.Id(), 81 54 TextFragments: fragments, 82 55 } 83 56 } 84 57 85 - // used by html elements as a unique ID for hrefs 86 - func (s *InterdiffFile) Id() string { 87 - return s.Name 58 + func (s *InterdiffFile) CanRender() string { 59 + if s.Status.IsUnchanged() { 60 + return "This file has not been changed." 61 + } else if s.Status.IsRebased() { 62 + return "This patch was likely rebased, as context lines do not match." 63 + } else if s.Status.IsError() { 64 + return "Failed to calculate interdiff for this file." 65 + } else { 66 + return "" 67 + } 68 + } 69 + 70 + func (s *InterdiffFile) Names() types.DiffFileName { 71 + var n types.DiffFileName 72 + n.New = s.Name 73 + return n 74 + } 75 + 76 + func (s *InterdiffFile) Stats() types.DiffFileStat { 77 + var ins, del int64 78 + 79 + if s.File != nil { 80 + for _, f := range s.TextFragments { 81 + ins += f.LinesAdded 82 + del += f.LinesDeleted 83 + } 84 + } 85 + 86 + return types.DiffFileStat{ 87 + Insertions: ins, 88 + Deletions: del, 89 + } 88 90 } 89 91 90 92 func (s *InterdiffFile) String() string {
+9
patchutil/patchutil_test.go
··· 4 4 "errors" 5 5 "reflect" 6 6 "testing" 7 + 8 + "tangled.org/core/types" 7 9 ) 8 10 9 11 func TestIsPatchValid(t *testing.T) { ··· 325 323 }) 326 324 } 327 325 } 326 + 327 + func TestImplsInterfaces(t *testing.T) { 328 + id := &InterdiffResult{} 329 + _ = isDiffsRenderer(id) 330 + } 331 + 332 + func isDiffsRenderer[S types.DiffRenderer](S) bool { return true }
+83 -35
types/diff.go
··· 1 1 package types 2 2 3 3 import ( 4 + "net/url" 5 + 4 6 "github.com/bluekeyes/go-gitdiff/gitdiff" 7 + "tangled.org/core/appview/filetree" 5 8 ) 6 9 7 10 type DiffOpts struct { 8 11 Split bool `json:"split"` 9 12 } 10 13 11 - type TextFragment struct { 12 - Header string `json:"comment"` 13 - Lines []gitdiff.Line `json:"lines"` 14 + func (d DiffOpts) Encode() string { 15 + values := make(url.Values) 16 + if d.Split { 17 + values.Set("diff", "split") 18 + } else { 19 + values.Set("diff", "unified") 20 + } 21 + return values.Encode() 22 + } 23 + 24 + // A nicer git diff representation. 25 + type NiceDiff struct { 26 + Commit Commit `json:"commit"` 27 + Stat DiffStat `json:"stat"` 28 + Diff []Diff `json:"diff"` 14 29 } 15 30 16 31 type Diff struct { ··· 41 26 IsRename bool `json:"is_rename"` 42 27 } 43 28 44 - type DiffStat struct { 45 - Insertions int64 46 - Deletions int64 47 - } 48 - 49 - func (d *Diff) Stats() DiffStat { 50 - var stats DiffStat 29 + func (d Diff) Stats() DiffFileStat { 30 + var stats DiffFileStat 51 31 for _, f := range d.TextFragments { 52 32 stats.Insertions += f.LinesAdded 53 33 stats.Deletions += f.LinesDeleted ··· 50 40 return stats 51 41 } 52 42 53 - // A nicer git diff representation. 54 - type NiceDiff struct { 55 - Commit Commit `json:"commit"` 56 - Stat struct { 57 - FilesChanged int `json:"files_changed"` 58 - Insertions int `json:"insertions"` 59 - Deletions int `json:"deletions"` 60 - } `json:"stat"` 61 - Diff []Diff `json:"diff"` 43 + type DiffStat struct { 44 + Insertions int64 `json:"insertions"` 45 + Deletions int64 `json:"deletions"` 46 + FilesChanged int `json:"files_changed"` 47 + } 48 + 49 + type DiffFileStat struct { 50 + Insertions int64 51 + Deletions int64 62 52 } 63 53 64 54 type DiffTree struct { ··· 68 58 Diff []*gitdiff.File `json:"diff"` 69 59 } 70 60 71 - func (d *NiceDiff) ChangedFiles() []string { 72 - files := make([]string, len(d.Diff)) 73 - 74 - for i, f := range d.Diff { 75 - if f.IsDelete { 76 - files[i] = f.Name.Old 77 - } else { 78 - files[i] = f.Name.New 79 - } 80 - } 81 - 82 - return files 61 + type DiffFileName struct { 62 + Old string 63 + New string 83 64 } 84 65 85 - // used by html elements as a unique ID for hrefs 86 - func (d *Diff) Id() string { 66 + func (d NiceDiff) ChangedFiles() []DiffFileRenderer { 67 + drs := make([]DiffFileRenderer, len(d.Diff)) 68 + for i, s := range d.Diff { 69 + drs[i] = s 70 + } 71 + return drs 72 + } 73 + 74 + func (d NiceDiff) FileTree() *filetree.FileTreeNode { 75 + fs := make([]string, len(d.Diff)) 76 + for i, s := range d.Diff { 77 + n := s.Names() 78 + if n.New == "" { 79 + fs[i] = n.Old 80 + } else { 81 + fs[i] = n.New 82 + } 83 + } 84 + return filetree.FileTree(fs) 85 + } 86 + 87 + func (d NiceDiff) Stats() DiffStat { 88 + return d.Stat 89 + } 90 + 91 + func (d Diff) Id() string { 87 92 if d.IsDelete { 88 93 return d.Name.Old 89 94 } 90 95 return d.Name.New 91 96 } 92 97 93 - func (d *Diff) Split() *SplitDiff { 98 + func (d Diff) Names() DiffFileName { 99 + var n DiffFileName 100 + if d.IsDelete { 101 + n.Old = d.Name.Old 102 + return n 103 + } else if d.IsCopy || d.IsRename { 104 + n.Old = d.Name.Old 105 + n.New = d.Name.New 106 + return n 107 + } else { 108 + n.New = d.Name.New 109 + return n 110 + } 111 + } 112 + 113 + func (d Diff) CanRender() string { 114 + if d.IsBinary { 115 + return "This is a binary file and will not be displayed." 116 + } 117 + 118 + return "" 119 + } 120 + 121 + func (d Diff) Split() SplitDiff { 94 122 fragments := make([]SplitFragment, len(d.TextFragments)) 95 123 for i, fragment := range d.TextFragments { 96 124 leftLines, rightLines := SeparateLines(&fragment) ··· 139 91 } 140 92 } 141 93 142 - return &SplitDiff{ 94 + return SplitDiff{ 143 95 Name: d.Id(), 144 96 TextFragments: fragments, 145 97 }
+11 -2
types/diff_test.go
··· 1 1 package types 2 2 3 - import "testing" 3 + import ( 4 + "testing" 5 + ) 4 6 5 7 func TestDiffId(t *testing.T) { 6 8 tests := []struct { ··· 107 105 } 108 106 109 107 for i, diff := range nd.Diff { 110 - if changedFiles[i] != diff.Id() { 108 + if changedFiles[i].Id() != diff.Id() { 111 109 t.Errorf("ChangedFiles()[%d] = %q, but Diff.Id() = %q", i, changedFiles[i], diff.Id()) 112 110 } 113 111 } 114 112 } 113 + 114 + func TestImplsInterfaces(t *testing.T) { 115 + nd := NiceDiff{} 116 + _ = isDiffsRenderer(nd) 117 + } 118 + 119 + func isDiffsRenderer[S DiffRenderer](S) bool { return true }
+1 -2
types/split.go
··· 22 22 TextFragments []SplitFragment `json:"fragments"` 23 23 } 24 24 25 - // used by html elements as a unique ID for hrefs 26 - func (d *SplitDiff) Id() string { 25 + func (d SplitDiff) Id() string { 27 26 return d.Name 28 27 } 29 28