Skip to content

Commit 9de4152

Browse files
authored
Merge pull request #29 from njam/context
Allow to omit large common parts in diff output
2 parents d2e9acf + 97ccda7 commit 9de4152

File tree

6 files changed

+211
-18
lines changed

6 files changed

+211
-18
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,11 @@ The above will install this plugin into your `$HELM_HOME/plugins` directory.
182182
- You need to have [Go](http://golang.org) installed. Make sure to set `$GOPATH`
183183
- If you don't have [Glide](http://glide.sh) installed, this will install it into
184184
`$GOPATH/bin` for you.
185+
186+
### Running Tests
187+
Automated tests are implemented with [*testing*](https://golang.org/pkg/testing/).
188+
189+
To run all tests:
190+
```
191+
go test -v ./...
192+
```

cmd/revision.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type revision struct {
1717
client helm.Interface
1818
suppressedKinds []string
1919
revisions []string
20+
outputContext int
2021
}
2122

2223
const revisionCmdLongUsage = `
@@ -69,6 +70,7 @@ func revisionCmd() *cobra.Command {
6970

7071
revisionCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output")
7172
revisionCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output")
73+
revisionCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes")
7274
revisionCmd.SuggestionsMinimumDistance = 1
7375
return revisionCmd
7476
}
@@ -89,7 +91,7 @@ func (d *revision) differentiate() error {
8991
return prettyError(err)
9092
}
9193

92-
diff.DiffManifests(manifest.Parse(revisionResponse.Release.Manifest), manifest.Parse(releaseResponse.Release.Manifest), d.suppressedKinds, os.Stdout)
94+
diff.DiffManifests(manifest.Parse(revisionResponse.Release.Manifest), manifest.Parse(releaseResponse.Release.Manifest), d.suppressedKinds, d.outputContext, os.Stdout)
9395

9496
case 2:
9597
revision1, _ := strconv.Atoi(d.revisions[0])
@@ -108,7 +110,7 @@ func (d *revision) differentiate() error {
108110
return prettyError(err)
109111
}
110112

111-
diff.DiffManifests(manifest.Parse(revisionResponse1.Release.Manifest), manifest.Parse(revisionResponse2.Release.Manifest), d.suppressedKinds, os.Stdout)
113+
diff.DiffManifests(manifest.Parse(revisionResponse1.Release.Manifest), manifest.Parse(revisionResponse2.Release.Manifest), d.suppressedKinds, d.outputContext, os.Stdout)
112114

113115
default:
114116
return errors.New("Invalid Arguments")

cmd/rollback.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type rollback struct {
1616
client helm.Interface
1717
suppressedKinds []string
1818
revisions []string
19+
outputContext int
1920
}
2021

2122
const rollbackCmdLongUsage = `
@@ -59,6 +60,7 @@ func rollbackCmd() *cobra.Command {
5960

6061
rollbackCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output")
6162
rollbackCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output")
63+
rollbackCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes")
6264
rollbackCmd.SuggestionsMinimumDistance = 1
6365
return rollbackCmd
6466
}
@@ -80,7 +82,7 @@ func (d *rollback) backcast() error {
8082
}
8183

8284
// create a diff between the current manifest and the version of the manifest that a user is intended to rollback
83-
diff.DiffManifests(manifest.Parse(releaseResponse.Release.Manifest), manifest.Parse(revisionResponse.Release.Manifest), d.suppressedKinds, os.Stdout)
85+
diff.DiffManifests(manifest.Parse(releaseResponse.Release.Manifest), manifest.Parse(revisionResponse.Release.Manifest), d.suppressedKinds, d.outputContext, os.Stdout)
8486

8587
return nil
8688
}

cmd/upgrade.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type diffCmd struct {
2222
resetValues bool
2323
allowUnreleased bool
2424
suppressedKinds []string
25+
outputContext int
2526
}
2627

2728
const globalUsage = `Show a diff explaining what a helm upgrade would change.
@@ -67,6 +68,7 @@ func newChartCommand() *cobra.Command {
6768
f.BoolVar(&diff.resetValues, "reset-values", false, "reset the values to the ones built into the chart and merge in any new values")
6869
f.BoolVar(&diff.allowUnreleased, "allow-unreleased", false, "enables diffing of releases that are not yet deployed via Helm")
6970
f.StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output")
71+
f.IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes")
7072

7173
return cmd
7274

@@ -137,7 +139,7 @@ func (d *diffCmd) run() error {
137139
newSpecs = manifest.Parse(upgradeResponse.Release.Manifest)
138140
}
139141

140-
diff.DiffManifests(currentSpecs, newSpecs, d.suppressedKinds, os.Stdout)
142+
diff.DiffManifests(currentSpecs, newSpecs, d.suppressedKinds, d.outputContext, os.Stdout)
141143

142144
return nil
143145
}

diff/diff.go

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,39 @@ import (
44
"fmt"
55
"io"
66
"strings"
7+
"math"
78

89
"github.com/aryann/difflib"
910
"github.com/mgutz/ansi"
1011

1112
"github.com/databus23/helm-diff/manifest"
1213
)
1314

14-
func DiffManifests(oldIndex, newIndex map[string]*manifest.MappingResult, suppressedKinds []string, to io.Writer) {
15+
func DiffManifests(oldIndex, newIndex map[string]*manifest.MappingResult, suppressedKinds []string, context int, to io.Writer) {
1516
for key, oldContent := range oldIndex {
1617
if newContent, ok := newIndex[key]; ok {
1718
if oldContent.Content != newContent.Content {
1819
// modified
1920
fmt.Fprintf(to, ansi.Color("%s has changed:", "yellow")+"\n", key)
20-
printDiff(suppressedKinds, oldContent.Kind, oldContent.Content, newContent.Content, to)
21+
printDiff(suppressedKinds, oldContent.Kind, context, oldContent.Content, newContent.Content, to)
2122
}
2223
} else {
2324
// removed
2425
fmt.Fprintf(to, ansi.Color("%s has been removed:", "yellow")+"\n", key)
25-
printDiff(suppressedKinds, oldContent.Kind, oldContent.Content, "", to)
26+
printDiff(suppressedKinds, oldContent.Kind, context, oldContent.Content, "", to)
2627
}
2728
}
2829

2930
for key, newContent := range newIndex {
3031
if _, ok := oldIndex[key]; !ok {
3132
// added
3233
fmt.Fprintf(to, ansi.Color("%s has been added:", "yellow")+"\n", key)
33-
printDiff(suppressedKinds, newContent.Kind, "", newContent.Content, to)
34+
printDiff(suppressedKinds, newContent.Kind, context, "", newContent.Content, to)
3435
}
3536
}
3637
}
3738

38-
func printDiff(suppressedKinds []string, kind, before, after string, to io.Writer) {
39+
func printDiff(suppressedKinds []string, kind string, context int, before, after string, to io.Writer) {
3940
diffs := difflib.Diff(strings.Split(before, "\n"), strings.Split(after, "\n"))
4041

4142
for _, ckind := range suppressedKinds {
@@ -46,16 +47,71 @@ func printDiff(suppressedKinds []string, kind, before, after string, to io.Write
4647
}
4748
}
4849

49-
for _, diff := range diffs {
50-
text := diff.Payload
50+
if context >= 0 {
51+
distances := calculateDistances(diffs)
52+
omitting := false
53+
for i, diff := range diffs {
54+
if distances[i] > context {
55+
if !omitting {
56+
fmt.Fprintln(to, "...")
57+
omitting = true
58+
}
59+
} else {
60+
omitting = false
61+
printDiffRecord(diff, to)
62+
}
63+
}
64+
} else {
65+
for _, diff := range diffs {
66+
printDiffRecord(diff, to)
67+
}
68+
}
69+
}
70+
71+
func printDiffRecord(diff difflib.DiffRecord, to io.Writer) {
72+
text := diff.Payload
73+
74+
switch diff.Delta {
75+
case difflib.RightOnly:
76+
fmt.Fprintf(to, "%s\n", ansi.Color("+ "+text, "green"))
77+
case difflib.LeftOnly:
78+
fmt.Fprintf(to, "%s\n", ansi.Color("- "+text, "red"))
79+
case difflib.Common:
80+
fmt.Fprintf(to, "%s\n", " "+text)
81+
}
82+
}
5183

52-
switch diff.Delta {
53-
case difflib.RightOnly:
54-
fmt.Fprintf(to, "%s\n", ansi.Color("+ "+text, "green"))
55-
case difflib.LeftOnly:
56-
fmt.Fprintf(to, "%s\n", ansi.Color("- "+text, "red"))
57-
case difflib.Common:
58-
fmt.Fprintf(to, "%s\n", " "+text)
84+
// Calculate distance of every diff-line to the closest change
85+
func calculateDistances(diffs []difflib.DiffRecord) map[int]int {
86+
distances := map[int]int{}
87+
88+
// Iterate forwards through diffs, set 'distance' based on closest 'change' before this line
89+
change := -1
90+
for i, diff := range diffs {
91+
if diff.Delta != difflib.Common {
92+
change = i
93+
}
94+
distance := math.MaxInt32
95+
if change != -1 {
96+
distance = i - change
97+
}
98+
distances[i] = distance
99+
}
100+
101+
// Iterate backwards through diffs, reduce 'distance' based on closest 'change' after this line
102+
change = -1
103+
for i := len(diffs) - 1; i >= 0; i-- {
104+
diff := diffs[i]
105+
if diff.Delta != difflib.Common {
106+
change = i
107+
}
108+
if change != -1 {
109+
distance := change - i
110+
if distance < distances[i] {
111+
distances[i] = distance
112+
}
59113
}
60114
}
115+
116+
return distances
61117
}

diff/diff_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package diff
2+
3+
import (
4+
"testing"
5+
"bytes"
6+
"github.com/mgutz/ansi"
7+
)
8+
9+
var text1 = "" +
10+
"line1\n" +
11+
"line2\n" +
12+
"line3\n" +
13+
"line4\n" +
14+
"line5\n" +
15+
"line6\n" +
16+
"line7\n" +
17+
"line8\n" +
18+
"line9\n" +
19+
"line10"
20+
21+
var text2 = "" +
22+
"line1 - different!\n" +
23+
"line2 - different!\n" +
24+
"line3\n" +
25+
"line4\n" +
26+
"line5\n" +
27+
"line6\n" +
28+
"line7\n" +
29+
"line8 - different!\n" +
30+
"line9\n" +
31+
"line10"
32+
33+
func TestPrintDiffWithContext(t *testing.T) {
34+
35+
t.Run("context-disabled", func(t *testing.T) {
36+
assertDiff(t, text1, text2, -1, ""+
37+
"- line1\n"+
38+
"- line2\n"+
39+
"+ line1 - different!\n"+
40+
"+ line2 - different!\n"+
41+
" line3\n"+
42+
" line4\n"+
43+
" line5\n"+
44+
" line6\n"+
45+
" line7\n"+
46+
"- line8\n"+
47+
"+ line8 - different!\n"+
48+
" line9\n"+
49+
" line10\n")
50+
})
51+
52+
t.Run("context-0", func(t *testing.T) {
53+
assertDiff(t, text1, text2, 0, ""+
54+
"- line1\n"+
55+
"- line2\n"+
56+
"+ line1 - different!\n"+
57+
"+ line2 - different!\n"+
58+
"...\n"+
59+
"- line8\n"+
60+
"+ line8 - different!\n"+
61+
"...\n")
62+
})
63+
64+
t.Run("context-1", func(t *testing.T) {
65+
assertDiff(t, text1, text2, 1, ""+
66+
"- line1\n"+
67+
"- line2\n"+
68+
"+ line1 - different!\n"+
69+
"+ line2 - different!\n"+
70+
" line3\n"+
71+
"...\n"+
72+
" line7\n"+
73+
"- line8\n"+
74+
"+ line8 - different!\n"+
75+
" line9\n"+
76+
"...\n")
77+
})
78+
79+
t.Run("context-2", func(t *testing.T) {
80+
assertDiff(t, text1, text2, 2, ""+
81+
"- line1\n"+
82+
"- line2\n"+
83+
"+ line1 - different!\n"+
84+
"+ line2 - different!\n"+
85+
" line3\n"+
86+
" line4\n"+
87+
"...\n"+
88+
" line6\n"+
89+
" line7\n"+
90+
"- line8\n"+
91+
"+ line8 - different!\n"+
92+
" line9\n"+
93+
" line10\n")
94+
})
95+
96+
t.Run("context-3", func(t *testing.T) {
97+
assertDiff(t, text1, text2, 3, ""+
98+
"- line1\n"+
99+
"- line2\n"+
100+
"+ line1 - different!\n"+
101+
"+ line2 - different!\n"+
102+
" line3\n"+
103+
" line4\n"+
104+
" line5\n"+
105+
" line6\n"+
106+
" line7\n"+
107+
"- line8\n"+
108+
"+ line8 - different!\n"+
109+
" line9\n"+
110+
" line10\n")
111+
})
112+
113+
}
114+
115+
func assertDiff(t *testing.T, before, after string, context int, expected string) {
116+
ansi.DisableColors(true)
117+
var output bytes.Buffer
118+
printDiff([]string{}, "some-resource", context, before, after, &output)
119+
actual := output.String()
120+
if actual != expected {
121+
t.Errorf("Unexpected diff output: \nExpected:\n#%v# \nActual:\n#%v#", expected, actual)
122+
}
123+
}

0 commit comments

Comments
 (0)