Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(201)

Side by Side Diff: rietveld.py

Issue 6792028: Move patch.py and rietveld.py from commit-queue. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: fixed remaining issues Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # Copyright (c) 2011 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 """Defines class Rietveld to easily access a rietveld instance.
5
6 Security implications:
7
8 The following hypothesis are made:
9 - Rietveld enforces:
10 - Nobody else than issue owner can upload a patch set
11 - Verifies the issue owner credentials when creating new issues
12 - A issue owner can't change once the issue is created
13 - A patch set cannot be modified
14 """
15
16 import logging
17 import os
18 import sys
19 import time
20 import urllib2
21
22 try:
23 import simplejson as json # pylint: disable=F0401
24 except ImportError:
25 try:
26 import json # pylint: disable=F0401
27 except ImportError:
28 # Import the one included in depot_tools.
29 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party'))
30 import simplejson as json # pylint: disable=F0401
31
32 from third_party import upload
33 import patch
34
35 # Hack out upload logging.info()
36 upload.logging = logging.getLogger('upload')
37 upload.logging.setLevel(logging.WARNING)
38
39
40 class Rietveld(object):
41 """Accesses rietveld."""
42 def __init__(self, url, email, password):
43 self.issue = None
44 self.user = email
45 self.url = url
46 self._get_creds = lambda: (email, password)
47 self._xsrf_token = None
48 self._xsrf_token_time = None
49 self.rpc_server = upload.HttpRpcServer(
50 self.url,
51 self._get_creds,
52 save_cookies=False)
53
54 def xsrf_token(self):
55 if (not self._xsrf_token_time or
56 (time.time() - self._xsrf_token_time) > 30*60):
57 self._xsrf_token_time = time.time()
58 self._xsrf_token = self.get(
59 '/xsrf_token',
60 extra_headers={'X-Requesting-XSRF-Token': '1'})
61 return self._xsrf_token
62
63 def get_pending_issues(self):
64 """Returns an array of dict of all the pending issues on the server."""
65 return json.loads(self.get(
66 '/search?format=json&commit=True&closed=False&keys_only=True')
67 )['results']
68
69 def close_issue(self, issue):
70 """Closes the Rietveld issue for this changelist."""
71 logging.info('closing issue %s' % issue)
72 self.post("/%d/close" % issue, [('xsrf_token', self.xsrf_token())])
73
74 def get_description(self, issue):
75 """Returns the issue's description."""
76 return self.get('/%d/description' % issue)
77
78 def get_issue_properties(self, issue, messages):
79 """Returns all the issue's metadata as a dictionary."""
80 url = '/api/%s' % issue
81 if messages:
82 url += '?messages=true'
83 return json.loads(self.get(url))
84
85 def get_patchset_properties(self, issue, patchset):
86 """Returns the patchset properties."""
87 url = '/api/%s/%s' % (issue, patchset)
88 return json.loads(self.get(url))
89
90 def get_file_content(self, issue, patchset, item):
91 """Returns the content of a new file.
92
93 Throws HTTP 302 exception if the file doesn't exist or is not a binary file.
94 """
95 # content = 0 is the old file, 1 is the new file.
96 content = 1
97 url = '/%s/image/%s/%s/%s' % (issue, patchset, item, content)
98 return self.get(url)
99
100 def get_file_diff(self, issue, patchset, item):
101 """Returns the diff of the file.
102
103 Returns a useless diff for binary files.
104 """
105 url = '/download/issue%s_%s_%s.diff' % (issue, patchset, item)
106 return self.get(url)
107
108 def get_patch(self, issue, patchset):
109 """Returns a PatchSet object containing the details to apply this patch."""
110 props = self.get_patchset_properties(issue, patchset) or {}
111 out = []
112 for filename, state in props.get('files', {}).iteritems():
113 status = state.get('status')
114 if status is None:
115 raise patch.UnsupportedPatchFormat(
116 filename, 'File\'s status is None, patchset upload is incomplete')
117
118 # TODO(maruel): That's bad, it confuses property change.
119 status = status.strip()
120
121 if status == 'D':
122 # Ignore the diff.
123 out.append(patch.FilePatchDelete(filename, state['is_binary']))
124 elif status in ('A', 'M'):
125 # TODO(maruel): Rietveld support is still weird, add this line once it's
126 # safe to use.
127 # props = state.get('property_changes', '').splitlines() or []
128 props = []
129 if state['is_binary']:
130 out.append(patch.FilePatchBinary(
131 filename,
132 self.get_file_content(issue, patchset, state['id']),
133 props))
134 else:
135 if state['num_chunks']:
136 diff = self.get_file_diff(issue, patchset, state['id'])
137 else:
138 diff = None
139 out.append(patch.FilePatchDiff(filename, diff, props))
140 else:
141 # Line too long (N/80)
142 # pylint: disable=C0301
143 # TODO: Add support for MM, A+, etc. Rietveld removes the svn properties
144 # from the diff.
145 # Example of mergeinfo across branches:
146 # http://codereview.chromium.org/202046/diff/1/third_party/libxml/xmlcat alog_dummy.cc
147 # svn:eol-style property that is lost in the diff
148 # http://codereview.chromium.org/202046/diff/1/third_party/libxml/xmllin t_dummy.cc
149 # Change with no diff, only svn property change:
150 # http://codereview.chromium.org/6462019/
151 raise patch.UnsupportedPatchFormat(filename, status)
152 return patch.PatchSet(out)
153
154 def update_description(self, issue, description):
155 """Sets the description for an issue on Rietveld."""
156 logging.info('new description for issue %s' % issue)
157 self.post('/%s/description' % issue, [
158 ('description', description),
159 ('xsrf_token', self.xsrf_token())])
160
161 def add_comment(self, issue, message):
162 logging.info('issue %s; comment: %s' % (issue, message))
163 return self.post('/%s/publish' % issue, [
164 ('xsrf_token', self.xsrf_token()),
165 ('message', message),
166 ('message_only', 'True'),
167 ('send_mail', 'True'),
168 ('no_redirect', 'True')])
169
170 def set_flag(self, issue, patchset, flag, value):
171 return self.post('/%s/edit_flags' % issue, [
172 ('last_patchset', str(patchset)),
173 ('xsrf_token', self.xsrf_token()),
174 (flag, value)])
175
176 def get(self, request_path, **kwargs):
177 return self._send(request_path, payload=None, **kwargs)
178
179 def post(self, request_path, data, **kwargs):
180 ctype, body = upload.EncodeMultipartFormData(data, [])
181 return self._send(request_path, payload=body, content_type=ctype, **kwargs)
182
183 def _send(self, request_path, **kwargs):
184 """Sends a POST/GET to Rietveld. Returns the response body."""
185 maxtries = 5
186 for retry in xrange(maxtries):
187 try:
188 result = self.rpc_server.Send(request_path, **kwargs)
189 # Sometimes GAE returns a HTTP 200 but with HTTP 500 as the content. How
190 # nice.
191 return result
192 except urllib2.HTTPError, e:
193 if retry >= (maxtries - 1):
194 raise
195 if e.code not in (500, 502, 503):
196 raise
197 except urllib2.URLError, e:
198 if retry >= (maxtries - 1):
199 raise
200 if not 'Name or service not known' in e.reason:
201 # Usually internal GAE flakiness.
202 raise
203 # If reaching this line, loop again. Uses a small backoff.
204 time.sleep(1+maxtries*2)
OLDNEW
« no previous file with comments | « pylintrc ('k') | tests/local_rietveld.py » ('j') | tests/local_rietveld.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698