1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-30 05:49:15 +02:00
lazygit/pkg/git/patch_modifier.go

218 lines
5.7 KiB
Go
Raw Normal View History

2018-12-02 17:29:17 +11:00
package git
import (
"fmt"
2018-12-02 17:29:17 +11:00
"regexp"
"strconv"
"strings"
"github.com/sirupsen/logrus"
)
var headerRegexp = regexp.MustCompile("(?m)^@@ -(\\d+)[^\\+]+\\+(\\d+)[^@]+@@(.*)$")
type PatchHunk struct {
header string
FirstLineIdx int
LastLineIdx int
bodyLines []string
2018-12-02 17:29:17 +11:00
}
func newHunk(header string, body string, firstLineIdx int) *PatchHunk {
bodyLines := strings.SplitAfter(header+body, "\n")[1:] // dropping the header line
return &PatchHunk{
header: header,
FirstLineIdx: firstLineIdx,
LastLineIdx: firstLineIdx + len(bodyLines),
bodyLines: bodyLines,
}
2018-12-02 17:29:17 +11:00
}
func (hunk *PatchHunk) updatedLinesForRange(firstLineIdx int, lastLineIdx int, reverse bool) []string {
skippedNewlineMessageIndex := -1
newLines := []string{}
lineIdx := hunk.FirstLineIdx
for _, line := range hunk.bodyLines {
lineIdx++ // incrementing at the start to skip the header line
if line == "" {
break
}
isLineInsideRange := (firstLineIdx <= lineIdx && lineIdx <= lastLineIdx)
firstChar, content := line[:1], line[1:]
transformedFirstChar := transformedFirstChar(firstChar, reverse, isLineInsideRange)
if isLineInsideRange || (transformedFirstChar == "\\" && skippedNewlineMessageIndex != lineIdx) || transformedFirstChar == " " {
newLines = append(newLines, transformedFirstChar+content)
continue
}
if transformedFirstChar == "+" {
// we don't want to include the 'newline at end of file' line if it involves an addition we're not including
skippedNewlineMessageIndex = lineIdx + 1
}
2018-12-05 19:33:46 +11:00
}
return newLines
}
func transformedFirstChar(firstChar string, reverse bool, isLineInsideRange bool) string {
if reverse {
if !isLineInsideRange && firstChar == "+" {
return " "
} else if firstChar == "-" {
return "+"
} else if firstChar == "+" {
return "-"
} else {
return firstChar
}
2018-12-05 19:33:46 +11:00
}
if !isLineInsideRange && firstChar == "-" {
return " "
}
2018-12-05 19:33:46 +11:00
return firstChar
2018-12-05 19:33:46 +11:00
}
func (hunk *PatchHunk) formatHeader(oldStart int, oldLength int, newStart int, newLength int, heading string) string {
return fmt.Sprintf("@@ -%d,%d +%d,%d @@%s\n", oldStart, oldLength, newStart, newLength, heading)
}
func (hunk *PatchHunk) formatWithChanges(firstLineIdx int, lastLineIdx int, reverse bool, startOffset int) (int, string) {
bodyLines := hunk.updatedLinesForRange(firstLineIdx, lastLineIdx, reverse)
startOffset, header, ok := hunk.updatedHeader(bodyLines, startOffset, reverse)
if !ok {
return startOffset, ""
2018-12-05 19:33:46 +11:00
}
return startOffset, header + strings.Join(bodyLines, "")
2018-12-05 19:33:46 +11:00
}
func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int, reverse bool) (int, string, bool) {
changeCount := 0
oldLength := 0
newLength := 0
for _, line := range newBodyLines {
switch line[:1] {
case "+":
newLength++
changeCount++
case "-":
oldLength++
changeCount++
case " ":
oldLength++
newLength++
}
2018-12-05 19:33:46 +11:00
}
2018-12-02 17:29:17 +11:00
if changeCount == 0 {
// if nothing has changed we just return nothing
return startOffset, "", false
2018-12-02 17:29:17 +11:00
}
// get oldstart, newstart, and heading from header
match := headerRegexp.FindStringSubmatch(hunk.header)
var oldStart int
if reverse {
oldStart = mustConvertToInt(match[2])
} else {
oldStart = mustConvertToInt(match[1])
}
heading := match[3]
var newStartOffset int
// if the hunk went from zero to positive length, we need to increment the starting point by one
// if the hunk went from positive to zero length, we need to decrement the starting point by one
if oldLength == 0 {
newStartOffset = 1
} else if newLength == 0 {
newStartOffset = -1
} else {
newStartOffset = 0
2018-12-02 17:29:17 +11:00
}
newStart := oldStart + startOffset + newStartOffset
2018-12-02 17:29:17 +11:00
newStartOffset = startOffset + newLength - oldLength
formattedHeader := hunk.formatHeader(oldStart, oldLength, newStart, newLength, heading)
return newStartOffset, formattedHeader, true
2018-12-02 17:29:17 +11:00
}
func mustConvertToInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}
func GetHunksFromDiff(diff string) []*PatchHunk {
headers := headerRegexp.FindAllString(diff, -1)
bodies := headerRegexp.Split(diff, -1)[1:] // discarding top bit
headerFirstLineIndices := []int{}
for lineIdx, line := range strings.Split(diff, "\n") {
if strings.HasPrefix(line, "@@ -") {
headerFirstLineIndices = append(headerFirstLineIndices, lineIdx)
2018-12-02 17:29:17 +11:00
}
}
2018-12-05 19:33:46 +11:00
hunks := make([]*PatchHunk, len(headers))
for index, header := range headers {
hunks[index] = newHunk(header, bodies[index], headerFirstLineIndices[index])
}
return hunks
2018-12-02 17:29:17 +11:00
}
type PatchModifier struct {
Log *logrus.Entry
filename string
hunks []*PatchHunk
}
func NewPatchModifier(log *logrus.Entry, filename string, diffText string) *PatchModifier {
return &PatchModifier{
Log: log,
filename: filename,
hunks: GetHunksFromDiff(diffText),
}
}
func (d *PatchModifier) ModifiedPatchForRange(firstLineIdx int, lastLineIdx int, reverse bool) string {
// step one is getting only those hunks which we care about
hunksInRange := []*PatchHunk{}
for _, hunk := range d.hunks {
if hunk.LastLineIdx >= firstLineIdx && hunk.FirstLineIdx <= lastLineIdx {
hunksInRange = append(hunksInRange, hunk)
2018-12-02 17:29:17 +11:00
}
}
// step 2 is collecting all the hunks with new headers
startOffset := 0
formattedHunks := ""
var formattedHunk string
for _, hunk := range hunksInRange {
startOffset, formattedHunk = hunk.formatWithChanges(firstLineIdx, lastLineIdx, reverse, startOffset)
formattedHunks += formattedHunk
}
if formattedHunks == "" {
return ""
2018-12-02 17:29:17 +11:00
}
fileHeader := fmt.Sprintf("--- a/%s\n+++ b/%s\n", d.filename, d.filename)
return fileHeader + formattedHunks
2018-12-02 17:29:17 +11:00
}
func ModifiedPatch(log *logrus.Entry, filename string, diffText string, firstLineIdx int, lastLineIdx int, reverse bool) string {
p := NewPatchModifier(log, filename, diffText)
return p.ModifiedPatchForRange(firstLineIdx, lastLineIdx, reverse)
2018-12-02 17:29:17 +11:00
}