1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
|
# 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))
|