mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-30 03:23:08 +03:00
refactor to use regex for matching git service URL
This commit is contained in:
@ -2,72 +2,81 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"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, ...)
|
// Service is a service that repository is on (Github, Bitbucket, ...)
|
||||||
type Service struct {
|
type Service struct {
|
||||||
Name string
|
Name string
|
||||||
pullRequestURLIntoDefaultBranch func(owner string, repository string, from string) string
|
pullRequestURLIntoDefaultBranch func(owner string, repository string, from string) string
|
||||||
pullRequestURLIntoTargetBranch func(owner string, repository string, from string, to 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 NewGithubService(repositoryDomain string, siteDomain string) *Service {
|
||||||
func NewService(typeName string, repositoryDomain string, siteDomain string) *Service {
|
return &Service{
|
||||||
var service *Service
|
Name: repositoryDomain,
|
||||||
|
pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string {
|
||||||
switch typeName {
|
return fmt.Sprintf("https://%s/%s/%s/compare/%s?expand=1", siteDomain, owner, repository, from)
|
||||||
case "github":
|
},
|
||||||
service = &Service{
|
pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string {
|
||||||
Name: repositoryDomain,
|
return fmt.Sprintf("https://%s/%s/%s/compare/%s...%s?expand=1", siteDomain, owner, repository, to, from)
|
||||||
pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string {
|
},
|
||||||
return fmt.Sprintf("https://%s/%s/%s/compare/%s?expand=1", siteDomain, owner, repository, from)
|
URLRegexStrings: defaultUrlRegexStrings,
|
||||||
},
|
|
||||||
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)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 == "" {
|
if to == "" {
|
||||||
return s.pullRequestURLIntoDefaultBranch(owner, repository, from)
|
return s.pullRequestURLIntoDefaultBranch(repoInfo.Owner, repoInfo.Repository, from)
|
||||||
} else {
|
} 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
|
// PullRequest opens a link in browser to create new pull request
|
||||||
// with selected branch
|
// with selected branch
|
||||||
type PullRequest struct {
|
type PullRequest struct {
|
||||||
GitServices []*Service
|
GitCommand *GitCommand
|
||||||
GitCommand *GitCommand
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoInformation holds some basic information about the repo
|
// RepoInformation holds some basic information about the repo
|
||||||
@ -76,42 +85,53 @@ type RepoInformation struct {
|
|||||||
Repository string
|
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
|
// NewPullRequest creates new instance of PullRequest
|
||||||
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
|
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
|
||||||
return &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
|
// Create opens link to new pull request in browser
|
||||||
func (pr *PullRequest) Create(from string, to string) (string, error) {
|
func (pr *PullRequest) Create(from string, to string) (string, error) {
|
||||||
pullRequestURL, err := pr.getPullRequestURL(from, to)
|
pullRequestURL, err := pr.getPullRequestURL(from, to)
|
||||||
@ -142,7 +162,7 @@ func (pr *PullRequest) getPullRequestURL(from string, to string) (string, error)
|
|||||||
repoURL := pr.GitCommand.GetRemoteURL()
|
repoURL := pr.GitCommand.GetRemoteURL()
|
||||||
var gitService *Service
|
var gitService *Service
|
||||||
|
|
||||||
for _, service := range pr.GitServices {
|
for _, service := range pr.getServices() {
|
||||||
if strings.Contains(repoURL, service.Name) {
|
if strings.Contains(repoURL, service.Name) {
|
||||||
gitService = service
|
gitService = service
|
||||||
break
|
break
|
||||||
@ -153,34 +173,22 @@ func (pr *PullRequest) getPullRequestURL(from string, to string) (string, error)
|
|||||||
return "", errors.New(pr.GitCommand.Tr.UnsupportedGitService)
|
return "", errors.New(pr.GitCommand.Tr.UnsupportedGitService)
|
||||||
}
|
}
|
||||||
|
|
||||||
repoInfo := getRepoInfoFromURL(repoURL)
|
pullRequestURL := gitService.PullRequestURL(repoURL, from, to)
|
||||||
|
|
||||||
pullRequestURL := gitService.PullRequestURL(repoInfo.Owner, repoInfo.Repository, from, to)
|
|
||||||
|
|
||||||
return pullRequestURL, nil
|
return pullRequestURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRepoInfoFromURL(url string) *RepoInformation {
|
func (s *Service) getRepoInfoFromURL(url string) *RepoInformation {
|
||||||
isHTTP := strings.HasPrefix(url, "http")
|
for _, regexStr := range s.URLRegexStrings {
|
||||||
|
re := regexp.MustCompile(regexStr)
|
||||||
if isHTTP {
|
matches := utils.FindNamedMatches(re, url)
|
||||||
splits := strings.Split(url, "/")
|
if matches != nil {
|
||||||
owner := strings.Join(splits[3:len(splits)-1], "/")
|
return &RepoInformation{
|
||||||
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
Owner: matches["owner"],
|
||||||
|
Repository: matches["repo"],
|
||||||
return &RepoInformation{
|
}
|
||||||
Owner: owner,
|
|
||||||
Repository: repo,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpSplit := strings.Split(url, ":")
|
return nil
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
// TestGetRepoInfoFromURL is a function.
|
// TestGetRepoInfoFromURL is a function.
|
||||||
func TestGetRepoInfoFromURL(t *testing.T) {
|
func TestGetRepoInfoFromURL(t *testing.T) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
|
service *Service
|
||||||
testName string
|
testName string
|
||||||
repoURL string
|
repoURL string
|
||||||
test func(*RepoInformation)
|
test func(*RepoInformation)
|
||||||
@ -16,6 +17,7 @@ func TestGetRepoInfoFromURL(t *testing.T) {
|
|||||||
|
|
||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
{
|
{
|
||||||
|
NewGithubService("github.com", "github.com"),
|
||||||
"Returns repository information for git remote url",
|
"Returns repository information for git remote url",
|
||||||
"git@github.com:petersmith/super_calculator",
|
"git@github.com:petersmith/super_calculator",
|
||||||
func(repoInfo *RepoInformation) {
|
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",
|
"Returns repository information for http remote url",
|
||||||
"https://my_username@bitbucket.org/johndoe/social_network.git",
|
"https://my_username@bitbucket.org/johndoe/social_network.git",
|
||||||
func(repoInfo *RepoInformation) {
|
func(repoInfo *RepoInformation) {
|
||||||
@ -35,7 +47,7 @@ func TestGetRepoInfoFromURL(t *testing.T) {
|
|||||||
|
|
||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
s.test(getRepoInfoFromURL(s.repoURL))
|
s.test(s.service.getRepoInfoFromURL(s.repoURL))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user