@@ -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>
2629const (
2730 legacyHostSuffix = "visualstudio.com"
2831 modernHostSuffix = "dev.azure.com"
@@ -49,6 +52,7 @@ func init() {
4952}
5053
5154type 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-
8487func (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