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

Unified 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, 9 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pylintrc ('k') | tests/local_rietveld.py » ('j') | tests/local_rietveld.py » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: rietveld.py
diff --git a/rietveld.py b/rietveld.py
new file mode 100644
index 0000000000000000000000000000000000000000..6984d393423b7108070aa71b1743c9aab9fe8927
--- /dev/null
+++ b/rietveld.py
@@ -0,0 +1,204 @@
+# Copyright (c) 2011 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.
+"""Defines class Rietveld to easily access a rietveld instance.
+
+Security implications:
+
+The following hypothesis are made:
+- Rietveld enforces:
+ - Nobody else than issue owner can upload a patch set
+ - Verifies the issue owner credentials when creating new issues
+ - A issue owner can't change once the issue is created
+ - A patch set cannot be modified
+"""
+
+import logging
+import os
+import sys
+import time
+import urllib2
+
+try:
+ import simplejson as json # pylint: disable=F0401
+except ImportError:
+ try:
+ import json # pylint: disable=F0401
+ except ImportError:
+ # Import the one included in depot_tools.
+ sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party'))
+ import simplejson as json # pylint: disable=F0401
+
+from third_party import upload
+import patch
+
+# Hack out upload logging.info()
+upload.logging = logging.getLogger('upload')
+upload.logging.setLevel(logging.WARNING)
+
+
+class Rietveld(object):
+ """Accesses rietveld."""
+ def __init__(self, url, email, password):
+ self.issue = None
+ self.user = email
+ self.url = url
+ self._get_creds = lambda: (email, password)
+ self._xsrf_token = None
+ self._xsrf_token_time = None
+ self.rpc_server = upload.HttpRpcServer(
+ self.url,
+ self._get_creds,
+ save_cookies=False)
+
+ def xsrf_token(self):
+ if (not self._xsrf_token_time or
+ (time.time() - self._xsrf_token_time) > 30*60):
+ self._xsrf_token_time = time.time()
+ self._xsrf_token = self.get(
+ '/xsrf_token',
+ extra_headers={'X-Requesting-XSRF-Token': '1'})
+ return self._xsrf_token
+
+ def get_pending_issues(self):
+ """Returns an array of dict of all the pending issues on the server."""
+ return json.loads(self.get(
+ '/search?format=json&commit=True&closed=False&keys_only=True')
+ )['results']
+
+ def close_issue(self, issue):
+ """Closes the Rietveld issue for this changelist."""
+ logging.info('closing issue %s' % issue)
+ self.post("/%d/close" % issue, [('xsrf_token', self.xsrf_token())])
+
+ def get_description(self, issue):
+ """Returns the issue's description."""
+ return self.get('/%d/description' % issue)
+
+ def get_issue_properties(self, issue, messages):
+ """Returns all the issue's metadata as a dictionary."""
+ url = '/api/%s' % issue
+ if messages:
+ url += '?messages=true'
+ return json.loads(self.get(url))
+
+ def get_patchset_properties(self, issue, patchset):
+ """Returns the patchset properties."""
+ url = '/api/%s/%s' % (issue, patchset)
+ return json.loads(self.get(url))
+
+ def get_file_content(self, issue, patchset, item):
+ """Returns the content of a new file.
+
+ Throws HTTP 302 exception if the file doesn't exist or is not a binary file.
+ """
+ # content = 0 is the old file, 1 is the new file.
+ content = 1
+ url = '/%s/image/%s/%s/%s' % (issue, patchset, item, content)
+ return self.get(url)
+
+ def get_file_diff(self, issue, patchset, item):
+ """Returns the diff of the file.
+
+ Returns a useless diff for binary files.
+ """
+ url = '/download/issue%s_%s_%s.diff' % (issue, patchset, item)
+ return self.get(url)
+
+ def get_patch(self, issue, patchset):
+ """Returns a PatchSet object containing the details to apply this patch."""
+ props = self.get_patchset_properties(issue, patchset) or {}
+ out = []
+ for filename, state in props.get('files', {}).iteritems():
+ status = state.get('status')
+ if status is None:
+ raise patch.UnsupportedPatchFormat(
+ filename, 'File\'s status is None, patchset upload is incomplete')
+
+ # TODO(maruel): That's bad, it confuses property change.
+ status = status.strip()
+
+ if status == 'D':
+ # Ignore the diff.
+ out.append(patch.FilePatchDelete(filename, state['is_binary']))
+ elif status in ('A', 'M'):
+ # TODO(maruel): Rietveld support is still weird, add this line once it's
+ # safe to use.
+ # props = state.get('property_changes', '').splitlines() or []
+ props = []
+ if state['is_binary']:
+ out.append(patch.FilePatchBinary(
+ filename,
+ self.get_file_content(issue, patchset, state['id']),
+ props))
+ else:
+ if state['num_chunks']:
+ diff = self.get_file_diff(issue, patchset, state['id'])
+ else:
+ diff = None
+ out.append(patch.FilePatchDiff(filename, diff, props))
+ else:
+ # Line too long (N/80)
+ # pylint: disable=C0301
+ # TODO: Add support for MM, A+, etc. Rietveld removes the svn properties
+ # from the diff.
+ # Example of mergeinfo across branches:
+ # http://codereview.chromium.org/202046/diff/1/third_party/libxml/xmlcatalog_dummy.cc
+ # svn:eol-style property that is lost in the diff
+ # http://codereview.chromium.org/202046/diff/1/third_party/libxml/xmllint_dummy.cc
+ # Change with no diff, only svn property change:
+ # http://codereview.chromium.org/6462019/
+ raise patch.UnsupportedPatchFormat(filename, status)
+ return patch.PatchSet(out)
+
+ def update_description(self, issue, description):
+ """Sets the description for an issue on Rietveld."""
+ logging.info('new description for issue %s' % issue)
+ self.post('/%s/description' % issue, [
+ ('description', description),
+ ('xsrf_token', self.xsrf_token())])
+
+ def add_comment(self, issue, message):
+ logging.info('issue %s; comment: %s' % (issue, message))
+ return self.post('/%s/publish' % issue, [
+ ('xsrf_token', self.xsrf_token()),
+ ('message', message),
+ ('message_only', 'True'),
+ ('send_mail', 'True'),
+ ('no_redirect', 'True')])
+
+ def set_flag(self, issue, patchset, flag, value):
+ return self.post('/%s/edit_flags' % issue, [
+ ('last_patchset', str(patchset)),
+ ('xsrf_token', self.xsrf_token()),
+ (flag, value)])
+
+ def get(self, request_path, **kwargs):
+ return self._send(request_path, payload=None, **kwargs)
+
+ def post(self, request_path, data, **kwargs):
+ ctype, body = upload.EncodeMultipartFormData(data, [])
+ return self._send(request_path, payload=body, content_type=ctype, **kwargs)
+
+ def _send(self, request_path, **kwargs):
+ """Sends a POST/GET to Rietveld. Returns the response body."""
+ maxtries = 5
+ for retry in xrange(maxtries):
+ try:
+ result = self.rpc_server.Send(request_path, **kwargs)
+ # Sometimes GAE returns a HTTP 200 but with HTTP 500 as the content. How
+ # nice.
+ return result
+ except urllib2.HTTPError, e:
+ if retry >= (maxtries - 1):
+ raise
+ if e.code not in (500, 502, 503):
+ raise
+ except urllib2.URLError, e:
+ if retry >= (maxtries - 1):
+ raise
+ if not 'Name or service not known' in e.reason:
+ # Usually internal GAE flakiness.
+ raise
+ # If reaching this line, loop again. Uses a small backoff.
+ time.sleep(1+maxtries*2)
« 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