2018-12-02 17:29:17 +11:00
|
|
|
package git
|
|
|
|
|
|
|
|
import (
|
2019-10-30 20:22:30 +11:00
|
|
|
"fmt"
|
2018-12-02 17:29:17 +11:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
2019-10-30 20:22:30 +11:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +11:00
|
|
|
if !isLineInsideRange && firstChar == "-" {
|
|
|
|
return " "
|
|
|
|
}
|
2018-12-05 19:33:46 +11:00
|
|
|
|
2019-10-30 20:22:30 +11:00
|
|
|
return firstChar
|
2018-12-05 19:33:46 +11:00
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
2019-10-30 20:22:30 +11:00
|
|
|
return startOffset, header + strings.Join(bodyLines, "")
|
2018-12-05 19:33:46 +11:00
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
|
2019-10-30 20:22:30 +11:00
|
|
|
if changeCount == 0 {
|
|
|
|
// if nothing has changed we just return nothing
|
|
|
|
return startOffset, "", false
|
2018-12-02 17:29:17 +11:00
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +11:00
|
|
|
newStart := oldStart + startOffset + newStartOffset
|
2018-12-02 17:29:17 +11:00
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|
|
|
|
|
2019-10-30 20:22:30 +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
|
|
|
}
|