azure-devops/azext_devops/dev/common/vsts_git_url_info.py (126 lines of code) (raw):
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
from msrest.serialization import Model
from knack.log import get_logger
from .file_cache import get_cli_cache
from .uri import uri_parse
logger = get_logger(__name__)
class VstsGitUrlInfo():
""" VstsGitUrlInfo.
"""
def __init__(self, remote_url):
from msrest import Serializer, Deserializer
from msrest.exceptions import DeserializationError, SerializationError
self.project = None
self.repo = None
self.uri = None
if remote_url is not None:
logger.debug("Remote url: %s", remote_url)
models = {'_RemoteInfo': self._RemoteInfo}
remote_url = remote_url.lower()
remote_info = None
if _git_remote_info_cache[remote_url]:
deserializer = Deserializer(models)
try:
remote_info = deserializer.deserialize_data(_git_remote_info_cache[remote_url], '_RemoteInfo')
except DeserializationError as ex:
logger.debug(ex, exc_info=True)
if remote_info is not None:
self.project = remote_info.project
self.repo = remote_info.repository
self.uri = remote_info.server_url
if remote_info is None:
vsts_info = self.get_vsts_info(remote_url)
if vsts_info is not None:
self.project = vsts_info.repository.project.id
self.repo = vsts_info.repository.id
apis_path_segment = '/_apis/'
apis_path_segment_pos = vsts_info.repository.url.find(apis_path_segment)
if apis_path_segment_pos >= 0:
self.uri = vsts_info.repository.url[:apis_path_segment_pos]
else:
self.uri = vsts_info.server_url
serializer = Serializer(models)
try:
_git_remote_info_cache[remote_url] = \
serializer.serialize_data(self._RemoteInfo(self.project, self.repo, self.uri),
'_RemoteInfo')
except SerializationError as ex:
logger.debug(ex, exc_info=True)
@staticmethod
def get_vsts_info(remote_url):
from azext_devops.devops_sdk.v5_0.git.git_client import GitClient
from .services import _get_credentials
components = uri_parse(remote_url.lower())
if components.scheme == 'ssh':
# Convert to https url.
netloc = VstsGitUrlInfo.convert_ssh_netloc_to_https_netloc(components.netloc)
if netloc is None:
return None
# New ssh urls do not have _ssh so path is like org/project/repo
# We need to convert it into project/_git/repo/ or org/project/_git/repo for dev.azure.com urls
path = components.path
ssh_path_segment = '_ssh/'
ssh_path_segment_pos = components.path.find(ssh_path_segment)
if ssh_path_segment_pos < 0: # new ssh url
path_vals = components.path.strip('/').split('/')
if path_vals and len(path_vals) == 3:
if 'visualstudio.com' in netloc:
path = '{proj}/{git}/{repo}'.format(proj=path_vals[1], git='_git', repo=path_vals[2])
elif 'dev.azure.com' in netloc:
path = '{org}/{proj}/{git}/{repo}'.format(
org=path_vals[0], proj=path_vals[1], git='_git', repo=path_vals[2])
else:
logger.debug("Unsupported url format encountered in git repo url discovery.")
uri = 'https://' + netloc + '/' + path
else: # old ssh urls
uri = 'https://' + netloc + '/' + path.strip('/')
ssh_path_segment_pos = uri.find(ssh_path_segment)
if ssh_path_segment_pos >= 0:
uri = uri[:ssh_path_segment_pos] + '_git/' + uri[ssh_path_segment_pos + len(ssh_path_segment):]
else:
uri = remote_url
credentials = _get_credentials(uri)
try:
return GitClient.get_vsts_info_by_remote_url(uri, credentials=credentials)
except Exception as ex: # pylint: disable=broad-except
exceptionTypeName = type(ex).__name__
if exceptionTypeName == 'AzureDevOpsAuthenticationError':
logger.warning('Auto-detect from git remote url failed because of insufficient permissions.')
return None
import sys
from six import reraise
reraise(*sys.exc_info())
@staticmethod
def convert_ssh_netloc_to_https_netloc(netloc):
if netloc is None:
return None
if netloc.find('@') < 0:
# on premise url
logger.warning('DevOps SSH URLs are not supported for repo auto-detection yet. See the following issue for \
latest updates: https://github.com/Microsoft/azure-devops-cli-extension/issues/142')
return None
# hosted url
import re
regex = re.compile(r'([^@]+)@[^\.]+(\.[^:]+)')
match = regex.match(netloc)
if match is not None:
# Handle new and old url formats
if match.group(1) == 'git' and match.group(2) == '.dev.azure.com':
return match.group(2).strip('.')
return match.group(1) + match.group(2)
return None
@staticmethod
def is_vsts_url_candidate(url):
if url is None:
return False
components = uri_parse(url.lower())
if components.netloc == 'github.com':
return False
if components.path is not None \
and (components.path.find('/_git/') >= 0 or components.path.find('/_ssh/') >= 0 or
components.scheme == 'ssh'):
return True
return False
class _RemoteInfo(Model):
_attribute_map = {
'project': {'key': 'project', 'type': 'str'},
'repository': {'key': 'repository', 'type': 'str'},
'server_url': {'key': 'serverUrl', 'type': 'str'}
}
def __init__(self, project=None, repository=None, server_url=None):
super(VstsGitUrlInfo._RemoteInfo, self).__init__() # pylint: disable=protected-access
self.project = project
self.repository = repository
self.server_url = server_url
_git_remote_info_cache = get_cli_cache('remotes', 0)