vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,12 @@
linters:
enable:
- ifshort
- gocritic
- godot
- gofumpt
- prealloc
- predeclared
- revive
- thelper
- unconvert
- unparam

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Peter Evans
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,176 @@
# patience
[![CI](https://github.com/peter-evans/patience/actions/workflows/ci.yml/badge.svg)](https://github.com/peter-evans/patience/actions/workflows/ci.yml)
[![GoReportCard](https://goreportcard.com/badge/github.com/peter-evans/patience)](https://goreportcard.com/report/github.com/peter-evans/patience)
[![GoDoc](https://godoc.org/github.com/peter-evans/patience?status.svg)](https://godoc.org/github.com/peter-evans/patience)
Go implementation of the Patience Diff algorithm.
This library generates line-oriented diffs between source and destination inputs, using the Patience Diff algorithm.
## Features
Supports both plain format and [Unified format](https://en.wikipedia.org/wiki/Diff#Unified_format) (unidiff).
Plain format:
```diff
the
quick
brown
-chicken
+fox
jumps
over
the
+lazy
dog
```
Unified format (unidiff):
```diff
--- a.txt
+++ b.txt
@@ -3,3 +3,3 @@
brown
-chicken
+fox
jumps
@@ -7,2 +7,3 @@
the
+lazy
dog
```
## Installation
```sh
go get github.com/peter-evans/patience
```
## Usage
```go
a := strings.Split(textA, "\n")
b := strings.Split(textB, "\n")
diffs := patience.Diff(a, b)
// Combined diff
diff := patience.DiffText(diffs)
// Split diffs
diffA := patience.DiffTextA(diffs)
diffB := patience.DiffTextB(diffs)
// Unified diff
unidiff := patience.UnifiedDiffText(diffs)
// Unified diff with options
unidiffopts := patience.UnifiedDiffTextWithOptions(
diffs,
UnifiedDiffOptions{
Precontext: 2,
Postcontext: 2,
SrcHeader: "a.txt",
DstHeader: "b.txt",
},
)
```
## About
Patience Diff is an algorithm credited to [Bram Cohen](https://bramcohen.livejournal.com/73318.html) that produces diffs tending to be more human-readable than the common diff algorithm.
The common diff algorithm is based on the [longest common subsequence](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem) problem.
It is credited to [Eugene Myers](http://www.xmailserver.org/diff2.pdf) and is the default diff algorithm in Git.
While the diffs generated by this algorithm are efficient, in many cases they tend not to correspond to what humans would naturally identify.
Patience Diff, while also relying on computing the longest common subsequence, takes a different approach. It only computes the longest common subsequence of the *unique*, *common* elements of both texts. This means that lines that are frequently non-unique, such as those containing a single brace or new line character, are ignored. The result is that distinctive lines, such as function declarations, become the anchor points of commonality between the two texts.
This is an example comparing Patience Diff to the common diff algorithm (Myers).
Patience Diff
```diff
#include <stdio.h>
+int fib(int n)
+{
+ if(n > 2)
+ {
+ return fib(n-1) + fib(n-2);
+ }
+ return 1;
+}
+
// Frobs foo heartily
int frobnitz(int foo)
{
int i;
for(i = 0; i < 10; i++)
{
- printf("Your answer is: ");
printf("%d\n", foo);
}
}
-int fact(int n)
-{
- if(n > 1)
- {
- return fact(n-1) * n;
- }
- return 1;
-}
-
int main(int argc, char **argv)
{
- frobnitz(fact(10));
+ frobnitz(fib(10));
}
```
Common diff (Myers)
```diff
#include <stdio.h>
-// Frobs foo heartily
-int frobnitz(int foo)
+int fib(int n)
{
- int i;
- for(i = 0; i < 10; i++)
+ if(n > 2)
{
- printf("Your answer is: ");
- printf("%d\n", foo);
+ return fib(n-1) + fib(n-2);
}
+ return 1;
}
-int fact(int n)
+// Frobs foo heartily
+int frobnitz(int foo)
{
- if(n > 1)
+ int i;
+ for(i = 0; i < 10; i++)
{
- return fact(n-1) * n;
+ printf("%d\n", foo);
}
- return 1;
}
int main(int argc, char **argv)
{
- frobnitz(fact(10));
+ frobnitz(fib(10));
}
```
## References
- [Patience Diff Advantages](https://bramcohen.livejournal.com/73318.html) by Bram Cohen
- [Patience Diff, a brief summary](https://alfedenzo.livejournal.com/170301.html) by Alfedenzo

View File

@@ -0,0 +1,108 @@
// Package patience implements the Patience Diff algorithm.
package patience
import (
"fmt"
"strings"
)
// typeSymbol returns the associated symbol of a DiffType.
func typeSymbol(t DiffType) string {
switch t {
case Equal:
return " "
case Insert:
return "+"
case Delete:
return "-"
default:
panic("unknown DiffType")
}
}
// DiffText returns the source and destination texts (all equalities, insertions and deletions).
func DiffText(diffs []DiffLine) string {
s := make([]string, len(diffs))
for i, l := range diffs {
if len(l.Text) == 0 && l.Type == Equal {
continue
}
s[i] = fmt.Sprintf("%s%s", typeSymbol(l.Type), l.Text)
}
return strings.Join(s, "\n")
}
// DiffTextA returns the source text (all equalities and deletions).
func DiffTextA(diffs []DiffLine) string {
s := []string{}
for _, l := range diffs {
if l.Type == Insert {
continue
}
if l.Type == Equal && len(l.Text) == 0 {
s = append(s, "")
} else {
s = append(s, fmt.Sprintf("%s%s", typeSymbol(l.Type), l.Text))
}
}
return strings.Join(s, "\n")
}
// DiffTextB returns the destination text (all equalities and insertions).
func DiffTextB(diffs []DiffLine) string {
s := []string{}
for _, l := range diffs {
if l.Type == Delete {
continue
}
if l.Type == Equal && len(l.Text) == 0 {
s = append(s, "")
} else {
s = append(s, fmt.Sprintf("%s%s", typeSymbol(l.Type), l.Text))
}
}
return strings.Join(s, "\n")
}
// UnifiedDiffOptions represents the options for UnifiedDiffTextWithOptions.
type UnifiedDiffOptions struct {
// Precontext is the number of lines of context before each change in a hunk.
Precontext int
// Postcontext is the number of lines of context after each change in a hunk.
Postcontext int
// SrcHeader is the header for the source file.
SrcHeader string
// DstHeader is the header for the destination file.
DstHeader string
}
// UnifiedDiffTextWithOptions returns the diff text in unidiff format.
func UnifiedDiffTextWithOptions(diffs []DiffLine, opts UnifiedDiffOptions) string {
hunks := makeHunks(diffs, opts.Precontext, opts.Postcontext)
s := []string{}
if len(opts.SrcHeader) > 0 {
s = append(s, fmt.Sprintf("--- %s", opts.SrcHeader))
}
if len(opts.DstHeader) > 0 {
s = append(s, fmt.Sprintf("+++ %s", opts.DstHeader))
}
for _, h := range hunks {
s = append(s, fmt.Sprintf("@@ -%d,%d +%d,%d @@", h.SrcStart, h.SrcLines, h.DstStart, h.DstLines))
for _, l := range h.Diffs {
if l.Type == Equal && len(l.Text) == 0 {
s = append(s, "")
} else {
s = append(s, fmt.Sprintf("%s%s", typeSymbol(l.Type), l.Text))
}
}
}
return strings.Join(s, "\n")
}
// UnifiedDiffText returns the diff text in unidiff format with a context of 3 lines.
func UnifiedDiffText(diffs []DiffLine) string {
return UnifiedDiffTextWithOptions(
diffs,
UnifiedDiffOptions{Precontext: 3, Postcontext: 3},
)
}

View File

@@ -0,0 +1,54 @@
// Package patience implements the Patience Diff algorithm.
package patience
// LCS computes the longest common subsequence of two string
// slices and returns the index pairs of the LCS.
func LCS(a, b []string) [][2]int {
// Initialize the LCS table.
lcs := make([][]int, len(a)+1)
for i := 0; i <= len(a); i++ {
lcs[i] = make([]int, len(b)+1)
}
// Populate the LCS table.
for i := 1; i < len(lcs); i++ {
for j := 1; j < len(lcs[i]); j++ {
if a[i-1] == b[j-1] {
lcs[i][j] = lcs[i-1][j-1] + 1
} else {
lcs[i][j] = max(lcs[i-1][j], lcs[i][j-1])
}
}
}
// Backtrack to find the LCS.
i, j := len(a), len(b)
s := make([][2]int, 0, lcs[i][j])
for i > 0 && j > 0 {
switch {
case a[i-1] == b[j-1]:
s = append(s, [2]int{i - 1, j - 1})
i--
j--
case lcs[i-1][j] > lcs[i][j-1]:
i--
default:
j--
}
}
// Reverse the backtracked LCS.
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
// max returns the maximum of two integers.
func max(a, b int) int {
if a > b {
return a
}
return b
}

View File

@@ -0,0 +1,115 @@
// Package patience implements the Patience Diff algorithm.
package patience
// DiffType defines the type of a diff element.
type DiffType int8
const (
// Delete represents a diff delete operation.
Delete DiffType = -1
// Insert represents a diff insert operation.
Insert DiffType = 1
// Equal represents no diff.
Equal DiffType = 0
)
// DiffLine represents a single line and its diff type.
type DiffLine struct {
Text string
Type DiffType
}
// toDiffLines is a convenience function to convert a slice of strings
// to a slice of DiffLines with the specified diff type.
func toDiffLines(a []string, t DiffType) []DiffLine {
diffs := make([]DiffLine, len(a))
for i, l := range a {
diffs[i] = DiffLine{l, t}
}
return diffs
}
// uniqueElements returns a slice of unique elements from a slice of
// strings, and a slice of the original indices of each element.
func uniqueElements(a []string) ([]string, []int) {
m := make(map[string]int)
for _, e := range a {
m[e]++
}
elements := []string{}
indices := []int{}
for i, e := range a {
if m[e] == 1 {
elements = append(elements, e)
indices = append(indices, i)
}
}
return elements, indices
}
// Diff returns the patience diff of two slices of strings.
func Diff(a, b []string) []DiffLine {
switch {
case len(a) == 0 && len(b) == 0:
return nil
case len(a) == 0:
return toDiffLines(b, Insert)
case len(b) == 0:
return toDiffLines(a, Delete)
}
// Find equal elements at the head of slices a and b.
i := 0
for i < len(a) && i < len(b) && a[i] == b[i] {
i++
}
if i > 0 {
return append(
toDiffLines(a[:i], Equal),
Diff(a[i:], b[i:])...,
)
}
// Find equal elements at the tail of slices a and b.
j := 0
for j < len(a) && j < len(b) && a[len(a)-1-j] == b[len(b)-1-j] {
j++
}
if j > 0 {
return append(
Diff(a[:len(a)-j], b[:len(b)-j]),
toDiffLines(a[len(a)-j:], Equal)...,
)
}
// Find the longest common subsequence of unique elements in a and b.
ua, idxa := uniqueElements(a)
ub, idxb := uniqueElements(b)
lcs := LCS(ua, ub)
// If the LCS is empty, the diff is all deletions and insertions.
if len(lcs) == 0 {
return append(toDiffLines(a, Delete), toDiffLines(b, Insert)...)
}
// Lookup the original indices of slices a and b.
for i, x := range lcs {
lcs[i][0] = idxa[x[0]]
lcs[i][1] = idxb[x[1]]
}
diffs := []DiffLine{}
ga, gb := 0, 0
for _, ip := range lcs {
// Diff the gaps between the lcs elements.
diffs = append(diffs, Diff(a[ga:ip[0]], b[gb:ip[1]])...)
// Append the LCS elements to the diff.
diffs = append(diffs, DiffLine{Type: Equal, Text: a[ip[0]]})
ga = ip[0] + 1
gb = ip[1] + 1
}
// Diff the remaining elements of a and b after the final LCS element.
diffs = append(diffs, Diff(a[ga:], b[gb:])...)
return diffs
}

View File

@@ -0,0 +1,151 @@
package patience
// Hunk represents a subsection of a diff.
type Hunk struct {
Diffs []DiffLine
SrcStart int
SrcLines int
DstStart int
DstLines int
}
// makeHunks returns the hunks of a diff.
func makeHunks(diffs []DiffLine, precontext, postcontext int) []Hunk {
if len(diffs) == 0 {
return nil
}
hunks := []Hunk{}
// Update hunks with a diff block.
updateHunks := func(block Hunk, lastBlock bool) {
curHunk := len(hunks) - 1
if block.Diffs[0].Type == Equal {
// Unmodified block.
if len(hunks) == 0 {
// Start a new hunk with the tail of the block.
ctxLen := min(precontext, len(block.Diffs))
hunks = append(
hunks,
Hunk{
Diffs: block.Diffs[len(block.Diffs)-ctxLen:],
SrcStart: len(block.Diffs) - ctxLen + block.SrcStart,
SrcLines: ctxLen,
DstStart: len(block.Diffs) - ctxLen + block.DstStart,
DstLines: ctxLen,
},
)
} else {
// Update the current hunk.
maxNonContext := precontext + postcontext
if lastBlock {
maxNonContext = postcontext
}
if len(block.Diffs) <= maxNonContext {
// Block is small enough to be appended to the current hunk.
hunks[curHunk].Diffs = append(hunks[curHunk].Diffs, block.Diffs...)
hunks[curHunk].SrcLines += len(block.Diffs)
hunks[curHunk].DstLines += len(block.Diffs)
} else {
// Append the head of the block to the current hunk.
hunks[curHunk].Diffs = append(hunks[curHunk].Diffs, block.Diffs[:postcontext]...)
hunks[curHunk].SrcLines += postcontext
hunks[curHunk].DstLines += postcontext
if !lastBlock {
// Start a new hunk with the tail of the block.
hunks = append(
hunks,
Hunk{
Diffs: block.Diffs[len(block.Diffs)-precontext:],
SrcStart: len(block.Diffs) - precontext + block.SrcStart,
SrcLines: precontext,
DstStart: len(block.Diffs) - precontext + block.DstStart,
DstLines: precontext,
},
)
}
}
// Update starting line numbers if the current hunk had no source or destination diff.
if hunks[curHunk].SrcStart == 0 {
hunks[curHunk].SrcStart = block.SrcStart
}
if hunks[curHunk].DstStart == 0 {
hunks[curHunk].DstStart = block.DstStart
}
}
} else {
// Modified block.
if len(hunks) > 0 {
hunks[curHunk].Diffs = append(hunks[curHunk].Diffs, block.Diffs...)
hunks[curHunk].SrcLines += block.SrcLines
hunks[curHunk].DstLines += block.DstLines
} else {
hunks = append(
hunks,
Hunk{
Diffs: block.Diffs,
SrcStart: block.SrcStart,
SrcLines: block.SrcLines,
DstStart: block.DstStart,
DstLines: block.DstLines,
},
)
}
}
}
// Aggregate blocks of modified and unmodified diff lines, creating
// or updating hunks after each block.
var block Hunk
modifiedLines := 0 //nolint:ifshort
srcLineNum, dstLineNum := 0, 0
for _, l := range diffs {
if len(block.Diffs) == 0 ||
block.Diffs[0].Type == l.Type ||
(block.Diffs[0].Type != l.Type && block.Diffs[0].Type != Equal && l.Type != Equal) {
block.Diffs = append(block.Diffs, l)
} else {
updateHunks(block, false)
block = Hunk{Diffs: []DiffLine{l}}
}
switch l.Type {
case Delete:
srcLineNum++
block.SrcLines++
modifiedLines++
case Insert:
dstLineNum++
block.DstLines++
modifiedLines++
case Equal:
srcLineNum++
dstLineNum++
block.SrcLines++
block.DstLines++
}
if block.SrcStart == 0 && (l.Type == Equal || l.Type == Delete) {
block.SrcStart = srcLineNum
}
if block.DstStart == 0 && (l.Type == Equal || l.Type == Insert) {
block.DstStart = dstLineNum
}
}
updateHunks(block, true)
// Return no hunks if the diffs contain only equal lines.
if modifiedLines == 0 {
return nil
}
return hunks
}
// min returns the minimum of two integers.
func min(a, b int) int {
if a < b {
return a
}
return b
}