summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2021-12-28 13:58:09 +1100
committerJesse Duffield <jessedduffield@gmail.com>2021-12-29 09:01:06 +1100
commit9ef65574db3f0fd30b3782a8582212619edf4650 (patch)
tree70a85c69018d6bec09d42095245fde3428e7fb01 /pkg
parentf89747451a609484bea0b2b5a004a6c3da66aaeb (diff)
refactor to rename pull_request to hosting_service and apply SRP
Diffstat (limited to 'pkg')
-rw-r--r--pkg/commands/hosting_service/definitions.go54
-rw-r--r--pkg/commands/hosting_service/hosting_service.go201
-rw-r--r--pkg/commands/hosting_service/hosting_service_test.go233
-rw-r--r--pkg/commands/pull_request.go281
-rw-r--r--pkg/commands/pull_request_default_test.go256
-rw-r--r--pkg/commands/pull_request_test.go64
-rw-r--r--pkg/commands/pull_request_windows_test.go256
-rw-r--r--pkg/gui/branches_panel.go16
-rw-r--r--pkg/gui/commits_panel.go10
-rw-r--r--pkg/gui/pull_request_menu_panel.go17
-rw-r--r--pkg/i18n/english.go3
-rw-r--r--pkg/i18n/i18n.go4
-rw-r--r--pkg/test/log.go41
13 files changed, 569 insertions, 867 deletions
diff --git a/pkg/commands/hosting_service/definitions.go b/pkg/commands/hosting_service/definitions.go
new file mode 100644
index 000000000..c70062a67
--- /dev/null
+++ b/pkg/commands/hosting_service/definitions.go
@@ -0,0 +1,54 @@
+package hosting_service
+
+// if you want to make a custom regex for a given service feel free to test it out
+// at regoio.herokuapp.com
+var defaultUrlRegexStrings = []string{
+ `^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
+ `^git@.*:(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
+}
+
+// we've got less type safety using go templates but this lends itself better to
+// users adding custom service definitions in their config
+var githubServiceDef = ServiceDefinition{
+ provider: "github",
+ pullRequestURLIntoDefaultBranch: "/compare/{{.From}}?expand=1",
+ pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}?expand=1",
+ commitURL: "/commit/{{.CommitSha}}",
+ regexStrings: defaultUrlRegexStrings,
+}
+
+var bitbucketServiceDef = ServiceDefinition{
+ provider: "bitbucket",
+ pullRequestURLIntoDefaultBranch: "/pull-requests/new?source={{.From}}&t=1",
+ pullRequestURLIntoTargetBranch: "/pull-requests/new?source={{.From}}&dest={{.To}}&t=1",
+ commitURL: "/commits/{{.CommitSha}}",
+ regexStrings: defaultUrlRegexStrings,
+}
+
+var gitLabServiceDef = ServiceDefinition{
+ provider: "gitlab",
+ pullRequestURLIntoDefaultBranch: "/merge_requests/new?merge_request[source_branch]={{.From}}",
+ pullRequestURLIntoTargetBranch: "/merge_requests/new?merge_request[source_branch]={{.From}}&merge_request[target_branch]={{.To}}",
+ commitURL: "/commit/{{.CommitSha}}",
+ regexStrings: defaultUrlRegexStrings,
+}
+
+var serviceDefinitions = []ServiceDefinition{githubServiceDef, bitbucketServiceDef, gitLabServiceDef}
+
+var defaultServiceDomains = []ServiceDomain{
+ {
+ serviceDefinition: githubServiceDef,
+ gitDomain: "github.com",
+ webDomain: "github.com",
+ },
+ {
+ serviceDefinition: bitbucketServiceDef,
+ gitDomain: "bitbucket.org",
+ webDomain: "bitbucket.org",
+ },
+ {
+ serviceDefinition: gitLabServiceDef,
+ gitDomain: "gitlab.com",
+ webDomain: "gitlab.com",
+ },
+}
diff --git a/pkg/commands/hosting_service/hosting_service.go b/pkg/commands/hosting_service/hosting_service.go
new file mode 100644
index 000000000..1762902a4
--- /dev/null
+++ b/pkg/commands/hosting_service/hosting_service.go
@@ -0,0 +1,201 @@
+package hosting_service
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/go-errors/errors"
+ "github.com/jesseduffield/lazygit/pkg/i18n"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/sirupsen/logrus"
+)
+
+// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, etc.
+// Different git hosting services have different URL formats for when you want to open a PR or view a commit,
+// and this package's responsibility is to determine which service you're using based on the remote URL,
+// and then which URL you need for whatever use case you have.
+
+type HostingServiceMgr struct {
+ log logrus.FieldLogger
+ tr *i18n.TranslationSet
+ remoteURL string // e.g. https://github.com/jesseduffield/lazygit
+
+ // see https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls
+ configServiceDomains map[string]string
+}
+
+// NewHostingServiceMgr creates new instance of PullRequest
+func NewHostingServiceMgr(log logrus.FieldLogger, tr *i18n.TranslationSet, remoteURL string, configServiceDomains map[string]string) *HostingServiceMgr {
+ return &HostingServiceMgr{
+ log: log,
+ tr: tr,
+ remoteURL: remoteURL,
+ configServiceDomains: configServiceDomains,
+ }
+}
+
+func (self *HostingServiceMgr) GetPullRequestURL(from string, to string) (string, error) {
+ gitService, err := self.getService()
+ if err != nil {
+ return "", err
+ }
+
+ if to == "" {
+ return gitService.getPullRequestURLIntoDefaultBranch(from), nil
+ } else {
+ return gitService.getPullRequestURLIntoTargetBranch(from, to), nil
+ }
+}
+
+func (self *HostingServiceMgr) GetCommitURL(commitSha string) (string, error) {
+ gitService, err := self.getService()
+ if err != nil {
+ return "", err
+ }
+
+ pullRequestURL := gitService.getCommitURL(commitSha)
+
+ return pullRequestURL, nil
+}
+
+func (self *HostingServiceMgr) getService() (*Service, error) {
+ serviceDomain, err := self.getServiceDomain(self.remoteURL)
+ if err != nil {
+ return nil, err
+ }
+
+ root, err := serviceDomain.getRootFromRemoteURL(self.remoteURL)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Service{
+ root: root,
+ ServiceDefinition: serviceDomain.serviceDefinition,
+ }, nil
+}
+
+func (self *HostingServiceMgr) getServiceDomain(repoURL string) (*ServiceDomain, error) {
+ candidateServiceDomains := self.getCandidateServiceDomains()
+
+ for _, serviceDomain := range candidateServiceDomains {
+ // I feel like it makes more sense to see if the repo url contains the service domain's git domain,
+ // but I don't want to break anything by changing that right now.
+ if strings.Contains(repoURL, serviceDomain.serviceDefinition.provider) {
+ return &serviceDomain, nil
+ }
+ }
+
+ return nil, errors.New(self.tr.UnsupportedGitService)
+}
+
+func (self *HostingServiceMgr) getCandidateServiceDomains() []ServiceDomain {
+ serviceDefinitionByProvider := map[string]ServiceDefinition{}
+ for _, serviceDefinition := range serviceDefinitions {
+ serviceDefinitionByProvider[serviceDefinition.provider] = serviceDefinition
+ }
+
+ var serviceDomains = make([]ServiceDomain, len(defaultServiceDomains))
+ copy(serviceDomains, defaultServiceDomains)
+
+ if len(self.configServiceDomains) > 0 {
+ for gitDomain, typeAndDomain := range self.configServiceDomains {
+ splitData := strings.Split(typeAndDomain, ":")
+ if len(splitData) != 2 {
+ self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
+ continue
+ }
+
+ provider := splitData[0]
+ webDomain := splitData[1]
+
+ serviceDefinition, ok := serviceDefinitionByProvider[provider]
+ if !ok {
+ providerNames := []string{}
+ for _, serviceDefinition := range serviceDefinitions {
+ providerNames = append(providerNames, serviceDefinition.provider)
+ }
+ self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
+ continue
+ }
+
+ serviceDomains = append(serviceDomains, ServiceDomain{
+ gitDomain: gitDomain,
+ webDomain: webDomain,
+ serviceDefinition: serviceDefinition,
+ })
+ }
+ }
+
+ return serviceDomains
+}
+
+// a service domains pairs a service definition with the actual domain it's being served from.
+// Sometimes the git service is hosted in a custom domains so although it'll use say
+// the github service definition, it'll actually be served from e.g. my-custom-github.com
+type ServiceDomain struct {
+ gitDomain string // the one that appears in the git remote url
+ webDomain string // the one that appears in the web url
+ serviceDefinition ServiceDefinition
+}
+
+func (self ServiceDomain) getRootFromRemoteURL(repoURL string) (string, error) {
+ // we may want to make this more specific to the service in future e.g. if
+ // some new service comes along which has a different root url structure.
+ repoInfo, err := self.serviceDefinition.getRepoInfoFromURL(repoURL)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("https://%s/%s/%s", self.webDomain, repoInfo.Owner, repoInfo.Repository), nil
+}
+
+// RepoInformation holds some basic information about the repo
+type RepoInformation struct {
+ Owner string
+ Repository string
+}
+
+type ServiceDefinition struct {
+ provider string
+ pullRequestURLIntoDefaultBranch string
+ pullRequestURLIntoTargetBranch string
+ commitURL string
+ regexStrings []string
+}
+
+func (self ServiceDefinition) getRepoInfoFromURL(url string) (*RepoInformation, error) {
+ for _, regexStr := range self.regexStrings {
+ re := regexp.MustCompile(regexStr)
+ matches := utils.FindNamedMatches(re, url)
+ if matches != nil {
+ return &RepoInformation{
+ Owner: matches["owner"],
+ Repository: matches["repo"],
+ }, nil
+ }
+ }
+
+ return nil, errors.New("Failed to parse repo information from url")
+}
+
+type Service struct {
+ root string
+ ServiceDefinition
+}
+
+func (self *Service) getPullRequestURLIntoDefaultBranch(from string) string {
+ return self.resolveUrl(self.pullRequestURLIntoDefaultBranch, map[string]string{"From": from})
+}
+
+func (self *Service) getPullRequestURLIntoTargetBranch(from string, to string) string {
+ return self.resolveUrl(self.pullRequestURLIntoTargetBranch, map[string]string{"From": from, "To": to})
+}
+
+func (self *Service) getCommitURL(commitSha string) string {
+ return self.resolveUrl(self.commitURL, map[string]string{"CommitSha": commitSha})
+}
+
+func (self *Service) resolveUrl(templateString string, args map[string]string) string {
+ return self.root + utils.ResolvePlaceholderString(templateString, args)
+}
diff --git a/pkg/commands/hosting_service/hosting_service_test.go b/pkg/commands/hosting_service/hosting_service_test.go
new file mode 100644
index 000000000..ab7a2b402
--- /dev/null
+++ b/pkg/commands/hosting_service/hosting_service_test.go
@@ -0,0 +1,233 @@
+package hosting_service
+
+import (
+ "testing"
+
+ "github.com/jesseduffield/lazygit/pkg/i18n"
+ "github.com/jesseduffield/lazygit/pkg/test"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetRepoInfoFromURL(t *testing.T) {
+ type scenario struct {
+ serviceDefinition ServiceDefinition
+ testName string
+ repoURL string
+ test func(*RepoInformation)
+ }
+
+ scenarios := []scenario{
+ {
+ githubServiceDef,
+ "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")
+ },
+ },
+ {
+ githubServiceDef,
+ "Returns repository information for git remote url, trimming trailing '.git'",
+ "git@github.com:petersmith/super_calculator.git",
+ func(repoInfo *RepoInformation) {
+ assert.EqualValues(t, repoInfo.Owner, "petersmith")
+ assert.EqualValues(t, repoInfo.Repository, "super_calculator")
+ },
+ },
+ {
+ githubServiceDef,
+ "Returns repository information for ssh remote url",
+ "ssh://git@github.com/petersmith/super_calculator",
+ func(repoInfo *RepoInformation) {
+ assert.EqualValues(t, repoInfo.Owner, "petersmith")
+ assert.EqualValues(t, repoInfo.Repository, "super_calculator")
+ },
+ },
+ {
+ githubServiceDef,
+ "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) {
+ result, err := s.serviceDefinition.getRepoInfoFromURL(s.repoURL)
+ assert.NoError(t, err)
+ s.test(result)
+ })
+ }
+}
+
+func TestGetPullRequestURL(t *testing.T) {
+ type scenario struct {
+ testName string
+ from string
+ to string
+ remoteUrl string
+ configServiceDomains map[string]string
+ test func(url string, err error)
+ expectedLoggedErrors []string
+ }
+
+ scenarios := []scenario{
+ {
+ testName: "Opens a link to new pull request on bitbucket",
+ from: "feature/profile-page",
+ remoteUrl: "git@bitbucket.org:johndoe/social_network.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on bitbucket with http remote url",
+ from: "feature/events",
+ remoteUrl: "https://my_username@bitbucket.org/johndoe/social_network.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on github",
+ from: "feature/sum-operation",
+ remoteUrl: "git@github.com:peter/calculator.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://github.com/peter/calculator/compare/feature/sum-operation?expand=1", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on bitbucket with specific target branch",
+ from: "feature/profile-page/avatar",
+ to: "feature/profile-page",
+ remoteUrl: "git@bitbucket.org:johndoe/social_network.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page/avatar&dest=feature/profile-page&t=1", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on bitbucket with http remote url with specified target branch",
+ from: "feature/remote-events",
+ to: "feature/events",
+ remoteUrl: "https://my_username@bitbucket.org/johndoe/social_network.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/remote-events&dest=feature/events&t=1", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on github with specific target branch",
+ from: "feature/sum-operation",
+ to: "feature/operations",
+ remoteUrl: "git@github.com:peter/calculator.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://github.com/peter/calculator/compare/feature/operations...feature/sum-operation?expand=1", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on gitlab",
+ from: "feature/ui",
+ remoteUrl: "git@gitlab.com:peter/calculator.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on gitlab in nested groups",
+ from: "feature/ui",
+ remoteUrl: "git@gitlab.com:peter/public/calculator.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/ui", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on gitlab with specific target branch",
+ from: "feature/commit-ui",
+ to: "epic/ui",
+ remoteUrl: "git@gitlab.com:peter/calculator.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui", url)
+ },
+ },
+ {
+ testName: "Opens a link to new pull request on gitlab with specific target branch in nested groups",
+ from: "feature/commit-ui",
+ to: "epic/ui",
+ remoteUrl: "git@gitlab.com:peter/public/calculator.git",
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui", url)
+ },
+ },
+ {
+ testName: "Throws an error if git service is unsupported",
+ from: "feature/divide-operation",
+ remoteUrl: "git@something.com:peter/calculator.git",
+ test: func(url string, err error) {
+ assert.EqualError(t, err, "Unsupported git service")
+ },
+ },
+ {
+ testName: "Does not log error when config service domains are valid",
+ from: "feature/profile-page",
+ remoteUrl: "git@bitbucket.org:johndoe/social_network.git",
+ configServiceDomains: map[string]string{
+ // valid configuration for a custom service URL
+ "git.work.com": "gitlab:code.work.com",
+ },
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1", url)
+ },
+ expectedLoggedErrors: nil,
+ },
+ {
+ testName: "Logs error when config service domain is malformed",
+ from: "feature/profile-page",
+ remoteUrl: "git@bitbucket.org:johndoe/social_network.git",
+ configServiceDomains: map[string]string{
+ "noservice.work.com": "noservice.work.com",
+ },
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1", url)
+ },
+ expectedLoggedErrors: []string{"Unexpected format for git service: 'noservice.work.com'. Expected something like 'github.com:github.com'"},
+ },
+ {
+ testName: "Logs error when config service domain uses unknown provider",
+ from: "feature/profile-page",
+ remoteUrl: "git@bitbucket.org:johndoe/social_network.git",
+ configServiceDomains: map[string]string{
+ "invalid.work.com": "noservice:invalid.work.com",
+ },
+ test: func(url string, err error) {
+ assert.NoError(t, err)
+ assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1", url)
+ },
+ expectedLoggedErrors: []string{"Unknown git service type: 'noservice'. Expected one of github, bitbucket, gitlab"},
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ tr := i18n.EnglishTranslationSet()
+ log := &test.FakeFieldLogger{}
+ hostingServiceMgr := NewHostingServiceMgr(log, &tr, s.remoteUrl, s.configServiceDomains)
+ s.test(hostingServiceMgr.GetPullRequestURL(s.from, s.to))
+ log.AssertErrors(t, s.expectedLoggedErrors)
+ })
+ }
+}
diff --git a/pkg/commands/pull_request.go b/pkg/commands/pull_request.go
deleted file mode 100644
index 2f2a9cd1d..000000000
--- a/pkg/commands/pull_request.go
+++ /dev/null
@@ -1,281 +0,0 @@
-package commands
-
-import (
- "fmt"
- "regexp"
- "strings"
-
- "github.com/go-errors/errors"
- "github.com/jesseduffield/lazygit/pkg/utils"
-)
-
-// if you want to make a custom regex for a given service feel free to test it out
-// at regoio.herokuapp.com
-var defaultUrlRegexStrings = []string{
- `^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
- `^git@.*:(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
-}
-
-type ServiceDefinition struct {
- provider string
- pullRequestURLIntoDefaultBranch string
- pullRequestURLIntoTargetBranch string
- commitURL string
- regexStrings []string
-}
-
-func (self ServiceDefinition) getRepoInfoFromURL(url string) (*RepoInformation, error) {
- for _, regexStr := range self.regexStrings {
- re := regexp.MustCompile(regexStr)
- matches := utils.FindNamedMatches(re, url)
- if matches != nil {
- return &RepoInformation{
- Owner: matches["owner"],
- Repository: matches["repo"],
- }, nil
- }
- }
-
- return nil, errors.New("Failed to parse repo information from url")
-}
-
-// a service domains pairs a service definition with the actual domain it's being served from.
-// Sometimes the git service is hosted in a custom domains so although it'll use say
-// the github service definition, it'll actually be served from e.g. my-custom-github.com
-type ServiceDomain struct {
- gitDomain string // the one that appears in the git remote url
- webDomain string // the one that appears in the web url
- serviceDefinition ServiceDefinition
-}
-
-func (self ServiceDomain) getRootFromRepoURL(repoURL string) (string, error) {
- // we may want to make this more specific to the service in future e.g. if
- // some new service comes along which has a different root url structure.
- repoInfo, err := self.serviceDefinition.getRepoInfoFromURL(repoURL)
- if err != nil {
- return "", err
- }
- return fmt.Sprintf("https://%s/%s/%s", self.webDomain, repoInfo.Owner, repoInfo.Repository), nil
-}
-
-// we've got less type safety using go templates but this lends itself better to
-// users adding custom service definitions in their config
-var GithubServiceDef = ServiceDefinition{
- provider: "github",
- pullRequestURLIntoDefaultBranch: "/compare/{{.From}}?expand=1",
- pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}?expand=1",
- commitURL: "/commit/{{.CommitSha}}",
- regexStrings: defaultUrlRegexStrings,
-}
-
-var BitbucketServiceDef = ServiceDefinition{
- provider: "bitbucket",
- pullRequestURLIntoDefaultBranch: "/pull-requests/new?source={{.From}}&t=1",
- pullRequestURLIntoTargetBranch: "/pull-requests/new?source={{.From}}&dest={{.To}}&t=1",
- commitURL: "/commits/{{.CommitSha}}",
- regexStrings: defaultUrlRegexStrings,
-}
-
-var GitLabServiceDef = ServiceDefinition{
- provider: "gitlab",
- pullRequestURLIntoDefaultBranch: "/merge_requests/new?merge_request[source_branch]={{.From}}",
- pullRequestURLIntoTargetBranch: "/merge_requests/new?merge_request[source_branch]={{.From}}&merge_request[target_branch]={{.To}}",
- commitURL: "/commit/{{.CommitSha}}",
- regexStrings: defaultUrlRegexStrings,
-}
-
-var serviceDefinitions = []ServiceDefinition{GithubServiceDef, BitbucketServiceDef, GitLabServiceDef}
-var defaultServiceDomains = []ServiceDomain{
- {
- serviceDefinition: GithubServiceDef,
- gitDomain: "github.com",
- webDomain: "github.com",
- },
- {
- serviceDefinition: BitbucketServiceDef,
- gitDomain: "bitbucket.org",
- webDomain: "bitbucket.org",
- },
- {
- serviceDefinition: GitLabServiceDef,
- gitDomain: "gitlab.com",
- webDomain: "gitlab.com",
- },
-}
-
-type Service struct {
- root string
- ServiceDefinition
-}
-
-func (self *Service) getPullRequestURLIntoDefaultBranch(from string) string {
- return self.resolveUrl(self.pullRequestURLIntoDefaultBranch, map[string]string{"From": from})
-}
-
-func (self *Service) getPullRequestURLIntoTargetBranch(from string, to string) string {
- return self.resolveUrl(self.pullRequestURLIntoTargetBranch, map[string]string{"From": from, "To": to})
-}
-
-func (self *Service) getCommitURL(commitSha string) string {
- return self.resolveUrl(self.commitURL, map[string]string{"CommitSha": commitSha})
-}
-
-func (self *Service) resolveUrl(templateString string, args map[string]string) string {
- return self.root + utils.ResolvePlaceholderString(templateString, args)
-}
-
-// PullRequest opens a link in browser to create new pull request
-// with selected branch
-type PullRequest struct {
- GitCommand *GitCommand
-}
-
-// RepoInformation holds some basic information about the repo
-type RepoInformation struct {
- Owner string
- Repository string
-}
-
-// NewPullRequest creates new instance of PullRequest
-func NewPullRequest(gitCommand *GitCommand) *PullRequest {
- return &PullRequest{
- GitCommand: gitCommand,
- }
-}
-
-func (pr *PullRequest) getService() (*Service, error) {
- serviceDomain, err := pr.getServiceDomain()
- if err != nil {
- return nil, err
- }
-
- repoURL := pr.GitCommand.GetRemoteURL()
-
- root, err := serviceDomain.getRootFromRepoURL(repoURL)
- if err != nil {
- return nil, err
- }
-
- return &Service{
- root: root,
- ServiceDefinition: serviceDomain.serviceDefinition,
- }, nil
-}
-
-func (pr *PullRequest) getServiceDomain() (*ServiceDomain, error) {
- candidateServiceDomains := pr.getCandidateServiceDomains()
-
- repoURL := pr.GitCommand.GetRemoteURL()
-
- for _, serviceDomain := range candidateServiceDomains {
- // I feel like it makes more sense to see if the repo url contains the service domain's git domain,
- // but I don't want to break anything by changing that right now.
- if strings.Contains(repoURL, serviceDomain.serviceDefinition.provider) {
- return &serviceDomain, nil
- }
- }
-
- return nil, errors.New(pr.GitCommand.Tr.UnsupportedGitService)
-}
-
-func (pr *PullRequest) getCandidateServiceDomains() []ServiceDomain {
- serviceDefinitionByProvider := map[string]ServiceDefinition{}
- for _, serviceDefinition := range serviceDefinitions {
- serviceDefinitionByProvider[serviceDefinition.provider] = serviceDefinition
- }
-
- var serviceDomains = make([]ServiceDomain, len(defaultServiceDomains))
- copy(serviceDomains, defaultServiceDomains)
-
- // see https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls
- configServices := pr.GitCommand.Config.GetUserConfig().Services
- if len(configServices) > 0 {
- for gitDomain, typeAndDomain := range configServices {
- splitData := strings.Split(typeAndDomain, ":")
- if len(splitData) != 2 {
- pr.GitCommand.Log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
- continue
- }
-
- provider := splitData[0]
- webDomain := splitData[1]
-
- serviceDefinition, ok := serviceDefinitionByProvider[provider]
- if !ok {
- providerNames := []string{}
- for _, serviceDefinition := range serviceDefinitions {
- providerNames = append(providerNames, serviceDefinition.provider)
- }
- pr.GitCommand.Log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
- continue
- }
-
- serviceDomains = append(serviceDomains, ServiceDomain{
- gitDomain: gitDomain,
- webDomain: webDomain,
- serviceDefinition: serviceDefinition,
- })
- }
- }
-
- return serviceDomains
-}
-
-// CreatePullRequest opens link to new pull request in browser
-func (pr *PullRequest) CreatePullRequest(from string, to string) (string, error) {
- pullRequestURL, err := pr.getPullRequestURL(from, to)
- if err != nil {
- return "", err
- }
-
- return pullRequestURL, pr.GitCommand.OSCommand.OpenLink(pullRequestURL)
-}
-
-// CopyURL copies the pull request URL to the clipboard
-func (pr *PullRequest) CopyURL(from string, to string) (string, error) {
- pullRequestURL, err := pr.getPullRequestURL(from, to)
- if err != nil {
- return "", err
- }
-
- return pullRequestURL, pr.GitCommand.OSCommand.CopyToClipboard(pullRequestURL)
-}
-
-func (pr *PullRequest) getPullRequestURL(from string, to string) (string, error) {
- branchExistsOnRemote := pr.GitCommand.CheckRemoteBranchExists(from)
-
- if !branchExistsOnRemote {
- return "", errors.New(pr.GitCommand.Tr.NoBranchOnRemote)
- }
-
- gitService, err := pr.getService()
- if err != nil {
- return "", err
- }
-
- if to == "" {
- return gitService.getPullRequestURLIntoDefaultBranch(from), nil
- } else {
- return gitService.getPullRequestURLIntoTargetBranch(from, to), nil
- }
-}
-
-func (pr *PullRequest) getCommitURL(commitSha string) (string, error) {
- gitService, err := pr.getService()
- if err != nil {
- return "", err
- }
-
- pullRequestURL := gitService.getCommitURL(commitSha)
-
- return pullRequestURL, nil
-}
-
-func (pr *PullRequest) OpenCommitInBrowser(commitSha string) (string, error) {
- url, err := pr.getCommitURL(commitSha)
- if err != nil {
- return "", err
- }
-
- return url, pr.GitCommand.OSCommand.OpenLink(url)
-}
diff --git a/pkg/commands/pull_request_default_test.go b/pkg/commands/pull_request_default_test.go
deleted file mode 100644
index 9a7d775ee..000000000
--- a/pkg/commands/pull_request_default_test.go
+++ /dev/null
@@ -1,256 +0,0 @@
-//go:build !windows
-// +build !windows
-
-package commands
-
-import (
- "os/exec"
- "strings"
- "testing"
-
- "github.com/jesseduffield/lazygit/pkg/commands/git_config"
- "github.com/jesseduffield/lazygit/pkg/secureexec"
- "github.com/stretchr/testify/assert"
-)
-
-// TestCreatePullRequest is a function.
-func TestCreatePullRequest(t *testing.T) {
- type scenario struct {
- testName string
- from string
- to string
- remoteUrl string
- command func(string, ...string) *exec.Cmd
- test func(url string, err error)
- }
-
- scenarios := []scenario{
- {
- testName: "Opens a link to new pull request on bitbucket",
- from: "feature/profile-page",
- remoteUrl: "git@bitbucket.org:johndoe/social_network.git",
- command: func(cmd string, args ...string) *exec.Cmd {
- // Handle git remote url call
- if strings.HasPrefix(cmd, "git") {
- return secureexec.Command("echo", "git@bitbucket.org:johndoe/social_network.git")
- }
-
- assert.Equal(t, cmd, "bash")
- assert.Equal(t, args, []string{"-c", `open "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1"`})
- return secureexec.Command("echo")
- },
- test: func(url string, err error) {
- assert.NoError(t, err)
- assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1", url)
- },
- },
- {
- testName: "Opens a link to new pull request on bitb