Skip to content

Commit 522be1d

Browse files
committed
fix: gitprovider azure organizationUrl for self-hosted Azure DevOps URLs
incoporate pr feedback and added tests for func NewProvider Signed-off-by: b3go <52070186+b3go@users.noreply.github.com>
1 parent 3c16e1d commit 522be1d

File tree

2 files changed

+194
-91
lines changed

2 files changed

+194
-91
lines changed

internal/gitprovider/azure/azure.go

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const ProviderName = "azure"
2323
// - https://<org>.visualstudio.com/<project>/_git/<repo>
2424
//
2525
// We support both forms.
26+
// Additionally, self-hosted Azure DevOps Server URLs can be of the following forms:
27+
// - https://<host>/<collection>/<project>/_git/<repo>
28+
// - https://<host>/tfs/<collection>/<project>/_git/<repo>
2629
const (
2730
legacyHostSuffix = "visualstudio.com"
2831
modernHostSuffix = "dev.azure.com"
@@ -49,6 +52,7 @@ func init() {
4952
}
5053

5154
type provider struct {
55+
baseUrl string
5256
org string
5357
project string
5458
repo string
@@ -63,14 +67,15 @@ func NewProvider(
6367
if opts == nil || opts.Token == "" {
6468
return nil, fmt.Errorf("token is required for Azure DevOps provider")
6569
}
66-
org, project, repo, err := parseRepoURL(repoURL)
70+
baseUrl, org, project, repo, err := parseRepoURL(repoURL)
6771
if err != nil {
6872
return nil, err
6973
}
70-
organizationUrl := fmt.Sprintf("https://%s/%s", modernHostSuffix, org)
74+
organizationUrl := fmt.Sprintf("https://%s/%s", baseUrl, org)
7175
connection := azuredevops.NewPatConnection(organizationUrl, opts.Token)
7276

7377
return &provider{
78+
baseUrl: baseUrl,
7479
org: org,
7580
project: project,
7681
repo: repo,
@@ -79,8 +84,6 @@ func NewProvider(
7984
}
8085

8186
// CreatePullRequest implements gitprovider.Interface.
82-
const errConvertPullRequestFmt = "error converting pull request %d: %w"
83-
8487
func (p *provider) CreatePullRequest(
8588
ctx context.Context,
8689
opts *gitprovider.CreatePullRequestOpts,
@@ -121,7 +124,7 @@ func (p *provider) CreatePullRequest(
121124
}
122125
pr, err := convertADOPullRequest(adoPR)
123126
if err != nil {
124-
return nil, fmt.Errorf(errConvertPullRequestFmt, adoPR.PullRequestId, err)
127+
return nil, fmt.Errorf("error converting pull request %d: %w", adoPR.PullRequestId, err)
125128
}
126129
return pr, nil
127130
}
@@ -145,7 +148,7 @@ func (p *provider) GetPullRequest(
145148
}
146149
pr, err := convertADOPullRequest(adoPR)
147150
if err != nil {
148-
return nil, fmt.Errorf(errConvertPullRequestFmt, id, err)
151+
return nil, fmt.Errorf("error converting pull request %d: %w", id, err)
149152
}
150153
return pr, nil
151154
}
@@ -176,7 +179,7 @@ func (p *provider) ListPullRequests(
176179
for _, adoPR := range *adoPRs {
177180
pr, err := convertADOPullRequest(&adoPR)
178181
if err != nil {
179-
return nil, fmt.Errorf(errConvertPullRequestFmt, adoPR.PullRequestId, err)
182+
return nil, fmt.Errorf("error converting pull request %d: %w", adoPR.PullRequestId, err)
180183
}
181184
pts = append(pts, *pr)
182185
}
@@ -211,52 +214,54 @@ func convertADOPullRequest(pr *adogit.GitPullRequest) (*gitprovider.PullRequest,
211214
}, nil
212215
}
213216

214-
func parseRepoURL(repoURL string) (string, string, string, error) {
217+
func parseRepoURL(repoURL string) (baseUrl string, org string, proj string, repo string, err error) {
215218
u, err := url.Parse(git.NormalizeURL(repoURL))
216219
if err != nil {
217-
return "", "", "", fmt.Errorf("error parsing Azure DevOps repository URL %q: %w", repoURL, err)
220+
return "", "", "", "", fmt.Errorf("error parsing Azure DevOps repository URL %q: %w", repoURL, err)
218221
}
219222
if u.Host == modernHostSuffix {
220-
return parseModernRepoURL(u)
223+
org, project, repo, err := parseModernRepoURL(u)
224+
return modernHostSuffix, org, project, repo, err
221225
} else if strings.HasSuffix(u.Host, legacyHostSuffix) {
222-
return parseLegacyRepoURL(u)
223-
} else if strings.Contains(u.Host, ProviderName) {
224-
return parseSelfHostedRepoUrl(u)
226+
org, project, repo, err := parseLegacyRepoURL(u)
227+
return modernHostSuffix, org, project, repo, err
225228
}
226-
return "", "", "", fmt.Errorf("unsupported host %q", u.Host)
229+
return parseSelfHostedRepoUrl(u)
227230
}
228231

229-
const errExtractRepoInfoFmt = "could not extract repository organization, project, and name from URL %q"
230-
231232
// parseModernRepoURL parses a modern Azure DevOps repository URL.
232-
func parseModernRepoURL(u *url.URL) (string, string, string, error) {
233+
func parseModernRepoURL(u *url.URL) (org string, proj string, repo string, err error) {
233234
parts := strings.Split(u.Path, "/")
234235
if len(parts) != 5 {
235-
return "", "", "", fmt.Errorf(errExtractRepoInfoFmt, u)
236+
return "", "", "", fmt.Errorf("could not extract repository organization, project, and name from URL %q", u)
236237
}
237238
return parts[1], parts[2], parts[4], nil
238239
}
239240

240241
// parseLegacyRepoURL parses a legacy Azure DevOps repository URL.
241-
func parseLegacyRepoURL(u *url.URL) (string, string, string, error) {
242+
func parseLegacyRepoURL(u *url.URL) (org string, proj string, repo string, err error) {
242243
organization := strings.TrimSuffix(u.Host, ".visualstudio.com")
243244
parts := strings.Split(u.Path, "/")
244245
if len(parts) != 4 {
245-
return "", "", "", fmt.Errorf(errExtractRepoInfoFmt, u)
246+
return "", "", "", fmt.Errorf("could not extract repository organization, project, and name from URL %q", u)
246247
}
247248
return organization, parts[1], parts[3], nil
248249
}
249250

250251
// parseSelfHostedRepoUrl parses a self hosted Azure DevOps Server URL.
251-
func parseSelfHostedRepoUrl(u *url.URL) (string, string, string, error) {
252-
parts := strings.Split(u.Path, "/")
253-
// Handle the case where the URL is in the format https://<host>/<collection>/<project>/_git/<repo>
254-
if len(parts) == 5 {
255-
return parts[1], parts[2], parts[4], nil
256-
}
257-
// Handle the case where the URL is in the format https://<host>/tfs/<collection>/<project>/_git/<repo>
258-
if len(parts) == 6 && parts[1] == "tfs" {
259-
return parts[2], parts[3], parts[5], nil
252+
func parseSelfHostedRepoUrl(u *url.URL) (baseUrl string, org string, proj string, repo string, err error) {
253+
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
254+
255+
switch {
256+
case len(parts) == 4 && parts[2] == "_git":
257+
// Format: https://<host>/<collection>/<project>/_git/<repo>
258+
return u.Host, parts[0], parts[1], parts[3], nil
259+
260+
case len(parts) == 5 && parts[0] == "tfs" && parts[3] == "_git":
261+
// Format: https://<host>/tfs/<collection>/<project>/_git/<repo>
262+
return u.Host + "/tfs", parts[1], parts[2], parts[4], nil
263+
264+
default:
265+
return "", "", "", "", fmt.Errorf("invalid Azure DevOps Server URL: %q", u)
260266
}
261-
return "", "", "", fmt.Errorf(errExtractRepoInfoFmt, u)
262267
}

0 commit comments

Comments
 (0)