rietveld_patcher.py revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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 5import json 6import tarfile 7from StringIO import StringIO 8 9from file_system import FileNotFoundError 10from future import Future 11from 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 23class RietveldPatcherError(Exception): 24 def __init__(self, message): 25 self.message = message 26 27class _AsyncFetchFuture(object): 28 def __init__(self, 29 issue, 30 patchset, 31 files, 32 fetcher): 33 self._issue = issue 34 self._patchset = patchset 35 self._files = files 36 self._tarball = fetcher.FetchAsync('tarball/%s/%s' % (issue, patchset)) 37 38 def Get(self): 39 tarball_result = self._tarball.Get() 40 if tarball_result.status_code != 200: 41 raise RietveldPatcherError( 42 'Failed to download tarball for issue %s patchset %s. Status: %s' % 43 (self._issue, self._patchset, tarball_result.status_code)) 44 45 try: 46 tar = tarfile.open(fileobj=StringIO(tarball_result.content)) 47 except tarfile.TarError as e: 48 raise RietveldPatcherError( 49 'Error loading tarball for issue %s patchset %s.' % (self._issue, 50 self._patchset)) 51 52 self._value = {} 53 for path in self._files: 54 tar_path = 'b/%s' % path 55 56 patched_file = None 57 try: 58 patched_file = tar.extractfile(tar_path) 59 data = patched_file.read() 60 except tarfile.TarError as e: 61 # Show appropriate error message in the unlikely case that the tarball 62 # is corrupted. 63 raise RietveldPatcherError( 64 'Error extracting tarball for issue %s patchset %s file %s.' % 65 (self._issue, self._patchset, tar_path)) 66 except KeyError as e: 67 raise FileNotFoundError( 68 'File %s not found in the tarball for issue %s patchset %s' % 69 (tar_path, self._issue, self._patchset)) 70 finally: 71 if patched_file: 72 patched_file.close() 73 74 self._value[path] = data 75 76 return self._value 77 78class RietveldPatcher(Patcher): 79 ''' Class to fetch resources from a patchset in Rietveld. 80 ''' 81 def __init__(self, 82 issue, 83 fetcher): 84 self._issue = issue 85 self._fetcher = fetcher 86 self._cache = None 87 88 # In RietveldPatcher, the version is the latest patchset number. 89 def GetVersion(self): 90 try: 91 issue_json = json.loads(self._fetcher.Fetch( 92 'api/%s' % self._issue).content) 93 except Exception as e: 94 raise RietveldPatcherError( 95 'Failed to fetch information for issue %s.' % self._issue) 96 97 if issue_json.get('closed'): 98 raise RietveldPatcherError('Issue %s has been closed.' % self._issue) 99 100 patchsets = issue_json.get('patchsets') 101 if not isinstance(patchsets, list) or len(patchsets) == 0: 102 raise RietveldPatcherError('Cannot parse issue %s.' % self._issue) 103 104 if not issue_json.get('base_url') in _CHROMIUM_REPO_BASEURLS: 105 raise RietveldPatcherError('Issue %s\'s base url is unknown.' % 106 self._issue) 107 108 return str(patchsets[-1]) 109 110 def GetPatchedFiles(self, version=None): 111 if version is None: 112 patchset = self.GetVersion() 113 else: 114 patchset = version 115 try: 116 patchset_json = json.loads(self._fetcher.Fetch( 117 'api/%s/%s' % (self._issue, patchset)).content) 118 except Exception as e: 119 raise RietveldPatcherError( 120 'Failed to fetch details for issue %s patchset %s.' % (self._issue, 121 patchset)) 122 123 files = patchset_json.get('files') 124 if files is None or not isinstance(files, dict): 125 raise RietveldPatcherError('Failed to parse issue %s patchset %s.' % 126 (self._issue, patchset)) 127 128 added = [] 129 deleted = [] 130 modified = [] 131 for f in files: 132 status = (files[f].get('status') or 'M') 133 # status can be 'A ' or 'A + ' 134 if 'A' in status: 135 added.append(f) 136 elif 'D' in status: 137 deleted.append(f) 138 elif 'M' in status: 139 modified.append(f) 140 else: 141 raise RietveldPatcherError('Unknown file status for file %s: "%s."' % 142 (key, status)) 143 144 return (added, deleted, modified) 145 146 def Apply(self, paths, file_system, version=None): 147 if version is None: 148 version = self.GetVersion() 149 return Future(delegate=_AsyncFetchFuture(self._issue, 150 version, 151 paths, 152 self._fetcher)) 153 154 def GetIdentity(self): 155 return self._issue 156