Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
165 lines
3.5 KiB
Go
165 lines
3.5 KiB
Go
package diff
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/hexops/gotextdiff"
|
|
)
|
|
|
|
// subtract two unified diffs from each other.
|
|
func subtract(a, b gotextdiff.Unified) gotextdiff.Unified {
|
|
return gotextdiff.Unified{
|
|
From: a.From,
|
|
To: a.To,
|
|
Hunks: subtractHunks(a.Hunks, b.Hunks),
|
|
}
|
|
}
|
|
|
|
func subtractHunks(src, base []*gotextdiff.Hunk) []*gotextdiff.Hunk {
|
|
sortHunks(src)
|
|
sortHunks(base)
|
|
|
|
res := make([]*gotextdiff.Hunk, 0, len(src))
|
|
offset := 0
|
|
for i, j := 0, 0; i < len(src) || j < len(base); {
|
|
if i >= len(src) {
|
|
break
|
|
}
|
|
if j >= len(base) {
|
|
res = append(res, src[i])
|
|
i++
|
|
continue
|
|
}
|
|
|
|
s := src[i]
|
|
b := base[j]
|
|
|
|
switch {
|
|
case beforeHunk(s, b, offset):
|
|
res = append(res, s)
|
|
offset += calculateHunkOffsetChange(s.Lines)
|
|
i++
|
|
case beforeHunk(b, s, -offset):
|
|
j++
|
|
case hunksOverlap(s, b, offset):
|
|
if s.FromLine < b.FromLine {
|
|
res = append(res, s)
|
|
offset += calculateHunkOffsetChange(s.Lines) - calculateHunkOffsetChange(b.Lines)
|
|
i++
|
|
} else {
|
|
offset += calculateHunkOffsetChange(s.Lines) - calculateHunkOffsetChange(b.Lines)
|
|
j++
|
|
}
|
|
default:
|
|
h := subtractHunk(s, b)
|
|
if !isHunkEmpty(h) {
|
|
res = append(res, subtractHunk(s, b))
|
|
}
|
|
offset += calculateHunkOffsetChange(s.Lines) - calculateHunkOffsetChange(b.Lines)
|
|
i++
|
|
j++
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func sortHunks(hunks []*gotextdiff.Hunk) {
|
|
sort.Slice(hunks, func(i, j int) bool {
|
|
return hunks[i].FromLine < hunks[j].FromLine
|
|
})
|
|
}
|
|
|
|
// beforeHunk returns true if a comes before b.
|
|
func beforeHunk(a, b *gotextdiff.Hunk, offset int) bool {
|
|
return a.ToLine-calculateEndEqualLines(a) < b.FromLine+calculateStartEqualLines(b)+offset
|
|
}
|
|
|
|
func calculateStartEqualLines(h *gotextdiff.Hunk) int {
|
|
lines := 0
|
|
for _, l := range h.Lines {
|
|
if l.Kind == gotextdiff.Equal {
|
|
lines++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func calculateEndEqualLines(h *gotextdiff.Hunk) int {
|
|
lines := 0
|
|
for i := len(h.Lines) - 1; i >= 0; i-- {
|
|
if h.Lines[i].Kind == gotextdiff.Equal {
|
|
lines++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func calculateHunkOffsetChange(lines []gotextdiff.Line) int {
|
|
offset := 0
|
|
for _, l := range lines {
|
|
if l.Kind == gotextdiff.Insert {
|
|
offset++
|
|
} else if l.Kind == gotextdiff.Delete {
|
|
offset--
|
|
}
|
|
}
|
|
return offset
|
|
}
|
|
|
|
func hunksOverlap(a, b *gotextdiff.Hunk, offset int) bool {
|
|
if !isLineInHunk(a.FromLine, b, offset) && isLineInHunk(a.ToLine, b, offset) {
|
|
return true
|
|
}
|
|
if isLineInHunk(a.FromLine, b, offset) && !isLineInHunk(a.ToLine, b, offset) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isLineInHunk(line int, h *gotextdiff.Hunk, offset int) bool {
|
|
return line-calculateStartEqualLines(h) > h.FromLine+offset && line+calculateEndEqualLines(h) < h.ToLine+offset
|
|
}
|
|
|
|
func subtractHunk(a, b *gotextdiff.Hunk) *gotextdiff.Hunk {
|
|
lines := subtractLines(a.Lines, b.Lines)
|
|
return &gotextdiff.Hunk{
|
|
FromLine: a.FromLine,
|
|
ToLine: a.ToLine + calculateHunkOffsetChange(a.Lines) - calculateHunkOffsetChange(lines),
|
|
Lines: lines,
|
|
}
|
|
}
|
|
|
|
func subtractLines(a, b []gotextdiff.Line) []gotextdiff.Line {
|
|
res := make([]gotextdiff.Line, 0, len(a))
|
|
for _, la := range a {
|
|
rep := false
|
|
for _, lb := range b {
|
|
if la.Kind != gotextdiff.Equal && la.Kind == lb.Kind && la.Content == lb.Content {
|
|
rep = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !rep {
|
|
res = append(res, la)
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func isHunkEmpty(h *gotextdiff.Hunk) bool {
|
|
effectiveLines := 0
|
|
for _, l := range h.Lines {
|
|
if l.Kind != gotextdiff.Equal {
|
|
effectiveLines++
|
|
}
|
|
}
|
|
return effectiveLines == 0
|
|
}
|