summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-10-23 09:43:25 +1100
committerGitHub <noreply@github.com>2018-10-23 09:43:25 +1100
commit467775fc1ca1630ca5c1b4bdb2bc87d53c4e72c6 (patch)
treef0af2140c32532d23a84d4adcd79217ccca1bd6b
parent3a23cb87b70ebb3c7c82be77a264a0b3499d76c0 (diff)
parent3b6b68a2a1541fe26b39cba676b039f72142a28b (diff)
Merge branch 'master' into master
-rw-r--r--pkg/commands/git.go16
-rw-r--r--pkg/commands/os.go15
-rw-r--r--pkg/commands/os_default_platform.go1
-rw-r--r--pkg/commands/pull_request.go105
-rw-r--r--pkg/commands/pull_request_test.go152
-rw-r--r--pkg/config/config_default_platform.go3
-rw-r--r--pkg/config/config_linux.go3
-rw-r--r--pkg/config/config_windows.go3
-rw-r--r--pkg/gui/branches_panel.go11
-rw-r--r--pkg/gui/keybindings.go6
-rw-r--r--pkg/i18n/dutch.go9
-rw-r--r--pkg/i18n/english.go9
-rw-r--r--pkg/i18n/polish.go9
13 files changed, 337 insertions, 5 deletions
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 0e974b567..bb4f28b75 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -555,6 +555,22 @@ func (c *GitCommand) Show(sha string) (string, error) {
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color %s", sha))
}
+// GetRemoteURL returns current repo remote url
+func (c *GitCommand) GetRemoteURL() string {
+ url, _ := c.OSCommand.RunCommandWithOutput("git config --get remote.origin.url")
+ return utils.TrimTrailingNewline(url)
+}
+
+// CheckRemoteBranchExists Returns remote branch
+func (c *GitCommand) CheckRemoteBranchExists(branch *Branch) bool {
+ _, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(
+ "git show-ref --verify -- refs/remotes/origin/%s",
+ branch.Name,
+ ))
+
+ return err == nil
+}
+
// Diff returns the diff of a file
func (c *GitCommand) Diff(file *File) string {
cachedArg := ""
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index c8ca40f29..2caedf07d 100644
--- a/pkg/commands/os.go
+++ b/pkg/commands/os.go
@@ -8,9 +8,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
-
"github.com/mgutz/str"
-
"github.com/sirupsen/logrus"
gitconfig "github.com/tcnksm/go-gitconfig"
)
@@ -22,6 +20,7 @@ type Platform struct {
shellArg string
escapedQuote string
openCommand string
+ openLinkCommand string
fallbackEscapedQuote string
}
@@ -110,6 +109,18 @@ func (c *OSCommand) OpenFile(filename string) error {
return err
}
+// OpenFile opens a file with the given
+func (c *OSCommand) OpenLink(link string) error {
+ commandTemplate := c.Config.GetUserConfig().GetString("os.openLinkCommand")
+ templateValues := map[string]string{
+ "link": c.Quote(link),
+ }
+
+ command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
+ err := c.RunCommand(command)
+ return err
+}
+
// EditFile opens a file in a subprocess using whatever editor is available,
// falling back to core.editor, VISUAL, EDITOR, then vi
func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) {
diff --git a/pkg/commands/os_default_platform.go b/pkg/commands/os_default_platform.go
index 7b063417b..73e453b6b 100644
--- a/pkg/commands/os_default_platform.go
+++ b/pkg/commands/os_default_platform.go
@@ -13,6 +13,7 @@ func getPlatform() *Platform {
shellArg: "-c",
escapedQuote: "'",
openCommand: "open {{filename}}",
+ openLinkCommand: "open {{link}}",
fallbackEscapedQuote: "\"",
}
}
diff --git a/pkg/commands/pull_request.go b/pkg/commands/pull_request.go
new file mode 100644
index 000000000..043a3c07d
--- /dev/null
+++ b/pkg/commands/pull_request.go
@@ -0,0 +1,105 @@
+package commands
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+)
+
+// Service is a service that repository is on (Github, Bitbucket, ...)
+type Service struct {
+ Name string
+ PullRequestURL string
+}
+
+// PullRequest opens a link in browser to create new pull request
+// with selected branch
+type PullRequest struct {
+ GitServices []*Service
+ GitCommand *GitCommand
+}
+
+// RepoInformation holds some basic information about the repo
+type RepoInformation struct {
+ Owner string
+ Repository string
+}
+
+func getServices() []*Service {
+ return []*Service{
+ {
+ Name: "github.com",
+ PullRequestURL: "https://github.com/%s/%s/compare/%s?expand=1",
+ },
+ {
+ Name: "bitbucket.org",
+ PullRequestURL: "https://bitbucket.org/%s/%s/pull-requests/new?t=%s",
+ },
+ {
+ Name: "gitlab.com",
+ PullRequestURL: "https://gitlab.com/%s/%s/merge_requests/new?merge_request[source_branch]=%s",
+ },
+ }
+}
+
+// NewPullRequest creates new instance of PullRequest
+func NewPullRequest(gitCommand *GitCommand) *PullRequest {
+ return &PullRequest{
+ GitServices: getServices(),
+ GitCommand: gitCommand,
+ }
+}
+
+// Create opens link to new pull request in browser
+func (pr *PullRequest) Create(branch *Branch) error {
+ branchExistsOnRemote := pr.GitCommand.CheckRemoteBranchExists(branch)
+
+ if !branchExistsOnRemote {
+ return errors.New(pr.GitCommand.Tr.SLocalize("NoBranchOnRemote"))
+ }
+
+ repoURL := pr.GitCommand.GetRemoteURL()
+ var gitService *Service
+
+ for _, service := range pr.GitServices {
+ if strings.Contains(repoURL, service.Name) {
+ gitService = service
+ break
+ }
+ }
+
+ if gitService == nil {
+ return errors.New(pr.GitCommand.Tr.SLocalize("UnsupportedGitService"))
+ }
+
+ repoInfo := getRepoInfoFromURL(repoURL)
+
+ return pr.GitCommand.OSCommand.OpenLink(fmt.Sprintf(
+ gitService.PullRequestURL, repoInfo.Owner, repoInfo.Repository, branch.Name,
+ ))
+}
+
+func getRepoInfoFromURL(url string) *RepoInformation {
+ isHTTP := strings.HasPrefix(url, "http")
+
+ if isHTTP {
+ splits := strings.Split(url, "/")
+ owner := splits[len(splits)-2]
+ repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
+
+ return &RepoInformation{
+ Owner: owner,
+ Repository: repo,
+ }
+ }
+
+ tmpSplit := strings.Split(url, ":")
+ splits := strings.Split(tmpSplit[1], "/")
+ owner := splits[0]
+ repo := strings.TrimSuffix(splits[1], ".git")
+
+ return &RepoInformation{
+ Owner: owner,
+ Repository: repo,
+ }
+}
diff --git a/pkg/commands/pull_request_test.go b/pkg/commands/pull_request_test.go
new file mode 100644
index 000000000..a551ee081
--- /dev/null
+++ b/pkg/commands/pull_request_test.go
@@ -0,0 +1,152 @@
+package commands
+
+import (
+ "os/exec"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetRepoInfoFromURL(t *testing.T) {
+ type scenario struct {
+ testName string
+ repoURL string
+ test func(*RepoInformation)
+ }
+
+ scenarios := []scenario{
+ {
+ "Returns repository information for git remote url",
+ "git@github.com:petersmith/super_calculator",
+ func(repoInfo *RepoInformation) {
+ assert.EqualValues(t, repoInfo.Owner, "petersmith")
+ assert.EqualValues(t, repoInfo.Repository, "super_calculator")
+ },
+ },
+ {
+ "Returns repository information for http remote url",
+ "https://my_username@bitbucket.org/johndoe/social_network.git",
+ func(repoInfo *RepoInformation) {
+ assert.EqualValues(t, repoInfo.Owner, "johndoe")
+ assert.EqualValues(t, repoInfo.Repository, "social_network")
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ s.test(getRepoInfoFromURL(s.repoURL))
+ })
+ }
+}
+
+func TestCreatePullRequest(t *testing.T) {
+ type scenario struct {
+ testName string
+ branch *Branch
+ command func(string, ...string) *exec.Cmd
+ test func(err error)
+ }
+
+ scenarios := []scenario{
+ {
+ "Opens a link to new pull request on bitbucket",
+ &Branch{
+ Name: "feature/profile-page",
+ },
+ func(cmd string, args ...string) *exec.Cmd {
+ // Handle git remote url call
+ if strings.HasPrefix(cmd, "git") {
+ return exec.Command("echo", "git@bitbucket.org:johndoe/social_network.git")
+ }
+
+ assert.Equal(t, cmd, "open")
+ assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?t=feature/profile-page"})
+ return exec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ {
+ "Opens a link to new pull request on bitbucket with http remote url",
+ &Branch{
+ Name: "feature/events",
+ },
+ func(cmd string, args ...string) *exec.Cmd {
+ // Handle git remote url call
+ if strings.HasPrefix(cmd, "git") {
+ return exec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git")
+ }
+
+ assert.Equal(t, cmd, "open")
+ assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?t=feature/events"})
+ return exec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ {
+ "Opens a link to new pull request on github",
+ &Branch{
+ Name: "feature/sum-operation",
+ },
+ func(cmd string, args ...string) *exec.Cmd {
+ // Handle git remote url call
+ if strings.HasPrefix(cmd, "git") {
+ return exec.Command("echo", "git@github.com:peter/calculator.git")
+ }
+
+ assert.Equal(t, cmd, "open")
+ assert.Equal(t, args, []string{"https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"})
+ return exec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ {
+ "Opens a link to new pull request on gitlab",
+ &Branch{
+ Name: "feature/ui",
+ },
+ func(cmd string, args ...string) *exec.Cmd {
+ // Handle git remote url call
+ if strings.HasPrefix(cmd, "git") {
+ return exec.Command("echo", "git@gitlab.com:peter/calculator.git")
+ }
+
+ assert.Equal(t, cmd, "open")
+ assert.Equal(t, args, []string{"https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"})
+ return exec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ {
+ "Throws an error if git service is unsupported",
+ &Branch{
+ Name: "feature/divide-operation",
+ },
+ func(cmd string, args ...string) *exec.Cmd {
+ return exec.Command("echo", "git@something.com:peter/calculator.git")
+ },
+ func(err error) {
+ assert.Error(t, err)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCommand := newDummyGitCommand()
+ gitCommand.OSCommand.command = s.command
+ gitCommand.OSCommand.Config.GetUserConfig().Set("os.openLinkCommand", "open {{link}}")
+ dummyPullRequest := NewPullRequest(gitCommand)
+ s.test(dummyPullRequest.Create(s.branch))
+ })
+ }
+}
diff --git a/pkg/config/config_default_platform.go b/pkg/config/config_default_platform.go
index f3c1a36e5..df205c0d7 100644
--- a/pkg/config/config_default_platform.go
+++ b/pkg/config/config_default_platform.go
@@ -6,5 +6,6 @@ package config
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
- openCommand: 'open {{filename}}'`)
+ openCommand: 'open {{filename}}'
+ openLinkCommand: 'open {{link}}'`)
}
diff --git a/pkg/config/config_linux.go b/pkg/config/config_linux.go
index 90e922416..2dfbdb1c6 100644
--- a/pkg/config/config_linux.go
+++ b/pkg/config/config_linux.go
@@ -4,5 +4,6 @@ package config
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
- openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'`)
+ openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
+ openLinkCommand: 'sh -c "xdg-open {{link}} >/dev/null"'`)
}
diff --git a/pkg/config/config_windows.go b/pkg/config/config_windows.go
index b81a5fdb5..6f6560316 100644
--- a/pkg/config/config_windows.go
+++ b/pkg/config/config_windows.go
@@ -4,5 +4,6 @@ package config
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
- openCommand: 'cmd /c "start "" {{filename}}"'`)
+ openCommand: 'cmd /c "start "" {{filename}}"'
+ openLinkCommand: 'cmd /c "start "" {{link}}"'`)
}
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go
index 5c33156be..dbf4b007a 100644
--- a/pkg/gui/branches_panel.go
+++ b/pkg/gui/branches_panel.go
@@ -22,6 +22,17 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
return gui.refreshSidePanels(g)
}
+func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
+ branch := gui.getSelectedBranch(gui.getBranchesView(g))
+ pullRequest := commands.NewPullRequest(gui.GitCommand)
+
+ if err := pullRequest.Create(branch); err != nil {
+ return gui.createErrorPanel(g, err.Error())
+ }
+
+ return nil
+}
+
func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch(v)
message := gui.Tr.SLocalize("SureForceCheckout")
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 4eea64303..8d07429a6 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -279,6 +279,12 @@ func (gui *Gui) GetKeybindings() []*Binding {
Description: gui.Tr.SLocalize("checkout"),
}, {
ViewName: "branches",
+ Key: 'o',
+ Modifier: gocui.ModNone,
+ Handler: gui.handleCreatePullRequestPress,
+ Description: gui.Tr.SLocalize("createPullRequest"),
+ }, {
+ ViewName: "branches",
Key: 'c',
Modifier: gocui.ModNone,
Handler: gui.handleCheckoutByName,
diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go
index 6b24a5174..74b230ab4 100644
--- a/pkg/i18n/dutch.go
+++ b/pkg/i18n/dutch.go
@@ -379,6 +379,15 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "ConfirmQuit",
Other: `Weet je zeker dat je dit programma wil sluiten?`,
+ }, &i18n.Message{
+ ID: "UnsupportedGitService",
+ Other: `Niet-ondersteunde git-service`,
+ }, &i18n.Message{
+ ID: "createPullRequest",
+ Other: `maak een pull-aanvraag`,
+ }, &i18n.Message{
+ ID: "NoBranchOnRemote",
+ Other: `Deze tak bestaat niet op de afstandsbediening. U moet eerst op de afstandsbediening drukken.`,
},
)
}
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 559dbb70c..9e0e60166 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -402,6 +402,15 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "SwitchRepo",
Other: `switch to a recent repo`,
+ }, &i18n.Message{
+ ID: "UnsupportedGitService",
+ Other: `Unsupported git service`,
+ }, &i18n.Message{
+ ID: "createPullRequest",
+ Other: `create pull request`,
+ }, &i18n.Message{
+ ID: "NoBranchOnRemote",
+ Other: `This branch doesn't exist on remote. You need to push it to remote first.`,
},
)
}
diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go
index c37bfe972..975035771 100644
--- a/pkg/i18n/polish.go
+++ b/pkg/i18n/polish.go
@@ -377,6 +377,15 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "ConfirmQuit",
Other: `Na pewno chcesz wyjść z programu?`,
+ }, &i18n.Message{
+ ID: "UnsupportedGitService",
+ Other: `Nieobsługiwana usługa git`,
+ }, &i18n.Message{
+ ID: "createPullRequest",
+ Other: `utwórz żądanie wyciągnięcia`,
+ }, &i18n.Message{
+ ID: "NoBranchOnRemote",
+ Other: `Ta gałąź nie istnieje na zdalnym. Najpierw musisz go odepchnąć na odległość.`,
},
)
}