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

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

Issue 2480593002: [Predator] Move time_util from common/ to lib/, split code review related part to code_review_util (Closed)
Patch Set: Rebase and fix nits. Created 4 years, 1 month 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
1 # Copyright 2014 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import base64 5 import base64
6 from datetime import datetime 6 from datetime import datetime
7 from datetime import timedelta 7 from datetime import timedelta
8 import json 8 import json
9 import re 9 import re
10 10
11 # TODO(http://crbug.com/660466): We should try to break dependencies. 11 # TODO(http://crbug.com/660466): We should try to break dependencies.
12 from lib.cache_decorator import Cached 12 from lib.cache_decorator import Cached
13 from lib.cache_decorator import CompressedMemCacher 13 from lib.cache_decorator import CompressedMemCacher
14 from lib.gitiles import commit_util
14 from lib.gitiles import diff 15 from lib.gitiles import diff
15 from lib.gitiles.blame import Blame 16 from lib.gitiles.blame import Blame
16 from lib.gitiles.blame import Region 17 from lib.gitiles.blame import Region
17 from lib.gitiles.change_log import ChangeLog 18 from lib.gitiles.change_log import ChangeLog
18 from lib.gitiles.change_log import FileChangeInfo 19 from lib.gitiles.change_log import FileChangeInfo
19 from lib.gitiles.git_repository import GitRepository 20 from lib.gitiles.git_repository import GitRepository
21 from lib.time_util import TimeZoneInfo
20 22
21 COMMIT_POSITION_PATTERN = re.compile( 23 COMMIT_POSITION_PATTERN = re.compile(
22 '^Cr-Commit-Position: refs/heads/master@{#(\d+)}$', re.IGNORECASE) 24 '^Cr-Commit-Position: refs/heads/master@{#(\d+)}$', re.IGNORECASE)
23 CODE_REVIEW_URL_PATTERN = re.compile( 25 CODE_REVIEW_URL_PATTERN = re.compile(
24 '^(?:Review URL|Review-Url): (.*\d+).*$', re.IGNORECASE) 26 '^(?:Review URL|Review-Url): (.*\d+).*$', re.IGNORECASE)
25 REVERTED_REVISION_PATTERN = re.compile( 27 REVERTED_REVISION_PATTERN = re.compile(
26 '^> Committed: https://.+/([0-9a-fA-F]{40})$', re.IGNORECASE) 28 '^> Committed: https://.+/([0-9a-fA-F]{40})$', re.IGNORECASE)
27 TIMEZONE_PATTERN = re.compile('[-+]\d{4}$') 29 TIMEZONE_PATTERN = re.compile('[-+]\d{4}$')
28 CACHE_EXPIRE_TIME_SECONDS = 24 * 60 * 60 30 CACHE_EXPIRE_TIME_SECONDS = 24 * 60 * 60
29 31
30 32
31 class GitilesRepository(GitRepository): 33 class GitilesRepository(GitRepository):
32 """Use Gitiles to access a repository on https://chromium.googlesource.com.""" 34 """Use Gitiles to access a repository on https://chromium.googlesource.com."""
33 35
36 # TODO(crbug.com/659449): Refactor the http_client to be required argument.
34 def __init__(self, repo_url=None, http_client=None): 37 def __init__(self, repo_url=None, http_client=None):
35 super(GitilesRepository, self).__init__() 38 super(GitilesRepository, self).__init__()
36 if repo_url and repo_url.endswith('/'): 39 if repo_url and repo_url.endswith('/'):
37 self._repo_url = repo_url[:-1] 40 self._repo_url = repo_url[:-1]
38 else: 41 else:
39 self._repo_url = repo_url 42 self._repo_url = repo_url
40 43
41 self._http_client = http_client 44 self._http_client = http_client
42 45
43 @property 46 @property
(...skipping 30 matching lines...) Expand all
74 77
75 return json.loads(content[len(prefix):]) 78 return json.loads(content[len(prefix):])
76 79
77 @Cached(namespace='Gitiles-text-view', expire_time=CACHE_EXPIRE_TIME_SECONDS) 80 @Cached(namespace='Gitiles-text-view', expire_time=CACHE_EXPIRE_TIME_SECONDS)
78 def _SendRequestForTextResponse(self, url): 81 def _SendRequestForTextResponse(self, url):
79 status_code, content = self.http_client.Get(url, {'format': 'text'}) 82 status_code, content = self.http_client.Get(url, {'format': 'text'})
80 if status_code != 200: 83 if status_code != 200:
81 return None 84 return None
82 return base64.b64decode(content) 85 return base64.b64decode(content)
83 86
84 def ExtractCommitPositionAndCodeReviewUrl(self, message):
85 """Returns the commit position and code review url in the commit message.
86
87 A "commit position" is something similar to SVN version ids; i.e.,
88 numeric identifiers which are issued in sequential order. The reason
89 we care about them is that they're easier for humans to read than
90 the hashes that Git uses internally for identifying commits. We
91 should never actually use them for *identifying* commits; they're
92 only for pretty printing to humans.
93
94 Returns:
95 (commit_position, code_review_url)
96 """
97 if not message:
98 return (None, None)
99
100 commit_position = None
101 code_review_url = None
102
103 # Commit position and code review url are in the last 5 lines.
104 lines = message.strip().split('\n')[-5:]
105 lines.reverse()
106
107 for line in lines:
108 if commit_position is None:
109 match = COMMIT_POSITION_PATTERN.match(line)
110 if match:
111 commit_position = int(match.group(1))
112
113 if code_review_url is None:
114 match = CODE_REVIEW_URL_PATTERN.match(line)
115 if match:
116 code_review_url = match.group(1)
117 return (commit_position, code_review_url)
118
119 def _NormalizeEmail(self, email):
120 """Normalizes the email from git repo.
121
122 Some email is like: test@chromium.org@bbb929c8-8fbe-4397-9dbb-9b2b20218538.
123 """
124 parts = email.split('@')
125 return '@'.join(parts[0:2])
126
127 def _GetDateTimeFromString(self, datetime_string, 87 def _GetDateTimeFromString(self, datetime_string,
128 date_format='%a %b %d %H:%M:%S %Y'): 88 date_format='%a %b %d %H:%M:%S %Y'):
129 if TIMEZONE_PATTERN.findall(datetime_string): 89 if TIMEZONE_PATTERN.findall(datetime_string):
130 # Need to handle timezone conversion. 90 # Need to handle timezone conversion.
131 naive_datetime_str, _, offset_str = datetime_string.rpartition(' ') 91 naive_datetime_str, _, offset_str = datetime_string.rpartition(' ')
132 naive_datetime = datetime.strptime(naive_datetime_str, 92 naive_datetime = datetime.strptime(naive_datetime_str, date_format)
133 date_format) 93 return TimeZoneInfo(offset_str).LocalToUTC(naive_datetime)
134 hour_offset = int(offset_str[-4:-2])
135 minute_offset = int(offset_str[-2:])
136 if(offset_str[0]) == '-':
137 hour_offset = -hour_offset
138 minute_offset = -minute_offset
139
140 time_delta = timedelta(hours=hour_offset, minutes=minute_offset)
141
142 utc_datetime = naive_datetime - time_delta
143 return utc_datetime
144 94
145 return datetime.strptime(datetime_string, date_format) 95 return datetime.strptime(datetime_string, date_format)
146 96
147 def _DownloadChangeLogData(self, revision): 97 def _DownloadChangeLogData(self, revision):
148 url = '%s/+/%s' % (self.repo_url, revision) 98 url = '%s/+/%s' % (self.repo_url, revision)
149 return url, self._SendRequestForJsonResponse(url) 99 return url, self._SendRequestForJsonResponse(url)
150 100
151 def GetRevertedRevision(self, message):
152 """Parse message to get the reverted revision if there is one."""
153 lines = message.strip().splitlines()
154 if not lines[0].lower().startswith('revert'):
155 return None
156
157 for line in reversed(lines): # pragma: no cover
158 # TODO: Handle cases where no reverted_revision in reverting message.
159 reverted_revision_match = REVERTED_REVISION_PATTERN.match(line)
160 if reverted_revision_match:
161 return reverted_revision_match.group(1)
162
163 def _ParseChangeLogFromLogData(self, data): 101 def _ParseChangeLogFromLogData(self, data):
164 commit_position, code_review_url = ( 102 commit_position, code_review_url = (
165 self.ExtractCommitPositionAndCodeReviewUrl(data['message'])) 103 commit_util.ExtractCommitPositionAndCodeReviewUrl(data['message']))
166 104
167 touched_files = [] 105 touched_files = []
168 for file_diff in data['tree_diff']: 106 for file_diff in data['tree_diff']:
169 change_type = file_diff['type'].lower() 107 change_type = file_diff['type'].lower()
170 if not diff.IsKnownChangeType(change_type): 108 if not diff.IsKnownChangeType(change_type):
171 raise Exception('Unknown change type "%s"' % change_type) 109 raise Exception('Unknown change type "%s"' % change_type)
172 touched_files.append( 110 touched_files.append(
173 FileChangeInfo( 111 FileChangeInfo(
174 change_type, file_diff['old_path'], file_diff['new_path'])) 112 change_type, file_diff['old_path'], file_diff['new_path']))
175 113
176 author_time = self._GetDateTimeFromString(data['author']['time']) 114 author_time = self._GetDateTimeFromString(data['author']['time'])
177 committer_time = self._GetDateTimeFromString(data['committer']['time']) 115 committer_time = self._GetDateTimeFromString(data['committer']['time'])
178 reverted_revision = self.GetRevertedRevision(data['message']) 116 reverted_revision = commit_util.GetRevertedRevision(data['message'])
179 url = '%s/+/%s' % (self.repo_url, data['commit']) 117 url = '%s/+/%s' % (self.repo_url, data['commit'])
180 118
181 return ChangeLog( 119 return ChangeLog(
182 data['author']['name'], self._NormalizeEmail(data['author']['email']), 120 data['author']['name'],
121 commit_util.NormalizeEmail(data['author']['email']),
183 author_time, 122 author_time,
184 data['committer']['name'], 123 data['committer']['name'],
185 self._NormalizeEmail(data['committer']['email']), 124 commit_util.NormalizeEmail(data['committer']['email']),
186 committer_time, data['commit'], commit_position, 125 committer_time, data['commit'], commit_position,
187 data['message'], touched_files, url, code_review_url, 126 data['message'], touched_files, url, code_review_url,
188 reverted_revision) 127 reverted_revision)
189 128
190 def GetChangeLog(self, revision): 129 def GetChangeLog(self, revision):
191 """Returns the change log of the given revision.""" 130 """Returns the change log of the given revision."""
192 _, data = self._DownloadChangeLogData(revision) 131 _, data = self._DownloadChangeLogData(revision)
193 if not data: 132 if not data:
194 return None 133 return None
195 134
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
243 return None 182 return None
244 183
245 blame = Blame(revision, path) 184 blame = Blame(revision, path)
246 for region in data['regions']: 185 for region in data['regions']:
247 author_time = self._GetDateTimeFromString( 186 author_time = self._GetDateTimeFromString(
248 region['author']['time'], '%Y-%m-%d %H:%M:%S') 187 region['author']['time'], '%Y-%m-%d %H:%M:%S')
249 188
250 blame.AddRegion( 189 blame.AddRegion(
251 Region(region['start'], region['count'], region['commit'], 190 Region(region['start'], region['count'], region['commit'],
252 region['author']['name'], 191 region['author']['name'],
253 self._NormalizeEmail(region['author']['email']), author_time)) 192 commit_util.NormalizeEmail(region['author']['email']),
193 author_time))
254 194
255 return blame 195 return blame
256 196
257 def GetSource(self, path, revision): 197 def GetSource(self, path, revision):
258 """Returns source code of the file at ``path`` of the given revision.""" 198 """Returns source code of the file at ``path`` of the given revision."""
259 url = '%s/+/%s/%s' % (self.repo_url, revision, path) 199 url = '%s/+/%s/%s' % (self.repo_url, revision, path)
260 return self._SendRequestForTextResponse(url) 200 return self._SendRequestForTextResponse(url)
261 201
262 def GetChangeLogs(self, start_revision, end_revision, n=1000): 202 def GetChangeLogs(self, start_revision, end_revision, n=1000):
263 """Gets a list of ChangeLogs in revision range by batch. 203 """Gets a list of ChangeLogs in revision range by batch.
(...skipping 18 matching lines...) Expand all
282 222
283 for log in data['log']: 223 for log in data['log']:
284 changelogs.append(self._ParseChangeLogFromLogData(log)) 224 changelogs.append(self._ParseChangeLogFromLogData(log))
285 225
286 if 'next' in data: 226 if 'next' in data:
287 next_end_revision = data['next'] 227 next_end_revision = data['next']
288 else: 228 else:
289 next_end_revision = None 229 next_end_revision = None
290 230
291 return changelogs 231 return changelogs
OLDNEW
« no previous file with comments | « appengine/findit/lib/gitiles/commit_util.py ('k') | appengine/findit/lib/gitiles/test/commit_util_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698