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

Side by Side Diff: appengine/findit/lib/gitiles/gitiles_repository.py

Issue 2538373003: [Culprit-Finder] Merge lib/ to libs/. (Closed)
Patch Set: . Created 4 years 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
OLDNEW
(Empty)
1 # Copyright 2014 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
5 import base64
6 from datetime import datetime
7 from datetime import timedelta
8 import json
9 import re
10
11 # TODO(http://crbug.com/660466): We should try to break dependencies.
12 from lib.cache_decorator import Cached
13 from lib.cache_decorator import CompressedMemCacher
14 from lib.gitiles import commit_util
15 from lib.gitiles import diff
16 from lib.gitiles.blame import Blame
17 from lib.gitiles.blame import Region
18 from lib.gitiles.change_log import ChangeLog
19 from lib.gitiles.change_log import FileChangeInfo
20 from lib.gitiles.git_repository import GitRepository
21 from lib.time_util import TimeZoneInfo
22
23 COMMIT_POSITION_PATTERN = re.compile(
24 '^Cr-Commit-Position: refs/heads/master@{#(\d+)}$', re.IGNORECASE)
25 CODE_REVIEW_URL_PATTERN = re.compile(
26 '^(?:Review URL|Review-Url): (.*\d+).*$', re.IGNORECASE)
27 REVERTED_REVISION_PATTERN = re.compile(
28 '^> Committed: https://.+/([0-9a-fA-F]{40})$', re.IGNORECASE)
29 TIMEZONE_PATTERN = re.compile('[-+]\d{4}$')
30 CACHE_EXPIRE_TIME_SECONDS = 24 * 60 * 60
31
32
33 class GitilesRepository(GitRepository):
34 """Use Gitiles to access a repository on https://chromium.googlesource.com."""
35
36 def __init__(self, http_client, repo_url=None):
37 super(GitilesRepository, self).__init__()
38 if repo_url and repo_url.endswith('/'):
39 self._repo_url = repo_url[:-1]
40 else:
41 self._repo_url = repo_url
42
43 self._http_client = http_client
44
45 @property
46 def repo_url(self):
47 return self._repo_url
48
49 @repo_url.setter
50 def repo_url(self, repo_url):
51 self._repo_url = repo_url
52
53 @property
54 def http_client(self):
55 return self._http_client
56
57 @property
58 def identifier(self):
59 return self.repo_url
60
61 @Cached(namespace='Gitiles-json-view', expire_time=CACHE_EXPIRE_TIME_SECONDS,
62 cacher=CompressedMemCacher())
63 def _SendRequestForJsonResponse(self, url, params=None):
64 if params is None: # pragma: no cover
65 params = {}
66 params['format'] = 'json'
67
68 # Gerrit prepends )]}' to json-formatted response.
69 prefix = ')]}\'\n'
70
71 status_code, content = self.http_client.Get(url, params)
72 if status_code != 200:
73 return None
74 elif not content or not content.startswith(prefix):
75 raise Exception('Response does not begin with %s' % prefix)
76
77 return json.loads(content[len(prefix):])
78
79 @Cached(namespace='Gitiles-text-view', expire_time=CACHE_EXPIRE_TIME_SECONDS)
80 def _SendRequestForTextResponse(self, url):
81 status_code, content = self.http_client.Get(url, {'format': 'text'})
82 if status_code != 200:
83 return None
84 return base64.b64decode(content)
85
86 def _GetDateTimeFromString(self, datetime_string,
87 date_format='%a %b %d %H:%M:%S %Y'):
88 if TIMEZONE_PATTERN.findall(datetime_string):
89 # Need to handle timezone conversion.
90 naive_datetime_str, _, offset_str = datetime_string.rpartition(' ')
91 naive_datetime = datetime.strptime(naive_datetime_str, date_format)
92 return TimeZoneInfo(offset_str).LocalToUTC(naive_datetime)
93
94 return datetime.strptime(datetime_string, date_format)
95
96 def _DownloadChangeLogData(self, revision):
97 url = '%s/+/%s' % (self.repo_url, revision)
98 return url, self._SendRequestForJsonResponse(url)
99
100 def _ParseChangeLogFromLogData(self, data):
101 commit_position, code_review_url = (
102 commit_util.ExtractCommitPositionAndCodeReviewUrl(data['message']))
103
104 touched_files = []
105 for file_diff in data['tree_diff']:
106 change_type = file_diff['type'].lower()
107 if not diff.IsKnownChangeType(change_type):
108 raise Exception('Unknown change type "%s"' % change_type)
109 touched_files.append(
110 FileChangeInfo(
111 change_type, file_diff['old_path'], file_diff['new_path']))
112
113 author_time = self._GetDateTimeFromString(data['author']['time'])
114 committer_time = self._GetDateTimeFromString(data['committer']['time'])
115 reverted_revision = commit_util.GetRevertedRevision(data['message'])
116 url = '%s/+/%s' % (self.repo_url, data['commit'])
117
118 return ChangeLog(
119 data['author']['name'],
120 commit_util.NormalizeEmail(data['author']['email']),
121 author_time,
122 data['committer']['name'],
123 commit_util.NormalizeEmail(data['committer']['email']),
124 committer_time, data['commit'], commit_position,
125 data['message'], touched_files, url, code_review_url,
126 reverted_revision)
127
128 def GetChangeLog(self, revision):
129 """Returns the change log of the given revision."""
130 _, data = self._DownloadChangeLogData(revision)
131 if not data:
132 return None
133
134 return self._ParseChangeLogFromLogData(data)
135
136 def GetCommitsBetweenRevisions(self, start_revision, end_revision, n=1000):
137 """Gets a list of commit hashes between start_revision and end_revision.
138
139 Args:
140 start_revision: The oldest revision in the range.
141 end_revision: The latest revision in the range.
142 n: The maximum number of revisions to request at a time.
143
144 Returns:
145 A list of commit hashes made since start_revision through and including
146 end_revision in order from most-recent to least-recent. This includes
147 end_revision, but not start_revision.
148 """
149 params = {'n': n}
150 next_end_revision = end_revision
151 commits = []
152
153 while next_end_revision:
154 url = '%s/+log/%s..%s' % (
155 self.repo_url, start_revision, next_end_revision)
156 data = self._SendRequestForJsonResponse(url, params)
157
158 if not data:
159 break
160
161 for log in data.get('log', []):
162 commit = log.get('commit')
163 if commit:
164 commits.append(commit)
165
166 next_end_revision = data.get('next')
167
168 return commits
169
170 def GetChangeDiff(self, revision):
171 """Returns the raw diff of the given revision."""
172 url = '%s/+/%s%%5E%%21/' % (self.repo_url, revision)
173 return self._SendRequestForTextResponse(url)
174
175 def GetBlame(self, path, revision):
176 """Returns blame of the file at ``path`` of the given revision."""
177 url = '%s/+blame/%s/%s' % (self.repo_url, revision, path)
178
179 data = self._SendRequestForJsonResponse(url)
180 if not data:
181 return None
182
183 blame = Blame(revision, path)
184 for region in data['regions']:
185 author_time = self._GetDateTimeFromString(
186 region['author']['time'], '%Y-%m-%d %H:%M:%S')
187
188 blame.AddRegion(
189 Region(region['start'], region['count'], region['commit'],
190 region['author']['name'],
191 commit_util.NormalizeEmail(region['author']['email']),
192 author_time))
193
194 return blame
195
196 def GetSource(self, path, revision):
197 """Returns source code of the file at ``path`` of the given revision."""
198 url = '%s/+/%s/%s' % (self.repo_url, revision, path)
199 return self._SendRequestForTextResponse(url)
200
201 def GetChangeLogs(self, start_revision, end_revision, n=1000):
202 """Gets a list of ChangeLogs in revision range by batch.
203
204 Args:
205 start_revision (str): The oldest revision in the range.
206 end_revision (str): The latest revision in the range.
207 n (int): The maximum number of revisions to request at a time (default
208 to 1000).
209
210 Returns:
211 A list of changelogs in (start_revision, end_revision].
212 """
213 next_end_revision = end_revision
214 changelogs = []
215
216 while next_end_revision:
217 url = '%s/+log/%s..%s' % (self.repo_url,
218 start_revision, next_end_revision)
219 data = self._SendRequestForJsonResponse(url, params={'n': str(n),
220 'name-status': '1'})
221
222 for log in data['log']:
223 changelogs.append(self._ParseChangeLogFromLogData(log))
224
225 if 'next' in data:
226 next_end_revision = data['next']
227 else:
228 next_end_revision = None
229
230 return changelogs
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698