blob: adc1b952a4c419d3068747d884cd6af6c8e80dc4 [file] [log] [blame]
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import tarfile
from StringIO import StringIO
from file_system import FileNotFoundError, ToUnicode
from future import Future
from patcher import Patcher
import svn_constants
_CHROMIUM_REPO_BASEURLS = [
'https://src.chromium.org/svn/trunk/src/',
'http://src.chromium.org/svn/trunk/src/',
'svn://svn.chromium.org/chrome/trunk/src',
'https://chromium.googlesource.com/chromium/src.git@master',
'http://git.chromium.org/chromium/src.git@master',
]
_DOCS_PATHS = [
svn_constants.API_PATH,
svn_constants.TEMPLATE_PATH,
svn_constants.STATIC_PATH
]
class RietveldPatcherError(Exception):
def __init__(self, message):
self.message = message
class _AsyncFetchFuture(object):
def __init__(self,
base_path,
issue,
patchset,
files,
binary,
fetcher):
self._base_path = base_path
self._issue = issue
self._patchset = patchset
self._files = files
self._binary = binary
self._tarball = fetcher.FetchAsync('tarball/%s/%s' % (issue, patchset))
def Get(self):
tarball_result = self._tarball.Get()
if tarball_result.status_code != 200:
raise RietveldPatcherError(
'Failed to download tarball for issue %s patchset %s. Status: %s' %
(self._issue, self._patchset, tarball_result.status_code))
try:
tar = tarfile.open(fileobj=StringIO(tarball_result.content))
except tarfile.TarError as e:
raise RietveldPatcherError(
'Error loading tarball for issue %s patchset %s.' % (self._issue,
self._patchset))
self._value = {}
for path in self._files:
if self._base_path:
tar_path = 'b/%s/%s' % (self._base_path, path)
else:
tar_path = 'b/%s' % path
patched_file = None
try:
patched_file = tar.extractfile(tar_path)
data = patched_file.read()
except tarfile.TarError as e:
# Show appropriate error message in the unlikely case that the tarball
# is corrupted.
raise RietveldPatcherError(
'Error extracting tarball for issue %s patchset %s file %s.' %
(self._issue, self._patchset, tar_path))
except KeyError as e:
raise FileNotFoundError(
'File %s not found in the tarball for issue %s patchset %s' %
(tar_path, self._issue, self._patchset))
finally:
if patched_file:
patched_file.close()
if self._binary:
self._value[path] = data
else:
self._value[path] = ToUnicode(data)
return self._value
class RietveldPatcher(Patcher):
''' Class to fetch resources from a patchset in Rietveld.
'''
def __init__(self,
base_path,
issue,
fetcher):
self._base_path = base_path
self._issue = issue
self._fetcher = fetcher
self._cache = None
# In RietveldPatcher, the version is the latest patchset number.
def GetVersion(self):
try:
issue_json = json.loads(self._fetcher.Fetch(
'api/%s' % self._issue).content)
except Exception as e:
raise RietveldPatcherError(
'Failed to fetch information for issue %s.' % self._issue)
if issue_json.get('closed'):
raise RietveldPatcherError('Issue %s has been closed.' % self._issue)
patchsets = issue_json.get('patchsets')
if not isinstance(patchsets, list) or len(patchsets) == 0:
raise RietveldPatcherError('Cannot parse issue %s.' % self._issue)
if not issue_json.get('base_url') in _CHROMIUM_REPO_BASEURLS:
raise RietveldPatcherError('Issue %s\'s base url is unknown.' %
self._issue)
return str(patchsets[-1])
def GetPatchedFiles(self, version=None):
if version is None:
patchset = self.GetVersion()
else:
patchset = version
try:
patchset_json = json.loads(self._fetcher.Fetch(
'api/%s/%s' % (self._issue, patchset)).content)
except Exception as e:
raise RietveldPatcherError(
'Failed to fetch details for issue %s patchset %s.' % (self._issue,
patchset))
files = patchset_json.get('files')
if files is None or not isinstance(files, dict):
raise RietveldPatcherError('Failed to parse issue %s patchset %s.' %
(self._issue, patchset))
added = []
deleted = []
modified = []
for key in files:
if not key.startswith(self._base_path + '/'):
continue
f = key.split(self._base_path + '/', 1)[1]
if any(f.startswith(path) for path in _DOCS_PATHS):
status = (files[key].get('status') or 'M')
# status can be 'A ' or 'A + '
if 'A' in status:
added.append(f)
elif 'D' in status:
deleted.append(f)
elif 'M' in status:
modified.append(f)
else:
raise RietveldPatcherError('Unknown file status for file %s: "%s."' %
(key, status))
return (added, deleted, modified)
def Apply(self, paths, file_system, binary, version=None):
if version is None:
version = self.GetVersion()
return Future(delegate=_AsyncFetchFuture(self._base_path,
self._issue,
version,
paths,
binary,
self._fetcher))
def GetIdentity(self):
return self._issue