summaryrefslogtreecommitdiffstats
path: root/pkg/git
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-12-05 19:33:46 +1100
committerJesse Duffield <jessedduffield@gmail.com>2018-12-05 19:33:46 +1100
commitc0f9795910dd840fb83e6992f7f59c77ec4c13fc (patch)
tree05fe245b822008f458025a5dd75cae384bfda845 /pkg/git
parent658e5a9faf8409c62f11f3ad6d636d0255e450f4 (diff)
staging lines and hunks
Diffstat (limited to 'pkg/git')
-rw-r--r--pkg/git/patch_modifier.go59
-rw-r--r--pkg/git/patch_modifier_test.go25
-rw-r--r--pkg/git/patch_parser.go9
-rw-r--r--pkg/git/patch_parser_test.go65
-rw-r--r--pkg/git/testdata/addedFile.diff7
-rw-r--r--pkg/git/testdata/testPatchAfter3.diff25
-rw-r--r--pkg/git/testdata/testPatchAfter4.diff19
-rw-r--r--pkg/git/testdata/testPatchBefore2.diff57
8 files changed, 249 insertions, 17 deletions
diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
index 05afcf5ba..3c523232e 100644
--- a/pkg/git/patch_modifier.go
+++ b/pkg/git/patch_modifier.go
@@ -6,11 +6,14 @@ import (
"strconv"
"strings"
+ "github.com/jesseduffield/lazygit/pkg/i18n"
+ "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
)
type PatchModifier struct {
Log *logrus.Entry
+ Tr *i18n.Localizer
}
// NewPatchModifier builds a new branch list builder
@@ -20,11 +23,49 @@ func NewPatchModifier(log *logrus.Entry) (*PatchModifier, error) {
}, nil
}
-// ModifyPatch takes the original patch, which may contain several hunks,
+// ModifyPatchForHunk takes the original patch, which may contain several hunks,
+// and removes any hunks that aren't the selected hunk
+func (p *PatchModifier) ModifyPatchForHunk(patch string, hunkStarts []int, currentLine int) (string, error) {
+ // get hunk start and end
+ lines := strings.Split(patch, "\n")
+ hunkStartIndex := utils.PrevIndex(hunkStarts, currentLine)
+ hunkStart := hunkStarts[hunkStartIndex]
+ nextHunkStartIndex := utils.NextIndex(hunkStarts, currentLine)
+ var hunkEnd int
+ if nextHunkStartIndex == 0 {
+ hunkEnd = len(lines) - 1
+ } else {
+ hunkEnd = hunkStarts[nextHunkStartIndex]
+ }
+
+ headerLength, err := p.getHeaderLength(lines)
+ if err != nil {
+ return "", err
+ }
+
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+ output += strings.Join(lines[hunkStart:hunkEnd], "\n") + "\n"
+
+ return output, nil
+}
+
+func (p *PatchModifier) getHeaderLength(patchLines []string) (int, error) {
+ for index, line := range patchLines {
+ if strings.HasPrefix(line, "@@") {
+ return index, nil
+ }
+ }
+ return 0, errors.New(p.Tr.SLocalize("CantFindHunks"))
+}
+
+// ModifyPatchForLine 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) {
+func (p *PatchModifier) ModifyPatchForLine(patch string, lineNumber int) (string, error) {
lines := strings.Split(patch, "\n")
- headerLength := 4
+ headerLength, err := p.getHeaderLength(lines)
+ if err != nil {
+ return "", err
+ }
output := strings.Join(lines[0:headerLength], "\n") + "\n"
hunkStart, err := p.getHunkStart(lines, lineNumber)
@@ -55,7 +96,8 @@ func (p *PatchModifier) getHunkStart(patchLines []string, lineNumber int) (int,
return hunkStart, nil
}
}
- return 0, errors.New("Could not find hunk")
+
+ return 0, errors.New(p.Tr.SLocalize("CantFindHunk"))
}
func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, lineNumber int) ([]string, error) {
@@ -101,13 +143,8 @@ func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, line
// @@ -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+)`)
- matches := re.FindStringSubmatch(currentHeader)
- if len(matches) < 2 {
- re = regexp.MustCompile(`^[^,]+,[^+]+\+(\d+)`)
- matches = re.FindStringSubmatch(currentHeader)
- }
- prevLengthString := matches[1]
+ re := regexp.MustCompile(`(\d+) @@`)
+ prevLengthString := re.FindStringSubmatch(currentHeader)[1]
prevLength, err := strconv.Atoi(prevLengthString)
if err != nil {
diff --git a/pkg/git/patch_modifier_test.go b/pkg/git/patch_modifier_test.go
index af7be3751..bc2073d55 100644
--- a/pkg/git/patch_modifier_test.go
+++ b/pkg/git/patch_modifier_test.go
@@ -19,7 +19,7 @@ func newDummyPatchModifier() *PatchModifier {
Log: newDummyLog(),
}
}
-func TestModifyPatch(t *testing.T) {
+func TestModifyPatchForLine(t *testing.T) {
type scenario struct {
testName string
patchFilename string
@@ -43,6 +43,27 @@ func TestModifyPatch(t *testing.T) {
false,
"testdata/testPatchAfter2.diff",
},
+ {
+ "Adding one line in top hunk in diff with multiple hunks",
+ "testdata/testPatchBefore2.diff",
+ 20,
+ false,
+ "testdata/testPatchAfter3.diff",
+ },
+ {
+ "Adding one line in top hunk in diff with multiple hunks",
+ "testdata/testPatchBefore2.diff",
+ 53,
+ false,
+ "testdata/testPatchAfter4.diff",
+ },
+ {
+ "adding unstaged file with a single line",
+ "testdata/addedFile.diff",
+ 6,
+ false,
+ "testdata/addedFile.diff",
+ },
}
for _, s := range scenarios {
@@ -52,7 +73,7 @@ func TestModifyPatch(t *testing.T) {
if err != nil {
panic("Cannot open file at " + s.patchFilename)
}
- afterPatch, err := p.ModifyPatch(string(beforePatch), s.lineNumber)
+ afterPatch, err := p.ModifyPatchForLine(string(beforePatch), s.lineNumber)
if s.shouldError {
assert.Error(t, err)
} else {
diff --git a/pkg/git/patch_parser.go b/pkg/git/patch_parser.go
index 8fa4d355b..1dbacd01c 100644
--- a/pkg/git/patch_parser.go
+++ b/pkg/git/patch_parser.go
@@ -21,15 +21,16 @@ func (p *PatchParser) ParsePatch(patch string) ([]int, []int, error) {
lines := strings.Split(patch, "\n")
hunkStarts := []int{}
stageableLines := []int{}
- headerLength := 4
- for offsetIndex, line := range lines[headerLength:] {
- index := offsetIndex + headerLength
+ pastHeader := false
+ for index, line := range lines {
if strings.HasPrefix(line, "@@") {
+ pastHeader = true
hunkStarts = append(hunkStarts, index)
}
- if strings.HasPrefix(line, "-") || strings.HasPrefix(line, "+") {
+ if pastHeader && (strings.HasPrefix(line, "-") || strings.HasPrefix(line, "+")) {
stageableLines = append(stageableLines, index)
}
}
+ p.Log.WithField("staging", "staging").Info(stageableLines)
return hunkStarts, stageableLines, nil
}
diff --git a/pkg/git/patch_parser_test.go b/pkg/git/patch_parser_test.go
new file mode 100644
index 000000000..6670aaea2
--- /dev/null
+++ b/pkg/git/patch_parser_test.go
@@ -0,0 +1,65 @@
+package git
+
+import (
+ "io/ioutil"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func newDummyPatchParser() *PatchParser {
+ return &PatchParser{
+ Log: newDummyLog(),
+ }
+}
+func TestParsePatch(t *testing.T) {
+ type scenario struct {
+ testName string
+ patchFilename string
+ shouldError bool
+ expectedStageableLines []int
+ expectedHunkStarts []int
+ }
+
+ scenarios := []scenario{
+ {
+ "Diff with one hunk",
+ "testdata/testPatchBefore.diff",
+ false,
+ []int{8, 9, 10, 11},
+ []int{4},
+ },
+ {
+ "Diff with two hunks",
+ "testdata/testPatchBefore2.diff",
+ false,
+ []int{8, 9, 10, 11, 12, 13, 20, 21, 22, 23, 24, 25, 26, 27, 28, 33, 34, 35, 36, 37, 45, 46, 47, 48, 49, 50, 51, 52, 53},
+ []int{4, 41},
+ },
+ {
+ "Unstaged file",
+ "testdata/addedFile.diff",
+ false,
+ []int{6},
+ []int{5},
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ p := newDummyPatchParser()
+ beforePatch, err := ioutil.ReadFile(s.patchFilename)
+ if err != nil {
+ panic("Cannot open file at " + s.patchFilename)
+ }
+ hunkStarts, stageableLines, err := p.ParsePatch(string(beforePatch))
+ if s.shouldError {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, s.expectedStageableLines, stageableLines)
+ assert.Equal(t, s.expectedHunkStarts, hunkStarts)
+ }
+ })
+ }
+}
diff --git a/pkg/git/testdata/addedFile.diff b/pkg/git/testdata/addedFile.diff
new file mode 100644
index 000000000..53966c4a1
--- /dev/null
+++ b/pkg/git/testdata/addedFile.diff
@@ -0,0 +1,7 @@
+diff --git a/blah b/blah
+new file mode 100644
+index 0000000..907b308
+--- /dev/null
++++ b/blah
+@@ -0,0 +1 @@
++blah
diff --git a/pkg/git/testdata/testPatchAfter3.diff b/pkg/git/testdata/testPatchAfter3.diff
new file mode 100644
index 000000000..03492450d
--- /dev/null
+++ b/pkg/git/testdata/testPatchAfter3.diff
@@ -0,0 +1,25 @@
+diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
+index a8fc600..6d8f7d7 100644
+--- a/pkg/git/patch_modifier.go
++++ b/pkg/git/patch_modifier.go
+@@ -36,18 +36,19 @@ func (p *PatchModifier) ModifyPatchForHunk(patch string, hunkStarts []int, curre
+ hunkEnd = hunkStarts[nextHunkStartIndex]
+ }
+
+ headerLength := 4
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+ output += strings.Join(lines[hunkStart:hunkEnd], "\n") + "\n"
+
+ return output, nil
+ }
+
++func getHeaderLength(patchLines []string) (int, error) {
+ // ModifyPatchForLine takes the original patch, which may contain several hunks,
+ // and the line number of the line we want to stage
+ func (p *PatchModifier) ModifyPatchForLine(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)
+
diff --git a/pkg/git/testdata/testPatchAfter4.diff b/pkg/git/testdata/testPatchAfter4.diff
new file mode 100644
index 000000000..99f894d9d
--- /dev/null
+++ b/pkg/git/testdata/testPatchAfter4.diff
@@ -0,0 +1,19 @@
+diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
+index a8fc600..6d8f7d7 100644
+--- a/pkg/git/patch_modifier.go
++++ b/pkg/git/patch_modifier.go
+@@ -124,13 +140,14 @@ func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, line
+ // @@ -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+)`)
+ matches := re.FindStringSubmatch(currentHeader)
+ if len(matches) < 2 {
+ re = regexp.MustCompile(`^[^,]+,[^+]+\+(\d+)`)
+ matches = re.FindStringSubmatch(currentHeader)
+ }
+ prevLengthString := matches[1]
++ prevLengthString := re.FindStringSubmatch(currentHeader)[1]
+
+ prevLength, err := strconv.Atoi(prevLengthString)
+ if err != nil {
diff --git a/pkg/git/testdata/testPatchBefore2.diff b/pkg/git/testdata/testPatchBefore2.diff
new file mode 100644
index 000000000..552c04f5e
--- /dev/null
+++ b/pkg/git/testdata/testPatchBefore2.diff
@@ -0,0 +1,57 @@
+diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
+index a8fc600..6d8f7d7 100644
+--- a/pkg/git/patch_modifier.go
++++ b/pkg/git/patch_modifier.go
+@@ -36,18 +36,34 @@ func (p *PatchModifier) ModifyPatchForHunk(patch string, hunkStarts []int, curre
+ hunkEnd = hunkStarts[nextHunkStartIndex]
+ }
+
+- headerLength := 4
++ headerLength, err := getHeaderLength(lines)
++ if err != nil {
++ return "", err
++ }
++
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+ output += strings.Join(lines[hunkStart:hunkEnd], "\n") + "\n"
+
+ return output, nil
+ }
+
++func getHeaderLength(patchLines []string) (int, error) {
++ for index, line := range patchLines {
++ if strings.HasPrefix(line, "@@") {
++ return index, nil
++ }
++ }
++ return 0, errors.New("Could not find any hunks in this patch")
++}
++
+ // ModifyPatchForLine takes the original patch, which may contain several hunks,
+ // and the line number of the line we want to stage
+ func (p *PatchModifier) ModifyPatchForLine(patch string, lineNumber int) (string, error) {
+ lines := strings.Split(patch, "\n")
+- headerLength := 4
++ headerLength, err := getHeaderLength(lines)
++ if err != nil {
++ return "", err
++ }
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+
+ hunkStart, err := p.getHunkStart(lines, lineNumber)
+@@ -124,13 +140,8 @@ func (p *PatchModifier) getModifiedHunk(patchLines []string, hunkStart int, line
+ // @@ -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+)`)
+- matches := re.FindStringSubmatch(currentHeader)
+- if len(matches) < 2 {
+- re = regexp.MustCompile(`^[^,]+,[^+]+\+(\d+)`)
+- matches = re.FindStringSubmatch(currentHeader)
+- }
+- prevLengthString := matches[1]
++ re := regexp.MustCompile(`(\d+) @@`)
++ prevLengthString := re.FindStringSubmatch(currentHeader)[1]
+
+ prevLength, err := strconv.Atoi(prevLengthString)
+ if err != nil {