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

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 self.repo_url = repo_url 31 self.repo_url = repo_url
(...skipping 24 matching lines...) Expand all
62 56
63 return json.loads(content[len(prefix):]) 57 return json.loads(content[len(prefix):])
64 58
65 @Cached(namespace='Gitiles-text-view', expire_time=CACHE_EXPIRE_TIME_SECONDS) 59 @Cached(namespace='Gitiles-text-view', expire_time=CACHE_EXPIRE_TIME_SECONDS)
66 def _SendRequestForTextResponse(self, url): 60 def _SendRequestForTextResponse(self, url):
67 status_code, content = self.http_client.Get(url, {'format': 'text'}) 61 status_code, content = self.http_client.Get(url, {'format': 'text'})
68 if status_code != 200: 62 if status_code != 200:
69 return None 63 return None
70 return base64.b64decode(content) 64 return base64.b64decode(content)
71 65
72 def ExtractCommitPositionAndCodeReviewUrl(self, message):
73 """Returns the commit position and code review url in the commit message.
74
75 A "commit position" is something similar to SVN version ids; i.e.,
76 numeric identifiers which are issued in sequential order. The reason
77 we care about them is that they're easier for humans to read than
78 the hashes that Git uses internally for identifying commits. We
79 should never actually use them for *identifying* commits; they're
80 only for pretty printing to humans.
81
82 Returns:
83 (commit_position, code_review_url)
84 """
85 if not message:
86 return (None, None)
87
88 commit_position = None
89 code_review_url = None
90
91 # Commit position and code review url are in the last 5 lines.
92 lines = message.strip().split('\n')[-5:]
93 lines.reverse()
94
95 for line in lines:
96 if commit_position is None:
97 match = COMMIT_POSITION_PATTERN.match(line)
98 if match:
99 commit_position = int(match.group(1))
100
101 if code_review_url is None:
102 match = CODE_REVIEW_URL_PATTERN.match(line)
103 if match:
104 code_review_url = match.group(1)
105 return (commit_position, code_review_url)
106
107 def _NormalizeEmail(self, email):
108 """Normalizes the email from git repo.
109
110 Some email is like: test@chromium.org@bbb929c8-8fbe-4397-9dbb-9b2b20218538.
111 """
112 parts = email.split('@')
113 return '@'.join(parts[0:2])
114
115 def _GetDateTimeFromString(self, datetime_string, 66 def _GetDateTimeFromString(self, datetime_string,
116 date_format='%a %b %d %H:%M:%S %Y'): 67 date_format='%a %b %d %H:%M:%S %Y'):
117 if TIMEZONE_PATTERN.findall(datetime_string): 68 if TIMEZONE_PATTERN.findall(datetime_string):
118 # Need to handle timezone conversion. 69 # Need to handle timezone conversion.
119 naive_datetime_str, _, offset_str = datetime_string.rpartition(' ') 70 naive_datetime_str, _, offset_str = datetime_string.rpartition(' ')
120 naive_datetime = datetime.strptime(naive_datetime_str, 71 naive_datetime = datetime.strptime(naive_datetime_str,
121 date_format) 72 date_format)
122 hour_offset = int(offset_str[-4:-2]) 73 hour_offset = int(offset_str[-4:-2])
123 minute_offset = int(offset_str[-2:]) 74 minute_offset = int(offset_str[-2:])
124 if(offset_str[0]) == '-': 75 if(offset_str[0]) == '-':
125 hour_offset = -hour_offset 76 hour_offset = -hour_offset
126 minute_offset = -minute_offset 77 minute_offset = -minute_offset
127 78
128 time_delta = timedelta(hours=hour_offset, minutes=minute_offset) 79 time_delta = timedelta(hours=hour_offset, minutes=minute_offset)
129 80
130 utc_datetime = naive_datetime - time_delta 81 utc_datetime = naive_datetime - time_delta
131 return utc_datetime 82 return utc_datetime
132 83
133 return datetime.strptime(datetime_string, date_format) 84 return datetime.strptime(datetime_string, date_format)
134 85
135 def _DownloadChangeLogData(self, revision): 86 def _DownloadChangeLogData(self, revision):
136 url = '%s/+/%s' % (self.repo_url, revision) 87 url = '%s/+/%s' % (self.repo_url, revision)
137 return url, self._SendRequestForJsonResponse(url) 88 return url, self._SendRequestForJsonResponse(url)
138 89
139 def GetRevertedRevision(self, message):
140 """Parse message to get the reverted revision if there is one."""
141 lines = message.strip().splitlines()
142 if not lines[0].lower().startswith('revert'):
143 return None
144
145 for line in reversed(lines): # pragma: no cover
146 # TODO: Handle cases where no reverted_revision in reverting message.
147 reverted_revision_match = REVERTED_REVISION_PATTERN.match(line)
148 if reverted_revision_match:
149 return reverted_revision_match.group(1)
150
151 def _ParseChangeLogFromLogData(self, data): 90 def _ParseChangeLogFromLogData(self, data):
152 commit_position, code_review_url = ( 91 commit_position, code_review_url = (
153 self.ExtractCommitPositionAndCodeReviewUrl(data['message'])) 92 repo_util.ExtractCommitPositionAndCodeReviewUrl(data['message']))
154 93
155 touched_files = [] 94 touched_files = []
156 for file_diff in data['tree_diff']: 95 for file_diff in data['tree_diff']:
157 change_type = file_diff['type'].lower() 96 change_type = file_diff['type'].lower()
158 if not diff.IsKnownChangeType(change_type): 97 if not diff.IsKnownChangeType(change_type):
159 raise Exception('Unknown change type "%s"' % change_type) 98 raise Exception('Unknown change type "%s"' % change_type)
160 touched_files.append( 99 touched_files.append(
161 FileChangeInfo( 100 FileChangeInfo(
162 change_type, file_diff['old_path'], file_diff['new_path'])) 101 change_type, file_diff['old_path'], file_diff['new_path']))
163 102
164 author_time = self._GetDateTimeFromString(data['author']['time']) 103 author_time = self._GetDateTimeFromString(data['author']['time'])
165 committer_time = self._GetDateTimeFromString(data['committer']['time']) 104 committer_time = self._GetDateTimeFromString(data['committer']['time'])
166 reverted_revision = self.GetRevertedRevision(data['message']) 105 reverted_revision = repo_util.GetRevertedRevision(data['message'])
167 url = '%s/+/%s' % (self.repo_url, data['commit']) 106 url = '%s/+/%s' % (self.repo_url, data['commit'])
168 107
169 return ChangeLog( 108 return ChangeLog(
170 data['author']['name'], self._NormalizeEmail(data['author']['email']), 109 data['author']['name'],
110 repo_util.NormalizeEmail(data['author']['email']),
171 author_time, 111 author_time,
172 data['committer']['name'], 112 data['committer']['name'],
173 self._NormalizeEmail(data['committer']['email']), 113 repo_util.NormalizeEmail(data['committer']['email']),
174 committer_time, data['commit'], commit_position, 114 committer_time, data['commit'], commit_position,
175 data['message'], touched_files, url, code_review_url, 115 data['message'], touched_files, url, code_review_url,
176 reverted_revision) 116 reverted_revision)
177 117
178 def GetChangeLog(self, revision): 118 def GetChangeLog(self, revision):
179 """Returns the change log of the given revision.""" 119 """Returns the change log of the given revision."""
180 _, data = self._DownloadChangeLogData(revision) 120 _, data = self._DownloadChangeLogData(revision)
181 if not data: 121 if not data:
182 return None 122 return None
183 123
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
231 return None 171 return None
232 172
233 blame = Blame(revision, path) 173 blame = Blame(revision, path)
234 for region in data['regions']: 174 for region in data['regions']:
235 author_time = self._GetDateTimeFromString( 175 author_time = self._GetDateTimeFromString(
236 region['author']['time'], '%Y-%m-%d %H:%M:%S') 176 region['author']['time'], '%Y-%m-%d %H:%M:%S')
237 177
238 blame.AddRegion( 178 blame.AddRegion(
239 Region(region['start'], region['count'], region['commit'], 179 Region(region['start'], region['count'], region['commit'],
240 region['author']['name'], 180 region['author']['name'],
241 self._NormalizeEmail(region['author']['email']), author_time)) 181 repo_util.NormalizeEmail(region['author']['email']),
182 author_time))
242 183
243 return blame 184 return blame
244 185
245 def GetSource(self, path, revision): 186 def GetSource(self, path, revision):
246 """Returns source code of the file at ``path`` of the given revision.""" 187 """Returns source code of the file at ``path`` of the given revision."""
247 url = '%s/+/%s/%s' % (self.repo_url, revision, path) 188 url = '%s/+/%s/%s' % (self.repo_url, revision, path)
248 return self._SendRequestForTextResponse(url) 189 return self._SendRequestForTextResponse(url)
249 190
250 def GetChangeLogs(self, start_revision, end_revision, n=1000): 191 def GetChangeLogs(self, start_revision, end_revision, n=1000):
251 """Gets a list of ChangeLogs in revision range by batch. 192 """Gets a list of ChangeLogs in revision range by batch.
(...skipping 18 matching lines...) Expand all
270 211
271 for log in data['log']: 212 for log in data['log']:
272 changelogs.append(self._ParseChangeLogFromLogData(log)) 213 changelogs.append(self._ParseChangeLogFromLogData(log))
273 214
274 if 'next' in data: 215 if 'next' in data:
275 next_end_revision = data['next'] 216 next_end_revision = data['next']
276 else: 217 else:
277 next_end_revision = None 218 next_end_revision = None
278 219
279 return changelogs 220 return changelogs
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698