Skip to content

Allow to omit large common parts in diff output #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,11 @@ The above will install this plugin into your `$HELM_HOME/plugins` directory.
- You need to have [Go](http://golang.org) installed. Make sure to set `$GOPATH`
- If you don't have [Glide](http://glide.sh) installed, this will install it into
`$GOPATH/bin` for you.

### Running Tests
Automated tests are implemented with [*testing*](https://golang.org/pkg/testing/).

To run all tests:
```
go test -v ./...
```
6 changes: 4 additions & 2 deletions cmd/revision.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type revision struct {
client helm.Interface
suppressedKinds []string
revisions []string
outputContext int
}

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

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

diff.DiffManifests(manifest.Parse(revisionResponse.Release.Manifest), manifest.Parse(releaseResponse.Release.Manifest), d.suppressedKinds, os.Stdout)
diff.DiffManifests(manifest.Parse(revisionResponse.Release.Manifest), manifest.Parse(releaseResponse.Release.Manifest), d.suppressedKinds, d.outputContext, os.Stdout)

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

diff.DiffManifests(manifest.Parse(revisionResponse1.Release.Manifest), manifest.Parse(revisionResponse2.Release.Manifest), d.suppressedKinds, os.Stdout)
diff.DiffManifests(manifest.Parse(revisionResponse1.Release.Manifest), manifest.Parse(revisionResponse2.Release.Manifest), d.suppressedKinds, d.outputContext, os.Stdout)

default:
return errors.New("Invalid Arguments")
Expand Down
4 changes: 3 additions & 1 deletion cmd/rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type rollback struct {
client helm.Interface
suppressedKinds []string
revisions []string
outputContext int
}

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

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

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

return nil
}
4 changes: 3 additions & 1 deletion cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type diffCmd struct {
resetValues bool
allowUnreleased bool
suppressedKinds []string
outputContext int
}

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

return cmd

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

diff.DiffManifests(currentSpecs, newSpecs, d.suppressedKinds, os.Stdout)
diff.DiffManifests(currentSpecs, newSpecs, d.suppressedKinds, d.outputContext, os.Stdout)

return nil
}
84 changes: 70 additions & 14 deletions diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,39 @@ import (
"fmt"
"io"
"strings"
"math"

"github.com/aryann/difflib"
"github.com/mgutz/ansi"

"github.com/databus23/helm-diff/manifest"
)

