mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-13 09:29:51 -06:00
240 lines
5.5 KiB
Go
240 lines
5.5 KiB
Go
// Copyright 2019 Dolthub, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
// This file incorporates work covered by the following copyright and
|
|
// permission notice:
|
|
//
|
|
// Copyright 2016 Attic Labs, Inc. All rights reserved.
|
|
// Licensed under the Apache License, version 2.0:
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
package diff
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/dolthub/dolt/go/store/types"
|
|
"github.com/dolthub/dolt/go/store/util/writers"
|
|
)
|
|
|
|
type prefixOp string
|
|
|
|
const (
|
|
ADD = "+ "
|
|
DEL = "- "
|
|
)
|
|
|
|
type (
|
|
printFunc func(ctx context.Context, w io.Writer, op prefixOp, key, val types.Value) error
|
|
)
|
|
|
|
// PrintDiff writes a textual reprensentation of the diff from |v1| to |v2|
|
|
// to |w|. If |leftRight| is true then the left-right diff is used for ordered
|
|
// sequences - see Diff vs DiffLeftRight in Set and Map.
|
|
func PrintDiff(ctx context.Context, w io.Writer, v1, v2 types.Value, leftRight bool) (err error) {
|
|
// In the case where the diff involves two simple values, just print out the
|
|
// diff and return. This is needed because the code below assumes that the
|
|
// values being compared have a parent.
|
|
if !ShouldDescend(v1, v2) {
|
|
err := line(ctx, w, DEL, nil, v1)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return line(ctx, w, ADD, nil, v2)
|
|
}
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
dChan := make(chan Difference, 16)
|
|
|
|
// From here on, we can assume that every Difference will have at least one
|
|
// element in the Path.
|
|
eg.Go(func() error {
|
|
defer close(dChan)
|
|
return Diff(ctx, v1, v2, dChan, leftRight, nil)
|
|
})
|
|
eg.Go(func() error {
|
|
var lastParentPath types.Path
|
|
wroteHdr := false
|
|
firstTime := true
|
|
|
|
for d := range dChan {
|
|
parentPath := d.Path[:len(d.Path)-1]
|
|
parentPathChanged := !parentPath.Equals(lastParentPath)
|
|
lastParentPath = parentPath
|
|
if parentPathChanged && wroteHdr {
|
|
err = writeFooter(w, &wroteHdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if parentPathChanged || firstTime {
|
|
firstTime = false
|
|
err = writeHeader(w, parentPath, &wroteHdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
lastPart := d.Path[len(d.Path)-1]
|
|
parentEl, err := parentPath.Resolve(ctx, v1, nil)
|
|
|
|
var key types.Value
|
|
var pfunc printFunc = line
|
|
|
|
switch parent := parentEl.(type) {
|
|
case types.Map:
|
|
if indexPath, ok := lastPart.(types.IndexPath); ok {
|
|
key = indexPath.Index
|
|
} else if hip, ok := lastPart.(types.HashIndexPath); ok {
|
|
// In this case, the map has a non-primitive key so the value
|
|
// is a ref to the key. We need the actual key, not a ref to it.
|
|
hip1 := hip
|
|
hip1.IntoKey = true
|
|
key, err = hip1.Resolve(ctx, parent, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
panic("unexpected Path type")
|
|
}
|
|
case types.Set:
|
|
// default values are ok
|
|
case types.Struct:
|
|
key = types.String(lastPart.(types.FieldPath).Name)
|
|
pfunc = field
|
|
case types.List:
|
|
// default values are ok
|
|
}
|
|
|
|
if d.OldValue != nil {
|
|
err = pfunc(ctx, w, DEL, key, d.OldValue)
|
|
}
|
|
if d.NewValue != nil {
|
|
err = pfunc(ctx, w, ADD, key, d.NewValue)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return writeFooter(w, &wroteHdr)
|
|
})
|
|
|
|
return eg.Wait()
|
|
}
|
|
|
|
func writeHeader(w io.Writer, p types.Path, wroteHdr *bool) error {
|
|
if *wroteHdr {
|
|
return nil
|
|
}
|
|
*wroteHdr = true
|
|
hdr := "(root)"
|
|
if len(p) > 0 {
|
|
hdr = p.String()
|
|
}
|
|
return write(w, []byte(hdr+" {\n"))
|
|
}
|
|
|
|
func writeFooter(w io.Writer, wroteHdr *bool) error {
|
|
if !*wroteHdr {
|
|
return nil
|
|
}
|
|
*wroteHdr = false
|
|
return write(w, []byte(" }\n"))
|
|
}
|
|
|
|
func line(ctx context.Context, w io.Writer, op prefixOp, key, val types.Value) error {
|
|
genPrefix := func(w *writers.PrefixWriter) []byte {
|
|
return []byte(op)
|
|
}
|
|
pw := &writers.PrefixWriter{Dest: w, PrefixFunc: genPrefix, NeedsPrefix: true}
|
|
if key != nil {
|
|
err := writeEncodedValue(ctx, pw, key)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = write(w, []byte(": "))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err := writeEncodedValue(ctx, pw, val)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return write(w, []byte("\n"))
|
|
}
|
|
|
|
func field(ctx context.Context, w io.Writer, op prefixOp, name, val types.Value) error {
|
|
genPrefix := func(w *writers.PrefixWriter) []byte {
|
|
return []byte(op)
|
|
}
|
|
pw := &writers.PrefixWriter{Dest: w, PrefixFunc: genPrefix, NeedsPrefix: true}
|
|
err := write(pw, []byte(name.(types.String)))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = write(w, []byte(": "))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeEncodedValue(ctx, pw, val)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return write(w, []byte("\n"))
|
|
}
|
|
|
|
func writeEncodedValue(ctx context.Context, w io.Writer, v types.Value) error {
|
|
if v.Kind() != types.BlobKind {
|
|
return types.WriteEncodedValue(ctx, w, v)
|
|
}
|
|
|
|
err := write(w, []byte("Blob ("))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = write(w, []byte(humanize.Bytes(v.(types.Blob).Len())))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return write(w, []byte(")"))
|
|
}
|
|
|
|
func write(w io.Writer, b []byte) error {
|
|
_, err := w.Write(b)
|
|
return err
|
|
}
|