- commit
- 09798a4
- parent
- 37bf4fe
- author
- Eric Bower
- date
- 2025-01-01 12:12:48 -0500 EST
refactor: better rendering of range diff for web
5 files changed,
+130,
-52
M
go.mod
+1,
-1
1@@ -15,7 +15,7 @@ require (
2 github.com/knadh/koanf/providers/file v1.0.0
3 github.com/knadh/koanf/v2 v2.1.1
4 github.com/oddg/hungarian-algorithm v0.0.0-20170809162819-9567cbc363de
5- github.com/sergi/go-diff v1.1.0
6+ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
7 github.com/urfave/cli/v2 v2.27.2
8 golang.org/x/crypto v0.21.0
9 modernc.org/sqlite v1.27.0
M
go.sum
+3,
-3
1@@ -133,8 +133,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
2 github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
3 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
4 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5-github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
6-github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
7+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
8+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
9 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
10 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
11 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
12@@ -174,7 +174,7 @@ golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk
13 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
14 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
15 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
16-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
17+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
18 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
19 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20 lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
+88,
-17
1@@ -4,7 +4,9 @@ import (
2 "fmt"
3 "math"
4 "sort"
5+ "strings"
6
7+ "github.com/bluekeyes/go-gitdiff/gitdiff"
8 ha "github.com/oddg/hungarian-algorithm"
9 "github.com/sergi/go-diff/diffmatchpatch"
10 )
11@@ -34,7 +36,7 @@ func NewPatchRange(patch *Patch) *PatchRange {
12 type RangeDiffOutput struct {
13 Header *RangeDiffHeader
14 Order int
15- Diff []diffmatchpatch.Diff
16+ Files []*RangeDiffFile
17 Type string
18 }
19
20@@ -86,7 +88,7 @@ func output(a []*PatchRange, b []*PatchRange) []*RangeDiffOutput {
21 &RangeDiffOutput{
22 Order: patchA.Matching + 1,
23 Header: hdr,
24- Diff: diff,
25+ Files: diff,
26 Type: "diff",
27 },
28 )
29@@ -98,32 +100,98 @@ func output(a []*PatchRange, b []*PatchRange) []*RangeDiffOutput {
30 return outputs
31 }
32
33-func DoDiff(src, dst string) []diffmatchpatch.Diff {
34+type RangeDiffDiff struct {
35+ OuterType string
36+ InnerType string
37+ Text string
38+}
39+
40+func toRangeDiffDiff(diff []diffmatchpatch.Diff) []RangeDiffDiff {
41+ result := []RangeDiffDiff{}
42+
43+ for _, line := range diff {
44+ outer := "equal"
45+ switch line.Type {
46+ case diffmatchpatch.DiffInsert:
47+ outer = "insert"
48+ case diffmatchpatch.DiffDelete:
49+ outer = "delete"
50+ }
51+
52+ fmtLine := strings.Split(line.Text, "\n")
53+ for idx, ln := range fmtLine {
54+ text := ln
55+ if idx < len(fmtLine)-1 {
56+ text = ln + "\n"
57+ }
58+ st := RangeDiffDiff{
59+ Text: text,
60+ OuterType: outer,
61+ InnerType: "equal",
62+ }
63+
64+ if strings.HasPrefix(text, "+") {
65+ st.InnerType = "insert"
66+ } else if strings.HasPrefix(text, "-") {
67+ st.InnerType = "delete"
68+ }
69+
70+ result = append(result, st)
71+ }
72+ }
73+
74+ return result
75+}
76+
77+func DoDiff(src, dst string) []RangeDiffDiff {
78 dmp := diffmatchpatch.New()
79 wSrc, wDst, warray := dmp.DiffLinesToChars(src, dst)
80 diffs := dmp.DiffMain(wSrc, wDst, false)
81 diffs = dmp.DiffCharsToLines(diffs, warray)
82- return diffs
83+ return toRangeDiffDiff(diffs)
84+}
85+
86+type RangeDiffFile struct {
87+ OldFile *gitdiff.File
88+ NewFile *gitdiff.File
89+ Diff []RangeDiffDiff
90 }
91
92-func outputDiff(patchA, patchB *PatchRange) []diffmatchpatch.Diff {
93- diffs := []diffmatchpatch.Diff{}
94+func outputDiff(patchA, patchB *PatchRange) []*RangeDiffFile {
95+ diffs := []*RangeDiffFile{}
96 for _, fileA := range patchA.Files {
97 for _, fileB := range patchB.Files {
98 if fileA.NewName == fileB.NewName {
99- strA := "\n@@ " + fileA.NewName + "\n"
100+ strA := ""
101 for _, frag := range fileA.TextFragments {
102 for _, line := range frag.Lines {
103 strA += line.String()
104 }
105 }
106- strB := "\n@@ " + fileB.NewName + "\n"
107+ strB := ""
108 for _, frag := range fileB.TextFragments {
109 for _, line := range frag.Lines {
110 strB += line.String()
111 }
112 }
113- diffs = append(diffs, DoDiff(strA, strB)...)
114+ curDiff := DoDiff(strA, strB)
115+ hasDiff := false
116+ for _, dd := range curDiff {
117+ if dd.OuterType != "equal" {
118+ hasDiff = true
119+ break
120+ }
121+ }
122+ if !hasDiff {
123+ continue
124+ }
125+ // curDiff := DoDiff(fileA.String(), fileB.String())
126+ fp := &RangeDiffFile{
127+ OldFile: fileA,
128+ NewFile: fileB,
129+ Diff: curDiff,
130+ }
131+ diffs = append(diffs, fp)
132 }
133 }
134 }
135@@ -212,14 +280,17 @@ func RangeDiffToStr(diffs []*RangeDiffOutput) string {
136 output := ""
137 for _, diff := range diffs {
138 output += diff.Header.String()
139- for _, d := range diff.Diff {
140- switch d.Type {
141- case diffmatchpatch.DiffEqual:
142- output += d.Text
143- case diffmatchpatch.DiffInsert:
144- output += d.Text
145- case diffmatchpatch.DiffDelete:
146- output += d.Text
147+ for _, f := range diff.Files {
148+ output += fmt.Sprintf("\n@@ %s\n", f.NewFile.NewName)
149+ for _, d := range f.Diff {
150+ switch d.OuterType {
151+ case "equal":
152+ output += d.Text
153+ case "insert":
154+ output += d.Text
155+ case "delete":
156+ output += d.Text
157+ }
158 }
159 }
160 }
+1,
-13
1@@ -24,21 +24,13 @@
2 {{template "user-pill" .UserData}}
3 <span class="font-bold">
4 {{if eq .Event "pr_created"}}
5- {{if eq $.Patchset.ID .Patchset.ID}}
6- created pr with <code>{{.FormattedPatchsetID}}</code>
7- {{else}}
8 created pr with <a href="/ps/{{.Patchset.ID}}"><code>{{.FormattedPatchsetID}}</code></a>
9- {{end}}
10 {{else if eq .Event "pr_patchset_added"}}
11- {{if eq $.Patchset.ID .Patchset.ID}}
12- added <code>{{.FormattedPatchsetID}}</code>
13- {{else}}
14 added <a href="/ps/{{.Patchset.ID}}"><code>{{.FormattedPatchsetID}}</code></a>
15- {{end}}
16 {{else if eq .Event "pr_patchset_deleted"}}
17 deleted <code>{{.FormattedPatchsetID}}</code>
18 {{else if eq .Event "pr_reviewed"}}
19- reviewed pr with <code class="pill-review">{{.FormattedPatchsetID}}</code>
20+ reviewed pr with <a href="/ps/{{.Patchset.ID}}"><code class="pill-review">{{.FormattedPatchsetID}}</code></a>
21 {{else if eq .Event "pr_patchset_replaced"}}
22 replaced <code>{{.FormattedPatchsetID}}</code>
23 {{else if eq .Event "pr_status_changed"}}
24@@ -75,11 +67,7 @@
25 {{end}}
26
27 <div>
28- {{if eq .Patchset.ID $.Patchset.ID}}
29- <code class="{{if .Review}}pill-review{{end}}">{{.FormattedID}}</code>
30- {{else}}
31 <a href="/ps/{{.Patchset.ID}}"><code class="{{if .Review}}pill-review{{end}}">{{.FormattedID}}</code></a>
32- {{end}}
33 <span> by </span>
34 {{template "user-pill" .UserData}}
35 <span>on <date>{{.Date}}</date></span>
+37,
-18
1@@ -53,27 +53,46 @@
2 </code>
3 </div>
4
5- <div class="group-h">
6- {{- if .Diff -}}
7- <pre>{{- range .Diff -}}
8- {{- if eq .Type -1 -}}
9- <span style="color: tomato;">{{.Text}}</span>
10- {{- else -}}
11- <span>{{.Text}}</span>
12- {{- end -}}
13- {{- end -}}</pre>
14- {{- end -}}
15+ <div>
16+ {{- if .Files -}}
17+ {{range .Files}}
18+ <div class="flex gap">
19+ <div class="flex-1" style="width: 48%;">
20+ <div>{{.OldFile.NewName}}</div>
21+ <pre>{{- range .Diff -}}
22+ {{- if eq .InnerType "insert" -}}
23+ <span style="color: limegreen;">{{.Text}}</span>
24+ {{- else if eq .InnerType "delete" -}}
25+ <span style="color: tomato;">{{.Text}}</span>
26+ {{- else if eq .OuterType "delete" -}}
27+ <span style="background-color: tomato;">{{.Text}}</span>
28+ {{- else if eq .OuterType "insert" -}}
29+ {{- else -}}
30+ <span>{{.Text}}</span>
31+ {{- end -}}
32+ {{- end -}}</pre>
33+ </div>
34
35- {{- if .Diff -}}
36- <pre>{{- range .Diff -}}
37- {{- if eq .Type 1 -}}
38- <span style="color: limegreen;">{{.Text}}</span>
39- {{- else -}}
40- <span>{{.Text}}</span>
41- {{- end -}}
42- {{- end -}}</pre>
43+ <div class="flex-1" style="width: 48%;">
44+ <div>{{.NewFile.NewName}}</div>
45+ <pre>{{- range .Diff -}}
46+ {{- if eq .InnerType "insert" -}}
47+ <span style="color: limegreen;">{{.Text}}</span>
48+ {{- else if eq .InnerType "delete" -}}
49+ <span style="color: tomato;">{{.Text}}</span>
50+ {{- else if eq .OuterType "insert" -}}
51+ <span style="background-color: limegreen;">{{.Text}}</span>
52+ {{- else if eq .OuterType "delete" -}}
53+ {{- else -}}
54+ <span>{{.Text}}</span>
55+ {{- end -}}
56+ {{- end -}}</pre>
57+ </div>
58+ </div>
59+ {{end}}
60 {{- end -}}
61 </div>
62+ </div>
63 {{- end -}}
64 </div>
65