Source File
diff.go
Belonging Package
internal/diff
// Copyright 2022 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package diffimport ()// A pair is a pair of values tracked for both the x and y side of a diff.// It is typically a pair of line indexes.type pair struct{ x, y int }// Diff returns an anchored diff of the two texts old and new// in the “unified diff” format. If old and new are identical,// Diff returns a nil slice (no output).//// Unix diff implementations typically look for a diff with// the smallest number of lines inserted and removed,// which can in the worst case take time quadratic in the// number of lines in the texts. As a result, many implementations// either can be made to run for a long time or cut off the search// after a predetermined amount of work.//// In contrast, this implementation looks for a diff with the// smallest number of “unique” lines inserted and removed,// where unique means a line that appears just once in both old and new.// We call this an “anchored diff” because the unique lines anchor// the chosen matching regions. An anchored diff is usually clearer// than a standard diff, because the algorithm does not try to// reuse unrelated blank lines or closing braces.// The algorithm also guarantees to run in O(n log n) time// instead of the standard O(n²) time.//// Some systems call this approach a “patience diff,” named for// the “patience sorting” algorithm, itself named for a solitaire card game.// We avoid that name for two reasons. First, the name has been used// for a few different variants of the algorithm, so it is imprecise.// Second, the name is frequently interpreted as meaning that you have// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm,// when in fact the algorithm is faster than the standard one.func ( string, []byte, string, []byte) []byte {if bytes.Equal(, ) {return nil}:= lines():= lines()// Print diff header.var bytes.Bufferfmt.Fprintf(&, "diff %s %s\n", , )fmt.Fprintf(&, "--- %s\n", )fmt.Fprintf(&, "+++ %s\n", )// Loop over matches to consider,// expanding each match to include surrounding lines,// and then printing diff chunks.// To avoid setup/teardown cases outside the loop,// tgs returns a leading {0,0} and trailing {len(x), len(y)} pair// in the sequence of matches.var (pair // printed up to x[:done.x] and y[:done.y]pair // start lines of current chunkpair // number of lines from each side in current chunk[]string // lines for current chunk)for , := range tgs(, ) {if .x < .x {// Already handled scanning forward from earlier match.continue}// Expand matching lines as far as possible,// establishing that x[start.x:end.x] == y[start.y:end.y].// Note that on the first (or last) iteration we may (or definitely do)// have an empty match: start.x==end.x and start.y==end.y.:=for .x > .x && .y > .y && [.x-1] == [.y-1] {.x--.y--}:=for .x < len() && .y < len() && [.x] == [.y] {.x++.y++}// Emit the mismatched lines before start into this chunk.// (No effect on first sentinel iteration, when start = {0,0}.)for , := range [.x:.x] {= append(, "-"+).x++}for , := range [.y:.y] {= append(, "+"+).y++}// If we're not at EOF and have too few common lines,// the chunk includes all the common lines and continues.const = 3 // number of context linesif (.x < len() || .y < len()) &&(.x-.x < || (len() > 0 && .x-.x < 2*)) {for , := range [.x:.x] {= append(, " "+).x++.y++}=continue}// End chunk with common lines for context.if len() > 0 {:= .x - .xif > {=}for , := range [.x : .x+] {= append(, " "+).x++.y++}= pair{.x + , .y + }// Format and emit chunk.// Convert line numbers to 1-indexed.// Special case: empty file shows up as 0,0 not 1,0.if .x > 0 {.x++}if .y > 0 {.y++}fmt.Fprintf(&, "@@ -%d,%d +%d,%d @@\n", .x, .x, .y, .y)for , := range {.WriteString()}.x = 0.y = 0= [:0]}// If we reached EOF, we're done.if .x >= len() && .y >= len() {break}// Otherwise start a new chunk.= pair{.x - , .y - }for , := range [.x:.x] {= append(, " "+).x++.y++}=}return .Bytes()}// lines returns the lines in the file x, including newlines.// If the file does not end in a newline, one is supplied// along with a warning about the missing newline.func lines( []byte) []string {:= strings.SplitAfter(string(), "\n")if [len()-1] == "" {= [:len()-1]} else {// Treat last line as having a message about the missing newline attached,// using the same text as BSD/GNU diff (including the leading backslash).[len()-1] += "\n\\ No newline at end of file\n"}return}// tgs returns the pairs of indexes of the longest common subsequence// of unique lines in x and y, where a unique line is one that appears// once in x and once in y.//// The longest common subsequence algorithm is as described in// Thomas G. Szymanski, “A Special Case of the Maximal Common// Subsequence Problem,” Princeton TR #170 (January 1975),// available at https://research.swtch.com/tgs170.pdf.func tgs(, []string) []pair {// Count the number of times each string appears in a and b.// We only care about 0, 1, many, counted as 0, -1, -2// for the x side and 0, -4, -8 for the y side.// Using negative numbers now lets us distinguish positive line numbers later.:= make(map[string]int)for , := range {if := []; > -2 {[] = - 1}}for , := range {if := []; > -8 {[] = - 4}}// Now unique strings can be identified by m[s] = -1+-4.//// Gather the indexes of those strings in x and y, building:// xi[i] = increasing indexes of unique strings in x.// yi[i] = increasing indexes of unique strings in y.// inv[i] = index j such that x[xi[i]] = y[yi[j]].var , , []intfor , := range {if [] == -1+-4 {[] = len()= append(, )}}for , := range {if , := []; && >= 0 {= append(, )= append(, )}}// Apply Algorithm A from Szymanski's paper.// In those terms, A = J = inv and B = [0, n).// We add sentinel pairs {0,0}, and {len(x),len(y)}// to the returned sequence, to help the processing loop.:=:= len():= make([]int, ):= make([]int, )for := range {[] = + 1}for := 0; < ; ++ {:= sort.Search(, func( int) bool {return [] >= []})[] = [][] = + 1}:= 0for , := range {if < {=}}:= make([]pair, 2+)[1+] = pair{len(), len()} // sentinel at end:=for := - 1; >= 0; -- {if [] == && [] < {[] = pair{[], [[]]}--}}[0] = pair{0, 0} // sentinel at startreturn}
![]() |
The pages are generated with Golds v0.7.9-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |