changelog generator & diff tool stormlightlabs.github.io/git-storm/
changelog changeset markdown golang git

feat: myer's & lcs diff algorithm

Changed files
+784 -145
internal
+1
.gitignore
··· 30 # Editor/IDE 31 # .idea/ 32 # .vscode/
··· 30 # Editor/IDE 31 # .idea/ 32 # .vscode/ 33 + .gocache/
+25 -35
go.mod
··· 3 go 1.24.5 4 5 require ( 6 - github.com/alecthomas/chroma/v2 v2.14.0 // indirect 7 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 8 - github.com/aymerick/douceur v0.2.0 // indirect 9 - github.com/charmbracelet/bubbles v0.21.0 // indirect 10 - github.com/charmbracelet/bubbletea v1.3.10 // indirect 11 github.com/charmbracelet/colorprofile v0.3.2 // indirect 12 - github.com/charmbracelet/fang v0.4.3 // indirect 13 - github.com/charmbracelet/glamour v0.10.0 // indirect 14 - github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect 15 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea // indirect 16 - github.com/charmbracelet/log v0.4.2 // indirect 17 github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef // indirect 18 github.com/charmbracelet/x/ansi v0.10.1 // indirect 19 github.com/charmbracelet/x/cellbuf v0.0.13 // indirect 20 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 // indirect 21 - github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect 22 github.com/charmbracelet/x/term v0.2.1 // indirect 23 github.com/charmbracelet/x/termios v0.1.1 // indirect 24 github.com/charmbracelet/x/windows v0.2.2 // indirect 25 - github.com/dlclark/regexp2 v1.11.0 // indirect 26 - github.com/emirpasic/gods v1.12.0 // indirect 27 - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 28 - github.com/go-git/go-git v4.7.0+incompatible // indirect 29 github.com/go-logfmt/logfmt v0.6.0 // indirect 30 - github.com/gorilla/css v1.0.1 // indirect 31 github.com/inconshreveable/mousetrap v1.1.0 // indirect 32 - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 33 - github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect 34 github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 35 github.com/mattn/go-isatty v0.0.20 // indirect 36 - github.com/mattn/go-localereader v0.0.1 // indirect 37 github.com/mattn/go-runewidth v0.0.16 // indirect 38 - github.com/microcosm-cc/bluemonday v1.0.27 // indirect 39 - github.com/mitchellh/go-homedir v1.1.0 // indirect 40 - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 41 github.com/muesli/cancelreader v0.2.2 // indirect 42 github.com/muesli/mango v0.1.0 // indirect 43 github.com/muesli/mango-cobra v1.2.0 // indirect 44 github.com/muesli/mango-pflag v0.1.0 // indirect 45 - github.com/muesli/reflow v0.3.0 // indirect 46 github.com/muesli/roff v0.1.0 // indirect 47 github.com/muesli/termenv v0.16.0 // indirect 48 github.com/rivo/uniseg v0.4.7 // indirect 49 github.com/sergi/go-diff v1.4.0 // indirect 50 - github.com/spf13/cobra v1.9.1 // indirect 51 github.com/spf13/pflag v1.0.6 // indirect 52 - github.com/src-d/gcfg v1.4.0 // indirect 53 - github.com/xanzy/ssh-agent v0.2.1 // indirect 54 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 55 - github.com/yuin/goldmark v1.7.8 // indirect 56 - github.com/yuin/goldmark-emoji v1.0.5 // indirect 57 - golang.org/x/crypto v0.31.0 // indirect 58 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 59 - golang.org/x/net v0.33.0 // indirect 60 golang.org/x/sync v0.17.0 // indirect 61 - golang.org/x/sys v0.36.0 // indirect 62 - golang.org/x/term v0.31.0 // indirect 63 - golang.org/x/text v0.24.0 // indirect 64 - gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect 65 - gopkg.in/src-d/go-git.v4 v4.13.1 // indirect 66 - gopkg.in/warnings.v0 v0.1.2 // indirect 67 )
··· 3 go 1.24.5 4 5 require ( 6 + github.com/charmbracelet/fang v0.4.3 7 + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 8 + github.com/charmbracelet/log v0.4.2 9 + github.com/go-git/go-git/v6 v6.0.0-20251103200709-47b1ed2930c9 10 + github.com/spf13/cobra v1.9.1 11 + ) 12 + 13 + require ( 14 + github.com/Microsoft/go-winio v0.6.2 // indirect 15 + github.com/ProtonMail/go-crypto v1.3.0 // indirect 16 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 17 github.com/charmbracelet/colorprofile v0.3.2 // indirect 18 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea // indirect 19 github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef // indirect 20 github.com/charmbracelet/x/ansi v0.10.1 // indirect 21 github.com/charmbracelet/x/cellbuf v0.0.13 // indirect 22 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 // indirect 23 + github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect 24 github.com/charmbracelet/x/term v0.2.1 // indirect 25 github.com/charmbracelet/x/termios v0.1.1 // indirect 26 github.com/charmbracelet/x/windows v0.2.2 // indirect 27 + github.com/cloudflare/circl v1.6.1 // indirect 28 + github.com/cyphar/filepath-securejoin v0.5.0 // indirect 29 + github.com/emirpasic/gods v1.18.1 // indirect 30 + github.com/go-git/gcfg/v2 v2.0.2 // indirect 31 + github.com/go-git/go-billy/v6 v6.0.0-20251022185412-61e52df296a5 // indirect 32 github.com/go-logfmt/logfmt v0.6.0 // indirect 33 + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 34 github.com/inconshreveable/mousetrap v1.1.0 // indirect 35 + github.com/kevinburke/ssh_config v1.4.0 // indirect 36 + github.com/klauspost/cpuid/v2 v2.3.0 // indirect 37 github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 38 github.com/mattn/go-isatty v0.0.20 // indirect 39 github.com/mattn/go-runewidth v0.0.16 // indirect 40 github.com/muesli/cancelreader v0.2.2 // indirect 41 github.com/muesli/mango v0.1.0 // indirect 42 github.com/muesli/mango-cobra v1.2.0 // indirect 43 github.com/muesli/mango-pflag v0.1.0 // indirect 44 github.com/muesli/roff v0.1.0 // indirect 45 github.com/muesli/termenv v0.16.0 // indirect 46 + github.com/pjbgf/sha1cd v0.5.0 // indirect 47 github.com/rivo/uniseg v0.4.7 // indirect 48 github.com/sergi/go-diff v1.4.0 // indirect 49 github.com/spf13/pflag v1.0.6 // indirect 50 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 51 + golang.org/x/crypto v0.43.0 // indirect 52 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 53 + golang.org/x/net v0.46.0 // indirect 54 golang.org/x/sync v0.17.0 // indirect 55 + golang.org/x/sys v0.37.0 // indirect 56 + golang.org/x/text v0.30.0 // indirect 57 )
+54 -110
go.sum
··· 1 - github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= 2 - github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= 3 - github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= 4 - github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 6 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 7 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 8 - github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 9 - github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 10 - github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= 11 - github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= 12 - github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= 13 - github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= 14 - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= 15 - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= 16 github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= 17 github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= 18 github.com/charmbracelet/fang v0.4.3 h1:qXeMxnL4H6mSKBUhDefHu8NfikFbP/MBNTfqTrXvzmY= 19 github.com/charmbracelet/fang v0.4.3/go.mod h1:wHJKQYO5ReYsxx+yZl+skDtrlKO/4LLEQ6EXsdHhRhg= 20 - github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= 21 - github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= 22 - github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 23 - github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 24 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= 25 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= 26 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea h1:g1HfUgSMvye8mgecMD1mPscpt+pzJoDEiSA+p2QXzdQ= ··· 29 github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 30 github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef h1:VrWaUi2LXYLjfjCHowdSOEc6dQ9Ro14KY7Bw4IWd19M= 31 github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef/go.mod h1:AThRsQH1t+dfyOKIwXRoJBniYFQUkUpQq4paheHMc2o= 32 - github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= 33 - github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= 34 github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= 35 github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= 36 - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= 37 - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 38 github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= 39 github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 40 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 h1:IJDiTgVE56gkAGfq0lBEloWgkXMk4hl/bmuPoicI4R0= 41 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0= 42 - github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= 43 - github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= 44 github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 45 github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 46 github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= 47 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= 48 github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 49 github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 50 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 51 - github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 52 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 53 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 54 - github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= 55 - github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 56 - github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 57 - github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 58 - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= 59 - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 60 - github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 61 - github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 62 - github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= 63 - github.com/go-git/go-git v4.7.0+incompatible/go.mod h1:6+421e08gnZWn30y26Vchf7efgYLe4dl5OQbBSUXShE= 64 github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 65 github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 66 - github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 67 - github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 68 - github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 69 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 70 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 71 - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 72 - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 73 - github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 74 - github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= 75 - github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 76 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 77 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 78 - github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 79 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 80 - github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 81 - github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 82 github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 83 github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 84 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 85 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 86 - github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 87 - github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 88 - github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 89 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 90 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 91 - github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 92 - github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 93 - github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 94 - github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 95 - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 96 - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 97 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 98 github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 99 github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= ··· 102 github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA= 103 github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg= 104 github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= 105 - github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 106 - github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 107 github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= 108 github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= 109 github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 110 github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 111 - github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= 112 - github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 113 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 114 - github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 115 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 116 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 117 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 118 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 119 - github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 120 github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= 121 github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 122 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 123 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 124 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 125 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 126 - github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= 127 - github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= 128 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 129 - github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 130 - github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 131 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 132 - github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= 133 - github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= 134 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 135 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 136 - github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 137 - github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= 138 - github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 139 - github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= 140 - github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= 141 - golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 142 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 143 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 144 - golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 145 - golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 146 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= 147 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 148 - golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 149 - golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 150 - golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 151 - golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 152 - golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 153 - golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 154 golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 155 golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 156 - golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 157 - golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 158 - golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 159 - golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 - golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 161 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 162 - golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 163 - golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 164 - golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 165 - golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 166 - golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 167 - golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 168 - golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 169 - golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 170 - golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 171 - golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 172 - golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 173 - golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 174 - golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 175 - golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 176 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 177 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 178 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 179 - gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= 180 - gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= 181 - gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= 182 - gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= 183 - gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= 184 - gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 185 - gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 186 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 187 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 188 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
··· 1 + github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 2 + github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 3 + github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= 4 + github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= 5 + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 6 + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 7 + github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 8 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 9 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 10 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 11 + github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= 12 + github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= 13 github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= 14 github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= 15 github.com/charmbracelet/fang v0.4.3 h1:qXeMxnL4H6mSKBUhDefHu8NfikFbP/MBNTfqTrXvzmY= 16 github.com/charmbracelet/fang v0.4.3/go.mod h1:wHJKQYO5ReYsxx+yZl+skDtrlKO/4LLEQ6EXsdHhRhg= 17 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= 18 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= 19 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea h1:g1HfUgSMvye8mgecMD1mPscpt+pzJoDEiSA+p2QXzdQ= ··· 22 github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 23 github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef h1:VrWaUi2LXYLjfjCHowdSOEc6dQ9Ro14KY7Bw4IWd19M= 24 github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef/go.mod h1:AThRsQH1t+dfyOKIwXRoJBniYFQUkUpQq4paheHMc2o= 25 github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= 26 github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= 27 github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= 28 github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 29 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 h1:IJDiTgVE56gkAGfq0lBEloWgkXMk4hl/bmuPoicI4R0= 30 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0= 31 + github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= 32 + github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= 33 github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 34 github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 35 github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= 36 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= 37 github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 38 github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 39 + github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= 40 + github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 41 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 42 + github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= 43 + github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 44 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 45 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 46 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 47 + github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= 48 + github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= 49 + github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 50 + github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 51 + github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 52 + github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 53 + github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo= 54 + github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs= 55 + github.com/go-git/go-billy/v6 v6.0.0-20251022185412-61e52df296a5 h1:9nXOQ3HupDEerUXxiPrw3olFy/jHGZ3O3DyM/o6ejdc= 56 + github.com/go-git/go-billy/v6 v6.0.0-20251022185412-61e52df296a5/go.mod h1:TpCYxdQ0tWZkrnAkd7yqK+z1C8RKcyjcaYAJNAcnUnM= 57 + github.com/go-git/go-git-fixtures/v5 v5.1.1 h1:OH8i1ojV9bWfr0ZfasfpgtUXQHQyVS8HXik/V1C099w= 58 + github.com/go-git/go-git-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU= 59 + github.com/go-git/go-git/v6 v6.0.0-20251103200709-47b1ed2930c9 h1:ku5ebH4vCvEjg1zSQd3fRNhEj5o8BgScVY83JKBsj6Y= 60 + github.com/go-git/go-git/v6 v6.0.0-20251103200709-47b1ed2930c9/go.mod h1:z9pQiXCfyOZIs/8qa5zmozzbcsDPtGN91UD7+qeX3hk= 61 github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 62 github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 63 + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 64 + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 65 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 66 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 67 + github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= 68 + github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= 69 + github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= 70 + github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 71 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 72 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 73 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 74 github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 75 github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 76 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 77 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 78 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 79 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 80 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 81 github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 82 github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= ··· 85 github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA= 86 github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg= 87 github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= 88 github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= 89 github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= 90 github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 91 github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 92 + github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= 93 + github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= 94 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 95 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 96 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 97 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 98 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 99 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 100 github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= 101 github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 102 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 103 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 104 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 105 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 106 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 107 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 108 + github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 109 + github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 110 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 111 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 112 + golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= 113 + golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= 114 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 115 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 116 + golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= 117 + golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= 118 golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 119 golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 120 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 121 + golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 122 + golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 123 + golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= 124 + golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= 125 + golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= 126 + golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= 127 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 128 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 129 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 130 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 131 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 132 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+351
internal/diff/diff.go
···
··· 1 + package diff 2 + 3 + // EditKind defines the type of diff operation. 4 + type EditKind int 5 + 6 + const ( 7 + Equal EditKind = iota // context: lines unchanged 8 + Insert // added lines ('+') 9 + Delete // removed lines ('-') 10 + Replace // changed lines (shown as Delete + Insert in unified view) 11 + ) 12 + 13 + func (e EditKind) String() string { 14 + switch e { 15 + case Equal: 16 + return "Equal" 17 + case Insert: 18 + return "Insert" 19 + case Delete: 20 + return "Delete" 21 + case Replace: 22 + return "Replace" 23 + default: 24 + return "Unknown" 25 + } 26 + } 27 + 28 + // Edit represents a single edit operation in a diff. 29 + type Edit struct { 30 + Kind EditKind // Equal, Insert, or Delete 31 + AIndex int // index in original sequence 32 + BIndex int // index in new sequence 33 + Content string // the line or token 34 + } 35 + 36 + // Diff represents a generic diffing algorithm. 37 + type Diff interface { 38 + // Compute computes the edit operations needed to transform a into b. 39 + Compute(a, b []string) ([]Edit, error) 40 + 41 + // Name returns the human-readable algorithm name (e.g., "LCS", "Hunt–McIlroy"). 42 + Name() string 43 + } 44 + 45 + // LCS implements a shortest-edit-script diff algorithm. 46 + // 47 + // For maintainability we use the classic dynamic-programming formulation based on the longest common subsequence. 48 + // While the original Myers paper achieves O(ND) time, this O(NM) variant is simpler and still practical for the small inputs handled by this package. 49 + type LCS struct{} 50 + 51 + func (l *LCS) Name() string { return "LCS" } 52 + 53 + // Compute computes the shortest edit script using the LCS diff algorithm. 54 + // 55 + // It builds an LCS matrix and walks it to emit the sequence of Equal, Insert, and Delete operations required to transform a into b. 56 + func (l *LCS) Compute(a, b []string) ([]Edit, error) { 57 + n := len(a) 58 + lenB := len(b) 59 + 60 + if n == 0 && lenB == 0 { 61 + return []Edit{}, nil 62 + } 63 + 64 + if n == 0 { 65 + edits := make([]Edit, lenB) 66 + for i := range lenB { 67 + edits[i] = Edit{Kind: Insert, AIndex: -1, BIndex: i, Content: b[i]} 68 + } 69 + return edits, nil 70 + } 71 + 72 + if lenB == 0 { 73 + edits := make([]Edit, n) 74 + for i := range n { 75 + edits[i] = Edit{Kind: Delete, AIndex: i, BIndex: -1, Content: a[i]} 76 + } 77 + return edits, nil 78 + } 79 + 80 + lcs := make([][]int, n+1) 81 + for i := range lcs { 82 + lcs[i] = make([]int, lenB+1) 83 + } 84 + 85 + for i := n - 1; i >= 0; i-- { 86 + for j := lenB - 1; j >= 0; j-- { 87 + if a[i] == b[j] { 88 + lcs[i][j] = lcs[i+1][j+1] + 1 89 + } else if lcs[i+1][j] >= lcs[i][j+1] { 90 + lcs[i][j] = lcs[i+1][j] 91 + } else { 92 + lcs[i][j] = lcs[i][j+1] 93 + } 94 + } 95 + } 96 + 97 + edits := make([]Edit, 0, n+lenB) 98 + 99 + i, j := 0, 0 100 + for i < n && j < lenB { 101 + switch { 102 + case a[i] == b[j]: 103 + edits = append(edits, Edit{ 104 + Kind: Equal, 105 + AIndex: i, 106 + BIndex: j, 107 + Content: a[i], 108 + }) 109 + i++ 110 + j++ 111 + case lcs[i+1][j] >= lcs[i][j+1]: 112 + edits = append(edits, Edit{ 113 + Kind: Delete, 114 + AIndex: i, 115 + BIndex: -1, 116 + Content: a[i], 117 + }) 118 + i++ 119 + default: 120 + edits = append(edits, Edit{ 121 + Kind: Insert, 122 + AIndex: -1, 123 + BIndex: j, 124 + Content: b[j], 125 + }) 126 + j++ 127 + } 128 + } 129 + 130 + for i < n { 131 + edits = append(edits, Edit{ 132 + Kind: Delete, 133 + AIndex: i, 134 + BIndex: -1, 135 + Content: a[i], 136 + }) 137 + i++ 138 + } 139 + 140 + for j < lenB { 141 + edits = append(edits, Edit{ 142 + Kind: Insert, 143 + AIndex: -1, 144 + BIndex: j, 145 + Content: b[j], 146 + }) 147 + j++ 148 + } 149 + 150 + return edits, nil 151 + } 152 + 153 + // Myers implements the Myers algorithm. 154 + type Myers struct{} 155 + 156 + // Name returns algorithm name. 157 + func (m *Myers) Name() string { 158 + return "Myers" 159 + } 160 + 161 + // Compute computes the diff edits needed to transform a into b. 162 + func (m *Myers) Compute(a, b []string) ([]Edit, error) { 163 + n := len(a) 164 + mLen := len(b) 165 + max := n + mLen 166 + 167 + if n == 0 && mLen == 0 { 168 + return []Edit{}, nil 169 + } 170 + 171 + if n == 0 { 172 + edits := make([]Edit, mLen) 173 + for i := range mLen { 174 + edits[i] = Edit{Kind: Insert, AIndex: -1, BIndex: i, Content: b[i]} 175 + } 176 + return edits, nil 177 + } 178 + 179 + if mLen == 0 { 180 + edits := make([]Edit, n) 181 + for i := range n { 182 + edits[i] = Edit{Kind: Delete, AIndex: i, BIndex: -1, Content: a[i]} 183 + } 184 + return edits, nil 185 + } 186 + 187 + offset := max 188 + size := 2*max + 1 189 + V := make([]int, size) 190 + trace := make([][]int, max+1) 191 + 192 + if offset+1 < size { 193 + V[offset+1] = 0 194 + } 195 + for D := 0; D <= max; D++ { 196 + currentV := make([]int, size) 197 + copy(currentV, V) 198 + trace[D] = currentV 199 + 200 + for k := -D; k <= D; k += 2 { 201 + idx := offset + k 202 + 203 + var x int 204 + if k == -D || (k != D && V[idx-1] < V[idx+1]) { 205 + x = V[idx+1] 206 + } else { 207 + x = V[idx-1] + 1 208 + } 209 + y := x - k 210 + 211 + for x < n && y < mLen && a[x] == b[y] { 212 + x++ 213 + y++ 214 + } 215 + 216 + V[idx] = x 217 + 218 + if x >= n && y >= mLen { 219 + return m.buildEdits(a, b, trace, D, offset), nil 220 + } 221 + } 222 + } 223 + 224 + return nil, nil 225 + } 226 + 227 + // buildEdits reconstructs the edit script from the trace of V arrays. 228 + func (m *Myers) buildEdits(a, b []string, trace [][]int, D, offset int) []Edit { 229 + var edits []Edit 230 + x := len(a) 231 + y := len(b) 232 + 233 + for d := D; d > 0; d-- { 234 + V := trace[d] 235 + k := x - y 236 + idx := offset + k 237 + 238 + var prevK int 239 + if k == -d || (k != d && V[idx-1] < V[idx+1]) { 240 + prevK = k + 1 241 + } else { 242 + prevK = k - 1 243 + } 244 + 245 + prevX := V[offset+prevK] 246 + prevY := prevX - prevK 247 + 248 + var xStart, yStart int 249 + if prevK == k-1 { 250 + xStart = prevX + 1 251 + yStart = prevY 252 + } else { 253 + xStart = prevX 254 + yStart = prevY + 1 255 + } 256 + 257 + for x > xStart && y > yStart { 258 + x-- 259 + y-- 260 + edits = append(edits, Edit{ 261 + Kind: Equal, 262 + AIndex: x, 263 + BIndex: y, 264 + Content: a[x], 265 + }) 266 + } 267 + 268 + if xStart == prevX+1 { 269 + x-- 270 + edits = append(edits, Edit{ 271 + Kind: Delete, 272 + AIndex: x, 273 + BIndex: -1, 274 + Content: a[x], 275 + }) 276 + } else { 277 + y-- 278 + edits = append(edits, Edit{ 279 + Kind: Insert, 280 + AIndex: -1, 281 + BIndex: y, 282 + Content: b[y], 283 + }) 284 + } 285 + 286 + x = prevX 287 + y = prevY 288 + } 289 + 290 + for x > 0 && y > 0 { 291 + if a[x-1] == b[y-1] { 292 + x-- 293 + y-- 294 + edits = append(edits, Edit{ 295 + Kind: Equal, 296 + AIndex: x, 297 + BIndex: y, 298 + Content: a[x], 299 + }) 300 + } else { 301 + break 302 + } 303 + } 304 + 305 + for x > 0 { 306 + x-- 307 + edits = append(edits, Edit{ 308 + Kind: Delete, 309 + AIndex: x, 310 + BIndex: -1, 311 + Content: a[x], 312 + }) 313 + } 314 + for y > 0 { 315 + y-- 316 + edits = append(edits, Edit{ 317 + Kind: Insert, 318 + AIndex: -1, 319 + BIndex: y, 320 + Content: b[y], 321 + }) 322 + } 323 + 324 + for i, j := 0, len(edits)-1; i < j; i, j = i+1, j-1 { 325 + edits[i], edits[j] = edits[j], edits[i] 326 + } 327 + return edits 328 + } 329 + 330 + // ApplyEdits applies a sequence of edits to reconstruct the target sequence to verify that the diff is correct. 331 + func ApplyEdits(_ []string, edits []Edit) []string { 332 + result := make([]string, 0) 333 + for _, edit := range edits { 334 + switch edit.Kind { 335 + case Equal, Insert: 336 + result = append(result, edit.Content) 337 + case Delete: 338 + // Skip deleted lines 339 + } 340 + } 341 + return result 342 + } 343 + 344 + // CountEditKinds returns a map counting occurrences of each [EditKind]. 345 + func CountEditKinds(edits []Edit) map[EditKind]int { 346 + counts := make(map[EditKind]int) 347 + for _, edit := range edits { 348 + counts[edit.Kind]++ 349 + } 350 + return counts 351 + }
+264
internal/diff/diff_test.go
···
··· 1 + package diff 2 + 3 + import ( 4 + _ "embed" 5 + "strings" 6 + "testing" 7 + ) 8 + 9 + type algorithmFactory struct { 10 + name string 11 + new func() Diff 12 + } 13 + 14 + var diffAlgorithms = []algorithmFactory{ 15 + {name: "LCS", new: func() Diff { return &LCS{} }}, 16 + {name: "Myers", new: func() Diff { return &Myers{} }}, 17 + } 18 + 19 + //go:embed fixtures/diffs_original.md 20 + var fixtureOriginal string 21 + 22 + //go:embed fixtures/diffs_updated.md 23 + var fixtureUpdated string 24 + 25 + func TestDiff_Compute_EmptySequences(t *testing.T) { 26 + for _, alg := range diffAlgorithms { 27 + alg := alg 28 + t.Run(alg.name, func(t *testing.T) { 29 + m := alg.new() 30 + 31 + t.Run("both empty", func(t *testing.T) { 32 + edits, err := m.Compute([]string{}, []string{}) 33 + if err != nil { 34 + t.Fatalf("unexpected error: %v", err) 35 + } 36 + if len(edits) != 0 { 37 + t.Errorf("expected 0 edits, got %d", len(edits)) 38 + } 39 + }) 40 + 41 + t.Run("a empty, b has content", func(t *testing.T) { 42 + b := []string{"line1", "line2"} 43 + edits, err := m.Compute([]string{}, b) 44 + if err != nil { 45 + t.Fatalf("unexpected error: %v", err) 46 + } 47 + if len(edits) != 2 { 48 + t.Fatalf("expected 2 edits, got %d", len(edits)) 49 + } 50 + for i, edit := range edits { 51 + if edit.Kind != Insert { 52 + t.Errorf("edit %d: expected Insert, got %v", i, edit.Kind) 53 + } 54 + if edit.Content != b[i] { 55 + t.Errorf("edit %d: expected content %q, got %q", i, b[i], edit.Content) 56 + } 57 + } 58 + }) 59 + 60 + t.Run("b empty, a has content", func(t *testing.T) { 61 + a := []string{"line1", "line2"} 62 + edits, err := m.Compute(a, []string{}) 63 + if err != nil { 64 + t.Fatalf("unexpected error: %v", err) 65 + } 66 + if len(edits) != 2 { 67 + t.Fatalf("expected 2 edits, got %d", len(edits)) 68 + } 69 + for i, edit := range edits { 70 + if edit.Kind != Delete { 71 + t.Errorf("edit %d: expected Delete, got %v", i, edit.Kind) 72 + } 73 + if edit.Content != a[i] { 74 + t.Errorf("edit %d: expected content %q, got %q", i, a[i], edit.Content) 75 + } 76 + } 77 + }) 78 + }) 79 + } 80 + } 81 + 82 + func TestDiff_Compute_IdenticalSequences(t *testing.T) { 83 + a := []string{"line1", "line2", "line3"} 84 + b := []string{"line1", "line2", "line3"} 85 + 86 + for _, alg := range diffAlgorithms { 87 + alg := alg 88 + t.Run(alg.name, func(t *testing.T) { 89 + m := alg.new() 90 + edits, err := m.Compute(a, b) 91 + if err != nil { 92 + t.Fatalf("unexpected error: %v", err) 93 + } 94 + 95 + if len(edits) != 3 { 96 + t.Fatalf("expected 3 edits, got %d", len(edits)) 97 + } 98 + 99 + for i, edit := range edits { 100 + if edit.Kind != Equal { 101 + t.Errorf("edit %d: expected Equal, got %v", i, edit.Kind) 102 + } 103 + if edit.AIndex != i || edit.BIndex != i { 104 + t.Errorf("edit %d: expected indices (%d,%d), got (%d,%d)", i, i, i, edit.AIndex, edit.BIndex) 105 + } 106 + } 107 + }) 108 + } 109 + } 110 + 111 + func TestDiff_Compute_SimpleInsert(t *testing.T) { 112 + a := []string{"line1", "line3"} 113 + b := []string{"line1", "line2", "line3"} 114 + 115 + for _, alg := range diffAlgorithms { 116 + alg := alg 117 + t.Run(alg.name, func(t *testing.T) { 118 + m := alg.new() 119 + edits, err := m.Compute(a, b) 120 + if err != nil { 121 + t.Fatalf("unexpected error: %v", err) 122 + } 123 + 124 + // Verify structure: Equal(line1), Insert(line2), Equal(line3) 125 + if len(edits) != 3 { 126 + t.Fatalf("expected 3 edits, got %d", len(edits)) 127 + } 128 + 129 + if edits[0].Kind != Equal || edits[0].Content != "line1" { 130 + t.Errorf("edit 0: expected Equal(line1), got %v(%s)", edits[0].Kind, edits[0].Content) 131 + } 132 + if edits[1].Kind != Insert || edits[1].Content != "line2" { 133 + t.Errorf("edit 1: expected Insert(line2), got %v(%s)", edits[1].Kind, edits[1].Content) 134 + } 135 + if edits[2].Kind != Equal || edits[2].Content != "line3" { 136 + t.Errorf("edit 2: expected Equal(line3), got %v(%s)", edits[2].Kind, edits[2].Content) 137 + } 138 + }) 139 + } 140 + } 141 + 142 + func TestDiff_Compute_SimpleDelete(t *testing.T) { 143 + a := []string{"line1", "line2", "line3"} 144 + b := []string{"line1", "line3"} 145 + 146 + for _, alg := range diffAlgorithms { 147 + alg := alg 148 + t.Run(alg.name, func(t *testing.T) { 149 + m := alg.new() 150 + edits, err := m.Compute(a, b) 151 + if err != nil { 152 + t.Fatalf("unexpected error: %v", err) 153 + } 154 + 155 + // Verify structure: Equal(line1), Delete(line2), Equal(line3) 156 + if len(edits) != 3 { 157 + t.Fatalf("expected 3 edits, got %d", len(edits)) 158 + } 159 + 160 + if edits[0].Kind != Equal || edits[0].Content != "line1" { 161 + t.Errorf("edit 0: expected Equal(line1), got %v(%s)", edits[0].Kind, edits[0].Content) 162 + } 163 + if edits[1].Kind != Delete || edits[1].Content != "line2" { 164 + t.Errorf("edit 1: expected Delete(line2), got %v(%s)", edits[1].Kind, edits[1].Content) 165 + } 166 + if edits[2].Kind != Equal || edits[2].Content != "line3" { 167 + t.Errorf("edit 2: expected Equal(line3), got %v(%s)", edits[2].Kind, edits[2].Content) 168 + } 169 + }) 170 + } 171 + } 172 + 173 + func TestDiff_Compute_CompleteReplacement(t *testing.T) { 174 + a := []string{"old1", "old2"} 175 + b := []string{"new1", "new2"} 176 + 177 + for _, alg := range diffAlgorithms { 178 + alg := alg 179 + t.Run(alg.name, func(t *testing.T) { 180 + m := alg.new() 181 + edits, err := m.Compute(a, b) 182 + if err != nil { 183 + t.Fatalf("unexpected error: %v", err) 184 + } 185 + 186 + // Should be all deletes followed by all inserts (or interleaved) 187 + deleteCount := 0 188 + insertCount := 0 189 + for _, edit := range edits { 190 + switch edit.Kind { 191 + case Delete: 192 + deleteCount++ 193 + case Insert: 194 + insertCount++ 195 + case Equal: 196 + t.Errorf("unexpected Equal edit when sequences are completely different") 197 + } 198 + } 199 + 200 + if deleteCount != 2 { 201 + t.Errorf("expected 2 deletes, got %d", deleteCount) 202 + } 203 + if insertCount != 2 { 204 + t.Errorf("expected 2 inserts, got %d", insertCount) 205 + } 206 + }) 207 + } 208 + } 209 + 210 + func TestDiff_Compute_Fixtures(t *testing.T) { 211 + original := strings.Split(strings.TrimSpace(fixtureOriginal), "\n") 212 + updated := strings.Split(strings.TrimSpace(fixtureUpdated), "\n") 213 + 214 + for _, alg := range diffAlgorithms { 215 + alg := alg 216 + t.Run(alg.name, func(t *testing.T) { 217 + m := alg.new() 218 + edits, err := m.Compute(original, updated) 219 + if err != nil { 220 + t.Fatalf("unexpected error: %v", err) 221 + } 222 + 223 + if len(edits) == 0 { 224 + t.Fatal("expected non-empty edit list") 225 + } 226 + 227 + reconstructed := ApplyEdits(original, edits) 228 + if len(reconstructed) != len(updated) { 229 + t.Fatalf("reconstructed length %d != updated length %d", len(reconstructed), len(updated)) 230 + } 231 + for i := range reconstructed { 232 + if reconstructed[i] != updated[i] { 233 + t.Errorf("line %d: reconstructed %q != updated %q", i, reconstructed[i], updated[i]) 234 + } 235 + } 236 + 237 + counts := CountEditKinds(edits) 238 + if counts[Equal] == 0 { 239 + t.Error("expected some Equal edits (files share common lines like blank lines)") 240 + } 241 + if counts[Insert] == 0 { 242 + t.Error("expected some Insert edits") 243 + } 244 + if counts[Delete] == 0 { 245 + t.Error("expected some Delete edits") 246 + } 247 + 248 + t.Logf("Edit statistics: Equal=%d, Insert=%d, Delete=%d, Total=%d", 249 + counts[Equal], counts[Insert], counts[Delete], len(edits)) 250 + }) 251 + } 252 + } 253 + 254 + func TestDiff_Name(t *testing.T) { 255 + for _, alg := range diffAlgorithms { 256 + alg := alg 257 + t.Run(alg.name, func(t *testing.T) { 258 + m := alg.new() 259 + if m.Name() != alg.name { 260 + t.Errorf("expected name %q, got %q", alg.name, m.Name()) 261 + } 262 + }) 263 + } 264 + }
+41
internal/diff/fixtures/diffs_original.md
···
··· 1 + # Text Differencing Algorithms 2 + 3 + Text differencing algorithms compute the minimal set of edits required to transform one sequence into another. 4 + They are widely used in version control systems, compilers, and data synchronization tools. 5 + 6 + ## The Myers Algorithm 7 + 8 + Eugene Myers proposed a diff algorithm in 1986 that computes the shortest edit script (SES) between two sequences. 9 + It models the problem as a traversal over a grid, where diagonal moves represent matches and horizontal or vertical moves represent insertions and deletions. 10 + 11 + ### Key Ideas 12 + 13 + - Based on the concept of *edit graph traversal*. 14 + - Uses a dynamic programming approach optimized with linear space. 15 + - Achieves **O(ND)** time complexity where `N` is sequence length and `D` is the edit distance. 16 + 17 + ### Pseudocode 18 + 19 + ```text 20 + for D from 0 to MAX: 21 + for k in range(-D, D+1, 2): 22 + choose move (insert or delete) 23 + extend along diagonal as far as possible 24 + if end reached: return path 25 + ``` 26 + 27 + ### Strengths 28 + 29 + - Produces minimal diffs. 30 + - Works efficiently for typical text files. 31 + - Used by `git diff`, `diffutils`, and many modern tools. 32 + 33 + ### Weaknesses 34 + 35 + - Complexity increases with extremely long or highly divergent sequences. 36 + - Implementation details are tricky due to path tracing. 37 + 38 + ## References 39 + 40 + - Myers, E. W. (1986). *An O(ND) Difference Algorithm and Its Variations.* 41 + - GNU diffutils documentation.
+48
internal/diff/fixtures/diffs_updated.md
···
··· 1 + # Text Differencing Algorithms 2 + 3 + Diff algorithms determine the smallest set of operations to make two sequences identical. 4 + They are essential to tools like `git`, `rsync`, and file synchronization systems. 5 + 6 + ## The Hunt–McIlroy Algorithm 7 + 8 + Developed by James W. Hunt and M. Douglas McIlroy in 1976, this algorithm underlies the original Unix `diff` utility. 9 + Unlike Myers, it relies on finding **longest common subsequences (LCS)** to compute differences. 10 + 11 + ### Core Principles 12 + 13 + - Operates on the *longest common subsequence* problem. 14 + - Identifies matching lines using hash-based comparison. 15 + - Produces intuitive, human-readable diffs. 16 + 17 + ### Simplified Outline 18 + 19 + ```text 20 + match = longest_common_subsequence(A, B) 21 + for each segment not in match: 22 + emit insertion or deletion 23 + ``` 24 + 25 + ### Advantages 26 + 27 + - Generates results similar to human intuition. 28 + - Performs well on structured text like source code. 29 + - Simple to implement and debug. 30 + 31 + ### Limitations 32 + 33 + - May not always yield the shortest possible edit script. 34 + - Space complexity can grow for large inputs. 35 + 36 + ## Comparison to Myers 37 + 38 + | Feature | Myers | Hunt–McIlroy | 39 + | ---------- | ----------------- | ------------------ | 40 + | Complexity | O(ND) | O(N log N) typical | 41 + | Output | Minimal | Readable | 42 + | Origin | 1986 | 1976 | 43 + | Use Cases | Modern diff tools | Unix `diff` | 44 + 45 + ## References 46 + 47 + - Hunt, J. W. & McIlroy, M. D. (1976). *An Algorithm for Differential File Comparison.* 48 + - Research on Longest Common Subsequence algorithms.