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

refactor to use regex for matching git service URL

This commit is contained in:
Jesse Duffield 2021-12-26 16:30:35 +11:00
parent 7bc8b96aba
commit d9db5ccfbe
2 changed files with 118 additions and 98 deletions

View File

@ -2,72 +2,81 @@ package commands
import (
"fmt"
"regexp"
"strings"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/config"
"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?://.*/(?P<owner>.*)/(?P<repo>.*?)(\.git)?$`,
`^git@.*:(?P<owner>.*)/(?P<repo>.*?)(\.git)?$`,
}
// Service is a service that repository is on (Github, Bitbucket, ...)
type Service struct {
Name string
pullRequestURLIntoDefaultBranch func(owner string, repository string, from string) string
pullRequestURLIntoTargetBranch func(owner string, repository string, from string, to string) string
URLRegexStrings []string
}
// NewService builds a Service based on the host type
func NewService(typeName string, repositoryDomain string, siteDomain string) *Service {
var service *Service
switch typeName {
case "github":
service = &Service{
Name: repositoryDomain,
pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string {
return fmt.Sprintf("https://%s/%s/%s/compare/%s?expand=1", siteDomain, owner, repository, from)
},
pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string {
return fmt.Sprintf("https://%s/%s/%s/compare/%s...%s?expand=1", siteDomain, owner, repository, to, from)
},
}
case "bitbucket":
service = &Service{
Name: repositoryDomain,
pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string {
return fmt.Sprintf("https://%s/%s/%s/pull-requests/new?source=%s&t=1", siteDomain, owner, repository, from)
},
pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string {
return fmt.Sprintf("https://%s/%s/%s/pull-requests/new?source=%s&dest=%s&t=1", siteDomain, owner, repository, from, to)
},
}
case "gitlab":
service = &Service{
Name: repositoryDomain,
pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string {
return fmt.Sprintf("https://%s/%s/%s/merge_requests/new?merge_request[source_branch]=%s", siteDomain, owner, repository, from)
},
pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string {
return fmt.Sprintf("https://%s/%s/%s/merge_requests/new?merge_request[source_branch]=%s&merge_request[target_branch]=%s", siteDomain, owner, repository, from, to)
},
}
func NewGithubService(repositoryDomain string, siteDomain string) *Service {
return &Service{
Name: repositoryDomain,
pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string {
return fmt.Sprintf("https://%s/%s/%s/compare/%s?expand=1", siteDomain, owner, repository, from)
},
pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string {
return fmt.Sprintf("https://%s/%s/%s/compare/%s...%s?expand=1", siteDomain, owner, repository, to, from)
},
URLRegexStrings: defaultUrlRegexStrings,
}
return service
}
func (s *Service) PullRequestURL(owner string, repository string, from string, to string) string {
func NewBitBucketService(repositoryDomain string, siteDomain string) *Service {
return &Service{
Name: repositoryDomain,
pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string {
return fmt.Sprintf("https://%s/%s/%s/pull-requests/new?source=%s&t=1", siteDomain, owner, repository, from)
},
pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string {
return fmt.Sprintf("https://%s/%s/%s/pull-requests/new?source=%s&dest=%s&t=1", siteDomain, owner, repository, from, to)
},
URLRegexStrings: defaultUrlRegexStrings,
}
}
func NewGitLabService(repositoryDomain string, siteDomain string) *Service {
return &Service{
Name: repositoryDomain,
pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string {
return fmt.Sprintf("https://%s/%s/%s/merge_requests/new?merge_request[source_branch]=%s", siteDomain, owner, repository, from)
},
pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string {
return fmt.Sprintf("https://%s/%s/%s/merge_requests/new?merge_request[source_branch]=%s&merge_request[target_branch]=%s", siteDomain, owner, repository, from, to)
},
URLRegexStrings: defaultUrlRegexStrings,
}
}
func (s *Service) PullRequestURL(repoURL string, from string, to string) string {
repoInfo := s.getRepoInfoFromURL(repoURL)
if to == "" {
return s.pullRequestURLIntoDefaultBranch(owner, repository, from)
return s.pullRequestURLIntoDefaultBranch(repoInfo.Owner, repoInfo.Repository, from)
} else {
return s.pullRequestURLIntoTargetBranch(owner, repository, from, to)
return s.pullRequestURLIntoTargetBranch(repoInfo.Owner, repoInfo.Repository, from, to)
}
}
// PullRequest opens a link in browser to create new pull request
// with selected branch
type PullRequest struct {
GitServices []*Service
GitCommand *GitCommand
GitCommand *GitCommand
}
// RepoInformation holds some basic information about the repo
@ -76,42 +85,53 @@ type RepoInformation struct {
Repository string
}
func getServices(config config.AppConfigurer) []*Service {
services := []*Service{
NewService("github", "github.com", "github.com"),
NewService("bitbucket", "bitbucket.org", "bitbucket.org"),
NewService("gitlab", "gitlab.com", "gitlab.com"),
}
configServices := config.GetUserConfig().Services
for repoDomain, typeAndDomain := range configServices {
splitData := strings.Split(typeAndDomain, ":")
if len(splitData) != 2 {
// TODO log this misconfiguration
continue
}
service := NewService(splitData[0], repoDomain, splitData[1])
if service == nil {
// TODO log this unsupported service
continue
}
services = append(services, service)
}
return services
}
// NewPullRequest creates new instance of PullRequest
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
return &PullRequest{
GitServices: getServices(gitCommand.Config),
GitCommand: gitCommand,
GitCommand: gitCommand,
}
}
func (pr *PullRequest) getServices() []*Service {
services := []*Service{
NewGithubService("github.com", "github.com"),
NewBitBucketService("bitbucket.org", "bitbucket.org"),
NewGitLabService("gitlab.com", "gitlab.com"),
}
configServices := pr.GitCommand.Config.GetUserConfig().Services
if len(configServices) > 0 {
serviceFuncMap := map[string]func(repositoryDomain string, siteDomain string) *Service{
"github": NewGithubService,
"bitbucket": NewBitBucketService,
"gitlab": NewGitLabService,
}
for repoDomain, 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
}
serviceFunc := serviceFuncMap[splitData[0]]
if serviceFunc == nil {
serviceNames := []string{}
for serviceName := range serviceFuncMap {
serviceNames = append(serviceNames, serviceName)
}
pr.GitCommand.Log.Errorf("Unknown git service type: '%s'. Expected one of %s", splitData[0], strings.Join(serviceNames, ", "))
continue
}
services = append(services, serviceFunc(repoDomain, splitData[1]))
}
}
return services
}
// Create opens link to new pull request in browser
func (pr *PullRequest) Create(from string, to string) (string, error) {
pullRequestURL, err := pr.getPullRequestURL(from, to)
@ -142,7 +162,7 @@ func (pr *PullRequest) getPullRequestURL(from string, to string) (string, error)
repoURL := pr.GitCommand.GetRemoteURL()
var gitService *Service
for _, service := range pr.GitServices {
for _, service := range pr.getServices() {
if strings.Contains(repoURL, service.Name) {
gitService = service
break
@ -153,34 +173,22 @@ func (pr *PullRequest) getPullRequestURL(from string, to string) (string, error)
return "", errors.New(pr.GitCommand.Tr.UnsupportedGitService)
}
repoInfo := getRepoInfoFromURL(repoURL)
pullRequestURL := gitService.PullRequestURL(repoInfo.Owner, repoInfo.Repository, from, to)
pullRequestURL := gitService.PullRequestURL(repoURL, from, to)
return pullRequestURL, nil
}
func getRepoInfoFromURL(url string) *RepoInformation {
isHTTP := strings.HasPrefix(url, "http")
if isHTTP {
splits := strings.Split(url, "/")
owner := strings.Join(splits[3:len(splits)-1], "/")
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
return &RepoInformation{
Owner: owner,
Repository: repo,
func (s *Service) getRepoInfoFromURL(url string) *RepoInformation {
for _, regexStr := range s.URLRegexStrings {
re := regexp.MustCompile(regexStr)
matches := utils.FindNamedMatches(re, url)
if matches != nil {
return &RepoInformation{
Owner: matches["owner"],
Repository: matches["repo"],
}
}
}
tmpSplit := strings.Split(url, ":")
splits := strings.Split(tmpSplit[1], "/")
owner := strings.Join(splits[0:len(splits)-1], "/")
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
return &RepoInformation{
Owner: owner,
Repository: repo,
}
return nil
}

View File

@ -9,6 +9,7 @@ import (
// TestGetRepoInfoFromURL is a function.
func TestGetRepoInfoFromURL(t *testing.T) {
type scenario struct {
service *Service
testName string
repoURL string
test func(*RepoInformation)
@ -16,6 +17,7 @@ func TestGetRepoInfoFromURL(t *testing.T) {
scenarios := []scenario{
{
NewGithubService("github.com", "github.com"),
"Returns repository information for git remote url",
"git@github.com:petersmith/super_calculator",
func(repoInfo *RepoInformation) {
@ -24,6 +26,16 @@ func TestGetRepoInfoFromURL(t *testing.T) {
},
},
{
NewGithubService("github.com", "github.com"),
"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")
},
},
{
NewGithubService("github.com", "github.com"),
"Returns repository information for http remote url",
"https://my_username@bitbucket.org/johndoe/social_network.git",
func(repoInfo *RepoInformation) {
@ -35,7 +47,7 @@ func TestGetRepoInfoFromURL(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
s.test(getRepoInfoFromURL(s.repoURL))
s.test(s.service.getRepoInfoFromURL(s.repoURL))
})
}
}