Files
pad/internal/diff/diff.go
T
xarmian 81579847c6 Initial release
Pad — project management for developers and AI agents.
Single Go binary with embedded SvelteKit web UI, SQLite storage,
CLI, and Claude Code /pad skill integration.

https://getpad.dev
2026-03-26 01:52:36 +00:00

78 lines
2.3 KiB
Go

// Package diff provides compact text diffing for version storage.
// It uses the Myers diff algorithm via go-diff to create and apply patches.
package diff
import (
"fmt"
"strings"
difflib "github.com/sergi/go-diff/diffmatchpatch"
)
var dmp = difflib.New()
// CreatePatch computes a compact patch that transforms oldText into newText.
// The patch can be applied with ApplyPatch to recover newText from oldText.
func CreatePatch(oldText, newText string) string {
diffs := dmp.DiffMain(oldText, newText, true)
patches := dmp.PatchMake(oldText, diffs)
return dmp.PatchToText(patches)
}
// ApplyPatch applies a patch to baseText to produce the target text.
// Returns an error if the patch cannot be applied cleanly.
func ApplyPatch(baseText, patchText string) (string, error) {
if patchText == "" {
return baseText, nil
}
patches, err := dmp.PatchFromText(patchText)
if err != nil {
return "", fmt.Errorf("parse patch: %w", err)
}
result, applied := dmp.PatchApply(patches, baseText)
// Check that all hunks applied
for i, ok := range applied {
if !ok {
return "", fmt.Errorf("patch hunk %d failed to apply", i)
}
}
return result, nil
}
// IsDiffSmaller returns true if storing a diff would be meaningfully smaller
// than storing the full content. Returns false for near-complete rewrites
// where the diff is >= 80% of the full content size.
func IsDiffSmaller(patch, fullContent string) bool {
if len(fullContent) == 0 {
return false
}
return len(patch) < (len(fullContent) * 80 / 100)
}
// CreateReversePatch computes a patch that transforms newText back into oldText.
// Used for reverse-diff version storage: current doc has latest content,
// versions store patches to go backwards.
func CreateReversePatch(oldText, newText string) string {
return CreatePatch(newText, oldText)
}
// FormatDiffSummary returns a human-readable summary of changes.
func FormatDiffSummary(oldText, newText string) string {
oldLines := strings.Count(oldText, "\n")
newLines := strings.Count(newText, "\n")
added := 0
removed := 0
diffs := dmp.DiffMain(oldText, newText, false)
for _, d := range diffs {
switch d.Type {
case difflib.DiffInsert:
added += strings.Count(d.Text, "\n") + 1
case difflib.DiffDelete:
removed += strings.Count(d.Text, "\n") + 1
}
}
_ = oldLines
_ = newLines
return fmt.Sprintf("+%d/-%d lines", added, removed)
}