mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	refactor to rename pull_request to hosting_service and apply SRP
This commit is contained in:
		
							
								
								
									
										54
									
								
								pkg/commands/hosting_service/definitions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								pkg/commands/hosting_service/definitions.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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", | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										201
									
								
								pkg/commands/hosting_service/hosting_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								pkg/commands/hosting_service/hosting_service.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										233
									
								
								pkg/commands/hosting_service/hosting_service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								pkg/commands/hosting_service/hosting_service_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
| @@ -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.Equal(t, cmd, "bash") | ||||
| 				assert.Equal(t, args, []string{"-c", `open "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&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/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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@github.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "bash") | ||||
| 				assert.Equal(t, args, []string{"-c", `open "https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"`}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			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/avatar&dest=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/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", | ||||
| 			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.Equal(t, cmd, "bash") | ||||
| 				assert.Equal(t, args, []string{"-c", `open "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/remote-events&dest=feature/events&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/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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@github.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "bash") | ||||
| 				assert.Equal(t, args, []string{"-c", `open "https://github.com/peter/calculator/compare/feature/operations...feature/sum-operation?expand=1"`}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "bash") | ||||
| 				assert.Equal(t, args, []string{"-c", `open "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"`}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "bash") | ||||
| 				assert.Equal(t, args, []string{"-c", `open "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"`}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "bash") | ||||
| 				assert.Equal(t, args, []string{"-c", `open "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui"`}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "bash") | ||||
| 				assert.Equal(t, args, []string{"-c", `open "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui"`}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			test: func(url string, 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.Platform.OS = "darwin" | ||||
| 			gitCommand.OSCommand.Platform.Shell = "bash" | ||||
| 			gitCommand.OSCommand.Platform.ShellArg = "-c" | ||||
| 			gitCommand.OSCommand.Config.GetUserConfig().OS.OpenLinkCommand = "open {{link}}" | ||||
| 			gitCommand.OSCommand.Config.GetUserConfig().Services = map[string]string{ | ||||
| 				// valid configuration for a custom service URL | ||||
| 				"git.work.com": "gitlab:code.work.com", | ||||
| 				// invalid configurations for a custom service URL | ||||
| 				"invalid.work.com":   "noservice:invalid.work.com", | ||||
| 				"noservice.work.com": "noservice.work.com", | ||||
| 			} | ||||
| 			gitCommand.GitConfig = git_config.NewFakeGitConfig(map[string]string{"remote.origin.url": s.remoteUrl}) | ||||
| 			dummyPullRequest := NewPullRequest(gitCommand) | ||||
| 			s.test(dummyPullRequest.CreatePullRequest(s.from, s.to)) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -1,64 +0,0 @@ | ||||
| package commands | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| // TestGetRepoInfoFromURL is a function. | ||||
| 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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -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" | ||||
| ) | ||||
|  | ||||
| // TestCreatePullRequestOnWindows is a function. | ||||
| func TestCreatePullRequestOnWindows(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, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "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.Equal(t, cmd, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events^&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/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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@github.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			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, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page/avatar^&dest=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/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", | ||||
| 			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.Equal(t, cmd, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/remote-events^&dest=feature/events^&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/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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@github.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://github.com/peter/calculator/compare/feature/operations...feature/sum-operation?expand=1"}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui^&merge_request[target_branch]=epic/ui"}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				// Handle git remote url call | ||||
| 				if strings.HasPrefix(cmd, "git") { | ||||
| 					return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") | ||||
| 				} | ||||
|  | ||||
| 				assert.Equal(t, cmd, "cmd") | ||||
| 				assert.Equal(t, args, []string{"/c", "start", "", "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui^&merge_request[target_branch]=epic/ui"}) | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			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", | ||||
| 			command: func(cmd string, args ...string) *exec.Cmd { | ||||
| 				return secureexec.Command("echo") | ||||
| 			}, | ||||
| 			test: func(url string, 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.Platform.OS = "windows" | ||||
| 			gitCommand.OSCommand.Platform.Shell = "cmd" | ||||
| 			gitCommand.OSCommand.Platform.ShellArg = "/c" | ||||
| 			gitCommand.OSCommand.Config.GetUserConfig().OS.OpenLinkCommand = `start "" {{link}}` | ||||
| 			gitCommand.OSCommand.Config.GetUserConfig().Services = map[string]string{ | ||||
| 				// valid configuration for a custom service URL | ||||
| 				"git.work.com": "gitlab:code.work.com", | ||||
| 				// invalid configurations for a custom service URL | ||||
| 				"invalid.work.com":   "noservice:invalid.work.com", | ||||
| 				"noservice.work.com": "noservice.work.com", | ||||
| 			} | ||||
| 			gitCommand.GitConfig = git_config.NewFakeGitConfig(map[string]string{"remote.origin.url": s.remoteUrl}) | ||||
| 			dummyPullRequest := NewPullRequest(gitCommand) | ||||
| 			s.test(dummyPullRequest.Create(s.from, s.to)) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +1,7 @@ | ||||
| package gui | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| @@ -104,13 +105,24 @@ func (gui *Gui) handleCreatePullRequestMenu() error { | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleCopyPullRequestURLPress() error { | ||||
| 	pullRequest := commands.NewPullRequest(gui.GitCommand) | ||||
| 	hostingServiceMgr := gui.getHostingServiceMgr() | ||||
|  | ||||
| 	branch := gui.getSelectedBranch() | ||||
| 	url, err := pullRequest.CopyURL(branch.Name, "") | ||||
|  | ||||
| 	branchExistsOnRemote := gui.GitCommand.CheckRemoteBranchExists(branch.Name) | ||||
|  | ||||
| 	if !branchExistsOnRemote { | ||||
| 		return gui.surfaceError(errors.New(gui.Tr.NoBranchOnRemote)) | ||||
| 	} | ||||
|  | ||||
| 	url, err := hostingServiceMgr.GetPullRequestURL(branch.Name, "") | ||||
| 	if err != nil { | ||||
| 		return gui.surfaceError(err) | ||||
| 	} | ||||
| 	if err := gui.GitCommand.OSCommand.CopyToClipboard(url); err != nil { | ||||
| 		return gui.surfaceError(err) | ||||
| 	} | ||||
|  | ||||
| 	gui.OnRunCommand(oscommands.NewCmdLogEntry(fmt.Sprintf("Copying to clipboard: '%s'", url), "Copy URL", false)) | ||||
|  | ||||
| 	gui.raiseToast(gui.Tr.PullRequestURLCopiedToClipboard) | ||||
|   | ||||
| @@ -804,11 +804,17 @@ func (gui *Gui) handleOpenCommitInBrowser() error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	pullRequest := commands.NewPullRequest(gui.GitCommand) | ||||
| 	url, err := pullRequest.OpenCommitInBrowser(commit.Sha) | ||||
| 	hostingServiceMgr := gui.getHostingServiceMgr() | ||||
|  | ||||
| 	url, err := hostingServiceMgr.GetCommitURL(commit.Sha) | ||||
| 	if err != nil { | ||||
| 		return gui.surfaceError(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := gui.GitCommand.OSCommand.OpenLink(url); err != nil { | ||||
| 		return gui.surfaceError(err) | ||||
| 	} | ||||
|  | ||||
| 	gui.OnRunCommand(oscommands.NewCmdLogEntry(fmt.Sprintf(gui.Tr.OpeningCommitInBrowser, url), gui.Tr.CreatePullRequest, false)) | ||||
|  | ||||
| 	return nil | ||||
|   | ||||
| @@ -3,7 +3,7 @@ package gui | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/jesseduffield/lazygit/pkg/commands" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/commands/hosting_service" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/commands/models" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/commands/oscommands" | ||||
| ) | ||||
| @@ -56,12 +56,23 @@ func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutB | ||||
| } | ||||
|  | ||||
| func (gui *Gui) createPullRequest(from string, to string) error { | ||||
| 	pullRequest := commands.NewPullRequest(gui.GitCommand) | ||||
| 	url, err := pullRequest.CreatePullRequest(from, to) | ||||
| 	hostingServiceMgr := gui.getHostingServiceMgr() | ||||
| 	url, err := hostingServiceMgr.GetPullRequestURL(from, to) | ||||
| 	if err != nil { | ||||
| 		return gui.surfaceError(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := gui.GitCommand.OSCommand.OpenLink(url); err != nil { | ||||
| 		return gui.surfaceError(err) | ||||
| 	} | ||||
|  | ||||
| 	gui.OnRunCommand(oscommands.NewCmdLogEntry(fmt.Sprintf(gui.Tr.CreatingPullRequestAtUrl, url), gui.Tr.CreatePullRequest, false)) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (gui *Gui) getHostingServiceMgr() *hosting_service.HostingServiceMgr { | ||||
| 	remoteUrl := gui.GitCommand.GetRemoteURL() | ||||
| 	configServices := gui.Config.GetUserConfig().Services | ||||
| 	return hosting_service.NewHostingServiceMgr(gui.Log, gui.Tr, remoteUrl, configServices) | ||||
| } | ||||
|   | ||||
| @@ -559,7 +559,8 @@ Thanks for using lazygit! Seriously you rock. Three things to share with you: | ||||
|     Or even just star the repo to share the love! | ||||
| ` | ||||
|  | ||||
| func englishTranslationSet() TranslationSet { | ||||
| // exporting this so we can use it in tests | ||||
| func EnglishTranslationSet() TranslationSet { | ||||
| 	return TranslationSet{ | ||||
| 		NotEnoughSpace:                      "Not enough space to render panels", | ||||
| 		DiffTitle:                           "Diff", | ||||
|   | ||||
| @@ -33,7 +33,7 @@ func NewTranslationSetFromConfig(log *logrus.Entry, configLanguage string) (*Tra | ||||
| func NewTranslationSet(log *logrus.Entry, language string) *TranslationSet { | ||||
| 	log.Info("language: " + language) | ||||
|  | ||||
| 	baseSet := englishTranslationSet() | ||||
| 	baseSet := EnglishTranslationSet() | ||||
|  | ||||
| 	for languageCode, translationSet := range GetTranslationSets() { | ||||
| 		if strings.HasPrefix(language, languageCode) { | ||||
| @@ -48,7 +48,7 @@ func GetTranslationSets() map[string]TranslationSet { | ||||
| 	return map[string]TranslationSet{ | ||||
| 		"pl": polishTranslationSet(), | ||||
| 		"nl": dutchTranslationSet(), | ||||
| 		"en": englishTranslationSet(), | ||||
| 		"en": EnglishTranslationSet(), | ||||
| 		"zh": chineseTranslationSet(), | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										41
									
								
								pkg/test/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								pkg/test/log.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| package test | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	_ logrus.FieldLogger = &FakeFieldLogger{} | ||||
| ) | ||||
|  | ||||
| // for now we're just tracking calls to the Error and Errorf methods | ||||
| type FakeFieldLogger struct { | ||||
| 	loggedErrors []string | ||||
| 	*logrus.Entry | ||||
| } | ||||
|  | ||||
| func (self *FakeFieldLogger) Error(args ...interface{}) { | ||||
| 	if len(args) != 1 { | ||||
| 		panic("Expected exactly one argument to FakeFieldLogger.Error") | ||||
| 	} | ||||
|  | ||||
| 	switch arg := args[0].(type) { | ||||
| 	case error: | ||||
| 		self.loggedErrors = append(self.loggedErrors, arg.Error()) | ||||
| 	case string: | ||||
| 		self.loggedErrors = append(self.loggedErrors, arg) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *FakeFieldLogger) Errorf(format string, args ...interface{}) { | ||||
| 	msg := fmt.Sprintf(format, args...) | ||||
| 	self.loggedErrors = append(self.loggedErrors, msg) | ||||
| } | ||||
|  | ||||
| func (self *FakeFieldLogger) AssertErrors(t *testing.T, expectedErrors []string) { | ||||
| 	assert.EqualValues(t, expectedErrors, self.loggedErrors) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user