1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-03 13:21:56 +02:00

Add option to create pull request form branches panel.

This commit is contained in:
Kristijan Husak 2018-10-12 14:06:03 +02:00
parent d5f64602a8
commit df0e3e52fe
8 changed files with 293 additions and 0 deletions

View File

@ -555,6 +555,12 @@ func (c *GitCommand) Show(sha string) (string, error) {
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color %s", sha)) 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)
}
// Diff returns the diff of a file // Diff returns the diff of a file
func (c *GitCommand) Diff(file *File) string { func (c *GitCommand) Diff(file *File) string {
cachedArg := "" cachedArg := ""

View File

@ -0,0 +1,101 @@
package commands
import (
"errors"
"fmt"
"regexp"
"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, error) {
return &PullRequest{
GitServices: getServices(),
GitCommand: gitCommand,
}, nil
}
// Create opens link to new pull request in browser
func (pr *PullRequest) Create(branch *Branch) error {
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.OpenFile(fmt.Sprintf(
gitService.PullRequestURL, repoInfo.Owner, repoInfo.Repository, branch.Name,
))
}
func getRepoInfoFromURL(url string) *RepoInformation {
isHTTP := strings.HasPrefix(url, "http")
removeGitExtension := regexp.MustCompile(`\.git$`)
if isHTTP {
splits := strings.Split(url, "/")
owner := splits[len(splits)-2]
repo := removeGitExtension.ReplaceAllString(splits[len(splits)-1], "")
return &RepoInformation{
Owner: owner,
Repository: repo,
}
}
tmpSplit := strings.Split(url, ":")
splits := strings.Split(tmpSplit[1], "/")
owner := splits[0]
repo := removeGitExtension.ReplaceAllString(splits[1], "")
return &RepoInformation{
Owner: owner,
Repository: repo,
}
}

View File

@ -0,0 +1,151 @@
package commands
import (
"github.com/stretchr/testify/assert"
"os/exec"
"strings"
"testing"
)
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.openCommand", "open {{filename}}")
dummyPullRequest, _ := NewPullRequest(gitCommand)
s.test(dummyPullRequest.Create(s.branch))
})
}
}

View File

@ -22,6 +22,17 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
return gui.refreshSidePanels(g) 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 { func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch(v) branch := gui.getSelectedBranch(v)
message := gui.Tr.SLocalize("SureForceCheckout") message := gui.Tr.SLocalize("SureForceCheckout")

View File

@ -277,6 +277,12 @@ func (gui *Gui) GetKeybindings() []*Binding {
Handler: gui.handleBranchPress, Handler: gui.handleBranchPress,
KeyReadable: "space", KeyReadable: "space",
Description: gui.Tr.SLocalize("checkout"), Description: gui.Tr.SLocalize("checkout"),
}, {
ViewName: "branches",
Key: 'o',
Modifier: gocui.ModNone,
Handler: gui.handleCreatePullRequestPress,
Description: gui.Tr.SLocalize("createPullRequest"),
}, { }, {
ViewName: "branches", ViewName: "branches",
Key: 'c', Key: 'c',

View File

@ -379,6 +379,12 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{ }, &i18n.Message{
ID: "ConfirmQuit", ID: "ConfirmQuit",
Other: `Weet je zeker dat je dit programma wil sluiten?`, 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`,
}, },
) )
} }

View File

@ -402,6 +402,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{ }, &i18n.Message{
ID: "SwitchRepo", ID: "SwitchRepo",
Other: `switch to a recent repo`, Other: `switch to a recent repo`,
}, &i18n.Message{
ID: "UnsupportedGitService",
Other: `Unsupported git service`,
}, &i18n.Message{
ID: "createPullRequest",
Other: `create pull request`,
}, },
) )
} }

View File

@ -377,6 +377,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{ }, &i18n.Message{
ID: "ConfirmQuit", ID: "ConfirmQuit",
Other: `Na pewno chcesz wyjść z programu?`, 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`,
}, },
) )
} }