1 # Copyright 2013 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import json 6 import tarfile 7 from StringIO import StringIO 8 9 from file_system import FileNotFoundError 10 from future import Future 11 from patcher import Patcher 12 13 14 _CHROMIUM_REPO_BASEURLS = [ 15 'https://src.chromium.org/svn/trunk/src/', 16 'http://src.chromium.org/svn/trunk/src/', 17 'svn://svn.chromium.org/chrome/trunk/src', 18 'https://chromium.googlesource.com/chromium/src.git@master', 19 'http://git.chromium.org/chromium/src.git@master', 20 ] 21 22 23 class RietveldPatcherError(Exception): 24 def __init__(self, message): 25 self.message = message 26 27 28 def _GetAsyncFetchCallback(issue, patchset, files, fetcher): 29 tarball = fetcher.FetchAsync('tarball/%s/%s' % (issue, patchset)) 30 31 def resolve(): 32 tarball_result = tarball.Get() 33 if tarball_result.status_code != 200: 34 raise RietveldPatcherError( 35 'Failed to download tarball for issue %s patchset %s. Status: %s' % 36 (issue, patchset, tarball_result.status_code)) 37 38 try: 39 tar = tarfile.open(fileobj=StringIO(tarball_result.content)) 40 except tarfile.TarError as e: 41 raise RietveldPatcherError( 42 'Error loading tarball for issue %s patchset %s.' % (issue, patchset)) 43 44 value = {} 45 for path in files: 46 tar_path = 'b/%s' % path 47 48 patched_file = None 49 try: 50 patched_file = tar.extractfile(tar_path) 51 data = patched_file.read() 52 except tarfile.TarError as e: 53 # Show appropriate error message in the unlikely case that the tarball 54 # is corrupted. 55 raise RietveldPatcherError( 56 'Error extracting tarball for issue %s patchset %s file %s.' % 57 (issue, patchset, tar_path)) 58 except KeyError as e: 59 raise FileNotFoundError( 60 'File %s not found in the tarball for issue %s patchset %s' % 61 (tar_path, issue, patchset)) 62 finally: 63 if patched_file: 64 patched_file.close() 65 66 value[path] = data 67 68 return value 69 70 return resolve 71 72 73 class RietveldPatcher(Patcher): 74 ''' Class to fetch resources from a patchset in Rietveld. 75 ''' 76 def __init__(self, 77 issue, 78 fetcher): 79 self._issue = issue 80 self._fetcher = fetcher 81 self._cache = None 82 83 # In RietveldPatcher, the version is the latest patchset number. 84 def GetVersion(self): 85 try: 86 issue_json = json.loads(self._fetcher.Fetch( 87 'api/%s' % self._issue).content) 88 except Exception as e: 89 raise RietveldPatcherError( 90 'Failed to fetch information for issue %s.' % self._issue) 91 92 if issue_json.get('closed'): 93 raise RietveldPatcherError('Issue %s has been closed.' % self._issue) 94 95 patchsets = issue_json.get('patchsets') 96 if not isinstance(patchsets, list) or len(patchsets) == 0: 97 raise RietveldPatcherError('Cannot parse issue %s.' % self._issue) 98 99 if not issue_json.get('base_url') in _CHROMIUM_REPO_BASEURLS: 100 raise RietveldPatcherError('Issue %s\'s base url is unknown.' % 101 self._issue) 102 103 return str(patchsets[-1]) 104 105 def GetPatchedFiles(self, version=None): 106 if version is None: 107 patchset = self.GetVersion() 108 else: 109 patchset = version 110 try: 111 patchset_json = json.loads(self._fetcher.Fetch( 112 'api/%s/%s' % (self._issue, patchset)).content) 113 except Exception as e: 114 raise RietveldPatcherError( 115 'Failed to fetch details for issue %s patchset %s.' % (self._issue, 116 patchset)) 117 118 files = patchset_json.get('files') 119 if files is None or not isinstance(files, dict): 120 raise RietveldPatcherError('Failed to parse issue %s patchset %s.' % 121 (self._issue, patchset)) 122 123 added = [] 124 deleted = [] 125 modified = [] 126 for f in files: 127 status = (files[f].get('status') or 'M') 128 # status can be 'A ' or 'A + ' 129 if 'A' in status: 130 added.append(f) 131 elif 'D' in status: 132 deleted.append(f) 133 elif 'M' in status: 134 modified.append(f) 135 else: 136 raise RietveldPatcherError('Unknown file status for file %s: "%s."' % 137 (key, status)) 138 139 return (added, deleted, modified) 140 141 def Apply(self, paths, file_system, version=None): 142 if version is None: 143 version = self.GetVersion() 144 return Future(callback=_GetAsyncFetchCallback(self._issue, 145 version, 146 paths, 147 self._fetcher)) 148 149 def GetIdentity(self): 150 return self._issue 151