diff options
-rw-r--r-- | pkg/commands/hosting_service/definitions.go | 54 | ||||
-rw-r--r-- | pkg/commands/hosting_service/hosting_service.go | 201 | ||||
-rw-r--r-- | pkg/commands/hosting_service/hosting_service_test.go | 233 | ||||
-rw-r--r-- | pkg/commands/pull_request.go | 281 | ||||
-rw-r--r-- | pkg/commands/pull_request_default_test.go | 256 | ||||
-rw-r--r-- | pkg/commands/pull_request_test.go | 64 | ||||
-rw-r--r-- | pkg/commands/pull_request_windows_test.go | 256 | ||||
-rw-r--r-- | pkg/gui/branches_panel.go | 16 | ||||
-rw-r--r-- | pkg/gui/commits_panel.go | 10 | ||||
-rw-r--r-- | pkg/gui/pull_request_menu_panel.go | 17 | ||||
-rw-r--r-- | pkg/i18n/english.go | 3 | ||||
-rw-r--r-- | pkg/i18n/i18n.go | 4 | ||||
-rw-r--r-- | pkg/test/log.go | 41 |
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 bitbucket with http remote url", - from: "feature/events", - remoteUrl: "https://my_username@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", "https://my_username@bitbucket.org/johndoe/social_network.git") - } - - assert |