mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-28 10:46:21 -06:00
222 lines
5.8 KiB
Go
222 lines
5.8 KiB
Go
// Copyright 2016 The Noms Authors. All rights reserved.
|
|
// Licensed under the Apache License, version 2.0:
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
package diff
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
|
|
"github.com/attic-labs/noms/go/d"
|
|
"github.com/attic-labs/noms/go/types"
|
|
)
|
|
|
|
const (
|
|
addPrefix = "+ "
|
|
subPrefix = "- "
|
|
)
|
|
|
|
func isPrimitiveOrRef(v1 types.Value) bool {
|
|
kind := v1.Type().Kind()
|
|
return types.IsPrimitiveKind(kind) || kind == types.RefKind
|
|
}
|
|
|
|
func canCompare(v1, v2 types.Value) bool {
|
|
return !isPrimitiveOrRef(v1) && v1.Type().Kind() == v2.Type().Kind()
|
|
}
|
|
|
|
func Diff(w io.Writer, v1, v2 types.Value) (err error) {
|
|
dq := NewDiffQueue()
|
|
di := diffInfo{path: types.NewPath().AddField("/"), v1: v1, v2: v2}
|
|
dq.PushBack(di)
|
|
|
|
err = d.Try(func() {
|
|
for di, ok := dq.PopFront(); ok; di, ok = dq.PopFront() {
|
|
p, key, v1, v2 := di.path, di.key, di.v1, di.v2
|
|
|
|
v1.Type().Kind()
|
|
if v1 == nil && v2 != nil {
|
|
line(w, addPrefix, key, v2)
|
|
}
|
|
if v1 != nil && v2 == nil {
|
|
line(w, subPrefix, key, v1)
|
|
}
|
|
if !v1.Equals(v2) {
|
|
if !canCompare(v1, v2) {
|
|
line(w, subPrefix, key, v1)
|
|
line(w, addPrefix, key, v2)
|
|
} else {
|
|
switch v1.Type().Kind() {
|
|
case types.ListKind:
|
|
diffLists(dq, w, p, v1.(types.List), v2.(types.List))
|
|
case types.MapKind:
|
|
diffMaps(dq, w, p, v1.(types.Map), v2.(types.Map))
|
|
case types.SetKind:
|
|
diffSets(dq, w, p, v1.(types.Set), v2.(types.Set))
|
|
case types.StructKind:
|
|
diffStructs(dq, w, p, v1.(types.Struct), v2.(types.Struct))
|
|
default:
|
|
panic("Unrecognized type in diff function")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
return
|
|
}
|
|
|
|
func diffLists(dq *diffQueue, w io.Writer, p types.Path, v1, v2 types.List) {
|
|
wroteHeader := false
|
|
splices, _ := v2.Diff(v1)
|
|
for _, splice := range splices {
|
|
if splice.SpRemoved == splice.SpAdded {
|
|
for i := uint64(0); i < splice.SpRemoved; i++ {
|
|
lastEl := v1.Get(splice.SpAt + i)
|
|
newEl := v2.Get(splice.SpFrom + i)
|
|
if canCompare(lastEl, newEl) {
|
|
idx := types.Number(splice.SpAt + i)
|
|
p1 := p.AddIndex(idx)
|
|
dq.PushBack(diffInfo{p1, idx, lastEl, newEl})
|
|
} else {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, subPrefix, nil, v1.Get(splice.SpAt+i))
|
|
line(w, addPrefix, nil, v2.Get(splice.SpFrom+i))
|
|
}
|
|
}
|
|
} else {
|
|
for i := uint64(0); i < splice.SpRemoved; i++ {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, subPrefix, nil, v1.Get(splice.SpAt+i))
|
|
}
|
|
for i := uint64(0); i < splice.SpAdded; i++ {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, addPrefix, nil, v2.Get(splice.SpFrom+i))
|
|
}
|
|
}
|
|
}
|
|
writeFooter(w, wroteHeader)
|
|
}
|
|
|
|
func diffMaps(dq *diffQueue, w io.Writer, p types.Path, v1, v2 types.Map) {
|
|
wroteHeader := false
|
|
|
|
added, removed, modified := v2.Diff(v1)
|
|
for _, k := range added {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, addPrefix, k, v2.Get(k))
|
|
}
|
|
for _, k := range removed {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, subPrefix, k, v1.Get(k))
|
|
}
|
|
for _, k := range modified {
|
|
c1, c2 := v1.Get(k), v2.Get(k)
|
|
if canCompare(c1, c2) {
|
|
buf := bytes.NewBuffer(nil)
|
|
d.Exp.NoError(types.WriteEncodedValueWithTags(buf, k))
|
|
p1 := p.AddField(buf.String())
|
|
dq.PushBack(diffInfo{path: p1, key: k, v1: c1, v2: c2})
|
|
} else {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, subPrefix, k, v1.Get(k))
|
|
line(w, addPrefix, k, v2.Get(k))
|
|
}
|
|
}
|
|
writeFooter(w, wroteHeader)
|
|
}
|
|
|
|
func diffStructs(dq *diffQueue, w io.Writer, p types.Path, v1, v2 types.Struct) {
|
|
changed := types.StructDiff(v1, v2)
|
|
wroteHeader := false
|
|
for _, field := range changed {
|
|
f1 := v1.Get(field)
|
|
f2 := v2.Get(field)
|
|
if canCompare(f1, f2) {
|
|
p1 := p.AddField(field)
|
|
dq.PushBack(diffInfo{path: p1, key: types.NewString(field), v1: f1, v2: f2})
|
|
} else {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, subPrefix, types.NewString(field), f1)
|
|
line(w, addPrefix, types.NewString(field), f2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func diffSets(dq *diffQueue, w io.Writer, p types.Path, v1, v2 types.Set) {
|
|
wroteHeader := false
|
|
added, removed := v2.Diff(v1)
|
|
if len(added) == 1 && len(removed) == 1 && canCompare(added[0], removed[0]) {
|
|
p1 := p.AddField(added[0].Hash().String())
|
|
dq.PushBack(diffInfo{path: p1, key: types.NewString(""), v1: removed[0], v2: added[0]})
|
|
} else {
|
|
for _, value := range removed {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, subPrefix, nil, value)
|
|
}
|
|
for _, value := range added {
|
|
wroteHeader = writeHeader(w, wroteHeader, p)
|
|
line(w, addPrefix, nil, value)
|
|
}
|
|
}
|
|
writeFooter(w, wroteHeader)
|
|
return
|
|
}
|
|
|
|
type prefixWriter struct {
|
|
w io.Writer
|
|
prefix []byte
|
|
}
|
|
|
|
// todo: Not sure if we want to use a writer to do this for the longterm but, if so, we can
|
|
// probably do better than writing byte by byte
|
|
func (pw prefixWriter) Write(bytes []byte) (n int, err error) {
|
|
for i, b := range bytes {
|
|
_, err = pw.w.Write([]byte{b})
|
|
if err != nil {
|
|
return i, err
|
|
}
|
|
if b == '\n' {
|
|
_, err := pw.w.Write(pw.prefix)
|
|
if err != nil {
|
|
return i, err
|
|
}
|
|
}
|
|
}
|
|
return len(bytes), nil
|
|
}
|
|
|
|
func line(w io.Writer, start string, key, v2 types.Value) {
|
|
var err error
|
|
pw := prefixWriter{w: w, prefix: []byte(start)}
|
|
_, err = w.Write([]byte(start))
|
|
d.Exp.NoError(err)
|
|
if key != nil {
|
|
d.Exp.NoError(types.WriteEncodedValueWithTags(pw, key))
|
|
_, err = w.Write([]byte(": "))
|
|
d.Exp.NoError(err)
|
|
}
|
|
d.Exp.NoError(types.WriteEncodedValueWithTags(pw, v2))
|
|
_, err = w.Write([]byte("\n"))
|
|
d.Exp.NoError(err)
|
|
}
|
|
|
|
func writeHeader(w io.Writer, wroteHeader bool, p types.Path) bool {
|
|
var err error
|
|
if !wroteHeader {
|
|
_, err = w.Write([]byte(p.String()))
|
|
d.Exp.NoError(err)
|
|
_, err = w.Write([]byte(" {\n"))
|
|
d.Exp.NoError(err)
|
|
wroteHeader = true
|
|
}
|
|
return wroteHeader
|
|
}
|
|
|
|
func writeFooter(w io.Writer, wroteHeader bool) {
|
|
if wroteHeader {
|
|
_, err := w.Write([]byte(" }\n"))
|
|
d.Exp.NoError(err)
|
|
}
|
|
}
|