diff --git a/cmd/revision.go b/cmd/revision.go index 2e4fc597..b0e35d7d 100644 --- a/cmd/revision.go +++ b/cmd/revision.go @@ -17,6 +17,7 @@ type revision struct { client helm.Interface suppressedKinds []string revisions []string + outputContext int } const revisionCmdLongUsage = ` @@ -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 } @@ -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]) @@ -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") diff --git a/cmd/rollback.go b/cmd/rollback.go index 97a5b2a2..0eeb2cc3 100644 --- a/cmd/rollback.go +++ b/cmd/rollback.go @@ -16,6 +16,7 @@ type rollback struct { client helm.Interface suppressedKinds []string revisions []string + outputContext int } const rollbackCmdLongUsage = ` @@ -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 } @@ -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 } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 13522316..4041b5e5 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -19,6 +19,7 @@ type diffCmd struct { reuseValues bool resetValues bool suppressedKinds []string + outputContext int } const globalUsage = `Show a diff explaining what a helm upgrade would change. @@ -63,6 +64,7 @@ func newChartCommand() *cobra.Command { f.BoolVar(&diff.reuseValues, "reuse-values", false, "reuse the last release's values and merge in any new values") f.BoolVar(&diff.resetValues, "reset-values", false, "reset the values to the ones built into the chart and merge in any new values") 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 @@ -104,7 +106,7 @@ func (d *diffCmd) run() error { currentSpecs := manifest.Parse(releaseResponse.Release.Manifest) 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 } diff --git a/diff/diff.go b/diff/diff.go index 2872a446..651ad633 100644 --- a/diff/diff.go +++ b/diff/diff.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "strings" + "math" "github.com/aryann/difflib" "github.com/mgutz/ansi" @@ -11,18 +12,18 @@ import ( "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) } } @@ -30,12 +31,12 @@ func DiffManifests(oldIndex, newIndex map[string]*manifest.MappingResult, suppre 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 { @@ -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 }