summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-12-02 17:29:17 +1100
committerJesse Duffield <jessedduffield@gmail.com>2018-12-04 22:11:48 +1100
commit99824c8a7b840cc2fa8c06f248acfaf01a3f964b (patch)
tree8ef3965e33c60f04a3a3a5205921eb9872a19511 /pkg
parent60060551bf775c7f61eb5bc025addd2b03c86ffb (diff)
add patch modifier struct
Diffstat (limited to 'pkg')
-rw-r--r--pkg/git/patch_modifier.go110
-rw-r--r--pkg/git/patch_modifier_test.go68
-rw-r--r--pkg/git/testdata/testPatchAfter1.diff13
-rw-r--r--pkg/git/testdata/testPatchAfter2.diff14
-rw-r--r--pkg/git/testdata/testPatchBefore.diff15
5 files changed, 220 insertions, 0 deletions
diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
new file mode 100644
index 000000000..0e31af952
--- /dev/null
+++ b/pkg/git/patch_modifier.go
@@ -0,0 +1,110 @@
+package git
+
+import (
+ "errors"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+)
+
+type PatchModifier struct {
+ Log *logrus.Entry
+}
+
+// NewPatchModifier builds a new branch list builder
+func NewPatchModifier(log *logrus.Entry) (*PatchModifier, error) {
+ return &PatchModifier{
+ Log: log,
+ }, nil
+}
+
+// ModifyPatch takes the original patch, which may contain several hunks,
+// and the line number of the line we want to stage
+func (p *PatchModifier) ModifyPatch(patch string, lineNumber int) (string, error) {
+ lines := strings.Split(patch, "\n")
+ headerLength := 4
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+
+ hunkStart, err := p.getHunkStart(lines, lineNumber)
+ if err != nil {
+ return "", err
+ }
+
+ hunk, err := p.getModifiedHunk(lines, hunkStart, lineNumber)
+ if err != nil {
+ return "", err
+ }
+
+ output += strings.Join(hunk, "\n")
+
+ return output, nil
+}
+
+// getHunkStart returns the line number of the hunk we're going to be modifying
+// in order to stage our line
+func (p *PatchModifier) getHunkStart(patchLines []string, lineNumber int) (int, error) {
+ // find the hunk that we're modifying
+ hunkStart := 0
+ for index, line := range patchLines {
+ if strings.HasPrefix(line, "@@") {
+ hunkStart = index
+ }
+ if index == lineNumber {
+ return hunkStart, nil
+ }
+ }
+ return 0, errors.New("Could not find hunk")
+}
+
+func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, lineNumber int) ([]string, error) {
+ lineChanges := 0
+ // strip the hunk down to just the line we want to stage
+ newHunk := []string{}
+ for offsetIndex, line := range patchLines[hunkStart:] {
+ index := offsetIndex + hunkStart
+ if index != lineNumber {
+ // we include other removals but treat them like context
+ if strings.HasPrefix(line, "-") {
+ newHunk = append(newHunk, " "+line[1:])
+ lineChanges += 1
+ continue
+ }
+ // we don't include other additions
+ if strings.HasPrefix(line, "+") {
+ lineChanges -= 1
+ continue
+ }
+ }
+ newHunk = append(newHunk, line)
+ }
+
+ var err error
+ newHunk[0], err = p.updatedHeader(newHunk[0], lineChanges)
+ if err != nil {
+ return nil, err
+ }
+
+ return newHunk, nil
+}
+
+// updatedHeader returns the hunk header with the updated line range
+// we need to update the hunk length to reflect the changes we made
+// if the hunk has three additions but we're only staging one, then
+// @@ -14,8 +14,11 @@ import (
+// becomes
+// @@ -14,8 +14,9 @@ import (
+func (p *PatchModifier) updatedHeader(currentHeader string, lineChanges int) (string, error) {
+ // current counter is the number after the second comma
+ re := regexp.MustCompile(`^[^,]+,[^,]+,(\d+)`)
+ prevLengthString := re.FindStringSubmatch(currentHeader)[1]
+
+ prevLength, err := strconv.Atoi(prevLengthString)
+ if err != nil {
+ return "", err
+ }
+ re = regexp.MustCompile(`\d+ @@`)
+ newLength := strconv.Itoa(prevLength + lineChanges)
+ return re.ReplaceAllString(currentHeader, newLength+" @@"), nil
+}
diff --git a/pkg/git/patch_modifier_test.go b/pkg/git/patch_modifier_test.go
new file mode 100644
index 000000000..af7be3751
--- /dev/null
+++ b/pkg/git/patch_modifier_test.go
@@ -0,0 +1,68 @@
+package git
+
+import (
+ "io/ioutil"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+ "github.com/stretchr/testify/assert"
+)
+
+func newDummyLog() *logrus.Entry {
+ log := logrus.New()
+ log.Out = ioutil.Discard
+ return log.WithField("test", "test")
+}
+
+func newDummyPatchModifier() *PatchModifier {
+ return &PatchModifier{
+ Log: newDummyLog(),
+ }
+}
+func TestModifyPatch(t *testing.T) {
+ type scenario struct {
+ testName string
+ patchFilename string
+ lineNumber int
+ shouldError bool
+ expectedPatchFilename string
+ }
+
+ scenarios := []scenario{
+ {
+ "Removing one line",
+ "testdata/testPatchBefore.diff",
+ 8,
+ false,
+ "testdata/testPatchAfter1.diff",
+ },
+ {
+ "Adding one line",
+ "testdata/testPatchBefore.diff",
+ 10,
+ false,
+ "testdata/testPatchAfter2.diff",
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ p := newDummyPatchModifier()
+ beforePatch, err := ioutil.ReadFile(s.patchFilename)
+ if err != nil {
+ panic("Cannot open file at " + s.patchFilename)
+ }
+ afterPatch, err := p.ModifyPatch(string(beforePatch), s.lineNumber)
+ if s.shouldError {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ expected, err := ioutil.ReadFile(s.expectedPatchFilename)
+ if err != nil {
+ panic("Cannot open file at " + s.expectedPatchFilename)
+ }
+ assert.Equal(t, string(expected), afterPatch)
+ }
+ })
+ }
+}
diff --git a/pkg/git/testdata/testPatchAfter1.diff b/pkg/git/testdata/testPatchAfter1.diff
new file mode 100644
index 000000000..88066e1c2
--- /dev/null
+++ b/pkg/git/testdata/testPatchAfter1.diff
@@ -0,0 +1,13 @@
+diff --git a/pkg/git/branch_list_builder.go b/pkg/git/branch_list_builder.go
+index 60ec4e0..db4485d 100644
+--- a/pkg/git/branch_list_builder.go
++++ b/pkg/git/branch_list_builder.go
+@@ -14,8 +14,7 @@ import (
+
+ // context:
+ // we want to only show 'safe' branches (ones that haven't e.g. been deleted)
+-// which `git branch -a` gives us, but we also want the recency data that
+ // git reflog gives us.
+ // So we get the HEAD, then append get the reflog branches that intersect with
+ // our safe branches, then add the remaining safe branches, ensuring uniqueness
+ // along the way
diff --git a/pkg/git/testdata/testPatchAfter2.diff b/pkg/git/testdata/testPatchAfter2.diff
new file mode 100644
index 000000000..0a17c2b67
--- /dev/null
+++ b/pkg/git/testdata/testPatchAfter2.diff
@@ -0,0 +1,14 @@
+diff --git a/pkg/git/branch_list_builder.go b/pkg/git/branch_list_builder.go
+index 60ec4e0..db4485d 100644
+--- a/pkg/git/branch_list_builder.go
++++ b/pkg/git/branch_list_builder.go
+@@ -14,8 +14,9 @@ import (
+
+ // context:
+ // we want to only show 'safe' branches (ones that haven't e.g. been deleted)
+ // which `git branch -a` gives us, but we also want the recency data that
+ // git reflog gives us.
++// test 2 - if I remove this, I decrement the end counter
+ // So we get the HEAD, then append get the reflog branches that intersect with
+ // our safe branches, then add the remaining safe branches, ensuring uniqueness
+ // along the way
diff --git a/pkg/git/testdata/testPatchBefore.diff b/pkg/git/testdata/testPatchBefore.diff
new file mode 100644
index 000000000..14e4b0e23
--- /dev/null
+++ b/pkg/git/testdata/testPatchBefore.diff
@@ -0,0 +1,15 @@
+diff --git a/pkg/git/branch_list_builder.go b/pkg/git/branch_list_builder.go
+index 60ec4e0..db4485d 100644
+--- a/pkg/git/branch_list_builder.go
++++ b/pkg/git/branch_list_builder.go
+@@ -14,8 +14,8 @@ import (
+
+ // context:
+ // we want to only show 'safe' branches (ones that haven't e.g. been deleted)
+-// which `git branch -a` gives us, but we also want the recency data that
+-// git reflog gives us.
++// test 2 - if I remove this, I decrement the end counter
++// test
+ // So we get the HEAD, then append get the reflog branches that intersect with
+ // our safe branches, then add the remaining safe branches, ensuring uniqueness
+ // along the way