Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / native_client / pynacl / repo_tools.py
index 46a5738..3751273 100644 (file)
@@ -5,13 +5,23 @@
 
 import logging
 import os
+import posixpath
 import subprocess
 import sys
+import urlparse
 
 import file_tools
 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)
+    self.expected_repo = expected_repo
+
 
 def GitCmd():
   """Return the git command to execute for the host platform."""
@@ -41,8 +51,50 @@ def SvnCmd():
     return ['svn']
 
 
+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
+  actually tracks an expected URL. If the directory does not exist nothing
+  will be done.
+
+  Args:
+  url: URL to look for.
+  directory: Directory to look for.
+  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:
+      if IsURLInRemoteRepoList(url, directory, include_fetch=True,
+                               include_push=False):
+        return
+
+      logger.warn('Local git repo (%s) does not track url (%s)',
+                   directory, url)
+    except:
+      logger.error('Invalid git repo: %s', directory)
+
+    if not clobber_mismatch:
+      raise InvalidRepoException(url, 'Invalid local git repo: %s', directory)
+    else:
+      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:
+      raise InvalidRepoException(url,
+                                 'Invalid non-empty repository destination %s',
+                                 directory)
+    else:
+      logger.debug('Clobbering intended repository destination: %s', directory)
+      file_tools.RemoveDirectoryIfPresent(directory)
+
+
 def SyncGitRepo(url, destination, revision, reclone=False, clean=False,
-                pathspec=None):
+                pathspec=None, git_cache=None, push_url=None, logger=None):
   """Sync an individual git repo.
 
   Args:
@@ -56,34 +108,143 @@ def SyncGitRepo(url, destination, revision, reclone=False, clean=False,
   pathspec: If not None, add the path to the git checkout command, which
             causes it to just update the working tree without switching
             branches.
+  git_cache: If set, assumes URL has been populated within the git cache
+             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:
+    git_cache_url = GetGitCacheURL(git_cache, url)
+  else:
+    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(url, destination, include_fetch=True,
+                                 include_push=False):
+      # If the git cache URL is being tracked instead of the fetch URL, we
+      # can safely redirect it to the fetch URL instead.
+      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:
+        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(destination) or len(os.listdir(destination)) == 0:
-    logging.info('Cloning %s...' % url)
-    log_tools.CheckCall(git + ['clone', '-n', url, destination])
+  if not os.path.exists(git_dir):
+    logger.info('Cloning %s...' % url)
+
+    file_tools.MakeDirectoryIfAbsent(destination)
+    clone_args = ['clone', '-n']
+    if git_cache_url:
+      clone_args.extend(['--reference', git_cache_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'],
+                        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)
-  if clean:
-    log_tools.CheckCall(git + ['clean', '-dffx'], 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, 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.
+  You can populate a cache directory then obtain the local cache url using
+  GetGitCacheURL(). It is best to sync with the shared option so that the
+  cloned repository shares the same git objects.
+
+  Args:
+    cache_dir: Local directory where git cache will be populated.
+    url_list: List of URLs which cache_dir should be populated with.
+  """
+  if url_list:
+    file_tools.MakeDirectoryIfAbsent(cache_dir)
+    git = GitCmd()
+    for url in url_list:
+      log_tools.CheckCall(git + ['cache', 'populate', '-c', '.', url],
+                          logger=logger, cwd=cache_dir)
+
+
+def GetGitCacheURL(cache_dir, url, logger=None):
+  """Converts a regular git URL to a git cache URL within a cache directory.
+
+  Args:
+    url: original Git URL that is already populated within the cache directory.
+    cache_dir: Git cache directory that has already populated the URL.
+
+  Returns:
+    Git Cache URL where a git repository can clone/fetch from.
+  """
+  # Make sure we are using absolute paths or else cache exists return relative.
+  cache_dir = os.path.abspath(cache_dir)
+
+  # For CygWin, we must first convert the cache_dir name to a non-cygwin path.
+  cygwin_path = False
+  if platform.IsCygWin() and cache_dir.startswith('/cygdrive/'):
+    cygwin_path = True
+    drive, file_path = cache_dir[len('/cygdrive/'):].split('/', 1)
+    cache_dir = drive + ':\\' + file_path.replace('/', '\\')
+
+  git_url = log_tools.CheckOutput(GitCmd() + ['cache', 'exists',
+                                              '-c', cache_dir,
+                                              url],
+                                  logger=logger).strip()
+
+  # For windows, make sure the git cache URL is a posix path.
+  if platform.IsWindows():
+    git_url = git_url.replace('\\', '/')
+  return git_url
 
 
 def GitRevInfo(directory):
