import logging
import os
+import posixpath
import subprocess
import sys
import urlparse
import log_tools
import platform
+GIT_ALTERNATES_PATH = os.path.join('.git', 'objects', 'info', 'alternates')
+
+
class InvalidRepoException(Exception):
def __init__(self, expected_repo, msg, *args):
Exception.__init__(self, msg % args)
return ['svn']
-def ValidateGitRepo(url, directory, clobber_mismatch=False):
+def ValidateGitRepo(url, directory, clobber_mismatch=False, logger=None):
"""Validates a git repository tracks a particular URL.
Given a git directory, this function will validate if the git directory
clobber_mismatch: If True, will delete invalid directories instead of raising
an exception.
"""
+ if logger is None:
+ logger = log_tools.GetConsoleLogger()
git_dir = os.path.join(directory, '.git')
if os.path.exists(git_dir):
try:
include_push=False):
return
- logging.warn('Local git repo (%s) does not track url (%s)',
+ logger.warn('Local git repo (%s) does not track url (%s)',
directory, url)
except:
- logging.error('Invalid git repo: %s', directory)
+ logger.error('Invalid git repo: %s', directory)
if not clobber_mismatch:
raise InvalidRepoException(url, 'Invalid local git repo: %s', directory)
else:
- logging.debug('Clobbering invalid git repo %s' % directory)
+ logger.debug('Clobbering invalid git repo %s' % directory)
file_tools.RemoveDirectoryIfPresent(directory)
elif os.path.exists(directory) and len(os.listdir(directory)) != 0:
if not clobber_mismatch:
'Invalid non-empty repository destination %s',
directory)
else:
- logging.debug('Clobbering intended repository destination: %s', directory)
+ logger.debug('Clobbering intended repository destination: %s', directory)
file_tools.RemoveDirectoryIfPresent(directory)
def SyncGitRepo(url, destination, revision, reclone=False, clean=False,
- pathspec=None, git_cache=None, push_url=None):
+ pathspec=None, git_cache=None, push_url=None, logger=None):
"""Sync an individual git repo.
Args:
directory specified and sets the fetch URL to be from the
git_cache.
"""
+ if logger is None:
+ logger = log_tools.GetConsoleLogger()
if reclone:
- logging.debug('Clobbering source directory %s' % destination)
+ logger.debug('Clobbering source directory %s' % destination)
file_tools.RemoveDirectoryIfPresent(destination)
if git_cache:
- fetch_url = GetGitCacheURL(git_cache, url)
+ git_cache_url = GetGitCacheURL(git_cache, url)
else:
- fetch_url = url
+ git_cache_url = None
# If the destination is a git repository, validate the tracked origin.
git_dir = os.path.join(destination, '.git')
if os.path.exists(git_dir):
- if not IsURLInRemoteRepoList(fetch_url, destination, include_fetch=True,
+ if not IsURLInRemoteRepoList(url, destination, include_fetch=True,
include_push=False):
- # If the original URL is being tracked instead of the fetch URL, we
+ # If the git cache URL is being tracked instead of the fetch URL, we
# can safely redirect it to the fetch URL instead.
- if (fetch_url != url and IsURLInRemoteRepoList(url, destination,
- include_fetch=True,
- include_push=False)):
- GitSetRemoteRepo(fetch_url, destination, push_url=push_url)
+ if git_cache_url and IsURLInRemoteRepoList(git_cache_url, destination,
+ include_fetch=True,
+ include_push=False):
+ GitSetRemoteRepo(url, destination, push_url=push_url,
+ logger=logger)
else:
- logging.error('Git Repo (%s) does not track URL: %s',
- destination, fetch_url)
- raise InvalidRepoException(fetch_url, 'Could not sync git repo: %s',
+ logger.error('Git Repo (%s) does not track URL: %s',
+ destination, url)
+ raise InvalidRepoException(url, 'Could not sync git repo: %s',
destination)
+ # Make sure the push URL is set correctly as well.
+ if not IsURLInRemoteRepoList(push_url, destination, include_fetch=False,
+ include_push=True):
+ GitSetRemoteRepo(url, destination, push_url=push_url)
+
git = GitCmd()
if not os.path.exists(git_dir):
- logging.info('Cloning %s...' % url)
- clone_args = ['clone', '-n']
- if git_cache:
- clone_args.append('-s')
+ logger.info('Cloning %s...' % url)
file_tools.MakeDirectoryIfAbsent(destination)
- log_tools.CheckCall(git + clone_args + [fetch_url, '.'], cwd=destination)
+ clone_args = ['clone', '-n']
+ if git_cache_url:
+ clone_args.extend(['--reference', git_cache_url])
- if fetch_url != url:
- GitSetRemoteRepo(fetch_url, destination, push_url=push_url)
+ log_tools.CheckCall(git + clone_args + [url, '.'],
+ logger=logger, cwd=destination)
+ if url != push_url:
+ GitSetRemoteRepo(url, destination, push_url=push_url, logger=logger)
elif clean:
- log_tools.CheckCall(git + ['clean', '-dffx'], cwd=destination)
- log_tools.CheckCall(git + ['reset', '--hard', 'HEAD'], cwd=destination)
+ log_tools.CheckCall(git + ['clean', '-dffx'],
+ logger=logger, cwd=destination)
+ log_tools.CheckCall(git + ['reset', '--hard', 'HEAD'],
+ logger=logger, cwd=destination)
+
+ # If a git cache URL is supplied, make sure it is setup as a git alternate.
+ if git_cache_url:
+ git_alternates = [git_cache_url]
+ else:
+ git_alternates = []
+
+ GitSetRepoAlternates(destination, git_alternates, append=False, logger=logger)
if revision is not None:
- logging.info('Checking out pinned revision...')
- log_tools.CheckCall(git + ['fetch', '--all'], cwd=destination)
+ logger.info('Checking out pinned revision...')
+ log_tools.CheckCall(git + ['fetch', '--all'],
+ logger=logger, cwd=destination)
checkout_flags = ['-f'] if clean else []
path = [pathspec] if pathspec else []
log_tools.CheckCall(
git + ['checkout'] + checkout_flags + [revision] + path,
- cwd=destination)
+ logger=logger, cwd=destination)
-def CleanGitWorkingDir(directory, path):
+def CleanGitWorkingDir(directory, path, logger=None):
"""Clean a particular path of an existing git checkout.
Args:
directory: Directory where the git repo is currently checked out
path: path to clean, relative to the repo directory
"""
- log_tools.CheckCall(GitCmd() + ['clean', '-f', path], cwd=directory)
+ log_tools.CheckCall(GitCmd() + ['clean', '-f', path],
+ logger=logger, cwd=directory)
-def PopulateGitCache(cache_dir, url_list):
+def PopulateGitCache(cache_dir, url_list, logger=None):
"""Fetches a git repo that combines a list of git repos.
This is an interface to the "git cache" command found within depot_tools.
git = GitCmd()
for url in url_list:
log_tools.CheckCall(git + ['cache', 'populate', '-c', '.', url],
- cwd=cache_dir)
+ logger=logger, cwd=cache_dir)
-def GetGitCacheURL(cache_dir, url):
+def GetGitCacheURL(cache_dir, url, logger=None):
"""Converts a regular git URL to a git cache URL within a cache directory.
Args:
git_url = log_tools.CheckOutput(GitCmd() + ['cache', 'exists',
'-c', cache_dir,
- url]).strip()
+ url],
+ logger=logger).strip()
- # For cygwin paths, convert forward slashes to backslashes to mimic URLs.
- if cygwin_path:
+ # For windows, make sure the git cache URL is a posix path.
+ if platform.IsWindows():
git_url = git_url.replace('\\', '/')
return git_url
return urlsplit.geturl()
-def GitRemoteRepoList(directory, include_fetch=True, include_push=True):
+def GitRemoteRepoList(directory, include_fetch=True, include_push=True,
+ logger=None):
"""Returns a list of remote git repos associated with a directory.
Args:
List of (repo_name, repo_url) for tracked remote repos.
"""
remote_repos = log_tools.CheckOutput(GitCmd() + ['remote', '-v'],
- cwd=directory)
+ logger=logger, cwd=directory)
repo_set = set()
for remote_repo_line in remote_repos.splitlines():
return sorted(repo_set)
-def GitSetRemoteRepo(url, directory, push_url=None, repo_name='origin'):
+def GitSetRemoteRepo(url, directory, push_url=None,
+ repo_name='origin', logger=None):
"""Sets the remotely tracked URL for a git repository.
Args:
git = GitCmd()
try:
log_tools.CheckCall(git + ['remote', 'set-url', repo_name, url],
- cwd=directory)
+ logger=logger, cwd=directory)
except subprocess.CalledProcessError:
# If setting the URL failed, repo_name may be new. Try adding the URL.
log_tools.CheckCall(git + ['remote', 'add', repo_name, url],
- cwd=directory)
+ logger=logger, cwd=directory)
if push_url:
log_tools.CheckCall(git + ['remote', 'set-url', '--push',
repo_name, push_url],
- cwd=directory)
+ logger=logger, cwd=directory)
def IsURLInRemoteRepoList(url, directory, include_fetch=True, include_push=True,
- try_authenticated_url=True):
+ try_authenticated_url=True, logger=None):
"""Returns whether or not a url is a remote repo in a local git directory.
Args:
remote_repo_list = GitRemoteRepoList(directory,
include_fetch=include_fetch,
- include_push=include_push)
+ include_push=include_push,
+ logger=logger)
return len([repo_name for
repo_name, repo_url in remote_repo_list
if repo_url in valid_urls]) > 0
+
+
+def GitGetRepoAlternates(directory):
+ """Gets the list of git alternates for a local git repo.
+
+ Args:
+ directory: Local git repository to get the git alternate for.
+
+ Returns:
+ List of git alternates set for the local git repository.
+ """
+ git_alternates_file = os.path.join(directory, GIT_ALTERNATES_PATH)
+ if os.path.isfile(git_alternates_file):
+ with open(git_alternates_file, 'rt') as f:
+ alternates_list = []
+ for line in f.readlines():
+ line = line.strip()
+ if line:
+ if posixpath.basename(line) == 'objects':
+ line = posixpath.dirname(line)
+ alternates_list.append(line)
+
+ return alternates_list
+
+ return []
+
+
+def GitSetRepoAlternates(directory, alternates_list, append=True, logger=None):
+ """Sets the list of git alternates for a local git repo.
+
+ Args:
+ directory: Local git repository.
+ alternates_list: List of local git repositories for the git alternates.
+ append: If True, will append the list to currently set list of alternates.
+ """
+ if logger is None:
+ logger = log_tools.GetConsoleLogger()
+ git_alternates_file = os.path.join(directory, GIT_ALTERNATES_PATH)
+ git_alternates_dir = os.path.dirname(git_alternates_file)
+ if not os.path.isdir(git_alternates_dir):
+ raise InvalidRepoException(directory,
+ 'Invalid local git repo: %s', directory)
+
+ original_alternates_list = GitGetRepoAlternates(directory)
+ if append:
+ alternates_list.extend(original_alternates_list)
+ alternates_list = sorted(set(alternates_list))
+
+ if set(original_alternates_list) != set(alternates_list):
+ lines = [posixpath.join(line, 'objects') + '\n' for line in alternates_list]
+ logger.info('Setting git alternates:\n\t%s', '\t'.join(lines))
+
+ with open(git_alternates_file, 'wb') as f:
+ f.writelines(lines)