Skip to content

Commit a9bdbd3

Browse files
committed
Try out different patch libraries
1 parent 5bd0ce8 commit a9bdbd3

File tree

3 files changed

+360
-1
lines changed

3 files changed

+360
-1
lines changed
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
package engine
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/sergi/go-diff/diffmatchpatch"
12+
"k8s.io/apimachinery/pkg/util/jsonmergepatch"
13+
"k8s.io/klog/v2"
14+
"sigs.k8s.io/yaml"
15+
16+
"github.com/hexops/gotextdiff"
17+
"github.com/hexops/gotextdiff/myers"
18+
"github.com/hexops/gotextdiff/span"
19+
)
20+
21+
func TestDiffGen(t *testing.T) {
22+
oldYAML := `
23+
apiVersion: v1
24+
kind: ConfigMap
25+
data:
26+
foo1: bar
27+
foo2: bar2
28+
foo3: bar3
29+
`
30+
newYAML := `
31+
apiVersion: v1
32+
kind: ConfigMap
33+
data:
34+
foo1: bar11
35+
foo2: bar22
36+
`
37+
38+
dmp := diffmatchpatch.New()
39+
40+
checklines := true
41+
diffs := dmp.DiffMain(oldYAML, newYAML, checklines)
42+
43+
// got := dmp.DiffPrettyText(diffs)
44+
got := dmp.DiffToDelta(diffs)
45+
46+
want := "=49\t+11\t=13\t-13\t+2\t=1"
47+
48+
got = strings.TrimSpace(got)
49+
want = strings.TrimSpace(want)
50+
51+
t.Logf("patch:\n%q", got)
52+
53+
if diff := cmp.Diff(want, got); diff != "" {
54+
t.Errorf("unexpected result from CreateThreeWayJSONMergePatch: (-want,+got): %s", diff)
55+
}
56+
}
57+
58+
func TestGoDiff(t *testing.T) {
59+
oldYAML := `
60+
apiVersion: v1
61+
kind: ConfigMap
62+
data:
63+
foo1: bar
64+
foo2: bar2
65+
foo3: bar3
66+
`
67+
newYAML := `
68+
apiVersion: v1
69+
kind: ConfigMap
70+
data:
71+
foo1: bar11
72+
foo2: bar22
73+
`
74+
75+
edits := myers.ComputeEdits(span.URIFromPath("a.txt"), oldYAML, newYAML)
76+
got := fmt.Sprint(gotextdiff.ToUnified("a.txt", "b.txt", oldYAML, edits))
77+
78+
want := `
79+
--- a.txt
80+
+++ b.txt
81+
@@ -2,6 +2,5 @@
82+
apiVersion: v1
83+
kind: ConfigMap
84+
data:
85+
- foo1: bar
86+
- foo2: bar2
87+
- foo3: bar3
88+
+ foo1: bar11
89+
+ foo2: bar22
90+
`
91+
92+
got = strings.TrimSpace(got)
93+
want = strings.TrimSpace(want)
94+
95+
t.Logf("patch:\n%v", got)
96+
97+
if diff := cmp.Diff(want, got); diff != "" {
98+
t.Errorf("unexpected result from myers.ComputeEdits: (-want,+got): %s", diff)
99+
}
100+
}
101+
102+
func TestKopsDiff(t *testing.T) {
103+
oldYAML := `
104+
apiVersion: v1
105+
kind: ConfigMap
106+
data:
107+
foo1: bar
108+
foo2: bar2
109+
foo3: bar3
110+
`
111+
newYAML := `
112+
apiVersion: v1
113+
kind: ConfigMap
114+
data:
115+
foo1: bar11
116+
foo2: bar22
117+
`
118+
119+
got := kopsFormatDiff(oldYAML, newYAML)
120+
121+
want := `
122+
...
123+
kind: ConfigMap
124+
data:
125+
+ foo1: bar11
126+
- foo1: bar
127+
- foo2: bar2
128+
+ foo2: bar22
129+
- foo3: bar3
130+
`
131+
132+
got = strings.TrimSpace(got)
133+
want = strings.TrimSpace(want)
134+
135+
t.Logf("patch:\n%v", got)
136+
137+
if diff := cmp.Diff(want, got); diff != "" {
138+
t.Errorf("unexpected result from kopsFormatDiff: (-want,+got): %s", diff)
139+
}
140+
}
141+
142+
func kopsFormatDiff(lString, rString string) string {
143+
results := buildDiffLines(lString, rString)
144+
145+
return renderText(results, 2)
146+
}
147+
148+
func renderText(results []lineRecord, context int) string {
149+
keep := make([]bool, len(results))
150+
for i := range results {
151+
if results[i].Type == diffmatchpatch.DiffEqual {
152+
continue
153+
}
154+
for j := i - context; j <= i+context; j++ {
155+
if j >= 0 && j < len(keep) {
156+
keep[j] = true
157+
}
158+
}
159+
}
160+
161+
var b bytes.Buffer
162+
wroteSkip := false
163+
for i := range results {
164+
if !keep[i] {
165+
if !wroteSkip {
166+
b.WriteString("...\n")
167+
wroteSkip = true
168+
}
169+
continue
170+
}
171+
172+
switch results[i].Type {
173+
case diffmatchpatch.DiffDelete:
174+
b.WriteString("- ")
175+
case diffmatchpatch.DiffInsert:
176+
b.WriteString("+ ")
177+
case diffmatchpatch.DiffEqual:
178+
b.WriteString(" ")
179+
}
180+
b.WriteString(results[i].Line)
181+
b.WriteString("\n")
182+
wroteSkip = false
183+
}
184+
185+
return b.String()
186+
}
187+
188+
type lineRecord struct {
189+
Type diffmatchpatch.Operation
190+
Line string
191+
}
192+
193+
func buildDiffLines(lString, rString string) []lineRecord {
194+
dmp := diffmatchpatch.New()
195+
diffs := dmp.DiffMain(lString, rString, false)
196+
197+
// We do need to cleanup, as otherwise we get some spurious changes on complex diffs
198+
diffs = dmp.DiffCleanupSemantic(diffs)
199+
200+
var l, r string
201+
202+
var results []lineRecord
203+
for _, diff := range diffs {
204+
lines := strings.Split(diff.Text, "\n")
205+
switch diff.Type {
206+
case diffmatchpatch.DiffInsert:
207+
if len(lines) > 0 {
208+
r += lines[0]
209+
if len(lines) > 1 {
210+
results = append(results, lineRecord{Type: diffmatchpatch.DiffInsert, Line: r})
211+
r = ""
212+
}
213+
}
214+
for i := 1; i < len(lines)-1; i++ {
215+
line := lines[i]
216+
results = append(results, lineRecord{Type: diffmatchpatch.DiffInsert, Line: line})
217+
}
218+
if len(lines) > 1 {
219+
r = lines[len(lines)-1]
220+
}
221+
222+
case diffmatchpatch.DiffDelete:
223+
if len(lines) > 0 {
224+
l += lines[0]
225+
if len(lines) > 1 {
226+
results = append(results, lineRecord{Type: diffmatchpatch.DiffDelete, Line: l})
227+
l = ""
228+
}
229+
}
230+
for i := 1; i < len(lines)-1; i++ {
231+
line := lines[i]
232+
results = append(results, lineRecord{Type: diffmatchpatch.DiffDelete, Line: line})
233+
}
234+
if len(lines) > 1 {
235+
l = lines[len(lines)-1]
236+
}
237+
238+
case diffmatchpatch.DiffEqual:
239+
if len(lines) == 1 {
240+
l += lines[0]
241+
r += lines[0]
242+
}
243+
if len(lines) > 1 {
244+
if l != "" || r != "" {
245+
l += lines[0]
246+
r += lines[0]
247+
} else {
248+
results = append(results, lineRecord{Type: diffmatchpatch.DiffEqual, Line: lines[0]})
249+
}
250+
if r != "" {
251+
results = append(results, lineRecord{Type: diffmatchpatch.DiffInsert, Line: r})
252+
r = ""
253+
}
254+
255+
if l != "" {
256+
results = append(results, lineRecord{Type: diffmatchpatch.DiffDelete, Line: l})
257+
l = ""
258+
}
259+
260+
}
261+
for i := 1; i < len(lines)-1; i++ {
262+
line := lines[i]
263+
results = append(results, lineRecord{Type: diffmatchpatch.DiffEqual, Line: line})
264+
}
265+
if len(lines) > 1 {
266+
l = lines[len(lines)-1]
267+
r = lines[len(lines)-1]
268+
}
269+
270+
default:
271+
klog.Fatalf("unexpected dmp type: %v", diff.Type)
272+
}
273+
}
274+
275+
if l != "" && l == r {
276+
results = append(results, lineRecord{Type: diffmatchpatch.DiffEqual, Line: l})
277+
} else {
278+
if l != "" {
279+
results = append(results, lineRecord{Type: diffmatchpatch.DiffDelete, Line: l})
280+
}
281+
if r != "" {
282+
results = append(results, lineRecord{Type: diffmatchpatch.DiffInsert, Line: r})
283+
}
284+
}
285+
286+
return results
287+
}
288+
289+
func TestPatchJSONGen(t *testing.T) {
290+
oldYAML := `
291+
apiVersion: v1
292+
kind: ConfigMap
293+
data:
294+
foo1: bar
295+
foo2: bar2
296+
foo3: bar3
297+
`
298+
newYAML := `
299+
apiVersion: v1
300+
kind: ConfigMap
301+
data:
302+
foo1: bar11
303+
foo2: bar22
304+
`
305+
oldObj := make(map[string]interface{})
306+
if err := yaml.Unmarshal([]byte(oldYAML), &oldObj); err != nil {
307+
t.Fatalf("error from yaml.Unmarshal: %v", err)
308+
}
309+
310+
newObj := make(map[string]interface{})
311+
if err := yaml.Unmarshal([]byte(newYAML), &newObj); err != nil {
312+
t.Fatalf("error from yaml.Unmarshal: %v", err)
313+
}
314+
315+
oldJSON, err := json.Marshal(oldObj)
316+
if err != nil {
317+
t.Fatalf("error from json.Marshal: %v", err)
318+
}
319+
newJSON, err := json.Marshal(newObj)
320+
if err != nil {
321+
t.Fatalf("error from json.Marshal: %v", err)
322+
}
323+
324+
patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(oldJSON, newJSON, oldJSON)
325+
if err != nil {
326+
t.Fatalf("error from CreateThreeWayJSONMergePatch: %v", err)
327+
}
328+
329+
patchObject := make(map[string]interface{})
330+
if err := json.Unmarshal(patch, &patchObject); err != nil {
331+
t.Errorf("error from json.Unmarshal: %v", err)
332+
}
333+
334+
patchYAML, err := yaml.Marshal(patchObject)
335+
if err != nil {
336+
t.Errorf("error from yaml.Marshal: %v", err)
337+
}
338+
339+
got := string(patchYAML)
340+
341+
want := `
342+
data:
343+
foo1: bar11
344+
foo2: bar22
345+
foo3: null
346+
`
347+
348+
got = strings.TrimSpace(got)
349+
want = strings.TrimSpace(want)
350+
351+
t.Logf("patch:\n%v", got)
352+
353+
if diff := cmp.Diff(want, got); diff != "" {
354+
t.Errorf("unexpected result from CreateThreeWayJSONMergePatch: (-want,+got): %s", diff)
355+
}
356+
}

porch/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ require (
2121
github.com/go-git/go-git/v5 v5.4.3-0.20220408232334-4f916225cb2f
2222
github.com/google/go-cmp v0.5.7
2323
github.com/google/go-containerregistry v0.8.0
24+
github.com/hexops/gotextdiff v1.0.3
25+
github.com/sergi/go-diff v1.1.0
2426
github.com/spf13/cobra v1.3.0
2527
github.com/spf13/pflag v1.0.5
2628
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0
@@ -141,7 +143,6 @@ require (
141143
github.com/qri-io/starlib v0.5.0 // indirect
142144
github.com/russross/blackfriday v1.5.2 // indirect
143145
github.com/russross/blackfriday/v2 v2.1.0 // indirect
144-
github.com/sergi/go-diff v1.1.0 // indirect
145146
github.com/sirupsen/logrus v1.8.1 // indirect
146147
github.com/stretchr/testify v1.7.1 // indirect
147148
github.com/vbatts/tar-split v0.11.2 // indirect

porch/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,8 @@ github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
636636
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
637637
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
638638
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
639+
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
640+
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
639641
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
640642
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
641643
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=

0 commit comments

Comments
 (0)