summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorMark Kopenga <mkopenga@gmail.com>2018-12-06 08:31:12 +0100
committerGitHub <noreply@github.com>2018-12-06 08:31:12 +0100
commit1b6d34e76a4d5d132f95072139fb3aba760eb771 (patch)
treefb6cc607a67f0d3d2ef0b9d66fafd439864e6d9a /pkg
parent67115436347dec591d12a1a31d1c971915cf0b05 (diff)
parent1a6a69a8f1f7c44978a384ba56321149f973223d (diff)
Merge branch 'master' into https-ask-for-username-password
Diffstat (limited to 'pkg')
-rw-r--r--pkg/commands/commit.go1
-rw-r--r--pkg/commands/git.go23
-rw-r--r--pkg/commands/git_test.go122
-rw-r--r--pkg/commands/os.go28
-rw-r--r--pkg/commands/os_test.go41
-rw-r--r--pkg/commands/pull_request_test.go2
-rw-r--r--pkg/git/patch_modifier.go156
-rw-r--r--pkg/git/patch_modifier_test.go89
-rw-r--r--pkg/git/patch_parser.go36
-rw-r--r--pkg/git/patch_parser_test.go65
-rw-r--r--pkg/git/testdata/addedFile.diff7
-rw-r--r--pkg/git/testdata/testPatchAfter1.diff13
-rw-r--r--pkg/git/testdata/testPatchAfter2.diff14
-rw-r--r--pkg/git/testdata/testPatchAfter3.diff25
-rw-r--r--pkg/git/testdata/testPatchAfter4.diff19
-rw-r--r--pkg/git/testdata/testPatchBefore.diff15
-rw-r--r--pkg/git/testdata/testPatchBefore2.diff57
-rw-r--r--pkg/gui/commit_message_panel.go1
-rw-r--r--pkg/gui/confirmation_panel.go12
-rw-r--r--pkg/gui/files_panel.go25
-rw-r--r--pkg/gui/gui.go21
-rw-r--r--pkg/gui/keybindings.go68
-rw-r--r--pkg/gui/recent_repos_panel.go1
-rw-r--r--pkg/gui/staging_panel.go235
-rw-r--r--pkg/gui/view_helpers.go13
-rw-r--r--pkg/i18n/dutch.go24
-rw-r--r--pkg/i18n/english.go24
-rw-r--r--pkg/i18n/i18n_test.go4
-rw-r--r--pkg/i18n/polish.go24
-rw-r--r--pkg/utils/utils.go21
-rw-r--r--pkg/utils/utils_test.go106
31 files changed, 1282 insertions, 10 deletions
diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go
index 54e94ef60..4c304271d 100644
--- a/pkg/commands/commit.go
+++ b/pkg/commands/commit.go
@@ -13,6 +13,7 @@ type Commit struct {
DisplayString string
}
+// GetDisplayStrings is a function.
func (c *Commit) GetDisplayStrings() []string {
red := color.New(color.FgRed)
yellow := color.New(color.FgGreen)
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 2d2710a42..584558d7e 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -267,6 +267,7 @@ func (c *GitCommand) NewBranch(name string) error {
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -b %s", name))
}
+// CurrentBranchName is a function.
func (c *GitCommand) CurrentBranchName() (string, error) {
branchName, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
if err != nil {
@@ -490,7 +491,6 @@ func (c *GitCommand) getMergeBase() (string, error) {
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git merge-base HEAD %s", baseBranch))
if err != nil {
// swallowing error because it's not a big deal; probably because there are no commits yet
- c.Log.Error(err)
}
return output, nil
}
@@ -576,9 +576,10 @@ func (c *GitCommand) CheckRemoteBranchExists(branch *Branch) bool {
}
// Diff returns the diff of a file
-func (c *GitCommand) Diff(file *File) string {
+func (c *GitCommand) Diff(file *File, plain bool) string {
cachedArg := ""
trackedArg := "--"
+ colorArg := "--color"
fileName := c.OSCommand.Quote(file.Name)
if file.HasStagedChanges && !file.HasUnstagedChanges {
cachedArg = "--cached"
@@ -586,9 +587,25 @@ func (c *GitCommand) Diff(file *File) string {
if !file.Tracked && !file.HasStagedChanges {
trackedArg = "--no-index /dev/null"
}
- command := fmt.Sprintf("git diff --color %s %s %s", cachedArg, trackedArg, fileName)
+ if plain {
+ colorArg = ""
+ }
+
+ command := fmt.Sprintf("git diff %s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
// for now we assume an error means the file was deleted
s, _ := c.OSCommand.RunCommandWithOutput(command)
return s
}
+
+func (c *GitCommand) ApplyPatch(patch string) (string, error) {
+ filename, err := c.OSCommand.CreateTempFile("patch", patch)
+ if err != nil {
+ c.Log.Error(err)
+ return "", err
+ }
+
+ defer func() { _ = c.OSCommand.RemoveFile(filename) }()
+
+ return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", filename))
+}
diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go
index 0410ceb2c..da6a90c98 100644
--- a/pkg/commands/git_test.go
+++ b/pkg/commands/git_test.go
@@ -23,26 +23,32 @@ type fileInfoMock struct {
sys interface{}
}
+// Name is a function.
func (f fileInfoMock) Name() string {
return f.name
}
+// Size is a function.
func (f fileInfoMock) Size() int64 {
return f.size
}
+// Mode is a function.
func (f fileInfoMock) Mode() os.FileMode {
return f.fileMode
}
+// ModTime is a function.
func (f fileInfoMock) ModTime() time.Time {
return f.fileModTime
}
+// IsDir is a function.
func (f fileInfoMock) IsDir() bool {
return f.isDir
}
+// Sys is a function.
func (f fileInfoMock) Sys() interface{} {
return f.sys
}
@@ -64,6 +70,7 @@ func newDummyGitCommand() *GitCommand {
}
}
+// TestVerifyInGitRepo is a function.
func TestVerifyInGitRepo(t *testing.T) {
type scenario struct {
testName string
@@ -100,6 +107,7 @@ func TestVerifyInGitRepo(t *testing.T) {
}
}
+// TestNavigateToRepoRootDirectory is a function.
func TestNavigateToRepoRootDirectory(t *testing.T) {
type scenario struct {
testName string
@@ -156,6 +164,7 @@ func TestNavigateToRepoRootDirectory(t *testing.T) {
}
}
+// TestSetupRepositoryAndWorktree is a function.
func TestSetupRepositoryAndWorktree(t *testing.T) {
type scenario struct {
testName string
@@ -224,6 +233,7 @@ func TestSetupRepositoryAndWorktree(t *testing.T) {
}
}
+// TestNewGitCommand is a function.
func TestNewGitCommand(t *testing.T) {
actual, err := os.Getwd()
assert.NoError(t, err)
@@ -271,6 +281,7 @@ func TestNewGitCommand(t *testing.T) {
}
}
+// TestGitCommandGetStashEntries is a function.
func TestGitCommandGetStashEntries(t *testing.T) {
type scenario struct {
testName string
@@ -323,6 +334,7 @@ func TestGitCommandGetStashEntries(t *testing.T) {
}
}
+// TestGitCommandGetStashEntryDiff is a function.
func TestGitCommandGetStashEntryDiff(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -337,6 +349,7 @@ func TestGitCommandGetStashEntryDiff(t *testing.T) {
assert.NoError(t, err)
}
+// TestGitCommandGetStatusFiles is a function.
func TestGitCommandGetStatusFiles(t *testing.T) {
type scenario struct {
testName string
@@ -423,6 +436,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
}
}
+// TestGitCommandStashDo is a function.
func TestGitCommandStashDo(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -435,6 +449,7 @@ func TestGitCommandStashDo(t *testing.T) {
assert.NoError(t, gitCmd.StashDo(1, "drop"))
}
+// TestGitCommandStashSave is a function.
func TestGitCommandStashSave(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -447,6 +462,7 @@ func TestGitCommandStashSave(t *testing.T) {
assert.NoError(t, gitCmd.StashSave("A stash message"))
}
+// TestGitCommandCommitAmend is a function.
func TestGitCommandCommitAmend(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -460,6 +476,7 @@ func TestGitCommandCommitAmend(t *testing.T) {
assert.NoError(t, err)
}
+// TestGitCommandMergeStatusFiles is a function.
func TestGitCommandMergeStatusFiles(t *testing.T) {
type scenario struct {
testName string
@@ -540,6 +557,7 @@ func TestGitCommandMergeStatusFiles(t *testing.T) {
}
}
+// TestGitCommandUpstreamDifferentCount is a function.
func TestGitCommandUpstreamDifferentCount(t *testing.T) {
type scenario struct {
testName string
@@ -597,6 +615,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) {
}
}
+// TestGitCommandGetCommitsToPush is a function.
func TestGitCommandGetCommitsToPush(t *testing.T) {
type scenario struct {
testName string
@@ -635,6 +654,7 @@ func TestGitCommandGetCommitsToPush(t *testing.T) {
}
}
+// TestGitCommandRenameCommit is a function.
func TestGitCommandRenameCommit(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -647,6 +667,7 @@ func TestGitCommandRenameCommit(t *testing.T) {
assert.NoError(t, gitCmd.RenameCommit("test"))
}
+// TestGitCommandResetToCommit is a function.
func TestGitCommandResetToCommit(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -659,6 +680,7 @@ func TestGitCommandResetToCommit(t *testing.T) {
assert.NoError(t, gitCmd.ResetToCommit("78976bc"))
}
+// TestGitCommandNewBranch is a function.
func TestGitCommandNewBranch(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -671,6 +693,7 @@ func TestGitCommandNewBranch(t *testing.T) {
assert.NoError(t, gitCmd.NewBranch("test"))
}
+// TestGitCommandDeleteBranch is a function.
func TestGitCommandDeleteBranch(t *testing.T) {
type scenario struct {
testName string
@@ -720,6 +743,7 @@ func TestGitCommandDeleteBranch(t *testing.T) {
}
}
+// TestGitCommandMerge is a function.
func TestGitCommandMerge(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -732,6 +756,7 @@ func TestGitCommandMerge(t *testing.T) {
assert.NoError(t, gitCmd.Merge("test"))
}
+// TestGitCommandUsingGpg is a function.
func TestGitCommandUsingGpg(t *testing.T) {
type scenario struct {
testName string
@@ -825,6 +850,7 @@ func TestGitCommandUsingGpg(t *testing.T) {
}
}
+// TestGitCommandCommit is a function.
func TestGitCommandCommit(t *testing.T) {
type scenario struct {
testName string
@@ -894,6 +920,7 @@ func TestGitCommandCommit(t *testing.T) {
}
}
+// TestGitCommandCommitAmendFromFiles is a function.
func TestGitCommandCommitAmendFromFiles(t *testing.T) {
type scenario struct {
testName string
@@ -963,6 +990,7 @@ func TestGitCommandCommitAmendFromFiles(t *testing.T) {
}
}
+// TestGitCommandPush is a function.
func TestGitCommandPush(t *testing.T) {
type scenario struct {
testName string
@@ -1025,6 +1053,7 @@ func TestGitCommandPush(t *testing.T) {
}
}
+// TestGitCommandSquashPreviousTwoCommits is a function.
func TestGitCommandSquashPreviousTwoCommits(t *testing.T) {
type scenario struct {
testName string
@@ -1088,6 +1117,7 @@ func TestGitCommandSquashPreviousTwoCommits(t *testing.T) {
}
}
+// TestGitCommandSquashFixupCommit is a function.
func TestGitCommandSquashFixupCommit(t *testing.T) {
type scenario struct {
testName string
@@ -1151,6 +1181,7 @@ func TestGitCommandSquashFixupCommit(t *testing.T) {
}
}
+// TestGitCommandCatFile is a function.
func TestGitCommandCatFile(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -1165,6 +1196,7 @@ func TestGitCommandCatFile(t *testing.T) {
assert.Equal(t, "test", o)
}
+// TestGitCommandStageFile is a function.
func TestGitCommandStageFile(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -1177,6 +1209,7 @@ func TestGitCommandStageFile(t *testing.T) {
assert.NoError(t, gitCmd.StageFile("test.txt"))
}
+// TestGitCommandUnstageFile is a function.
func TestGitCommandUnstageFile(t *testing.T) {
type scenario struct {
testName string
@@ -1223,6 +1256,7 @@ func TestGitCommandUnstageFile(t *testing.T) {
}
}
+// TestGitCommandIsInMergeState is a function.
func TestGitCommandIsInMergeState(t *testing.T) {
type scenario struct {
testName string
@@ -1291,6 +1325,7 @@ func TestGitCommandIsInMergeState(t *testing.T) {
}
}
+// TestGitCommandRemoveFile is a function.
func TestGitCommandRemoveFile(t *testing.T) {
type scenario struct {
testName string
@@ -1492,6 +1527,7 @@ func TestGitCommandRemoveFile(t *testing.T) {
}
}
+// TestGitCommandShow is a function.
func TestGitCommandShow(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -1505,6 +1541,7 @@ func TestGitCommandShow(t *testing.T) {
assert.NoError(t, err)
}
+// TestGitCommandCheckout is a function.
func TestGitCommandCheckout(t *testing.T) {
type scenario struct {
testName string
@@ -1551,6 +1588,7 @@ func TestGitCommandCheckout(t *testing.T) {
}
}
+// TestGitCommandGetBranchGraph is a function.
func TestGitCommandGetBranchGraph(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
@@ -1564,6 +1602,7 @@ func TestGitCommandGetBranchGraph(t *testing.T) {
assert.NoError(t, err)
}
+// TestGitCommandGetCommits is a function.
func TestGitCommandGetCommits(t *testing.T) {
type scenario struct {
testName string
@@ -1685,6 +1724,7 @@ func TestGitCommandGetCommits(t *testing.T) {
}
}
+// TestGitCommandGetLog is a function.
func TestGitCommandGetLog(t *testing.T) {
type scenario struct {
testName string
@@ -1727,11 +1767,13 @@ func TestGitCommandGetLog(t *testing.T) {
}
}
+// TestGitCommandDiff is a function.
func TestGitCommandDiff(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
file *File
+ plain bool
}
scenarios := []scenario{
@@ -1748,6 +1790,22 @@ func TestGitCommandDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: true,
},
+ false,
+ },
+ {
+ "Default case",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"diff", "--", "test.txt"}, args)
+
+ return exec.Command("echo")
+ },
+ &File{
+ Name: "test.txt",
+ HasStagedChanges: false,
+ Tracked: true,
+ },
+ true,
},
{
"All changes staged",
@@ -1763,6 +1821,7 @@ func TestGitCommandDiff(t *testing.T) {
HasUnstagedChanges: false,
Tracked: true,
},
+ false,
},
{
"File not tracked and file has no staged changes",
@@ -1777,6 +1836,7 @@ func TestGitCommandDiff(t *testing.T) {
HasStagedChanges: false,
Tracked: false,
},
+ false,
},
}
@@ -1784,11 +1844,12 @@ func TestGitCommandDiff(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command
- gitCmd.Diff(s.file)
+ gitCmd.Diff(s.file, s.plain)
})
}
}
+// TestGitCommandGetMergeBase is a function.
func TestGitCommandGetMergeBase(t *testing.T) {
type scenario struct {
testName string
@@ -1878,6 +1939,7 @@ func TestGitCommandGetMergeBase(t *testing.T) {
}
}
+// TestGitCommandCurrentBranchName is a function.
func TestGitCommandCurrentBranchName(t *testing.T) {
type scenario struct {
testName string
@@ -1939,3 +2001,61 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
})
}
}
+
+func TestGitCommandApplyPatch(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func(string, error)
+ }
+
+ scenarios := []scenario{
+ {
+ "valid case",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.Equal(t, "git", cmd)
+ assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2])
+ filename := args[2]
+ content, err := ioutil.ReadFile(filename)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "test", string(content))
+
+ return exec.Command("echo", "done")
+ },
+ func(output string, err error) {
+ assert.NoError(t, err)
+ assert.EqualValues(t, "done\n", output)
+ },
+ },
+ {
+ "command returns error",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.Equal(t, "git", cmd)
+ assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2])
+ filename := args[2]
+ // TODO: Ideally we want to mock out OSCommand here so that we're not
+ // double handling testing it's CreateTempFile functionality,
+ // but it is going to take a bit of work to make a proper mock for it
+ // so I'm leaving it for another PR
+ content, err := ioutil.ReadFile(filename)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "test", string(content))
+
+ return exec.Command("test")
+ },
+ func(output string, err error) {
+ assert.Error(t, err)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := newDummyGitCommand()
+ gitCmd.OSCommand.command = s.command
+ s.test(gitCmd.ApplyPatch("test"))
+ })
+ }
+}
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index f64f0732e..faf6c5aec 100644
--- a/pkg/commands/os.go
+++ b/pkg/commands/os.go
@@ -2,6 +2,7 @@ package commands
import (
"errors"
+ "io/ioutil"
"os"
"os/exec"
"regexp"
@@ -157,7 +158,7 @@ func (c *OSCommand) OpenFile(filename string) error {
return err
}
-// OpenFile opens a file with the given
+// OpenLink opens a file with the given
func (c *OSCommand) OpenLink(link string) error {
commandTemplate := c.Config.GetUserConfig().GetString("os.openLinkCommand")
templateValues := map[string]string{
@@ -224,3 +225,28 @@ func (c *OSCommand) AppendLineToFile(filename, line string) error {
_, err = f.WriteString("\n" + line)
return err
}
+
+// CreateTempFile writes a string to a new temp file and returns the file's name
+func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
+ tmpfile, err := ioutil.TempFile("", filename)
+ if err != nil {
+ c.Log.Error(err)
+ return "", err
+ }
+
+ if _, err := tmpfile.Write([]byte(content)); err != nil {
+ c.Log.Error(err)
+ return "", err
+ }
+ if err := tmpfile.Close(); err != nil {
+ c.Log.Error(err)
+ return "", err
+ }
+
+ return tmpfile.Name(), nil
+}
+
+// RemoveFile removes a file at the specified path
+func (c *OSCommand) RemoveFile(filename string) error {
+ return os.Remove(filename)
+}
diff --git a/pkg/commands/os_test.go b/pkg/commands/os_test.go
index aeef4a6e5..a08c4b57d 100644
--- a/pkg/commands/os_test.go
+++ b/pkg/commands/os_test.go
@@ -1,6 +1,7 @@
package commands
import (
+ "io/ioutil"
"os"
"os/exec"
"testing"
@@ -29,6 +30,7 @@ func newDummyAppConfig() *config.AppConfig {
return appConfig
}
+// TestOSCommandRunCommandWithOutput is a function.
func TestOSCommandRunCommandWithOutput(t *testing.T) {
type scenario struct {
command string
@@ -56,6 +58,7 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) {
}
}
+// TestOSCommandRunCommand is a function.
func TestOSCommandRunCommand(t *testing.T) {
type scenario struct {
command string
@@ -76,6 +79,7 @@ func TestOSCommandRunCommand(t *testing.T) {
}
}
+// TestOSCommandOpenFile is a function.
func TestOSCommandOpenFile(t *testing.T) {
type scenario struct {
filename string
@@ -126,6 +130,7 @@ func TestOSCommandOpenFile(t *testing.T) {
}
}
+// TestOSCommandEditFile is a function.
func TestOSCommandEditFile(t *testing.T) {
type scenario struct {
filename string
@@ -255,6 +260,7 @@ func TestOSCommandEditFile(t *testing.T) {
}
}
+// TestOSCommandQuote is a function.
func TestOSCommandQuote(t *testing.T) {
osCommand := newDummyOSCommand()
@@ -278,7 +284,7 @@ func TestOSCommandQuoteSingleQuote(t *testing.T) {
assert.EqualValues(t, expected, actual)
}
-// TestOSCommandQuoteSingleQuote tests the quote function with " quotes explicitly for Linux
+// TestOSCommandQuoteDoubleQuote tests the quote function with " quotes explicitly for Linux
func TestOSCommandQuoteDoubleQuote(t *testing.T) {
osCommand := newDummyOSCommand()
@@ -291,6 +297,7 @@ func TestOSCommandQuoteDoubleQuote(t *testing.T) {
assert.EqualValues(t, expected, actual)
}
+// TestOSCommandUnquote is a function.
func TestOSCommandUnquote(t *testing.T) {
osCommand := newDummyOSCommand()
@@ -301,6 +308,7 @@ func TestOSCommandUnquote(t *testing.T) {
assert.EqualValues(t, expected, actual)
}
+// TestOSCommandFileType is a function.
func TestOSCommandFileType(t *testing.T) {
type scenario struct {
path string
@@ -357,3 +365,34 @@ func TestOSCommandFileType(t *testing.T) {
_ = os.RemoveAll(s.path)
}
}
+
+func TestOSCommandCreateTempFile(t *testing.T) {
+ type scenario struct {
+ testName string
+ filename string
+ content string
+ test func(string, error)
+ }
+
+ scenarios := []scenario{
+ {
+ "valid case",
+ "filename",
+ "content",
+ func(path string, err error) {
+ assert.NoError(t, err)
+
+ content, err := ioutil.ReadFile(path)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "content", string(content))
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ s.test(newDummyOSCommand().CreateTempFile(s.filename, s.content))
+ })
+ }
+}
diff --git a/pkg/commands/pull_request_test.go b/pkg/commands/pull_request_test.go
index a551ee081..774baf92e 100644
--- a/pkg/commands/pull_request_test.go
+++ b/pkg/commands/pull_request_test.go
@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
)
+// TestGetRepoInfoFromURL is a function.
func TestGetRepoInfoFromURL(t *testing.T) {
type scenario struct {
testName string
@@ -41,6 +42,7 @@ func TestGetRepoInfoFromURL(t *testing.T) {
}
}
+// TestCreatePullRequest is a function.
func TestCreatePullRequest(t *testing.T) {
type scenario struct {
testName string
diff --git a/pkg/git/patch_modifier.go b/pkg/git/patch_modifier.go
new file mode 100644
index 000000000..3c523232e
--- /dev/null
+++ b/pkg/git/patch_modifier.go
@@ -0,0 +1,156 @@
+package git
+
+import (
+ "errors"
+ "regexp"
+ "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
+func NewPatchModifier(log *logrus.Entry) (*PatchModifier, error) {
+ return &PatchModifier{
+ Log: log,
+ }, nil
+}
+
+// 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) ModifyPatchForLine(patch string, lineNumber int) (string, error) {
+ lines := strings.Split(patch, "\n")
+ headerLength, err := p.getHeaderLength(lines)
+ if err != nil {
+ return "", err
+ }
+ output := strings.Join(lines[0:headerLength], "\n") + "\n"
+
+ hunkStart, err := p.getHunkStart(lines, lineNumber)
+ if err != nil {