diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2020-08-15 11:18:40 +1000 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2020-08-15 11:41:37 +1000 |
commit | 826d1660c97b7c5c55420ffed21eaa5f16118118 (patch) | |
tree | 8f2f65a3d2617aa242034273c4a41b6ec5aea659 /pkg/commands/patch/patch_modifier.go | |
parent | 291a8e4de0f5d5557cf6fbd7b5b6b9d42bcaa16e (diff) |
move patch stuff into its own package
Diffstat (limited to 'pkg/commands/patch/patch_modifier.go')
-rw-r--r-- | pkg/commands/patch/patch_modifier.go | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/pkg/commands/patch/patch_modifier.go b/pkg/commands/patch/patch_modifier.go new file mode 100644 index 000000000..c2bbc60f6 --- /dev/null +++ b/pkg/commands/patch/patch_modifier.go @@ -0,0 +1,158 @@ +package patch + +import ( + "fmt" + "regexp" + "strings" + + "github.com/sirupsen/logrus" +) + +var hunkHeaderRegexp = regexp.MustCompile(`(?m)^@@ -(\d+)[^\+]+\+(\d+)[^@]+@@(.*)$`) +var patchHeaderRegexp = regexp.MustCompile(`(?ms)(^diff.*?)^@@`) + +func GetHeaderFromDiff(diff string) string { + match := patchHeaderRegexp.FindStringSubmatch(diff) + if len(match) <= 1 { + return "" + } + return match[1] +} + +func GetHunksFromDiff(diff string) []*PatchHunk { + hunks := []*PatchHunk{} + firstLineIdx := -1 + var hunkLines []string + pastDiffHeader := false + + for lineIdx, line := range strings.SplitAfter(diff, "\n") { + isHunkHeader := strings.HasPrefix(line, "@@ -") + + if isHunkHeader { + if pastDiffHeader { // we need to persist the current hunk + hunks = append(hunks, newHunk(hunkLines, firstLineIdx)) + } + pastDiffHeader = true + firstLineIdx = lineIdx + hunkLines = []string{line} + continue + } + + if !pastDiffHeader { // skip through the stuff that precedes the first hunk + continue + } + + hunkLines = append(hunkLines, line) + } + + if pastDiffHeader { + hunks = append(hunks, newHunk(hunkLines, firstLineIdx)) + } + + return hunks +} + +type PatchModifier struct { + Log *logrus.Entry + filename string + hunks []*PatchHunk + header string +} + +func NewPatchModifier(log *logrus.Entry, filename string, diffText string) *PatchModifier { + return &PatchModifier{ + Log: log, + filename: filename, + hunks: GetHunksFromDiff(diffText), + header: GetHeaderFromDiff(diffText), + } +} + +func (d *PatchModifier) ModifiedPatchForLines(lineIndices []int, reverse bool, keepOriginalHeader bool) string { + // step one is getting only those hunks which we care about + hunksInRange := []*PatchHunk{} +outer: + for _, hunk := range d.hunks { + // if there is any line in our lineIndices array that the hunk contains, we append it + for _, lineIdx := range lineIndices { + if lineIdx >= hunk.FirstLineIdx && lineIdx <= hunk.LastLineIdx() { + hunksInRange = append(hunksInRange, hunk) + continue outer + } + } + } + + // step 2 is collecting all the hunks with new headers + startOffset := 0 + formattedHunks := "" + var formattedHunk string + for _, hunk := range hunksInRange { + startOffset, formattedHunk = hunk.formatWithChanges(lineIndices, reverse, startOffset) + formattedHunks += formattedHunk + } + + if formattedHunks == "" { + return "" + } + + var fileHeader string + // for staging/unstaging lines we don't want the original header because + // it makes git confused e.g. when dealing with deleted/added files + // but with building and applying patches the original header gives git + // information it needs to cleanly apply patches + if keepOriginalHeader { + fileHeader = d.header + } else { + fileHeader = fmt.Sprintf("--- a/%s\n+++ b/%s\n", d.filename, d.filename) + } + + return fileHeader + formattedHunks +} + +func (d *PatchModifier) ModifiedPatchForRange(firstLineIdx int, lastLineIdx int, reverse bool, keepOriginalHeader bool) string { + // generate array of consecutive line indices from our range + selectedLines := []int{} + for i := firstLineIdx; i <= lastLineIdx; i++ { + selectedLines = append(selectedLines, i) + } + return d.ModifiedPatchForLines(selectedLines, reverse, keepOriginalHeader) +} + +func (d *PatchModifier) OriginalPatchLength() int { + if len(d.hunks) == 0 { + return 0 + } + + return d.hunks[len(d.hunks)-1].LastLineIdx() +} + +func ModifiedPatchForRange(log *logrus.Entry, filename string, diffText string, firstLineIdx int, lastLineIdx int, reverse bool, keepOriginalHeader bool) string { + p := NewPatchModifier(log, filename, diffText) + return p.ModifiedPatchForRange(firstLineIdx, lastLineIdx, reverse, keepOriginalHeader) +} + +func ModifiedPatchForLines(log *logrus.Entry, filename string, diffText string, includedLineIndices []int, reverse bool, keepOriginalHeader bool) string { + p := NewPatchModifier(log, filename, diffText) + return p.ModifiedPatchForLines(includedLineIndices, reverse, keepOriginalHeader) +} + +// I want to know, given a hunk, what line a given index is on +func (hunk *PatchHunk) LineNumberOfLine(idx int) int { + lines := hunk.bodyLines[0 : idx-hunk.FirstLineIdx-1] + + offset := nLinesWithPrefix(lines, []string{"+", " "}) + + return hunk.newStart + offset +} + +func nLinesWithPrefix(lines []string, chars []string) int { + result := 0 + for _, line := range lines { + for _, char := range chars { + if line[:1] == char { + result++ + } + } + } + return result +} |