Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | |
|
stgao
2016/11/02 02:04:20
Why this file should be in lib/gitiles? It is not
wrengr
2016/11/03 17:09:01
Fwiw, it makes sense to me for the file to live in
Sharu Jiang
2016/11/03 20:59:48
I found out that app engine does not allow any wri
wrengr
2016/11/03 21:16:09
I don't think util_script is the right place for t
| |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """Parse output of local git commands into Gitile response format.""" | |
| 6 | |
| 7 from collections import defaultdict | |
| 8 from datetime import datetime | |
| 9 import re | |
| 10 | |
| 11 from common import time_util | |
|
wrengr
2016/11/03 17:09:01
Things in ./lib shouldn't depend on things in ./co
Sharu Jiang
2016/11/03 20:59:48
Move the time_util to lib/
| |
| 12 from lib.gitiles.blame import Blame | |
| 13 from lib.gitiles.blame import Region | |
| 14 from lib.gitiles.change_log import ChangeLog | |
| 15 from lib.gitiles import repo_util | |
| 16 | |
| 17 REGION_START_COUNT_PATTERN = re.compile(r'^(\S+) \d+ (\d+) (\d+)') | |
| 18 | |
| 19 DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' | |
| 20 | |
| 21 AUTHOR_NAME_PATTERN = re.compile(r'^author (.*)') | |
| 22 AUTHOR_MAIL_PATTERN = re.compile(r'^author-mail (\S+)') | |
| 23 AUTHOR_TIME_PATTERN = re.compile(r'^author-time (.+)') | |
| 24 AUTHOR_TIMEZONE_PATTERN = re.compile(r'^author-tz (.*)') | |
| 25 | |
| 26 COMMITTER_NAME_PATTERN = re.compile(r'^committer (.*)') | |
| 27 COMMITTER_MAIL_PATTERN = re.compile(r'^committer-mail (\S+)') | |
| 28 COMMITTER_TIME_PATTERN = re.compile(r'^committer-time (.+)') | |
| 29 | |
| 30 COMMIT_HASH_PATTERN = re.compile(r'^commit (\S+)') | |
| 31 | |
| 32 MESSAGE_START_PATTERN = re.compile(r'^--Message start--') | |
| 33 MESSAGE_END_PATTERN = re.compile(r'^--Message end--') | |
| 34 | |
| 35 # This pattern is for M, A, D. | |
| 36 CHANGED_FILE_PATTERN1 = re.compile(r':(\d+) (\d+) (\S+) (\S+) (\w)\s+(\S+)') | |
| 37 # This pattern is for R, C. | |
| 38 CHANGED_FILE_PATTERN2 = re.compile( | |
| 39 r':(\d+) (\d+) (\S+) (\S+) ([A-Z0-9]*)\s+(\S+)\s(\S+)') | |
| 40 | |
| 41 CHANGELOG_START_PATTERN = re.compile(r'^\*\*Changelog start\*\*') | |
| 42 | |
| 43 INITIAL_TO_CHANGE_TYPE = { | |
| 44 'M': 'modify', | |
| 45 'A': 'add', | |
| 46 'D': 'delete', | |
| 47 'C': 'copy', | |
| 48 'R': 'rename' | |
| 49 } | |
| 50 | |
| 51 | |
| 52 class GitParser(object): | |
| 53 | |
| 54 def __call__(self, output): | |
| 55 raise NotImplementedError() | |
| 56 | |
| 57 | |
| 58 class GitBlameParser(GitParser): | |
| 59 """Parses output of 'git blame --porcelain <rev> <file_path>'. | |
| 60 | |
| 61 For example: | |
| 62 Git blame output of a Region is: | |
| 63 ed268bfed3205347a90557c5029f37e90cc01956 18 18 3 | |
| 64 author test@google.com | |
| 65 author-mail <test@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | |
| 66 author-time 1363032816 | |
| 67 author-tz +0000 | |
| 68 committer test@google.com | |
| 69 committer-mail <test@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | |
| 70 committer-time 1363032816 | |
| 71 committer-tz +0000 | |
| 72 summary add (mac) test for ttcindex in SkFontStream | |
| 73 previous fe7533eebe777cc66c7f8fa7a03f00572755c5b4 src/core/SkFontStream.h | |
| 74 filename src/core/SkFontStream.h | |
| 75 * Return the number of shared directories. | |
| 76 ed268bfed3205347a90557c5029f37e90cc01956 19 19 | |
| 77 * if the stream is a normal sfnt (ttf). If there is an error or | |
| 78 ed268bfed3205347a90557c5029f37e90cc01956 20 20 | |
| 79 * no directory is found, return 0. | |
| 80 | |
| 81 Returns: | |
| 82 A list of parsed Blame objects. | |
| 83 """ | |
| 84 def __call__(self, output, path, revision): # pylint:disable=W | |
| 85 blame = Blame(revision, path) | |
| 86 commit_info = defaultdict(dict) | |
| 87 region_info = None | |
| 88 for line in output.splitlines(): | |
| 89 # Sample: ec3ed6... 2 1 7. | |
| 90 match = REGION_START_COUNT_PATTERN.match(line) | |
| 91 if match: | |
| 92 if region_info: | |
| 93 blame.AddRegion( | |
| 94 Region(region_info['start'], | |
| 95 region_info['count'], | |
| 96 region_info['revision'], | |
| 97 commit_info[region_info['revision']]['author_name'], | |
| 98 commit_info[region_info['revision']]['author_email'], | |
| 99 commit_info[region_info['revision']]['author_time'])) | |
| 100 | |
| 101 region_info = {'start': int(match.group(2)), | |
| 102 'count': int(match.group(3)), | |
| 103 'revision': match.group(1)} | |
| 104 elif region_info: | |
| 105 # Sample: author test@google.com. | |
| 106 if AUTHOR_NAME_PATTERN.match(line): | |
| 107 commit_info[region_info['revision']]['author_name'] = ( | |
| 108 AUTHOR_NAME_PATTERN.match(line).group(1)) | |
| 109 # Sample: author-mail <test@google.com@2eff-a529-9590-31e7-b00076f81>. | |
| 110 elif AUTHOR_MAIL_PATTERN.match(line): | |
| 111 commit_info[region_info['revision']]['author_email'] = ( | |
| 112 repo_util.NormalizeEmail( | |
| 113 AUTHOR_MAIL_PATTERN.match(line).group(1).replace( | |
| 114 '<', '').replace('>', ''))) | |
| 115 # Sample: author-time 1311863160. | |
| 116 elif AUTHOR_TIME_PATTERN.match(line): | |
| 117 commit_info[region_info['revision']]['author_time'] = ( | |
| 118 AUTHOR_TIME_PATTERN.match(line).group(1)) | |
| 119 # Sample: author-tz +0800. | |
| 120 elif AUTHOR_TIMEZONE_PATTERN.match(line): | |
| 121 time_zone = time_util.TimeZoneInfo( | |
| 122 AUTHOR_TIMEZONE_PATTERN.match(line).group(1)) | |
| 123 commit_info[region_info['revision']]['author_time'] = ( | |
| 124 time_zone.LocalToUTC(datetime.fromtimestamp( | |
| 125 int(commit_info[region_info['revision']]['author_time'])))) | |
| 126 | |
| 127 if region_info: | |
| 128 blame.AddRegion( | |
| 129 Region(region_info['start'], | |
| 130 region_info['count'], | |
| 131 region_info['revision'], | |
| 132 commit_info[region_info['revision']]['author_name'], | |
| 133 commit_info[region_info['revision']]['author_email'], | |
| 134 commit_info[region_info['revision']]['author_time'])) | |
| 135 | |
| 136 return blame | |
| 137 | |
| 138 | |
| 139 def GetChangeType(initial): | |
| 140 """Gets Change type based on the initial character.""" | |
| 141 return INITIAL_TO_CHANGE_TYPE.get(initial[0]) | |
| 142 | |
| 143 | |
| 144 def GetFileChangeInfo(change_type, path1, path2): | |
| 145 """Set old/new path and old/new mode.""" | |
| 146 if change_type.lower() == 'modify': | |
| 147 return { | |
| 148 'change_type': change_type, | |
| 149 'old_path': path1, | |
| 150 'new_path': path1 | |
| 151 } | |
| 152 | |
| 153 if change_type.lower() == 'add': | |
| 154 # Stay the same as gitile. | |
| 155 return { | |
| 156 'change_type': change_type, | |
| 157 'old_path': None, | |
| 158 'new_path': path1 | |
| 159 } | |
| 160 | |
| 161 if change_type.lower() == 'delete': | |
| 162 return { | |
| 163 'change_type': change_type, | |
| 164 'old_path': path1, | |
| 165 'new_path': None | |
| 166 } | |
| 167 | |
| 168 if change_type.lower() == 'rename' or change_type.lower() == 'copy': | |
| 169 return { | |
| 170 'change_type': change_type, | |
| 171 'old_path': path1, | |
| 172 'new_path': path2 | |
| 173 } | |
| 174 | |
| 175 return None | |
| 176 | |
| 177 | |
| 178 class GitChangeLogParser(GitParser): | |
| 179 | |
| 180 def __call__(self, output, repo_url): # pylint:disable=W | |
| 181 """Parses output of 'git log --pretty=format:<format>. | |
| 182 | |
| 183 For example: | |
| 184 Git changelog output is: | |
| 185 commit 21a8979218c096f4a96b07b67c9531f5f09e28a3 | |
| 186 tree 7d9a79c9b060c9a030abe20a8429d2b81ca1d4db | |
| 187 parents 9640406d426a2d153b16e1d9ae7f9105268b36c9 | |
| 188 | |
| 189 author Test | |
| 190 author-email test@google.com | |
| 191 author-time 2016-10-24 22:21:45 | |
| 192 | |
| 193 committer Test | |
| 194 committer-email test@google.com | |
| 195 committer-time 2016-10-24 22:25:45 | |
| 196 | |
| 197 --Message start-- | |
| 198 Commit messages... | |
| 199 --Message end-- | |
| 200 | |
| 201 :100644 100644 25f95f c766f1 M src/a/delta/git_parsers.py | |
| 202 | |
| 203 Returns: | |
| 204 Parsed ChangeLog object. | |
| 205 """ | |
| 206 is_message_line = False | |
| 207 info = {'message':'', 'touched_files':[]} | |
| 208 for line in output.splitlines(): | |
| 209 if MESSAGE_START_PATTERN.match(line): | |
| 210 is_message_line = True | |
| 211 continue | |
| 212 | |
| 213 if MESSAGE_END_PATTERN.match(line): | |
| 214 is_message_line = False | |
| 215 # Remove the added '\n' at the end. | |
| 216 info['message'] = info['message'][:-1] | |
| 217 continue | |
| 218 | |
| 219 if is_message_line: | |
| 220 info['message'] += line + '\n' | |
| 221 elif COMMIT_HASH_PATTERN.match(line): | |
| 222 info['revision'] = COMMIT_HASH_PATTERN.match(line).group(1) | |
| 223 elif AUTHOR_NAME_PATTERN.match(line): | |
| 224 info['author_name'] = AUTHOR_NAME_PATTERN.match(line).group(1) | |
| 225 elif AUTHOR_MAIL_PATTERN.match(line): | |
| 226 info['author_email'] = repo_util.NormalizeEmail( | |
| 227 AUTHOR_MAIL_PATTERN.match(line).group(1)) | |
| 228 elif AUTHOR_TIME_PATTERN.match(line): | |
| 229 info['author_time'] = datetime.strptime( | |
| 230 AUTHOR_TIME_PATTERN.match(line).group(1), DATETIME_FORMAT) | |
| 231 elif COMMITTER_NAME_PATTERN.match(line): | |
| 232 info['committer_name'] = ( | |
| 233 COMMITTER_NAME_PATTERN.match(line).group(1)) | |
| 234 elif COMMITTER_MAIL_PATTERN.match(line): | |
| 235 info['committer_email'] = repo_util.NormalizeEmail( | |
| 236 COMMITTER_MAIL_PATTERN.match(line).group(1)) | |
| 237 elif COMMITTER_TIME_PATTERN.match(line): | |
| 238 info['committer_time'] = datetime.strptime( | |
| 239 COMMITTER_TIME_PATTERN.match(line).group(1), DATETIME_FORMAT) | |
| 240 elif (CHANGED_FILE_PATTERN1.match(line) or | |
| 241 CHANGED_FILE_PATTERN2.match(line)): | |
| 242 match = (CHANGED_FILE_PATTERN1.match(line) or | |
| 243 CHANGED_FILE_PATTERN2.match(line)) | |
| 244 # For modify, add, delete, the pattern is like: | |
| 245 # :100644 100644 df565d 6593e M modules/audio_coding/BUILD.gn | |
| 246 # For rename, copy, the pattern is like: | |
| 247 # :100644 100644 3f2e 20a5 R078 path1 path2 | |
| 248 info['touched_files'].append( | |
| 249 GetFileChangeInfo(GetChangeType(match.group(5)), | |
| 250 match.group(6), | |
| 251 None if len(match.groups()) < 7 | |
| 252 else match.group(7))) | |
| 253 | |
| 254 # If commit is not parsed, the changelog will be {'author': {}, 'committer': | |
| 255 # {}, 'message': ''}, return None instead. | |
| 256 if not 'revision' in info: | |
| 257 return None | |
| 258 | |
| 259 info['commit_position'], info['code_review_url'] = ( | |
| 260 repo_util.ExtractCommitPositionAndCodeReviewUrl(info['message'])) | |
| 261 info['reverted_revision'] = repo_util.GetRevertedRevision(info['message']) | |
| 262 info['commit_url'] = '%s/+/%s' % (repo_url, info['revision']) | |
| 263 | |
| 264 return ChangeLog.FromDict(info) | |
| 265 | |
| 266 | |
| 267 class GitChangeLogsParser(GitParser): | |
| 268 | |
| 269 def __call__(self, output, repo_url): # pylint:disable=W | |
| 270 """Parses output of 'git log --pretty=format:<format> s_rev..e_rev'. | |
| 271 | |
| 272 For example: | |
| 273 The output is: | |
| 274 **Changelog start** | |
| 275 commit 9af040a364c15bdc2adeea794e173a2c529a3ddc | |
| 276 tree 27b0421273ed4aea25e497c6d26d9c7db6481852 | |
| 277 parents c39b0cc8a516de1fa57d032dc0135a4eadfe2c9e | |
| 278 | |
| 279 author author1 | |
| 280 author-mail author1@chromium.org | |
| 281 author-time 2016-10-24 22:21:45 | |
| 282 | |
| 283 committer Commit bot | |
| 284 committer-mail commit-bot@chromium.org | |
| 285 committer-time 2016-10-24 22:23:45 | |
| 286 | |
| 287 --Message start-- | |
| 288 Message 1 | |
| 289 --Message end-- | |
| 290 | |
| 291 :100644 100644 28e117 f12d3 M tools/win32.txt | |
| 292 | |
| 293 | |
| 294 **Changelog start** | |
| 295 commit c39b0cc8a516de1fa57d032dc0135a4eadfe2c9e | |
| 296 tree d22d3786e135b83183cfeba5f3d8913959f56299 | |
| 297 parents ac7ee4ce7b8d39b22a710c58d110e0039c11cf9a | |
| 298 | |
| 299 author author2 | |
| 300 author-mail author2@chromium.org | |
| 301 author-time 2016-10-24 22:22:45 | |
| 302 | |
| 303 committer Commit bot | |
| 304 committer-mail commit-bot@chromium.org | |
| 305 committer-time 2016-10-24 22:23:45 | |
| 306 | |
| 307 --Message start-- | |
| 308 Message2 | |
| 309 --Message end-- | |
| 310 | |
| 311 :100644 100644 7280f df186 M tools/perf/benchmarks/memory_infra.py | |
| 312 | |
| 313 Returns: | |
| 314 A list of parsed ChangeLog objects. | |
| 315 """ | |
| 316 git_changelog_parser = GitChangeLogParser() | |
| 317 | |
| 318 changelog_str = '' | |
| 319 changelogs = [] | |
| 320 for line in output.splitlines(): | |
| 321 if CHANGELOG_START_PATTERN.match(line): | |
| 322 if not changelog_str: | |
| 323 continue | |
| 324 | |
| 325 change_log = git_changelog_parser(changelog_str, repo_url) | |
| 326 if change_log: | |
| 327 changelogs.append(change_log) | |
| 328 changelog_str = '' | |
| 329 else: | |
| 330 changelog_str += line + '\n' | |
| 331 | |
| 332 change_log = git_changelog_parser(changelog_str, repo_url) | |
| 333 if change_log: | |
| 334 changelogs.append(change_log) | |
| 335 | |
| 336 return changelogs | |
| 337 | |
| 338 | |
| 339 class GitDiffParser(GitParser): | |
| 340 | |
| 341 def __call__(self, output): | |
| 342 """Returns the raw text output of 'git log --format="" --max-count=1'. | |
| 343 | |
| 344 For example: | |
| 345 The output is like: | |
| 346 | |
| 347 diff --git a/chrome/print_header.js b/chrome/print_header.js | |
| 348 index 51f25e7..4eec37f 100644 | |
| 349 --- a/chrome/browser/resources/print_preview/print_header.js | |
| 350 +++ b/chrome/browser/resources/print_preview/print_header.js | |
| 351 @@ -188,20 +188,25 @@ cr.define('print_preview', function() { | |
| 352 var html; | |
| 353 var label; | |
| 354 if (numPages != numSheets) { | |
| 355 - html = loadTimeData.getStringF('printPreviewSummaryFormatLong', | |
| 356 - '<b>' + numSheets + '</b>', | |
| 357 - '<b>' + summaryLabel + '</b>', | |
| 358 - numPages, | |
| 359 - pagesLabel); | |
| 360 + html = loadTimeData.getStringF( | |
| 361 + 'printPreviewSummaryFormatLong', | |
| 362 + '<b>' + numSheets.toLocaleString() + '</b>', | |
| 363 + '<b>' + summaryLabel + '</b>', | |
| 364 + numPages.toLocaleString(), | |
| 365 + pagesLabel); | |
| 366 label = loadTimeData.getStringF('printPreviewSummaryFormatLong', | |
| 367 - numSheets, summaryLabel, | |
| 368 - numPages, pagesLabel); | |
| 369 + numSheets.toLocaleString(), | |
| 370 + summaryLabel, | |
| 371 + numPages.toLocaleString(), | |
| 372 + pagesLabel); | |
| 373 } else { | |
| 374 - html = loadTimeData.getStringF('printPreviewSummaryFormatShort', | |
| 375 - '<b>' + numSheets + '</b>', | |
| 376 - '<b>' + summaryLabel + '</b>'); | |
| 377 + html = loadTimeData.getStringF( | |
| 378 + 'printPreviewSummaryFormatShort', | |
| 379 + '<b>' + numSheets.toLocaleString() + '</b>', | |
| 380 + '<b>' + summaryLabel + '</b>'); | |
| 381 label = loadTimeData.getStringF('printPreviewSummaryFormatShort', | |
| 382 - numSheets, summaryLabel); | |
| 383 + numSheets.toLocaleString(), | |
| 384 + summaryLabel); | |
| 385 } | |
| 386 """ | |
| 387 return output | |
| 388 | |
| 389 | |
| 390 class GitSourceParser(GitParser): | |
| 391 | |
| 392 def __call__(self, output): | |
| 393 """Returns the raw text of a file source from 'git show <rev>:<file>'.""" | |
| 394 return output | |
| OLD | NEW |