@@ -98,6 +259,7 @@ def GitRevInfo(directory):
                               cwd=directory)
   return url.strip(), rev.strip()
 
+
 def SvnRevInfo(directory):
   """Get the SVN revision information of an existing svn/gclient checkout.
 
@@ -118,3 +280,143 @@ def SvnRevInfo(directory):
   if not url or not rev:
     raise RuntimeError('Missing svn info url: %s and rev: %s' % (url, rev))
   return url, rev
+
+
+def GetAuthenticatedGitURL(url):
+  """Returns the authenticated version of a git URL.
+
+  In chromium, there is a special URL that is the "authenticated" version. The
+  URLs are identical but the authenticated one has special privileges.
+  """
+  urlsplit = urlparse.urlsplit(url)
+  if urlsplit.scheme in ('https', 'http'):
+    urldict = urlsplit._asdict()
+    urldict['scheme'] = 'https'
+    urldict['path'] = '/a' + urlsplit.path
+    urlsplit = urlparse.SplitResult(**urldict)
+
+  return urlsplit.geturl()
+
+
+def GitRemoteRepoList(directory, include_fetch=True, include_push=True,
+                      logger=None):
+  """Returns a list of remote git repos associated with a directory.
+
+  Args:
+      directory: Existing git working directory.
+  Returns:
+      List of (repo_name, repo_url) for tracked remote repos.
+  """
+  remote_repos = log_tools.CheckOutput(GitCmd() + ['remote', '-v'],
+                                       logger=logger, cwd=directory)
+
+  repo_set = set()
+  for remote_repo_line in remote_repos.splitlines():
+    repo_name, repo_url, repo_type = remote_repo_line.split()
+    if include_fetch and repo_type == '(fetch)':
+      repo_set.add((repo_name, repo_url))
+    elif include_push and repo_type == '(push)':
+      repo_set.add((repo_name, repo_url))
+
+  return sorted(repo_set)
+
+
+def GitSetRemoteRepo(url, directory, push_url=None,
+                     repo_name='origin', logger=None):
+  """Sets the remotely tracked URL for a git repository.
+
+  Args:
+      url: Remote git URL to set.
+      directory: Local git repository to set tracked repo for.
+      push_url: If specified, uses a different URL for pushing.
+      repo_name: set the URL for a particular remote repo name.
+  """
+  git = GitCmd()
+  try:
+    log_tools.CheckCall(git + ['remote', 'set-url', repo_name, url],
+                        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],
+                        logger=logger, cwd=directory)
+
+  if push_url:
+    log_tools.CheckCall(git + ['remote', 'set-url', '--push',
+                               repo_name, push_url],
+                        logger=logger, cwd=directory)
+
+
+def IsURLInRemoteRepoList(url, directory, include_fetch=True, include_push=True,
+                          try_authenticated_url=True, logger=None):
+  """Returns whether or not a url is a remote repo in a local git directory.
+
+  Args:
+      url: URL to look for in remote repo list.
+      directory: Existing git working directory.
+  """
+  if try_authenticated_url:
+    valid_urls = (url, GetAuthenticatedGitURL(url))
+  else:
+    valid_urls = (url,)
+
+  remote_repo_list = GitRemoteRepoList(directory,
+                                       include_fetch=include_fetch,
+                                       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)