func DiffManifests(oldIndex, newIndex map[string]*manifest.MappingResult, suppressedKinds []string, to io.Writer) {
func DiffManifests(oldIndex, newIndex map[string]*manifest.MappingResult, suppressedKinds []string, context int, to io.Writer) {
for key, oldContent := range oldIndex {
if newContent, ok := newIndex[key]; ok {
if oldContent.Content != newContent.Content {
// modified
fmt.Fprintf(to, ansi.Color("%s has changed:", "yellow")+"\n", key)
printDiff(suppressedKinds, oldContent.Kind, oldContent.Content, newContent.Content, to)
printDiff(suppressedKinds, oldContent.Kind, context, oldContent.Content, newContent.Content, to)
}
} else {
// removed
fmt.Fprintf(to, ansi.Color("%s has been removed:", "yellow")+"\n", key)
printDiff(suppressedKinds, oldContent.Kind, oldContent.Content, "", to)
printDiff(suppressedKinds, oldContent.Kind, context, oldContent.Content, "", to)
}
}

for key, newContent := range newIndex {
if _, ok := oldIndex[key]; !ok {
// added
fmt.Fprintf(to, ansi.Color("%s has been added:", "yellow")+"\n", key)
printDiff(suppressedKinds, newContent.Kind, "", newContent.Content, to)
printDiff(suppressedKinds, newContent.Kind, context, "", newContent.Content, to)
}
}
}

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

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

for _, diff := range diffs {
text := diff.Payload
if context >= 0 {
distances := calculateDistances(diffs)
omitting := false
for i, diff := range diffs {
if distances[i] > context {
if !omitting {
fmt.Fprintln(to, "...")
omitting = true
}
} else {
omitting = false
printDiffRecord(diff, to)
}
}
} else {
for _, diff := range diffs {
printDiffRecord(diff, to)
}
}
}

func printDiffRecord(diff difflib.DiffRecord, to io.Writer) {
text := diff.Payload

switch diff.Delta {
case difflib.RightOnly:
fmt.Fprintf(to, "%s\n", ansi.Color("+ "+text, "green"))
case difflib.LeftOnly:
fmt.Fprintf(to, "%s\n", ansi.Color("- "+text, "red"))
case difflib.Common:
fmt.Fprintf(to, "%s\n", " "+text)
}
}

switch diff.Delta {
case difflib.RightOnly:
fmt.Fprintf(to, "%s\n", ansi.Color("+ "+text, "green"))
case difflib.LeftOnly:
fmt.Fprintf(to, "%s\n", ansi.Color("- "+text, "red"))
case difflib.Common:
fmt.Fprintf(to, "%s\n", " "+text)
// Calculate distance of every diff-line to the closest change
func calculateDistances(diffs []difflib.DiffRecord) map[int]int {
distances := map[int]int{}

// Iterate forwards through diffs, set 'distance' based on closest 'change' before this line
change := -1
for i, diff := range diffs {
if diff.Delta != difflib.Common {
change = i
}
distance := math.MaxInt32
if change != -1 {
distance = i - change
}
distances[i] = distance
}

// Iterate backwards through diffs, reduce 'distance' based on closest 'change' after this line
change = -1
for i := len(diffs) - 1; i >= 0; i-- {
diff := diffs[i]
if diff.Delta != difflib.Common {
change = i
}
if change != -1 {
distance := change - i
if distance < distances[i] {
distances[i] = distance
}
}
}

return distances
}
123 changes: 123 additions & 0 deletions diff/diff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package diff

import (
"testing"
"bytes"
"github.com/mgutz/ansi"
)

var text1 = "" +
"line1\n" +
"line2\n" +
"line3\n" +
"line4\n" +
"line5\n" +
"line6\n" +
"line7\n" +
"line8\n" +
"line9\n" +
"line10"

var text2 = "" +
"line1 - different!\n" +
"line2 - different!\n" +
"line3\n" +
"line4\n" +
"line5\n" +
"line6\n" +
"line7\n" +
"line8 - different!\n" +
"line9\n" +
"line10"

func TestPrintDiffWithContext(t *testing.T) {

t.Run("context-disabled", func(t *testing.T) {
assertDiff(t, text1, text2, -1, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
" line3\n"+
" line4\n"+
" line5\n"+
" line6\n"+
" line7\n"+
"- line8\n"+
"+ line8 - different!\n"+
" line9\n"+
" line10\n")
})

t.Run("context-0", func(t *testing.T) {
assertDiff(t, text1, text2, 0, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
"...\n"+
"- line8\n"+
"+ line8 - different!\n"+
"...\n")
})

t.Run("context-1", func(t *testing.T) {
assertDiff(t, text1, text2, 1, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
" line3\n"+
"...\n"+
" line7\n"+
"- line8\n"+
"+ line8 - different!\n"+
" line9\n"+
"...\n")
})

t.Run("context-2", func(t *testing.T) {
assertDiff(t, text1, text2, 2, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
" line3\n"+
" line4\n"+
"...\n"+
" line6\n"+
" line7\n"+
"- line8\n"+
"+ line8 - different!\n"+
" line9\n"+
" line10\n")
})

t.Run("context-3", func(t *testing.T) {
assertDiff(t, text1, text2, 3, ""+
"- line1\n"+
"- line2\n"+
"+ line1 - different!\n"+
"+ line2 - different!\n"+
" line3\n"+
" line4\n"+
" line5\n"+
" line6\n"+
" line7\n"+
"- line8\n"+
"+ line8 - different!\n"+
" line9\n"+
" line10\n")
})

}

func assertDiff(t *testing.T, before, after string, context int, expected string) {
ansi.DisableColors(true)
var output bytes.Buffer
printDiff([]string{}, "some-resource", context, before, after, &output)
actual := output.String()
if actual != expected {
t.Errorf("Unexpected diff output: \nExpected:\n#%v# \nActual:\n#%v#", expected, actual)
}
}