azure-devops/azext_devops/dev/common/git.py (134 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.
# --------------------------------------------------------------------------------------------
import subprocess
import sys
from knack.log import get_logger
from knack.util import CLIError
from .uri import uri_parse
logger = get_logger(__name__)
_GIT_EXE = 'git'
def set_config(key, value, local=True):
scope = _get_git_config_scope_arg(local)
subprocess.check_output([_GIT_EXE, 'config', scope, key, value])
def unset_config(key, local=True):
scope = _get_git_config_scope_arg(local)
subprocess.check_output([_GIT_EXE, 'config', scope, '--unset', key])
def get_config(key, local=True):
scope = _get_git_config_scope_arg(local)
return subprocess.check_output([_GIT_EXE, 'config', scope, key])
def _get_git_config_scope_arg(local):
if local:
return '--local'
return '--global'
def fetch_remote_and_checkout(refName, remote_name):
subprocess.run([_GIT_EXE, 'fetch', remote_name, refName], check=False)
subprocess.run([_GIT_EXE, 'checkout', get_branch_name_from_ref(refName)], check=False)
subprocess.run([_GIT_EXE, 'pull', remote_name, get_branch_name_from_ref(refName)], check=False)
def get_current_branch_name():
try:
output = subprocess.check_output([_GIT_EXE, 'symbolic-ref', '--short', '-q', 'HEAD'])
except BaseException as ex: # pylint: disable=broad-except
logger.info('GitDetect: Could not detect current branch based on current working directory.')
logger.debug(ex, exc_info=True)
return None
if sys.stdout.encoding is not None:
result = output.decode(sys.stdout.encoding)
else:
result = output.decode()
return result.strip()
def get_remote_url(validation_function=None):
remotes = get_git_remotes()
if remotes is not None:
if _ORIGIN_PUSH_KEY in remotes and (validation_function is None or
validation_function(remotes[_ORIGIN_PUSH_KEY])):
return remotes[_ORIGIN_PUSH_KEY]
for k, value in remotes.items():
if k != _ORIGIN_PUSH_KEY and k.endswith('(push)') and (validation_function is None or
validation_function(value)):
return value
return None
def get_git_credentials(organization):
parse_result = uri_parse(organization)
protocol = parse_result.scheme
host = parse_result.netloc
standard_in = bytes('protocol={protocol}\nhost={host}'.format(protocol=protocol, host=host), 'utf-8')
try:
# pylint: disable=unexpected-keyword-arg
output = subprocess.check_output([_GIT_EXE, 'credential-manager', 'get'], input=standard_in)
except BaseException as ex: # pylint: disable=broad-except
logger.info('GitDetect: Could not detect git credentials for current working directory.')
logger.debug(ex, exc_info=True)
return None
if sys.stdout.encoding is not None:
lines = output.decode(sys.stdout.encoding).split('\n')
else:
lines = output.decode().split('\n')
properties = {}
for line in lines:
equal_position = line.find('=')
if equal_position >= 0:
properties[line[0:equal_position]] = line[equal_position + 1:]
return properties
def get_git_remotes():
if _git_remotes:
return _git_remotes
try:
# Example output:
# git remote - v
# full https://mseng.visualstudio.com/DefaultCollection/VSOnline/_git/_full/VSO (fetch)
# full https://mseng.visualstudio.com/DefaultCollection/VSOnline/_git/_full/VSO (push)
# origin https://mseng.visualstudio.com/defaultcollection/VSOnline/_git/VSO (fetch)
# origin https://mseng.visualstudio.com/defaultcollection/VSOnline/_git/VSO (push)
output = subprocess.check_output([_GIT_EXE, 'remote', '-v'], stderr=subprocess.STDOUT)
except BaseException as ex: # pylint: disable=broad-except
logger.info('GitDetect: Could not detect current remotes based on current working directory.')
logger.debug(ex, exc_info=True)
return None
if sys.stdout.encoding is not None:
lines = output.decode(sys.stdout.encoding).split('\n')
else:
lines = output.decode().split('\n')
for line in lines:
components = line.strip().split()
if len(components) == 3:
_git_remotes[components[0] + components[2]] = components[1]
return _git_remotes
def resolve_git_refs(ref):
"""Prepends 'refs/' prefix to ref str if not already there.
:param str ref: The text to validate.
:rtype: str
"""
if ref is not None and not ref.startswith(REFS_PREFIX):
ref = REFS_PREFIX + ref
return ref
def get_ref_name_from_ref(ref):
"""Removes 'refs/' prefix from ref str if there.
:param ref: The text to validate.
:type ref: str
:rtype: str
"""
if ref is not None and ref.startswith(REFS_PREFIX):
ref = ref[len(REFS_PREFIX):]
return ref
def resolve_git_ref_heads(ref):
"""Prepends 'refs/heads/' prefix to ref str if not already there.
:param ref: The text to validate.
:type ref: str
:rtype: str
"""
if (ref is not None and not ref.startswith(REF_HEADS_PREFIX) and
not ref.startswith(REF_PULL_PREFIX) and not ref.startswith(REF_TAGS_PREFIX)):
ref = REF_HEADS_PREFIX + ref
return ref
def get_branch_name_from_ref(ref):
"""Removes 'refs/heads/' prefix from ref str if there.
:param ref: The text to validate.
:type ref: str
:rtype: str
"""
if ref is not None and ref.startswith(REF_HEADS_PREFIX):
ref = ref[len(REF_HEADS_PREFIX):]
return ref
def setup_git_alias(alias, command, local=False):
try:
set_config(key=_get_alias_key(alias),
value=_get_alias_value(command),
local=local)
except OSError:
raise CLIError('Setting the git alias failed. Ensure git is installed and in your path.')
def clear_git_alias(alias, local=False):
unset_config(key=_get_alias_key(alias), local=local)
def is_git_alias_setup(alias, command, local=False):
try:
try:
value = get_config(key=_get_alias_key(alias), local=local)
except subprocess.CalledProcessError:
return False
return _get_alias_value(command) == value.decode(sys.stdout.encoding).strip()
except OSError:
raise CLIError('Checking the git config values failed. Ensure git is installed and in your path.')
def _get_alias_key(alias):
return 'alias.' + alias
def _get_alias_value(command):
mime = '.cmd' if sys.platform.lower().startswith('win') else ''
return '!f() { exec az' + mime + ' ' + command + ' \"$@\"; }; f'
_git_remotes = {}
_ORIGIN_PUSH_KEY = 'origin(push)'
REFS_PREFIX = 'refs/'
REF_HEADS_PREFIX = 'refs/heads/'
REF_PULL_PREFIX = 'refs/pull/'
REF_TAGS_PREFIX = 'refs/tags/'
GIT_CREDENTIALS_USERNAME_KEY = 'username'
GIT_CREDENTIALS_PASSWORD_KEY = 'password'