mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 21:46:49 -04:00
Parse is lenient: it takes any text and reads a diff out of it, which is what we want when we hand it a diff, but it has no way to say "this isn't one". We're about to parse the *rendered* contents of a diff view, which a diff renderer is free to restructure — putting the line numbers in a gutter, say, shifts the +/- marker off the start of each body line, so every line reads as context and the parse silently lies about which lines are changes. Comparing each hunk's body against the lengths its header declares catches exactly that, without teaching us anything about any particular renderer's layout: a faithful unified diff agrees with its headers, a restructured one doesn't. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
101 lines
2.3 KiB
Go
101 lines
2.3 KiB
Go
package patch
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
)
|
|
|
|
// Captures, in order: the old start, the old length (omitted by git when it is
|
|
// 1), the new start, the new length (likewise), and the trailing context.
|
|
var hunkHeaderRegexp = regexp.MustCompile(`(?m)^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$`)
|
|
|
|
func Parse(patchStr string) *Patch {
|
|
// ignore trailing newline.
|
|
lines := strings.Split(strings.TrimSuffix(patchStr, "\n"), "\n")
|
|
|
|
hunks := []*Hunk{}
|
|
patchHeader := []string{}
|
|
|
|
var currentHunk *Hunk
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "@@") {
|
|
oldStart, oldLength, newStart, newLength, headerContext := headerInfo(line)
|
|
|
|
currentHunk = &Hunk{
|
|
oldStart: oldStart,
|
|
newStart: newStart,
|
|
declaredOldLength: oldLength,
|
|
declaredNewLength: newLength,
|
|
headerContext: headerContext,
|
|
bodyLines: []*PatchLine{},
|
|
}
|
|
hunks = append(hunks, currentHunk)
|
|
} else if currentHunk != nil {
|
|
currentHunk.bodyLines = append(currentHunk.bodyLines, newHunkLine(line))
|
|
} else {
|
|
patchHeader = append(patchHeader, line)
|
|
}
|
|
}
|
|
|
|
return &Patch{
|
|
hunks: hunks,
|
|
header: patchHeader,
|
|
}
|
|
}
|
|
|
|
func headerInfo(header string) (oldStart int, oldLength int, newStart int, newLength int, headerContext string) {
|
|
match := hunkHeaderRegexp.FindStringSubmatch(header)
|
|
|
|
oldStart = utils.MustConvertToInt(match[1])
|
|
oldLength = declaredLength(match[2])
|
|
newStart = utils.MustConvertToInt(match[3])
|
|
newLength = declaredLength(match[4])
|
|
headerContext = match[5]
|
|
|
|
return oldStart, oldLength, newStart, newLength, headerContext
|
|
}
|
|
|
|
// declaredLength parses a length capture of a hunk header, which git omits when
|
|
// it is 1 (e.g. "@@ -0,0 +1 @@").
|
|
func declaredLength(match string) int {
|
|
if match == "" {
|
|
return 1
|
|
}
|
|
return utils.MustConvertToInt(match)
|
|
}
|
|
|
|
func newHunkLine(line string) *PatchLine {
|
|
if line == "" {
|
|
return &PatchLine{
|
|
Kind: CONTEXT,
|
|
Content: "",
|
|
}
|
|
}
|
|
|
|
firstChar := line[:1]
|
|
|
|
kind := parseFirstChar(firstChar)
|
|
|
|
return &PatchLine{
|
|
Kind: kind,
|
|
Content: line,
|
|
}
|
|
}
|
|
|
|
func parseFirstChar(firstChar string) PatchLineKind {
|
|
switch firstChar {
|
|
case " ":
|
|
return CONTEXT
|
|
case "+":
|
|
return ADDITION
|
|
case "-":
|
|
return DELETION
|
|
case "\\":
|
|
return NEWLINE_MESSAGE
|
|
}
|
|
|
|
return CONTEXT
|
|
}
|