repos / git-pr

a self-hosted git collaboration server
git clone https://github.com/picosh/git-pr.git

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
M go.sum
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=
M range_diff.go
+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 	}
M tmpl/pr-detail.html
+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>
M tmpl/range-diff.html
+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