···11+// Taken from Go's internal/diff with only very minor tweaks; those being:
22+// - Adding an extra space between the diff character (+/-) and the line so we can easily colour it
33+// - Lint ignores
44+// - Renaming diff test package to diff_test
55+//
66+// Copyright 2022 The Go Authors. All rights reserved.
77+// Use of this source code is governed by a BSD-style
88+// license that can be found in the LICENSE file.
99+1010+package diff
1111+1212+import (
1313+ "bytes"
1414+ "fmt"
1515+ "sort"
1616+ "strings"
1717+)
1818+1919+// A pair is a pair of values tracked for both the x and y side of a diff.
2020+// It is typically a pair of line indexes.
2121+type pair struct{ x, y int }
2222+2323+// Diff returns an anchored diff of the two texts old and new
2424+// in the “unified diff” format. If old and new are identical,
2525+// Diff returns a nil slice (no output).
2626+//
2727+// Unix diff implementations typically look for a diff with
2828+// the smallest number of lines inserted and removed,
2929+// which can in the worst case take time quadratic in the
3030+// number of lines in the texts. As a result, many implementations
3131+// either can be made to run for a long time or cut off the search
3232+// after a predetermined amount of work.
3333+//
3434+// In contrast, this implementation looks for a diff with the
3535+// smallest number of “unique” lines inserted and removed,
3636+// where unique means a line that appears just once in both old and new.
3737+// We call this an “anchored diff” because the unique lines anchor
3838+// the chosen matching regions. An anchored diff is usually clearer
3939+// than a standard diff, because the algorithm does not try to
4040+// reuse unrelated blank lines or closing braces.
4141+// The algorithm also guarantees to run in O(n log n) time
4242+// instead of the standard O(n²) time.
4343+//
4444+// Some systems call this approach a “patience diff,” named for
4545+// the “patience sorting” algorithm, itself named for a solitaire card game.
4646+// We avoid that name for two reasons. First, the name has been used
4747+// for a few different variants of the algorithm, so it is imprecise.
4848+// Second, the name is frequently interpreted as meaning that you have
4949+// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm,
5050+// when in fact the algorithm is faster than the standard one.
5151+func Diff( //nolint: gocyclo
5252+ oldName string,
5353+ old []byte,
5454+ newName string,
5555+ new []byte, //nolint: predeclared
5656+) []byte {
5757+ if bytes.Equal(old, new) {
5858+ return nil
5959+ }
6060+ x := lines(old)
6161+ y := lines(new)
6262+6363+ // Print diff header.
6464+ var out bytes.Buffer
6565+ fmt.Fprintf(&out, "diff %s %s\n", oldName, newName)
6666+ fmt.Fprintf(&out, "--- %s\n", oldName)
6767+ fmt.Fprintf(&out, "+++ %s\n", newName)
6868+6969+ // Loop over matches to consider,
7070+ // expanding each match to include surrounding lines,
7171+ // and then printing diff chunks.
7272+ // To avoid setup/teardown cases outside the loop,
7373+ // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair
7474+ // in the sequence of matches.
7575+ var (
7676+ done pair // printed up to x[:done.x] and y[:done.y]
7777+ chunk pair // start lines of current chunk
7878+ count pair // number of lines from each side in current chunk
7979+ ctext []string // lines for current chunk
8080+ )
8181+ for _, m := range tgs(x, y) {
8282+ if m.x < done.x {
8383+ // Already handled scanning forward from earlier match.
8484+ continue
8585+ }
8686+8787+ // Expand matching lines as far possible,
8888+ // establishing that x[start.x:end.x] == y[start.y:end.y].
8989+ // Note that on the first (or last) iteration we may (or definitey do)
9090+ // have an empty match: start.x==end.x and start.y==end.y.
9191+ start := m
9292+ for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] {
9393+ start.x--
9494+ start.y--
9595+ }
9696+ end := m
9797+ for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] {
9898+ end.x++
9999+ end.y++
100100+ }
101101+102102+ // Emit the mismatched lines before start into this chunk.
103103+ // (No effect on first sentinel iteration, when start = {0,0}.)
104104+ for _, s := range x[done.x:start.x] {
105105+ ctext = append(ctext, "- "+s)
106106+ count.x++
107107+ }
108108+ for _, s := range y[done.y:start.y] {
109109+ ctext = append(ctext, "+ "+s)
110110+ count.y++
111111+ }
112112+113113+ // If we're not at EOF and have too few common lines,
114114+ // the chunk includes all the common lines and continues.
115115+ const C = 3 // number of context lines
116116+ if (end.x < len(x) || end.y < len(y)) &&
117117+ (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) {
118118+ for _, s := range x[start.x:end.x] {
119119+ ctext = append(ctext, " "+s)
120120+ count.x++
121121+ count.y++
122122+ }
123123+ done = end
124124+ continue
125125+ }
126126+127127+ // End chunk with common lines for context.
128128+ if len(ctext) > 0 {
129129+ n := end.x - start.x
130130+ if n > C {
131131+ n = C
132132+ }
133133+ for _, s := range x[start.x : start.x+n] {
134134+ ctext = append(ctext, " "+s)
135135+ count.x++
136136+ count.y++
137137+ }
138138+ done = pair{start.x + n, start.y + n}
139139+140140+ // Format and emit chunk.
141141+ // Convert line numbers to 1-indexed.
142142+ // Special case: empty file shows up as 0,0 not 1,0.
143143+ if count.x > 0 {
144144+ chunk.x++
145145+ }
146146+ if count.y > 0 {
147147+ chunk.y++
148148+ }
149149+ fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y)
150150+ for _, s := range ctext {
151151+ out.WriteString(s)
152152+ }
153153+ count.x = 0
154154+ count.y = 0
155155+ ctext = ctext[:0]
156156+ }
157157+158158+ // If we reached EOF, we're done.
159159+ if end.x >= len(x) && end.y >= len(y) {
160160+ break
161161+ }
162162+163163+ // Otherwise start a new chunk.
164164+ chunk = pair{end.x - C, end.y - C}
165165+ for _, s := range x[chunk.x:end.x] {
166166+ ctext = append(ctext, " "+s)
167167+ count.x++
168168+ count.y++
169169+ }
170170+ done = end
171171+ }
172172+173173+ return out.Bytes()
174174+}
175175+176176+// lines returns the lines in the file x, including newlines.
177177+// If the file does not end in a newline, one is supplied
178178+// along with a warning about the missing newline.
179179+func lines(x []byte) []string {
180180+ l := strings.SplitAfter(string(x), "\n")
181181+ if l[len(l)-1] == "" {
182182+ l = l[:len(l)-1]
183183+ } else {
184184+ // Treat last line as having a message about the missing newline attached,
185185+ // using the same text as BSD/GNU diff (including the leading backslash).
186186+ l[len(l)-1] += "\n\\ No newline at end of file\n"
187187+ }
188188+ return l
189189+}
190190+191191+// tgs returns the pairs of indexes of the longest common subsequence
192192+// of unique lines in x and y, where a unique line is one that appears
193193+// once in x and once in y.
194194+//
195195+// The longest common subsequence algorithm is as described in
196196+// Thomas G. Szymanski, “A Special Case of the Maximal Common
197197+// Subsequence Problem,” Princeton TR #170 (January 1975),
198198+// available at https://research.swtch.com/tgs170.pdf.
199199+func tgs(x, y []string) []pair {
200200+ // Count the number of times each string appears in a and b.
201201+ // We only care about 0, 1, many, counted as 0, -1, -2
202202+ // for the x side and 0, -4, -8 for the y side.
203203+ // Using negative numbers now lets us distinguish positive line numbers later.
204204+ m := make(map[string]int)
205205+ for _, s := range x {
206206+ if c := m[s]; c > -2 {
207207+ m[s] = c - 1
208208+ }
209209+ }
210210+ for _, s := range y {
211211+ if c := m[s]; c > -8 {
212212+ m[s] = c - 4 //nolint: mnd
213213+ }
214214+ }
215215+216216+ // Now unique strings can be identified by m[s] = -1+-4.
217217+ //
218218+ // Gather the indexes of those strings in x and y, building:
219219+ // xi[i] = increasing indexes of unique strings in x.
220220+ // yi[i] = increasing indexes of unique strings in y.
221221+ // inv[i] = index j such that x[xi[i]] = y[yi[j]].
222222+ var xi, yi, inv []int
223223+ for i, s := range y {
224224+ if m[s] == -1+-4 {
225225+ m[s] = len(yi)
226226+ yi = append(yi, i)
227227+ }
228228+ }
229229+ for i, s := range x {
230230+ if j, ok := m[s]; ok && j >= 0 {
231231+ xi = append(xi, i)
232232+ inv = append(inv, j)
233233+ }
234234+ }
235235+236236+ // Apply Algorithm A from Szymanski's paper.
237237+ // In those terms, A = J = inv and B = [0, n).
238238+ // We add sentinel pairs {0,0}, and {len(x),len(y)}
239239+ // to the returned sequence, to help the processing loop.
240240+ J := inv
241241+ n := len(xi)
242242+ T := make([]int, n)
243243+ L := make([]int, n)
244244+ for i := range T {
245245+ T[i] = n + 1
246246+ }
247247+ for i := 0; i < n; i++ {
248248+ k := sort.Search(n, func(k int) bool {
249249+ return T[k] >= J[i]
250250+ })
251251+ T[k] = J[i]
252252+ L[i] = k + 1
253253+ }
254254+ k := 0
255255+ for _, v := range L {
256256+ if k < v {
257257+ k = v
258258+ }
259259+ }
260260+ seq := make([]pair, 2+k) //nolint:mnd
261261+ seq[1+k] = pair{len(x), len(y)} // sentinel at end
262262+ lastj := n
263263+ for i := n - 1; i >= 0; i-- {
264264+ if L[i] == k && J[i] < lastj {
265265+ seq[k] = pair{xi[i], yi[J[i]]}
266266+ k--
267267+ }
268268+ }
269269+ seq[0] = pair{0, 0} // sentinel at start
270270+ return seq
271271+}
+57
internal/diff/diff_test.go
···11+// Copyright 2022 The Go Authors. All rights reserved.
22+// Use of this source code is governed by a BSD-style
33+// license that can be found in the LICENSE file.
44+55+package diff_test
66+77+import (
88+ "bytes"
99+ "os"
1010+ "path/filepath"
1111+ "testing"
1212+1313+ "github.com/FollowTheProcess/test/internal/diff"
1414+ "golang.org/x/tools/txtar"
1515+)
1616+1717+func clean(text []byte) []byte {
1818+ text = bytes.ReplaceAll(text, []byte("$\n"), []byte("\n"))
1919+ text = bytes.TrimSuffix(text, []byte("^D\n"))
2020+ return text
2121+}
2222+2323+func Test(t *testing.T) {
2424+ files, err := filepath.Glob(filepath.Join("testdata", "*.txtar"))
2525+ if err != nil {
2626+ t.Fatalf("could not glob txtar files: %v", err)
2727+ }
2828+ if len(files) == 0 {
2929+ t.Fatalf("no testdata")
3030+ }
3131+3232+ for _, file := range files {
3333+ t.Run(filepath.Base(file), func(t *testing.T) {
3434+ contents, err := os.ReadFile(file)
3535+ if err != nil {
3636+ t.Fatalf("could not read %s: %v", file, err)
3737+ }
3838+ // Stupid windows
3939+ contents = bytes.ReplaceAll(contents, []byte("\r\n"), []byte("\n"))
4040+ archive := txtar.Parse(contents)
4141+ if len(archive.Files) != 3 || archive.Files[2].Name != "diff" {
4242+ t.Fatalf("%s: want three files, third named \"diff\", got: %v", file, archive.Files)
4343+ }
4444+ diffs := diff.Diff(
4545+ archive.Files[0].Name,
4646+ clean(archive.Files[0].Data),
4747+ archive.Files[1].Name,
4848+ clean(archive.Files[1].Data),
4949+ )
5050+ want := clean(archive.Files[2].Data)
5151+ if !bytes.Equal(diffs, want) {
5252+ t.Fatalf("%s: have:\n%s\nwant:\n%s\n%s", file,
5353+ diffs, want, diff.Diff("have", diffs, "want", want))
5454+ }
5555+ })
5656+ }
5757+}
+13
internal/diff/testdata/allnew.txtar
···11+-- old --
22+-- new --
33+a
44+b
55+c
66+-- diff --
77+diff old new
88+--- old
99++++ new
1010+@@ -0,0 +1,3 @@
1111++ a
1212++ b
1313++ c
+13
internal/diff/testdata/allold.txtar
···11+-- old --
22+a
33+b
44+c
55+-- new --
66+-- diff --
77+diff old new
88+--- old
99++++ new
1010+@@ -1,3 +0,0 @@
1111+- a
1212+- b
1313+- c
+35
internal/diff/testdata/basic.txtar
···11+# Example from Hunt and McIlroy, “An Algorithm for Differential File Comparison.”
22+# https://www.cs.dartmouth.edu/~doug/diff.pdf
33+44+-- old --
55+a
66+b
77+c
88+d
99+e
1010+f
1111+g
1212+-- new --
1313+w
1414+a
1515+b
1616+x
1717+y
1818+z
1919+e
2020+-- diff --
2121+diff old new
2222+--- old
2323++++ new
2424+@@ -1,7 +1,7 @@
2525++ w
2626+ a
2727+ b
2828+- c
2929+- d
3030++ x
3131++ y
3232++ z
3333+ e
3434+- f
3535+- g
+40
internal/diff/testdata/dups.txtar
···11+-- old --
22+a
33+44+b
55+66+c
77+88+d
99+1010+e
1111+1212+f
1313+-- new --
1414+a
1515+1616+B
1717+1818+C
1919+2020+d
2121+2222+e
2323+2424+f
2525+-- diff --
2626+diff old new
2727+--- old
2828++++ new
2929+@@ -1,8 +1,8 @@
3030+ a
3131+ $
3232+- b
3333+-
3434+- c
3535++ B
3636++
3737++ C
3838+ $
3939+ d
4040+ $
+38
internal/diff/testdata/end.txtar
···11+-- old --
22+1
33+2
44+3
55+4
66+5
77+6
88+7
99+eight
1010+nine
1111+ten
1212+eleven
1313+-- new --
1414+1
1515+2
1616+3
1717+4
1818+5
1919+6
2020+7
2121+8
2222+9
2323+10
2424+-- diff --
2525+diff old new
2626+--- old
2727++++ new
2828+@@ -5,7 +5,6 @@
2929+ 5
3030+ 6
3131+ 7
3232+- eight
3333+- nine
3434+- ten
3535+- eleven
3636++ 8
3737++ 9
3838++ 10
+9
internal/diff/testdata/eof.txtar
···11+-- old --
22+a
33+b
44+c^D
55+-- new --
66+a
77+b
88+c^D
99+-- diff --
+18
internal/diff/testdata/eof1.txtar
···11+-- old --
22+a
33+b
44+c
55+-- new --
66+a
77+b
88+c^D
99+-- diff --
1010+diff old new
1111+--- old
1212++++ new
1313+@@ -1,3 +1,3 @@
1414+ a
1515+ b
1616+- c
1717++ c
1818+\ No newline at end of file
+18
internal/diff/testdata/eof2.txtar
···11+-- old --
22+a
33+b
44+c^D
55+-- new --
66+a
77+b
88+c
99+-- diff --
1010+diff old new
1111+--- old
1212++++ new
1313+@@ -1,3 +1,3 @@
1414+ a
1515+ b
1616+- c
1717+\ No newline at end of file
1818++ c
···11+-- old --
22+hello world
33+-- new --
44+hello world
55+-- diff --
+34
internal/diff/testdata/start.txtar
···11+-- old --
22+e
33+pi
44+4
55+5
66+6
77+7
88+8
99+9
1010+10
1111+-- new --
1212+1
1313+2
1414+3
1515+4
1616+5
1717+6
1818+7
1919+8
2020+9
2121+10
2222+-- diff --
2323+diff old new
2424+--- old
2525++++ new
2626+@@ -1,5 +1,6 @@
2727+- e
2828+- pi
2929++ 1
3030++ 2
3131++ 3
3232+ 4
3333+ 5
3434+ 6
+40
internal/diff/testdata/triv.txtar
···11+# Another example from Hunt and McIlroy,
22+# “An Algorithm for Differential File Comparison.”
33+# https://www.cs.dartmouth.edu/~doug/diff.pdf
44+55+# Anchored diff gives up on finding anything,
66+# since there are no unique lines.
77+88+-- old --
99+a
1010+b
1111+c
1212+a
1313+b
1414+b
1515+a
1616+-- new --
1717+c
1818+a
1919+b
2020+a
2121+b
2222+c
2323+-- diff --
2424+diff old new
2525+--- old
2626++++ new
2727+@@ -1,7 +1,6 @@
2828+- a
2929+- b
3030+- c
3131+- a
3232+- b
3333+- b
3434+- a
3535++ c
3636++ a
3737++ b
3838++ a
3939++ b
4040++ c