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

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

Issue 2435863003: [Findit] Add local git parsers. (Closed)
Patch Set: Rebase 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 diff 14 from lib.gitiles import diff
15 from lib.gitiles import repo_util
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
20 21
21 COMMIT_POSITION_PATTERN = re.compile( 22 COMMIT_POSITION_PATTERN = re.compile(
22 '^Cr-Commit-Position: refs/heads/master@{#(\d+)}$', re.IGNORECASE) 23 '^Cr-Commit-Position: refs/heads/master@{#(\d+)}$', re.IGNORECASE)
23 CODE_REVIEW_URL_PATTERN = re.compile( 24 CODE_REVIEW_URL_PATTERN = re.compile(
24 '^(?:Review URL|Review-Url): (.*\d+).*$', re.IGNORECASE) 25 '^(?:Review URL|Review-Url): (.*\d+).*$', re.IGNORECASE)
25 REVERTED_REVISION_PATTERN = re.compile( 26 REVERTED_REVISION_PATTERN = re.compile(
26 '^> Committed: https://.+/([0-9a-fA-F]{40})$', re.IGNORECASE) 27 '^> Committed: https://.+/([0-9a-fA-F]{40})$', re.IGNORECASE)
27 TIMEZONE_PATTERN = re.compile('[-+]\d{4}$') 28 TIMEZONE_PATTERN = re.compile('[-+]\d{4}$')
28 CACHE_EXPIRE_TIME_SECONDS = 24 * 60 * 60 29 CACHE_EXPIRE_TIME_SECONDS = 24 * 60 * 60
29 30
30 31
31 class GitilesRepository(GitRepository): 32 class GitilesRepository(GitRepository):
32 """Use Gitiles to access a repository on https://chromium.googlesource.com.""" 33 """Use Gitiles to access a repository on https://chromium.googlesource.com."""
33 34
35 # TODO(crbug.com/659449): Refactor the http_client to be required argument.
34 def __init__(self, repo_url=None, http_client=None): 36 def __init__(self, repo_url=None, http_client=None):
35 super(GitilesRepository, self).__init__() 37 super(GitilesRepository, self).__init__()
36 if repo_url and repo_url.endswith('/'): 38 if repo_url and repo_url.endswith('/'):
37 self._repo_url = repo_url[:-1] 39 self._repo_url = repo_url[:-1]
38 else: 40 else:
39 self._repo_url = repo_url 41 self._repo_url = repo_url
40 42
41 self._http_client = http_client 43 self._http_client = http_client
42 44
43 @property 45 @property
(...skipping 30 matching lines...) Expand all
74 76
75 return json.loads(content[len(prefix):]) 77 return json.loads(content[len(prefix):])
76 78
77 @Cached(namespace='Gitiles-text-view', expire_time=CACHE_EXPIRE_TIME_SECONDS) 79 @Cached(namespace='Gitiles-text-view', expire_time=CACHE_EXPIRE_TIME_SECONDS)
78 def _SendRequestForTextResponse(self, url): 80 def _SendRequestForTextResponse(self, url):
79 status_code, content = self.http_client.Get(url, {'format': 'text'}) 81 status_code, content = self.http_client.Get(url, {'format': 'text'})
80 if status_code != 200: 82 if status_code != 200:
81 return None 83 return None
82 return base64.b64decode(content) 84 return base64.b64decode(content)
83 85
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, 86 def _GetDateTimeFromString(self, datetime_string,
128 date_format='%a %b %d %H:%M:%S %Y'): 87 date_format='%a %b %d %H:%M:%S %Y'):
129 if TIMEZONE_PATTERN.findall(datetime_string): 88 if TIMEZONE_PATTERN.findall(datetime_string):
130 # Need to handle timezone conversion. 89 # Need to handle timezone conversion.
131 naive_datetime_str, _, offset_str = datetime_string.rpartition(' ') 90 naive_datetime_str, _, offset_str = datetime_string.rpartition(' ')
132 naive_datetime = datetime.strptime(naive_datetime_str, 91 naive_datetime = datetime.strptime(naive_datetime_str,
133 date_format) 92 date_format)
134 hour_offset = int(offset_str[-4:-2]) 93 hour_offset = int(offset_str[-4:-2])
135 minute_offset = int(offset_str[-2:]) 94 minute_offset = int(offset_str[-2:])
136 if(offset_str[0]) == '-': 95 if(offset_str[0]) == '-':
137 hour_offset = -hour_offset 96 hour_offset = -hour_offset
138 minute_offset = -minute_offset 97 minute_offset = -minute_offset
139 98
140 time_delta = timedelta(hours=hour_offset, minutes=minute_offset) 99 time_delta = timedelta(hours=hour_offset, minutes=minute_offset)
141 100
142 utc_datetime = naive_datetime - time_delta 101 utc_datetime = naive_datetime - time_delta
143 return utc_datetime 102 return utc_datetime
144 103
145 return datetime.strptime(datetime_string, date_format) 104 return datetime.strptime(datetime_string, date_format)
146 105
147 def _DownloadChangeLogData(self, revision): 106 def _DownloadChangeLogData(self, revision):
148 url = '%s/+/%s' % (self.repo_url, revision) 107 url = '%s/+/%s' % (self.repo_url, revision)
149 return url, self._SendRequestForJsonResponse(url) 108 return url, self._SendRequestForJsonResponse(url)
150 109
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): 110 def _ParseChangeLogFromLogData(self, data):
164 commit_position, code_review_url = ( 111 commit_position, code_review_url = (
165 self.ExtractCommitPositionAndCodeReviewUrl(data['message'])) 112 repo_util.ExtractCommitPositionAndCodeReviewUrl(data['message']))
166 113
167 touched_files = [] 114 touched_files = []
168 for file_diff in data['tree_diff']: 115 for file_diff in data['tree_diff']:
169 change_type = file_diff['type'].lower() 116 change_type = file_diff['type'].lower()
170 if not diff.IsKnownChangeType(change_type): 117 if not diff.IsKnownChangeType(change_type):
171 raise Exception('Unknown change type "%s"' % change_type) 118 raise Exception('Unknown change type "%s"' % change_type)
172 touched_files.append( 119 touched_files.append(
173 FileChangeInfo( 120 FileChangeInfo(
174 change_type, file_diff['old_path'], file_diff['new_path'])) 121 change_type, file_diff['old_path'], file_diff['new_path']))
175 122
176 author_time = self._GetDateTimeFromString(data['author']['time']) 123 author_time = self._GetDateTimeFromString(data['author']['time'])
177 committer_time = self._GetDateTimeFromString(data['committer']['time']) 124 committer_time = self._GetDateTimeFromString(data['committer']['time'])
178 reverted_revision = self.GetRevertedRevision(data['message']) 125 reverted_revision = repo_util.GetRevertedRevision(data['message'])
179 url = '%s/+/%s' % (self.repo_url, data['commit']) 126 url = '%s/+/%s' % (self.repo_url, data['commit'])
180 127
181 return ChangeLog( 128 return ChangeLog(
182 data['author']['name'], self._NormalizeEmail(data['author']['email']), 129 data['author']['name'],
130 repo_util.NormalizeEmail(data['author']['email']),
183 author_time, 131 author_time,
184 data['committer']['name'], 132 data['committer']['name'],
185 self._NormalizeEmail(data['committer']['email']), 133 repo_util.NormalizeEmail(data['committer']['email']),
186 committer_time, data['commit'], commit_position, 134 committer_time, data['commit'], commit_position,
187 data['message'], touched_files, url, code_review_url, 135 data['message'], touched_files, url, code_review_url,
188 reverted_revision) 136 reverted_revision)
189 137
190 def GetChangeLog(self, revision): 138 def GetChangeLog(self, revision):
191 """Returns the change log of the given revision.""" 139 """Returns the change log of the given revision."""
192 _, data = self._DownloadChangeLogData(revision) 140 _, data = self._DownloadChangeLogData(revision)
193 if not data: 141 if not data:
194 return None 142 return None
195 143
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
243 return None 191 return None
244 192
245 blame = Blame(revision, path) 193 blame = Blame(revision, path)
246 for region in data['regions']: 194 for region in data['regions']:
247 author_time = self._GetDateTimeFromString( 195 author_time = self._GetDateTimeFromString(
248 region['author']['time'], '%Y-%m-%d %H:%M:%S') 196 region['author']['time'], '%Y-%m-%d %H:%M:%S')
249 197
250 blame.AddRegion( 198 blame.AddRegion(
251 Region(region['start'], region['count'], region['commit'], 199 Region(region['start'], region['count'], region['commit'],
252 region['author']['name'], 200 region['author']['name'],
253 self._NormalizeEmail(region['author']['email']), author_time)) 201 repo_util.NormalizeEmail(region['author']['email']),
202 author_time))
254 203
255 return blame 204 return blame
256 205
257 def GetSource(self, path, revision): 206 def GetSource(self, path, revision):
258 """Returns source code of the file at ``path`` of the given revision.""" 207 """Returns source code of the file at ``path`` of the given revision."""
259 url = '%s/+/%s/%s' % (self.repo_url, revision, path) 208 url = '%s/+/%s/%s' % (self.repo_url, revision, path)
260 return self._SendRequestForTextResponse(url) 209 return self._SendRequestForTextResponse(url)
261 210
262 def GetChangeLogs(self, start_revision, end_revision, n=1000): 211 def GetChangeLogs(self, start_revision, end_revision, n=1000):
263 """Gets a list of ChangeLogs in revision range by batch. 212 """Gets a list of ChangeLogs in revision range by batch.
(...skipping 18 matching lines...) Expand all
282 231
283 for log in data['log']: 232 for log in data['log']:
284 changelogs.append(self._ParseChangeLogFromLogData(log)) 233 changelogs.append(self._ParseChangeLogFromLogData(log))
285 234
286 if 'next' in data: 235 if 'next' in data:
287 next_end_revision = data['next'] 236 next_end_revision = data['next']
288 else: 237 else:
289 next_end_revision = None 238 next_end_revision = None
290 239
291 return changelogs 240 return changelogs
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698