From 9a49bf956766084e5648a41017b3ac98b36d23f6 Mon Sep 17 00:00:00 2001 From: Dan Willhite Date: Tue, 7 Jun 2016 17:02:21 -0700 Subject: [PATCH] Print out diffs in noms-log --- cmd/noms-diff/diff/diff.go | 221 ++++++++++++++++++ .../{noms_diff_test.go => diff/diff_test.go} | 47 ++-- cmd/noms-diff/{ => diff}/queue.go | 6 +- cmd/noms-diff/{ => diff}/queue_test.go | 6 +- cmd/noms-diff/noms_diff.go | 206 +--------------- cmd/noms-log/commit_iterator.go | 9 +- cmd/noms-log/max_line_writer.go | 63 +++++ cmd/noms-log/noms_log.go | 115 ++++----- cmd/noms-log/noms_log_test.go | 33 ++- 9 files changed, 401 insertions(+), 305 deletions(-) create mode 100644 cmd/noms-diff/diff/diff.go rename cmd/noms-diff/{noms_diff_test.go => diff/diff_test.go} (77%) rename cmd/noms-diff/{ => diff}/queue.go (72%) rename cmd/noms-diff/{ => diff}/queue_test.go (77%) create mode 100644 cmd/noms-log/max_line_writer.go diff --git a/cmd/noms-diff/diff/diff.go b/cmd/noms-diff/diff/diff.go new file mode 100644 index 0000000000..e10e115cd2 --- /dev/null +++ b/cmd/noms-diff/diff/diff.go @@ -0,0 +1,221 @@ +// 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) + } +} diff --git a/cmd/noms-diff/noms_diff_test.go b/cmd/noms-diff/diff/diff_test.go similarity index 77% rename from cmd/noms-diff/noms_diff_test.go rename to cmd/noms-diff/diff/diff_test.go index 1d8cf7dd95..e2c1aa05db 100644 --- a/cmd/noms-diff/noms_diff_test.go +++ b/cmd/noms-diff/diff/diff_test.go @@ -1,7 +1,10 @@ -package main +// 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 ( - "os" "testing" "github.com/attic-labs/noms/go/types" @@ -18,8 +21,6 @@ var ( mm3 = createMap("m1", "m-one", "v2", "m-two", "m3", "m-three", "m4", aa1) mm3x = createMap("m1", "m-one", "v2", "m-two", "m3", "m-three-diff", "m4", aa1x) mm4 = createMap("n1", "n-one", "n2", "n-two", "n3", "n-three", "n4", aa1) - - startPath = types.NewPath().AddField("/") ) func valToTypesValue(v interface{}) types.Value { @@ -73,26 +74,26 @@ func TestNomsMapdiff(t *testing.T) { m1 := createMap("map-1", mm1, "map-2", mm2, "map-3", mm3, "map-4", mm4) m2 := createMap("map-1", mm1, "map-2", mm2, "map-3", mm3x, "map-4", mm4) - diffQ.PushBack(diffInfo{path: startPath, key: nil, v1: m1, v2: m2}) buf := util.NewBuffer(nil) - diff(buf) + Diff(buf, m1, m2) assert.Equal(expected, buf.String()) } func TestNomsSetDiff(t *testing.T) { assert := assert.New(t) - expected := "./.sha1-c26be7ea6e815f747c1552fe402a773ad466be88 {\n- \"m3\": \"m-three\"\n+ \"m3\": \"m-three-diff\"\n }\n./.sha1-c26be7ea6e815f747c1552fe402a773ad466be88.\"m4\" {\n- \"a1\": \"a-one\"\n+ \"a1\": \"a-one-diff\"\n }\n" + expected := "./ {\n- \"five\"\n+ \"five-diff\"\n }\n" s1 := createSet("one", "three", "five", "seven", "nine") s2 := createSet("one", "three", "five-diff", "seven", "nine") - diffQ.PushBack(diffInfo{path: startPath, key: nil, v1: s1, v2: s2}) - diff(os.Stdout) + buf := util.NewBuffer(nil) + Diff(buf, s1, s2) + assert.Equal(expected, buf.String()) + expected = "./.sha1-c26be7ea6e815f747c1552fe402a773ad466be88 {\n- \"m3\": \"m-three\"\n+ \"m3\": \"m-three-diff\"\n }\n./.sha1-c26be7ea6e815f747c1552fe402a773ad466be88.\"m4\" {\n- \"a1\": \"a-one\"\n+ \"a1\": \"a-one-diff\"\n }\n" s1 = createSet(mm1, mm2, mm3, mm4) s2 = createSet(mm1, mm2, mm3x, mm4) - diffQ.PushBack(diffInfo{path: startPath, key: nil, v1: s1, v2: s2}) - buf := util.NewBuffer(nil) - diff(buf) + buf = util.NewBuffer(nil) + Diff(buf, s1, s2) assert.Equal(expected, buf.String()) } @@ -112,30 +113,32 @@ func TestNomsStructDiff(t *testing.T) { m1 := createMap("one", 1, "two", 2, "three", s1, "four", "four") m2 := createMap("one", 1, "two", 2, "three", s2, "four", "four-diff") - diffQ.PushBack(diffInfo{path: startPath, key: nil, v1: m1, v2: m2}) buf := util.NewBuffer(nil) - diff(buf) + Diff(buf, m1, m2) assert.Equal(expected, buf.String()) } func TestNomsListDiff(t *testing.T) { assert := assert.New(t) - expected := "./[2] {\n- \"m3\": \"m-three\"\n+ \"m3\": \"m-three-diff\"\n }\n./[2].\"m4\" {\n- \"a1\": \"a-one\"\n+ \"a1\": \"a-one-diff\"\n }\n" + expected := "./ {\n- 2\n+ 22\n- 44\n }\n" l1 := createList(1, 2, 3, 4, 44, 5, 6) l2 := createList(1, 22, 3, 4, 5, 6) - diffQ.PushBack(diffInfo{path: startPath, key: nil, v1: l1, v2: l2}) - diff(os.Stdout) + buf := util.NewBuffer(nil) + Diff(buf, l1, l2) + assert.Equal(expected, buf.String()) + expected = "./ {\n+ \"seven\"\n }\n" l1 = createList("one", "two", "three", "four", "five", "six") l2 = createList("one", "two", "three", "four", "five", "six", "seven") - diffQ.PushBack(diffInfo{path: startPath, key: nil, v1: l1, v2: l2}) - diff(os.Stdout) + buf = util.NewBuffer(nil) + Diff(buf, l1, l2) + assert.Equal(expected, buf.String()) + expected = "./[2] {\n- \"m3\": \"m-three\"\n+ \"m3\": \"m-three-diff\"\n }\n./[2].\"m4\" {\n- \"a1\": \"a-one\"\n+ \"a1\": \"a-one-diff\"\n }\n" l1 = createList(mm1, mm2, mm3, mm4) l2 = createList(mm1, mm2, mm3x, mm4) - diffQ.PushBack(diffInfo{path: startPath, key: nil, v1: l1, v2: l2}) - buf := util.NewBuffer(nil) - diff(buf) + buf = util.NewBuffer(nil) + Diff(buf, l1, l2) assert.Equal(expected, buf.String()) } diff --git a/cmd/noms-diff/queue.go b/cmd/noms-diff/diff/queue.go similarity index 72% rename from cmd/noms-diff/queue.go rename to cmd/noms-diff/diff/queue.go index 30bc611f35..2d637170b8 100644 --- a/cmd/noms-diff/queue.go +++ b/cmd/noms-diff/diff/queue.go @@ -1,4 +1,8 @@ -package main +// 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 ( "container/list" diff --git a/cmd/noms-diff/queue_test.go b/cmd/noms-diff/diff/queue_test.go similarity index 77% rename from cmd/noms-diff/queue_test.go rename to cmd/noms-diff/diff/queue_test.go index df85f3d35a..eb7c6fd81c 100644 --- a/cmd/noms-diff/queue_test.go +++ b/cmd/noms-diff/diff/queue_test.go @@ -1,4 +1,8 @@ -package main +// 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 ( "testing" diff --git a/cmd/noms-diff/noms_diff.go b/cmd/noms-diff/noms_diff.go index beec0a3adf..4b338f119f 100644 --- a/cmd/noms-diff/noms_diff.go +++ b/cmd/noms-diff/noms_diff.go @@ -1,14 +1,16 @@ +// 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 main import ( - "bytes" "errors" "flag" "fmt" - "io" "os" - "github.com/attic-labs/noms/go/types" + "github.com/attic-labs/noms/cmd/noms-diff/diff" "github.com/attic-labs/noms/go/util/outputpager" "github.com/attic-labs/noms/samples/go/flags" "github.com/attic-labs/noms/samples/go/util" @@ -21,7 +23,6 @@ const ( var ( showHelp = flag.Bool("help", false, "show help text") - diffQ = NewDiffQueue() ) func main() { @@ -55,17 +56,9 @@ func main() { util.CheckError(err) defer db2.Close() - di := diffInfo{ - path: types.NewPath().AddField("/"), - key: nil, - v1: value1, - v2: value2, - } - diffQ.PushBack(di) - waitChan := outputpager.PageOutput(!*outputpager.NoPager) - diff(os.Stdout) + diff.Diff(os.Stdout, value1, value2) fmt.Fprintf(os.Stdout, "\n") if waitChan != nil { @@ -73,190 +66,3 @@ func main() { <-waitChan } } - -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) { - for di, ok := diffQ.PopFront(); ok; di, ok = diffQ.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(w, p, v1.(types.List), v2.(types.List)) - case types.MapKind: - diffMaps(w, p, v1.(types.Map), v2.(types.Map)) - case types.SetKind: - diffSets(w, p, v1.(types.Set), v2.(types.Set)) - case types.StructKind: - diffStructs(w, p, v1.(types.Struct), v2.(types.Struct)) - default: - panic("Unrecognized type in diff function") - } - } - } - } -} - -func diffLists(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) - diffQ.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(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) - types.WriteEncodedValueWithTags(buf, k) - p1 := p.AddField(buf.String()) - diffQ.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(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) - diffQ.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(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()) - diffQ.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) -} - -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) { - pw := prefixWriter{w: w, prefix: []byte(start)} - w.Write([]byte(start)) - if key != nil { - types.WriteEncodedValueWithTags(pw, key) - w.Write([]byte(": ")) - } - types.WriteEncodedValueWithTags(pw, v2) - w.Write([]byte("\n")) -} - -func writeHeader(w io.Writer, wroteHeader bool, p types.Path) bool { - if !wroteHeader { - w.Write([]byte(p.String())) - w.Write([]byte(" {\n")) - wroteHeader = true - } - return wroteHeader -} - -func writeFooter(w io.Writer, wroteHeader bool) { - if wroteHeader { - w.Write([]byte(" }\n")) - } -} diff --git a/cmd/noms-log/commit_iterator.go b/cmd/noms-log/commit_iterator.go index 58a9b90ca2..b24e6ca359 100644 --- a/cmd/noms-log/commit_iterator.go +++ b/cmd/noms-log/commit_iterator.go @@ -64,7 +64,7 @@ func (iter *CommitIterator) Next() (LogNode, bool) { // Now that the branchlist has been adusted, check to see if there are branches with common // ancestors that will be folded together on this commit's graph. foldedCols := iter.branches.HighestBranchIndexes() - return LogNode{ + node := LogNode{ cr: br.cr, commit: br.commit, startingColCount: startingColCount, @@ -73,7 +73,8 @@ func (iter *CommitIterator) Next() (LogNode, bool) { newCols: newCols, foldedCols: foldedCols, lastCommit: iter.branches.IsEmpty(), - }, true + } + return node, true } type LogNode struct { @@ -88,7 +89,7 @@ type LogNode struct { } func (n LogNode) String() string { - return fmt.Sprintf("cr: %s, startingColCount: %d, endingColCount: %d, col: %d, newCols: %v, foldedCols: %v, expanding: %t, shrunk: %t, shrinking: %t", n.cr.TargetHash(), n.startingColCount, n.endingColCount, n.col, n.newCols, n.foldedCols, n.Expanding(), n.Shrunk(), n.Shrinking()) + return fmt.Sprintf("cr: %s(%d), startingColCount: %d, endingColCount: %d, col: %d, newCols: %v, foldedCols: %v, expanding: %t, shrunk: %t, shrinking: %t", n.cr.TargetHash().String()[0:9], n.cr.Height(), n.startingColCount, n.endingColCount, n.col, n.newCols, n.foldedCols, n.Expanding(), n.Shrunk(), n.Shrinking()) } // True if this commit's graph will expand to show an additional branch @@ -112,7 +113,7 @@ type branch struct { } func (b branch) String() string { - return b.cr.TargetHash().String() + return fmt.Sprintf("%s(%d)", b.cr.TargetHash().String()[0:9], b.cr.Height()) } type branchList []branch diff --git a/cmd/noms-log/max_line_writer.go b/cmd/noms-log/max_line_writer.go new file mode 100644 index 0000000000..f4cbebb492 --- /dev/null +++ b/cmd/noms-log/max_line_writer.go @@ -0,0 +1,63 @@ +// 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 main + +import ( + "io" +) + +var ( + maxLinesError = MaxLinesError{"Maximum number of lines written"} +) + +type MaxLinesError struct { + msg string +} + +func (e MaxLinesError) Error() string { return e.msg } + +type maxLineWriter struct { + numLines int + maxLines int + node LogNode + dest io.Writer + needsPrefix bool +} + +func (w *maxLineWriter) writeMax(data []byte, enforceMax bool) (byteCnt int, err error) { + for _, b := range data { + if w.needsPrefix { + w.needsPrefix = false + w.numLines++ + if *showGraph { + s := genGraph(w.node, w.numLines) + _, err = w.dest.Write([]byte(s)) + } + if err != nil { + return + } + } + byteCnt++ + if enforceMax && w.maxLines >= 0 && w.numLines >= w.maxLines { + err = maxLinesError + return + } + // TODO: This is not technically correct due to utf-8, but ... meh. + w.needsPrefix = b == byte('\n') + _, err = w.dest.Write(data[byteCnt-1 : byteCnt]) + if err != nil { + return + } + } + return +} + +func (w *maxLineWriter) Write(data []byte) (byteCnt int, err error) { + return w.writeMax(data, true) +} + +func (w *maxLineWriter) forceWrite(data []byte) (byteCnt int, err error) { + return w.writeMax(data, false) +} diff --git a/cmd/noms-log/noms_log.go b/cmd/noms-log/noms_log.go index f2eee30c37..6b25e2566a 100644 --- a/cmd/noms-log/noms_log.go +++ b/cmd/noms-log/noms_log.go @@ -13,6 +13,7 @@ import ( "os" "strings" + "github.com/attic-labs/noms/cmd/noms-diff/diff" "github.com/attic-labs/noms/go/datas" "github.com/attic-labs/noms/go/types" "github.com/attic-labs/noms/go/util/outputpager" @@ -22,10 +23,9 @@ import ( ) var ( - color, maxLines, maxCommits *int - showHelp, showGraph *bool - useColor = false - maxLinesError = errors.New("Maximum number of lines written") + color, maxLines, maxCommits *int + showHelp, showGraph, showValue *bool + useColor = false ) func main() { @@ -34,6 +34,7 @@ func main() { maxCommits = flag.Int("n", 0, "max number of commits to display (0 for all commits)") showHelp = flag.Bool("help", false, "show help text") showGraph = flag.Bool("graph", false, "show ascii-based commit hierarcy on left side of output") + showValue = flag.Bool("show-value", false, "show commit value rather than diff information -- this is temporary") flag.Usage = func() { fmt.Fprintln(os.Stderr, "Displays the history of a Noms dataset\n") @@ -76,7 +77,7 @@ func main() { *maxCommits = math.MaxInt32 } for ln, ok := iter.Next(); ok && displayed < *maxCommits; ln, ok = iter.Next() { - if printCommit(ln) != nil { + if printCommit(ln, database) != nil { break } displayed++ @@ -90,7 +91,7 @@ func main() { // Prints the information for one commit in the log, including ascii graph on left side of commits if // -graph arg is true. -func printCommit(node LogNode) (err error) { +func printCommit(node LogNode, db datas.Database) (err error) { lineno := 0 doColor := func(s string) string { return s } if useColor { @@ -113,12 +114,13 @@ func printCommit(node LogNode) (err error) { } if *maxLines != 0 { var n int - n, err = writeCommitLines(node, *maxLines, lineno, os.Stdout) + if *showValue { + n, err = writeCommitLines(node, *maxLines, lineno, os.Stdout) + } else { + n, err = writeDiffLines(node, db, *maxLines, lineno, os.Stdout) + } lineno += n } - if !node.lastCommit { - fmt.Printf("%s\n", genGraph(node, lineno)) - } return } @@ -175,65 +177,44 @@ func genGraph(node LogNode, lineno int) string { return string(buf) } -type maxLineWriter struct { - numLines int - maxLines int - node LogNode - dest io.Writer - first bool -} - -func (w *maxLineWriter) Write(data []byte) (n int, err error) { - doGraph := func() error { - var err error - w.numLines++ - if *showGraph { - s := genGraph(w.node, w.numLines) - _, err = w.dest.Write([]byte(s)) - } - return err - } - - if w.first && len(data) > 0 { - w.first = false - err = doGraph() - if err != nil { - return - } - } - - for _, b := range data { - n++ - if w.numLines == w.maxLines { - err = maxLinesError - return - } - // TODO: This is not technically correct due to utf-8, but ... meh. - newLine := b == byte('\n') - _, err = w.dest.Write(data[n-1 : n]) - if err != nil { - return - } - if newLine { - err = doGraph() - } - if err != nil { - return - } - } - return -} - -func writeCommitLines(node LogNode, maxLines, lineno int, w io.Writer) (int, error) { - out := &maxLineWriter{numLines: lineno, maxLines: maxLines, node: node, dest: w, first: true} - err := types.WriteEncodedValueWithTags(out, node.commit.Get(datas.ValueField)) - if err == maxLinesError { - fmt.Fprint(w, "...") - out.numLines++ +func writeCommitLines(node LogNode, maxLines, lineno int, w io.Writer) (lineCnt int, err error) { + mlw := &maxLineWriter{numLines: lineno, maxLines: maxLines, node: node, dest: w, needsPrefix: true} + err = types.WriteEncodedValueWithTags(mlw, node.commit.Get(datas.ValueField)) + if err != nil { + mlw.forceWrite([]byte("...")) + mlw.numLines++ err = nil } - fmt.Fprintln(w) - return out.numLines, err + mlw.forceWrite([]byte("\n")) + if !node.lastCommit { + mlw.forceWrite([]byte("\n")) + } + return mlw.numLines, err +} + +func writeDiffLines(node LogNode, db datas.Database, maxLines, lineno int, w io.Writer) (lineCnt int, err error) { + mlw := &maxLineWriter{numLines: lineno, maxLines: maxLines, node: node, dest: w, needsPrefix: true} + parents := node.commit.Get(datas.ParentsField).(types.Set) + var parent types.Value = nil + if parents.Len() > 0 { + parent = parents.First() + } + if parent == nil { + _, err = fmt.Fprint(mlw, "\n") + return 1, err + } + + parentCommit := parent.(types.Ref).TargetValue(db).(types.Struct) + err = diff.Diff(mlw, parentCommit.Get(datas.ValueField), node.commit.Get(datas.ValueField)) + if err != nil { + mlw.forceWrite([]byte("...\n")) + mlw.numLines++ + err = nil + } + if !node.lastCommit { + mlw.forceWrite([]byte("\n")) + } + return mlw.numLines, err } func shouldUseColor() bool { diff --git a/cmd/noms-log/noms_log_test.go b/cmd/noms-log/noms_log_test.go index 30074e234c..66c8acb491 100644 --- a/cmd/noms-log/noms_log_test.go +++ b/cmd/noms-log/noms_log_test.go @@ -47,7 +47,6 @@ func testCommitInResults(s *nomsShowTestSuite, spec string, i int) { ds, err = ds.Commit(types.Number(i)) s.NoError(err) commit := ds.Head() - fmt.Printf("commit hash: %s, type: %s\n", commit.Hash(), commit.Type().Name()) ds.Database().Close() s.Contains(s.Run(main, []string{spec}), commit.Hash().String()) } @@ -168,7 +167,8 @@ func (s *nomsShowTestSuite) TestNomsGraph1() { s.NoError(err) b1.Database().Close() - s.Equal(graphRes1, s.Run(main, []string{"-graph", test_util.CreateValueSpecString("ldb", s.LdbDir, "b1")})) + s.Equal(graphRes1, s.Run(main, []string{"-graph", "-show-value=true", test_util.CreateValueSpecString("ldb", s.LdbDir, "b1")})) + s.Equal(diffRes1, s.Run(main, []string{"-graph", "-show-value=false", test_util.CreateValueSpecString("ldb", s.LdbDir, "b1")})) } func (s *nomsShowTestSuite) TestNomsGraph2() { @@ -198,7 +198,8 @@ func (s *nomsShowTestSuite) TestNomsGraph2() { s.NoError(err) db.Close() - s.Equal(graphRes2, s.Run(main, []string{"-graph", test_util.CreateValueSpecString("ldb", s.LdbDir, "ba")})) + s.Equal(graphRes2, s.Run(main, []string{"-graph", "-show-value=true", test_util.CreateValueSpecString("ldb", s.LdbDir, "ba")})) + s.Equal(diffRes2, s.Run(main, []string{"-graph", "-show-value=false", test_util.CreateValueSpecString("ldb", s.LdbDir, "ba")})) } func (s *nomsShowTestSuite) TestNomsGraph3() { @@ -238,7 +239,8 @@ func (s *nomsShowTestSuite) TestNomsGraph3() { s.NoError(err) db.Close() - s.Equal(graphRes3, s.Run(main, []string{"-graph", test_util.CreateValueSpecString("ldb", s.LdbDir, "w")})) + s.Equal(graphRes3, s.Run(main, []string{"-graph", "-show-value=true", test_util.CreateValueSpecString("ldb", s.LdbDir, "w")})) + s.Equal(diffRes3, s.Run(main, []string{"-graph", "-show-value=false", test_util.CreateValueSpecString("ldb", s.LdbDir, "w")})) } func (s *nomsShowTestSuite) TestTruncation() { @@ -267,9 +269,14 @@ func (s *nomsShowTestSuite) TestTruncation() { db.Close() dsSpec := test_util.CreateValueSpecString("ldb", s.LdbDir, "truncate") - s.Equal(truncRes1, s.Run(main, []string{"-graph", dsSpec})) - s.Equal(truncRes2, s.Run(main, []string{"-graph", "-max-lines=-1", dsSpec})) - s.Equal(truncRes3, s.Run(main, []string{"-graph", "-max-lines=0", dsSpec})) + s.Equal(truncRes1, s.Run(main, []string{"-graph", "-show-value=true", dsSpec})) + s.Equal(diffTrunc1, s.Run(main, []string{"-graph", "-show-value=false", dsSpec})) + + s.Equal(truncRes2, s.Run(main, []string{"-graph", "-show-value=true", "-max-lines=-1", dsSpec})) + s.Equal(diffTrunc2, s.Run(main, []string{"-graph", "-show-value=false", "-max-lines=-1", dsSpec})) + + s.Equal(truncRes3, s.Run(main, []string{"-graph", "-show-value=true", "-max-lines=0", dsSpec})) + s.Equal(diffTrunc3, s.Run(main, []string{"-graph", "-show-value=false", "-max-lines=0", dsSpec})) } func TestBranchlistSplice(t *testing.T) { @@ -297,14 +304,20 @@ func TestBranchlistSplice(t *testing.T) { const ( graphRes1 = "* sha1-0b0a92f5515b5778194d07d8011b7c18bf9be178\n| Parent: sha1-14491727789ebd03ca0ddd5e3a1307ca3e2651dc\n| \"7\"\n| \n* sha1-14491727789ebd03ca0ddd5e3a1307ca3e2651dc\n| Parent: sha1-b5a8493d7be003673216bdc0ed275fbbacbb9b08\n| \"6\"\n| \n* sha1-b5a8493d7be003673216bdc0ed275fbbacbb9b08\n| Parent: sha1-2b837c2e2dd0e6ef015e4742bd969f9fa1641f38\n| \"5\"\n| \n* sha1-2b837c2e2dd0e6ef015e4742bd969f9fa1641f38\n|\\ Merge: sha1-1f622d4006278a63cc77e95c6efcaf83f46606c4 sha1-eca65988a4ade585b8502d8f9fc37a5a5da94ab2\n| | \"4\"\n| | \n| * sha1-eca65988a4ade585b8502d8f9fc37a5a5da94ab2\n| | Parent: sha1-890cf3b1d49228ace9d691a627131a2872e42eb4\n| | \"3.7\"\n| | \n| * sha1-890cf3b1d49228ace9d691a627131a2872e42eb4\n| |\\ Merge: sha1-4f72aeacef670bc6884a97de5a363b0674ccd008 sha1-fa78bedf601fc6d49ebf70865944dfccf5b5a133\n| | | \"3.5\"\n| | | \n| | * sha1-fa78bedf601fc6d49ebf70865944dfccf5b5a133\n| | | Parent: sha1-72f530e2ba8ba3168de8da8d15271cd856ac5c2b\n| | | \"3.1.7\"\n| | | \n| | * sha1-72f530e2ba8ba3168de8da8d15271cd856ac5c2b\n| | | Parent: sha1-dda04a898b2048558b4238a4f111364ff5c92ab8\n| | | \"3.1.5\"\n| | | \n* | | sha1-1f622d4006278a63cc77e95c6efcaf83f46606c4\n| | | Parent: sha1-e1583426c4201c7602b4aa1ae835915666d933f0\n| | | \"3.6\"\n| | | \n| | * sha1-dda04a898b2048558b4238a4f111364ff5c92ab8\n| | | Parent: sha1-4f72aeacef670bc6884a97de5a363b0674ccd008\n| | | \"3.1.3\"\n| | | \n* | | sha1-e1583426c4201c7602b4aa1ae835915666d933f0\n| |/ Parent: sha1-27290eda714ce5d47df05e8f77a0986647887e32\n| | \"3.2\"\n| | \n| * sha1-4f72aeacef670bc6884a97de5a363b0674ccd008\n|/ Parent: sha1-27290eda714ce5d47df05e8f77a0986647887e32\n| \"3.1\"\n| \n* sha1-27290eda714ce5d47df05e8f77a0986647887e32\n| Parent: sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| \"3\"\n| \n* sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| Parent: sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| \"2\"\n| \n* sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| Parent: None\n| \"1\"\n" + diffRes1 = "* sha1-0b0a92f5515b5778194d07d8011b7c18bf9be178\n| Parent: sha1-14491727789ebd03ca0ddd5e3a1307ca3e2651dc\n| - \"6\"\n| + \"7\"\n| \n* sha1-14491727789ebd03ca0ddd5e3a1307ca3e2651dc\n| Parent: sha1-b5a8493d7be003673216bdc0ed275fbbacbb9b08\n| - \"5\"\n| + \"6\"\n| \n* sha1-b5a8493d7be003673216bdc0ed275fbbacbb9b08\n| Parent: sha1-2b837c2e2dd0e6ef015e4742bd969f9fa1641f38\n| - \"4\"\n| + \"5\"\n| \n* sha1-2b837c2e2dd0e6ef015e4742bd969f9fa1641f38\n|\\ Merge: sha1-1f622d4006278a63cc77e95c6efcaf83f46606c4 sha1-eca65988a4ade585b8502d8f9fc37a5a5da94ab2\n| | - \"3.6\"\n| | + \"4\"\n| | \n| * sha1-eca65988a4ade585b8502d8f9fc37a5a5da94ab2\n| | Parent: sha1-890cf3b1d49228ace9d691a627131a2872e42eb4\n| | - \"3.5\"\n| | + \"3.7\"\n| | \n| * sha1-890cf3b1d49228ace9d691a627131a2872e42eb4\n| |\\ Merge: sha1-4f72aeacef670bc6884a97de5a363b0674ccd008 sha1-fa78bedf601fc6d49ebf70865944dfccf5b5a133\n| | | - \"3.1\"\n| | | + \"3.5\"\n| | | \n| | * sha1-fa78bedf601fc6d49ebf70865944dfccf5b5a133\n| | | Parent: sha1-72f530e2ba8ba3168de8da8d15271cd856ac5c2b\n| | | - \"3.1.5\"\n| | | + \"3.1.7\"\n| | | \n| | * sha1-72f530e2ba8ba3168de8da8d15271cd856ac5c2b\n| | | Parent: sha1-dda04a898b2048558b4238a4f111364ff5c92ab8\n| | | - \"3.1.3\"\n| | | + \"3.1.5\"\n| | | \n* | | sha1-1f622d4006278a63cc77e95c6efcaf83f46606c4\n| | | Parent: sha1-e1583426c4201c7602b4aa1ae835915666d933f0\n| | | - \"3.2\"\n| | | + \"3.6\"\n| | | \n| | * sha1-dda04a898b2048558b4238a4f111364ff5c92ab8\n| | | Parent: sha1-4f72aeacef670bc6884a97de5a363b0674ccd008\n| | | - \"3.1\"\n| | | + \"3.1.3\"\n| | | \n* | | sha1-e1583426c4201c7602b4aa1ae835915666d933f0\n| |/ Parent: sha1-27290eda714ce5d47df05e8f77a0986647887e32\n| | - \"3\"\n| | + \"3.2\"\n| | \n| * sha1-4f72aeacef670bc6884a97de5a363b0674ccd008\n|/ Parent: sha1-27290eda714ce5d47df05e8f77a0986647887e32\n| - \"3\"\n| + \"3.1\"\n| \n* sha1-27290eda714ce5d47df05e8f77a0986647887e32\n| Parent: sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| - \"2\"\n| + \"3\"\n| \n* sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| Parent: sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| - \"1\"\n| + \"2\"\n| \n* sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| Parent: None\n| \n" graphRes2 = "* sha1-65c7e849861df97129cbd49352e52eef6dad3b11\n|\\ Merge: sha1-4578a4c35f09b6fe85608a95145a36f4c701d030 sha1-aecf8991bf5af5bb0b725ff2bdeb260426508ac5\n| | \"101\"\n| | \n* | sha1-4578a4c35f09b6fe85608a95145a36f4c701d030\n|\\ \\ Merge: sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5 sha1-d9cf6dcbf03a014c28c80a9baa34525b4f9095c8\n| | | \"11\"\n| | | \n* | sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| | Parent: None\n| | \"1\"\n| | \n* sha1-d9cf6dcbf03a014c28c80a9baa34525b4f9095c8\n| Parent: None\n| \"10\"\n| \n* sha1-aecf8991bf5af5bb0b725ff2bdeb260426508ac5\n| Parent: None\n| \"100\"\n" + diffRes2 = "* sha1-65c7e849861df97129cbd49352e52eef6dad3b11\n|\\ Merge: sha1-4578a4c35f09b6fe85608a95145a36f4c701d030 sha1-aecf8991bf5af5bb0b725ff2bdeb260426508ac5\n| | - \"11\"\n| | + \"101\"\n| | \n* | sha1-4578a4c35f09b6fe85608a95145a36f4c701d030\n|\\ \\ Merge: sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5 sha1-d9cf6dcbf03a014c28c80a9baa34525b4f9095c8\n| | | - \"1\"\n| | | + \"11\"\n| | | \n* | sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| | Parent: None\n| | \n* sha1-d9cf6dcbf03a014c28c80a9baa34525b4f9095c8\n| Parent: None\n| \n* sha1-aecf8991bf5af5bb0b725ff2bdeb260426508ac5\n| Parent: None\n| \n" graphRes3 = "* sha1-a859b0936cc42f073c03a27eb820f29a57f025f3\n|\\ Merge: sha1-d996bef5350a0316da794ae1f262dfcdcd4b2e8c sha1-65c99ad8e4db74b2cb3bf00bff806a4d46a220eb\n| | \"2222-wz\"\n| | \n* | sha1-d996bef5350a0316da794ae1f262dfcdcd4b2e8c\n|\\ \\ Merge: sha1-da122844f28f7a814fb99a4950ff673ea7e3611c sha1-ba3e907b42eaba15bf01752b012c92f8e5821536\n| | | \"222-wy\"\n| | | \n| * | sha1-ba3e907b42eaba15bf01752b012c92f8e5821536\n| |\\ \\ Merge: sha1-8af6c17c296c36f2b5adf19b02a83918a61088d1 sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| | | | \"22-wx\"\n| | | | \n* | | | sha1-da122844f28f7a814fb99a4950ff673ea7e3611c\n| | | | Parent: sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| | | | \"200-y\"\n| | | | \n| * | | sha1-8af6c17c296c36f2b5adf19b02a83918a61088d1\n| | | | Parent: sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| | | | \"20-x\"\n| | | | \n| | | * sha1-65c99ad8e4db74b2cb3bf00bff806a4d46a220eb\n|/ / / Parent: sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| \"2000-z\"\n| \n* sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| Parent: sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| \"2\"\n| \n* sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| Parent: None\n| \"1\"\n" + diffRes3 = "* sha1-a859b0936cc42f073c03a27eb820f29a57f025f3\n|\\ Merge: sha1-d996bef5350a0316da794ae1f262dfcdcd4b2e8c sha1-65c99ad8e4db74b2cb3bf00bff806a4d46a220eb\n| | - \"222-wy\"\n| | + \"2222-wz\"\n| | \n* | sha1-d996bef5350a0316da794ae1f262dfcdcd4b2e8c\n|\\ \\ Merge: sha1-da122844f28f7a814fb99a4950ff673ea7e3611c sha1-ba3e907b42eaba15bf01752b012c92f8e5821536\n| | | - \"200-y\"\n| | | + \"222-wy\"\n| | | \n| * | sha1-ba3e907b42eaba15bf01752b012c92f8e5821536\n| |\\ \\ Merge: sha1-8af6c17c296c36f2b5adf19b02a83918a61088d1 sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| | | | - \"20-x\"\n| | | | + \"22-wx\"\n| | | | \n* | | | sha1-da122844f28f7a814fb99a4950ff673ea7e3611c\n| | | | Parent: sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| | | | - \"2\"\n| | | | + \"200-y\"\n| | | | \n| * | | sha1-8af6c17c296c36f2b5adf19b02a83918a61088d1\n| | | | Parent: sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| | | | - \"2\"\n| | | | + \"20-x\"\n| | | | \n| | | * sha1-65c99ad8e4db74b2cb3bf00bff806a4d46a220eb\n|/ / / Parent: sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| - \"2\"\n| + \"2000-z\"\n| \n* sha1-624532f5d8a5839344ccfc465957a975e1962b6d\n| Parent: sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| - \"1\"\n| + \"2\"\n| \n* sha1-611d5d868352d4d6ae9b778d6627b81f769cdef5\n| Parent: None\n| \n" - truncRes1 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| List([\n| \"one\",\n| \"two\",\n| \"three\",\n| \"four\",\n| \"five\",\n| \"six\",\n| \"seven\",\n| ...\n| \n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n| \"the first line\"\n" + truncRes1 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| List([\n| \"one\",\n| \"two\",\n| \"three\",\n| \"four\",\n| \"five\",\n| \"six\",\n| \"seven\",\n| ...\n| \n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n| \"the first line\"\n" + diffTrunc1 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| - \"the first line\"\n| + List([\n| + \"one\",\n| + \"two\",\n| + \"three\",\n| + \"four\",\n| + \"five\",\n| + \"six\",\n| ...\n| \n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n| \n" - truncRes2 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| List([\n| \"one\",\n| \"two\",\n| \"three\",\n| \"four\",\n| \"five\",\n| \"six\",\n| \"seven\",\n| \"eight\",\n| \"nine\",\n| \"ten\",\n| \"eleven\",\n| ])\n| \n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n| \"the first line\"\n" + truncRes2 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| List([\n| \"one\",\n| \"two\",\n| \"three\",\n| \"four\",\n| \"five\",\n| \"six\",\n| \"seven\",\n| \"eight\",\n| \"nine\",\n| \"ten\",\n| \"eleven\",\n| ])\n| \n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n| \"the first line\"\n" + diffTrunc2 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| - \"the first line\"\n| + List([\n| + \"one\",\n| + \"two\",\n| + \"three\",\n| + \"four\",\n| + \"five\",\n| + \"six\",\n| + \"seven\",\n| + \"eight\",\n| + \"nine\",\n| + \"ten\",\n| + \"eleven\",\n| + ])\n| \n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n| \n" - truncRes3 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| \n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n" + truncRes3 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n" + diffTrunc3 = "* sha1-39d1f600887364b2e4832fe80f6853b0966a9e6c\n| Parent: sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n* sha1-185ad8e966cba1a70b7f6cf19cd7bc5a7983c3d2\n| Parent: None\n" )