diff --git a/assets/issues/issue-686/from.yml b/assets/issues/issue-686/from.yml new file mode 100644 index 00000000..d7abe42d --- /dev/null +++ b/assets/issues/issue-686/from.yml @@ -0,0 +1,15 @@ +data: + values.yaml: | + image: + registry: custom-registry + service: + port: 80 + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + metrics: {} + resources: + limits: + cpu: 100m + priorityClassName: my-priority diff --git a/assets/issues/issue-686/to.yml b/assets/issues/issue-686/to.yml new file mode 100644 index 00000000..d45dbb54 --- /dev/null +++ b/assets/issues/issue-686/to.yml @@ -0,0 +1,15 @@ +data: + values.yaml: | + image: + registry: custom-registry + service: + port: 80 + autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 10 + metrics: {} + resources: + limits: + cpu: 100m + priorityClassName: my-priority diff --git a/assets/kubernetes/rename/expected-dyff.human b/assets/kubernetes/rename/expected-dyff.human index c751fa5f..72b229ae 100644 --- a/assets/kubernetes/rename/expected-dyff.human +++ b/assets/kubernetes/rename/expected-dyff.human @@ -1,13 +1,6 @@ data.pinniped.yaml ± value change in multiline text (one insert, no deletions) - discovery: - url: null - api: - servingCertificate: - - [two lines unchanged)] - apiGroupSuffix: pinniped.dev # aggregatedAPIServerPort may be set here, although other YAML references to the default port (10250) may also need to be updated # impersonationProxyServerPort may be set here, although other YAML references to the default port (8444) may also need to be updated @@ -17,13 +10,6 @@ data.pinniped.yaml credentialIssuer: pinniped-concierge-config apiService: pinniped-concierge-api impersonationLoadBalancerService: pinniped-concierge-impersonation-proxy-load-balancer - - [five lines unchanged)] - - labels: {"app": "pinniped-concierge"} - kubeCertAgent: - namePrefix: pinniped-concierge-kube-cert-agent- - image: projects.registry.vmware.com/pinniped/pinniped-server:latest diff --git a/assets/multiline/expected-dyff-spruce.github b/assets/multiline/expected-dyff-spruce.github index 7910c32f..9a71f0eb 100644 --- a/assets/multiline/expected-dyff-spruce.github +++ b/assets/multiline/expected-dyff-spruce.github @@ -38,13 +38,6 @@ @@ files.complex.content @@ ! ± value change in multiline text (two inserts, two deletions) - Begin line 1 - Begin line 2 - Begin line 3 - Begin line 4 -  - [four lines unchanged)] -   PreChange line 1  PreChange line 2  PreChange line 3 @@ -84,11 +77,4 @@  PostDelete line 2  PostDelete line 3  PostDelete line 4 -  - [22 lines unchanged)] -  - End line 1 - End line 2 - End line 3 - End line 4 diff --git a/assets/multiline/expected-dyff-spruce.human b/assets/multiline/expected-dyff-spruce.human index 413a13ca..70263313 100644 --- a/assets/multiline/expected-dyff-spruce.human +++ b/assets/multiline/expected-dyff-spruce.human @@ -40,13 +40,6 @@ files.complex.content ± value change in multiline text (two inserts, two deletions) -  Begin line 1 -  Begin line 2 -  Begin line 3 -  Begin line 4 -   -  [four lines unchanged)] -    PreChange line 1  PreChange line 2  PreChange line 3 @@ -86,12 +79,5 @@  PostDelete line 2  PostDelete line 3  PostDelete line 4 -   -  [22 lines unchanged)] -   -  End line 1 -  End line 2 -  End line 3 -  End line 4 diff --git a/pkg/dyff/output_human.go b/pkg/dyff/output_human.go index 6379cf47..65ed68a8 100644 --- a/pkg/dyff/output_human.go +++ b/pkg/dyff/output_human.go @@ -368,7 +368,7 @@ func (report *HumanReport) writeStringDiff(output stringWriter, from string, to var ins, del int var buf bytes.Buffer multilineContextLines := report.MultilineContextLines - for _, d := range diff { + for i, d := range diff { // color and format each diff by type switch d.Type { case diffmatchpatch.DiffInsert: @@ -384,6 +384,24 @@ func (report *HumanReport) writeStringDiff(output stringWriter, from string, to if multilineContextLines <= 0 || len(d.Text) == 0 { continue } + + // Only keep context adjacent to actual changes (issue #686). Leading equal + // hunks used to also emit the start of the file; trailing ones the end. + hasChangeBefore := false + for j := 0; j < i; j++ { + if diff[j].Type != diffmatchpatch.DiffEqual { + hasChangeBefore = true + break + } + } + hasChangeAfter := false + for j := i + 1; j < len(diff); j++ { + if diff[j].Type != diffmatchpatch.DiffEqual { + hasChangeAfter = true + break + } + } + // add amount of unchanged lines as configured lines := strings.Split(d.Text, "\n") lower := int(math.Min(float64(len(lines)), float64(multilineContextLines))) @@ -393,9 +411,17 @@ func (report *HumanReport) writeStringDiff(output stringWriter, from string, to upper-- } var val string - if upper <= lower { + switch { + case upper <= lower: val = strings.Join(lines, "\n") - } else { + case !hasChangeBefore && hasChangeAfter: + // Leading equal hunk: context before the following change only. + val = strings.Join(lines[upper:], "\n") + case hasChangeBefore && !hasChangeAfter: + // Trailing equal hunk: context after the preceding change only. + val = strings.Join(lines[:lower], "\n") + default: + // Equal hunk between changes: keep both ends. val = fmt.Sprintf("%s\n\n[%s unchanged)]\n\n%s", strings.Join(lines[:lower], "\n"), text.Plural((upper-lower), "line"), diff --git a/pkg/dyff/output_human_test.go b/pkg/dyff/output_human_test.go index abc04671..9eab8348 100644 --- a/pkg/dyff/output_human_test.go +++ b/pkg/dyff/output_human_test.go @@ -21,6 +21,8 @@ package dyff_test import ( + "bufio" + "bytes" "fmt" . "github.com/onsi/ginkgo/v2" @@ -227,6 +229,40 @@ variables.ROUTER_TLS_PEM.options false, ) }) + + It("should only keep multiline context adjacent to changes (https://github.com/homeport/dyff/issues/686)", func() { + from, to := loadFiles( + assets("issues", "issue-686", "from.yml"), + assets("issues", "issue-686", "to.yml"), + ) + + report, err := dyff.CompareInputFiles(from, to) + Expect(err).ToNot(HaveOccurred()) + + reportWriter := &dyff.HumanReport{ + Report: report, + Indent: 2, + UseIndentLines: true, + OmitHeader: true, + MinorChangeThreshold: 0.1, + MultilineContextLines: 2, + } + + buffer := &bytes.Buffer{} + writer := bufio.NewWriter(buffer) + Expect(reportWriter.WriteReport(writer)).To(Succeed()) + Expect(writer.Flush()).To(Succeed()) + + actual := RemoveAllEscapeSequences(buffer.String()) + Expect(actual).To(ContainSubstring("minReplicas: 2")) + Expect(actual).To(ContainSubstring("minReplicas: 3")) + Expect(actual).To(ContainSubstring("autoscaling:")) + Expect(actual).To(ContainSubstring("maxReplicas: 10")) + // Leading/trailing file bookends must not appear when context is local only. + Expect(actual).ToNot(ContainSubstring("registry: custom-registry")) + Expect(actual).ToNot(ContainSubstring("priorityClassName")) + Expect(actual).ToNot(ContainSubstring("unchanged)")) + }) }) Context("reported output issues (without colors)", func() {