// wrapper for diff.Granular() -- should only concatanate changes for similar text nodes func granular(gran int, dd diffData, changes []diff.Change) []diff.Change { ret := make([]diff.Change, 0, len(changes)) startSame := 0 changeCount := 0 lastAleaf, lastBleaf := (*dd.a)[0].leaf, (*dd.b)[0].leaf for c, cc := range changes { if cc.A < len(*dd.a) && cc.B < len(*dd.b) && lastAleaf.Type == html.TextNode && lastBleaf.Type == html.TextNode && (*dd.a)[cc.A].leaf == lastAleaf && (*dd.b)[cc.B].leaf == lastBleaf && nodeEqualExText(lastAleaf, lastBleaf) { // TODO is this last constraint required? // do nothing yet, queue it up until there is a difference changeCount++ } else { // no match if changeCount > 0 { // flush ret = append(ret, diff.Granular(gran, changes[startSame:startSame+changeCount])...) } ret = append(ret, cc) startSame = c + 1 // the one after this changeCount = 0 if cc.A < len(*dd.a) && cc.B < len(*dd.b) { lastAleaf, lastBleaf = (*dd.a)[cc.A].leaf, (*dd.b)[cc.B].leaf } } } if changeCount > 0 { // flush ret = append(ret, diff.Granular(gran, changes[startSame:])...) } return ret }
// Diff computes the difference between old and new. A granularity of 1 or more // combines changes with no greater than that many bytes between them. func Diff(old, new []byte, granularity int) (patch []byte) { changes := diff.Bytes(old, new) if granularity > 0 { changes = diff.Granular(granularity, changes) } for i, c := range changes { a, b := c.A, c.B for _, prev := range changes[:i] { if prev.A < c.A { a -= prev.Del a += prev.Ins } if prev.B < c.B { b -= prev.Ins b += prev.Del } } patch = writeUvarint(patch, a) patch = writeUvarint(patch, b) patch = writeUvarint(patch, c.Del) patch = append(patch, old[c.A:c.A+c.Del]...) patch = writeUvarint(patch, c.Ins) patch = append(patch, new[c.B:c.B+c.Ins]...) } return }
func ExampleGranular() { a := "hElLo!" b := "hello!" changes := diff.Granular(5, diff.ByteStrings(a, b)) // ignore small gaps in differences for l := len(changes) - 1; l >= 0; l-- { change := changes[l] b = b[:change.B] + "|" + b[change.B:change.B+change.Ins] + "|" + b[change.B+change.Ins:] } fmt.Println(b) // Output: // h|ell|o! }
func byteDiff(a, b []byte) []byte { var buf bytes.Buffer n := 0 for _, c := range diff.Granular(1, diff.Bytes(a, b)) { buf.Write(b[n:c.B]) if c.Ins > 0 { buf.Write(colorize(b[c.B : c.B+c.Ins])) } n = c.B + c.Ins } buf.Write(b[n:]) return buf.Bytes() }
func TestGranularStrings(t *testing.T) { a := "abcdefghijklmnopqrstuvwxyza" b := "AbCdeFghiJklmnOpqrstUvwxyzab" // each iteration of i increases granularity and will absorb one more lower-letter-followed-by-upper-letters sequence changesI := [][]diff.Change{ {{0, 0, 1, 1}, {2, 2, 1, 1}, {5, 5, 1, 1}, {9, 9, 1, 1}, {14, 14, 1, 1}, {20, 20, 1, 1}, {27, 27, 0, 1}}, {{0, 0, 3, 3}, {5, 5, 1, 1}, {9, 9, 1, 1}, {14, 14, 1, 1}, {20, 20, 1, 1}, {27, 27, 0, 1}}, {{0, 0, 6, 6}, {9, 9, 1, 1}, {14, 14, 1, 1}, {20, 20, 1, 1}, {27, 27, 0, 1}}, {{0, 0, 10, 10}, {14, 14, 1, 1}, {20, 20, 1, 1}, {27, 27, 0, 1}}, {{0, 0, 15, 15}, {20, 20, 1, 1}, {27, 27, 0, 1}}, {{0, 0, 21, 21}, {27, 27, 0, 1}}, {{0, 0, 27, 28}}, } for i := 0; i < len(changesI); i++ { diffs := diff.Granular(i, diff.ByteStrings(a, b)) if !diffsEqual(diffs, changesI[i]) { t.Errorf("expected %v, got %v", diffs, changesI[i]) } } }