From 4cd72b09a2c10b692e93b24cf164e62183825675 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 21 Mar 2026 15:15:01 +0100 Subject: [PATCH] Refactor hosting service to support repo name extraction Extract parseRemoteUrl helper to avoid duplication between getRepoURLFromRemoteURL and new getRepoNameFromRemoteURL. Add repoNameTemplate to ServiceDefinition and GetRepoName() to HostingServiceMgr. Add GetRepoInfoFromURL for callers that need owner/repo without a full service lookup. --- pkg/commands/hosting_service/definitions.go | 17 ++++- .../hosting_service/hosting_service.go | 74 +++++++++++++++++-- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/pkg/commands/hosting_service/definitions.go b/pkg/commands/hosting_service/definitions.go index 25e449340..130bf0481 100644 --- a/pkg/commands/hosting_service/definitions.go +++ b/pkg/commands/hosting_service/definitions.go @@ -6,7 +6,11 @@ var defaultUrlRegexStrings = []string{ `^(?:https?|ssh)://[^/]+/(?P.*)/(?P.*?)(?:\.git)?$`, `^(.*?@)?.*:/*(?P.*)/(?P.*?)(?:\.git)?$`, } -var defaultRepoURLTemplate = "https://{{.webDomain}}/{{.owner}}/{{.repo}}" + +var ( + defaultRepoURLTemplate = "https://{{.webDomain}}/{{.owner}}/{{.repo}}" + defaultRepoNameTemplate = "{{.owner}}/{{.repo}}" +) // we've got less type safety using go templates but this lends itself better to // users adding custom service definitions in their config @@ -17,6 +21,7 @@ var githubServiceDef = ServiceDefinition{ commitURL: "/commit/{{.CommitHash}}", regexStrings: defaultUrlRegexStrings, repoURLTemplate: defaultRepoURLTemplate, + repoNameTemplate: defaultRepoNameTemplate, } var bitbucketServiceDef = ServiceDefinition{ @@ -28,7 +33,8 @@ var bitbucketServiceDef = ServiceDefinition{ `^(?:https?|ssh)://.*/(?P.*)/(?P.*?)(?:\.git)?$`, `^.*@.*:/*(?P.*)/(?P.*?)(?:\.git)?$`, }, - repoURLTemplate: defaultRepoURLTemplate, + repoURLTemplate: defaultRepoURLTemplate, + repoNameTemplate: defaultRepoNameTemplate, } var gitLabServiceDef = ServiceDefinition{ @@ -38,6 +44,7 @@ var gitLabServiceDef = ServiceDefinition{ commitURL: "/-/commit/{{.CommitHash}}", regexStrings: defaultUrlRegexStrings, repoURLTemplate: defaultRepoURLTemplate, + repoNameTemplate: defaultRepoNameTemplate, } var azdoServiceDef = ServiceDefinition{ @@ -51,7 +58,8 @@ var azdoServiceDef = ServiceDefinition{ `^https://.*@dev.azure.com/(?P.*?)/(?P.*?)/_git/(?P.*?)(?:\.git)?$`, `^https://.*/(?P.*?)/(?P.*?)/_git/(?P.*?)(?:\.git)?$`, }, - repoURLTemplate: "https://{{.webDomain}}/{{.org}}/{{.project}}/_git/{{.repo}}", + repoURLTemplate: "https://{{.webDomain}}/{{.org}}/{{.project}}/_git/{{.repo}}", + repoNameTemplate: "{{.org}}/{{.project}}/{{.repo}}", } var bitbucketServerServiceDef = ServiceDefinition{ @@ -63,7 +71,8 @@ var bitbucketServerServiceDef = ServiceDefinition{ `^ssh://git@.*/(?P.*)/(?P.*?)(?:\.git)?$`, `^https://.*/scm/(?P.*)/(?P.*?)(?:\.git)?$`, }, - repoURLTemplate: "https://{{.webDomain}}/projects/{{.project}}/repos/{{.repo}}", + repoURLTemplate: "https://{{.webDomain}}/projects/{{.project}}/repos/{{.repo}}", + repoNameTemplate: "{{.project}}/{{.repo}}", } var giteaServiceDef = ServiceDefinition{ diff --git a/pkg/commands/hosting_service/hosting_service.go b/pkg/commands/hosting_service/hosting_service.go index 1c328fd0d..620d0d0a7 100644 --- a/pkg/commands/hosting_service/hosting_service.go +++ b/pkg/commands/hosting_service/hosting_service.go @@ -61,6 +61,18 @@ func (self *HostingServiceMgr) GetCommitURL(commitHash string) (string, error) { return pullRequestURL, nil } +// e.g. 'jesseduffield/lazygit' +func (self *HostingServiceMgr) GetRepoName() (string, error) { + gitService, err := self.getService() + if err != nil { + return "", err + } + + repoName := gitService.repoName + + return repoName, nil +} + func (self *HostingServiceMgr) getService() (*Service, error) { serviceDomain, err := self.getServiceDomain(self.remoteURL) if err != nil { @@ -72,8 +84,14 @@ func (self *HostingServiceMgr) getService() (*Service, error) { return nil, err } + repoName, err := serviceDomain.serviceDefinition.getRepoNameFromRemoteURL(self.remoteURL) + if err != nil { + return nil, err + } + return &Service{ repoURL: repoURL, + repoName: repoName, ServiceDefinition: serviceDomain.serviceDefinition, }, nil } @@ -144,24 +162,68 @@ type ServiceDefinition struct { regexStrings []string // can expect 'webdomain' to be passed in. Otherwise, you get to pick what we match in the regex - repoURLTemplate string + repoURLTemplate string + repoNameTemplate string } func (self ServiceDefinition) getRepoURLFromRemoteURL(url string, webDomain string) (string, error) { + matches, err := self.parseRemoteUrl(url) + if err != nil { + return "", err + } + + matches["webDomain"] = webDomain + return utils.ResolvePlaceholderString(self.repoURLTemplate, matches), nil +} + +func (self ServiceDefinition) getRepoNameFromRemoteURL(url string) (string, error) { + matches, err := self.parseRemoteUrl(url) + if err != nil { + return "", err + } + + return utils.ResolvePlaceholderString(self.repoNameTemplate, matches), nil +} + +func (self ServiceDefinition) parseRemoteUrl(url string) (map[string]string, error) { for _, regexStr := range self.regexStrings { re := regexp.MustCompile(regexStr) - input := utils.FindNamedMatches(re, url) - if input != nil { - input["webDomain"] = webDomain - return utils.ResolvePlaceholderString(self.repoURLTemplate, input), nil + matches := utils.FindNamedMatches(re, url) + if matches != nil { + return matches, nil } } - return "", errors.New("Failed to parse repo information from url") + return nil, errors.New("Failed to parse repo information from url") +} + +// RepoInformation holds the owner and repository name parsed from a remote URL. +type RepoInformation struct { + Owner string + Repository string +} + +// GetRepoInfoFromURL parses a remote URL (SSH or HTTPS) and extracts the +// owner and repository name using the default URL regex patterns. +func GetRepoInfoFromURL(url string) (RepoInformation, error) { + for _, regexStr := range defaultUrlRegexStrings { + re := regexp.MustCompile(regexStr) + matches := utils.FindNamedMatches(re, url) + if matches != nil { + return RepoInformation{ + Owner: matches["owner"], + Repository: matches["repo"], + }, nil + } + } + + return RepoInformation{}, errors.New("Failed to parse repo information from url") } type Service struct { repoURL string + // e.g. 'jesseduffield/lazygit' + repoName string ServiceDefinition }