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

Side by Side Diff: appengine/findit/common/git_repository.py

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

Powered by Google App Engine
This is Rietveld 408576698