2021-12-28 04:58:09 +02:00
|
|
|
package hosting_service
|
|
|
|
|
|
|
|
import (
|
2022-01-18 15:05:48 +02:00
|
|
|
"net/url"
|
2021-12-28 04:58:09 +02:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/go-errors/errors"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2023-07-24 05:06:42 +02:00
|
|
|
"github.com/samber/lo"
|
2021-12-28 04:58:09 +02:00
|
|
|
"github.com/sirupsen/logrus"
|
2022-03-19 00:38:49 +02:00
|
|
|
|
2023-07-24 05:06:42 +02:00
|
|
|
"golang.org/x/exp/slices"
|
2021-12-28 04:58:09 +02:00
|
|
|
)
|
|
|
|
|
2023-04-18 18:16:09 +02:00
|
|
|
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, gitea, etc.
|
2021-12-28 04:58:09 +02:00
|
|
|
// 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 == "" {
|
2022-01-10 21:13:38 +02:00
|
|
|
return gitService.getPullRequestURLIntoDefaultBranch(url.QueryEscape(from)), nil
|
2021-12-28 04:58:09 +02:00
|
|
|
} else {
|
2022-01-10 21:13:38 +02:00
|
|
|
return gitService.getPullRequestURLIntoTargetBranch(url.QueryEscape(from), url.QueryEscape(to)), nil
|
2021-12-28 04:58:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-12-30 17:04:49 +02:00
|
|
|
repoURL, err := serviceDomain.serviceDefinition.getRepoURLFromRemoteURL(self.remoteURL, serviceDomain.webDomain)
|
2021-12-28 04:58:09 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Service{
|
2021-12-30 17:04:49 +02:00
|
|
|
repoURL: repoURL,
|
2021-12-28 04:58:09 +02:00
|
|
|
ServiceDefinition: serviceDomain.serviceDefinition,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *HostingServiceMgr) getServiceDomain(repoURL string) (*ServiceDomain, error) {
|
|
|
|
candidateServiceDomains := self.getCandidateServiceDomains()
|
|
|
|
|
|
|
|
for _, serviceDomain := range candidateServiceDomains {
|
2022-01-20 16:31:57 +02:00
|
|
|
if strings.Contains(repoURL, serviceDomain.gitDomain) {
|
2021-12-28 04:58:09 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-03-19 00:38:49 +02:00
|
|
|
serviceDomains := slices.Clone(defaultServiceDomains)
|
2021-12-28 04:58:09 +02:00
|
|
|
|
|
|
|
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 {
|
2023-07-24 05:06:42 +02:00
|
|
|
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
|
2022-03-19 10:51:48 +02:00
|
|
|
return serviceDefinition.provider
|
|
|
|
})
|
|
|
|
|
2021-12-28 04:58:09 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServiceDefinition struct {
|
|
|
|
provider string
|
|
|
|
pullRequestURLIntoDefaultBranch string
|
|
|
|
pullRequestURLIntoTargetBranch string
|
|
|
|
commitURL string
|
|
|
|
regexStrings []string
|
2021-12-30 17:04:49 +02:00
|
|
|
|
|
|
|
// can expect 'webdomain' to be passed in. Otherwise, you get to pick what we match in the regex
|
|
|
|
repoURLTemplate string
|
2021-12-28 04:58:09 +02:00
|
|
|
}
|
|
|
|
|
2021-12-30 17:04:49 +02:00
|
|
|
func (self ServiceDefinition) getRepoURLFromRemoteURL(url string, webDomain string) (string, error) {
|
2021-12-28 04:58:09 +02:00
|
|
|
for _, regexStr := range self.regexStrings {
|
|
|
|
re := regexp.MustCompile(regexStr)
|
2021-12-30 17:04:49 +02:00
|
|
|
input := utils.FindNamedMatches(re, url)
|
|
|
|
if input != nil {
|
|
|
|
input["webDomain"] = webDomain
|
|
|
|
return utils.ResolvePlaceholderString(self.repoURLTemplate, input), nil
|
2021-12-28 04:58:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-30 17:04:49 +02:00
|
|
|
return "", errors.New("Failed to parse repo information from url")
|
2021-12-28 04:58:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type Service struct {
|
2021-12-30 17:04:49 +02:00
|
|
|
repoURL string
|
2021-12-28 04:58:09 +02:00
|
|
|
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 {
|
2021-12-30 17:04:49 +02:00
|
|
|
return self.repoURL + utils.ResolvePlaceholderString(templateString, args)
|
2021-12-28 04:58:09 +02:00
|
|
|
}
|