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