| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 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 """Parse output of local git commands into Gitile response format.""" | 5 """Parse output of local git commands into Gitile response format.""" |
| 6 | 6 |
| 7 from collections import namedtuple |
| 7 from collections import defaultdict | 8 from collections import defaultdict |
| 8 from datetime import datetime | 9 from datetime import datetime |
| 9 import re | 10 import re |
| 10 | 11 |
| 11 from lib import time_util | 12 from lib import time_util |
| 12 from lib.gitiles import commit_util | 13 from lib.gitiles import commit_util |
| 13 from lib.gitiles.blame import Blame | 14 from lib.gitiles.blame import Blame |
| 14 from lib.gitiles.blame import Region | 15 from lib.gitiles.blame import Region |
| 15 from lib.gitiles.change_log import ChangeLog | 16 from lib.gitiles.change_log import ChangeLog |
| 17 from lib.gitiles.change_log import FileChangeInfo |
| 16 from lib.gitiles.diff import ChangeType | 18 from lib.gitiles.diff import ChangeType |
| 17 | 19 |
| 18 REGION_START_COUNT_PATTERN = re.compile(r'^(\S+) \d+ (\d+) (\d+)') | 20 REGION_START_COUNT_PATTERN = re.compile(r'^(\S+) \d+ (\d+) (\d+)') |
| 19 | 21 |
| 20 DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' | 22 DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' |
| 21 | 23 |
| 22 AUTHOR_NAME_PATTERN = re.compile(r'^author (.*)') | 24 AUTHOR_NAME_PATTERN = re.compile(r'^author (.*)') |
| 23 AUTHOR_MAIL_PATTERN = re.compile(r'^author-mail (\S+)') | 25 AUTHOR_MAIL_PATTERN = re.compile(r'^author-mail (\S+)') |
| 24 AUTHOR_TIME_PATTERN = re.compile(r'^author-time (.+)') | 26 AUTHOR_TIME_PATTERN = re.compile(r'^author-time (.+)') |
| 25 AUTHOR_TIMEZONE_PATTERN = re.compile(r'^author-tz (.*)') | 27 AUTHOR_TIMEZONE_PATTERN = re.compile(r'^author-tz (.*)') |
| (...skipping 17 matching lines...) Expand all Loading... |
| 43 | 45 |
| 44 INITIAL_TO_CHANGE_TYPE = { | 46 INITIAL_TO_CHANGE_TYPE = { |
| 45 'M': ChangeType.MODIFY, | 47 'M': ChangeType.MODIFY, |
| 46 'A': ChangeType.ADD, | 48 'A': ChangeType.ADD, |
| 47 'D': ChangeType.DELETE, | 49 'D': ChangeType.DELETE, |
| 48 'C': ChangeType.COPY, | 50 'C': ChangeType.COPY, |
| 49 'R': ChangeType.RENAME | 51 'R': ChangeType.RENAME |
| 50 } | 52 } |
| 51 | 53 |
| 52 | 54 |
| 55 class RegionInfo(namedtuple('RegionInfo', ['start', 'count', 'revision'])): |
| 56 __slots__ = () |
| 57 def __new__(cls, start, count, revision): |
| 58 return super(cls, RegionInfo).__new__(cls, int(start), int(count), revision) |
| 59 |
| 60 |
| 53 class GitParser(object): | 61 class GitParser(object): |
| 54 | 62 |
| 55 def __call__(self, output): | 63 def __call__(self, output): |
| 56 raise NotImplementedError() | 64 raise NotImplementedError() |
| 57 | 65 |
| 58 | 66 |
| 59 class GitBlameParser(GitParser): | 67 class GitBlameParser(GitParser): |
| 60 """Parses output of 'git blame --porcelain <rev> <file_path>'. | 68 """Parses output of 'git blame --porcelain <rev> <file_path>'. |
| 61 | 69 |
| 62 For example: | 70 For example: |
| (...skipping 25 matching lines...) Expand all Loading... |
| 88 | 96 |
| 89 blame = Blame(revision, path) | 97 blame = Blame(revision, path) |
| 90 commit_info = defaultdict(dict) | 98 commit_info = defaultdict(dict) |
| 91 region_info = None | 99 region_info = None |
| 92 for line in output.splitlines(): | 100 for line in output.splitlines(): |
| 93 # Sample: ec3ed6... 2 1 7. | 101 # Sample: ec3ed6... 2 1 7. |
| 94 match = REGION_START_COUNT_PATTERN.match(line) | 102 match = REGION_START_COUNT_PATTERN.match(line) |
| 95 if match: | 103 if match: |
| 96 if region_info: | 104 if region_info: |
| 97 blame.AddRegion( | 105 blame.AddRegion( |
| 98 Region(region_info['start'], | 106 Region(region_info.start, |
| 99 region_info['count'], | 107 region_info.count, |
| 100 region_info['revision'], | 108 region_info.revision, |
| 101 commit_info[region_info['revision']]['author_name'], | 109 commit_info[region_info.revision]['author_name'], |
| 102 commit_info[region_info['revision']]['author_email'], | 110 commit_info[region_info.revision]['author_email'], |
| 103 commit_info[region_info['revision']]['author_time'])) | 111 commit_info[region_info.revision]['author_time'])) |
| 104 | 112 |
| 105 region_info = {'start': int(match.group(2)), | 113 region_info = RegionInfo( |
| 106 'count': int(match.group(3)), | 114 start = int(match.group(2)), |
| 107 'revision': match.group(1)} | 115 count = int(match.group(3)), |
| 116 revision = match.group(1)) |
| 117 |
| 108 elif region_info: | 118 elif region_info: |
| 109 # Sample: author test@google.com. | 119 # Sample: author test@google.com. |
| 110 if AUTHOR_NAME_PATTERN.match(line): | 120 if AUTHOR_NAME_PATTERN.match(line): |
| 111 commit_info[region_info['revision']]['author_name'] = ( | 121 commit_info[region_info.revision]['author_name'] = ( |
| 112 AUTHOR_NAME_PATTERN.match(line).group(1)) | 122 AUTHOR_NAME_PATTERN.match(line).group(1)) |
| 113 # Sample: author-mail <test@google.com@2eff-a529-9590-31e7-b00076f81>. | 123 # Sample: author-mail <test@google.com@2eff-a529-9590-31e7-b00076f81>. |
| 114 elif AUTHOR_MAIL_PATTERN.match(line): | 124 elif AUTHOR_MAIL_PATTERN.match(line): |
| 115 commit_info[region_info['revision']]['author_email'] = ( | 125 commit_info[region_info.revision]['author_email'] = ( |
| 116 commit_util.NormalizeEmail( | 126 commit_util.NormalizeEmail( |
| 117 AUTHOR_MAIL_PATTERN.match(line).group(1).replace( | 127 AUTHOR_MAIL_PATTERN.match(line).group(1).replace( |
| 118 '<', '').replace('>', ''))) | 128 '<', '').replace('>', ''))) |
| 119 # Sample: author-time 1311863160. | 129 # Sample: author-time 1311863160. |
| 120 elif AUTHOR_TIME_PATTERN.match(line): | 130 elif AUTHOR_TIME_PATTERN.match(line): |
| 121 commit_info[region_info['revision']]['author_time'] = ( | 131 commit_info[region_info.revision]['author_time'] = ( |
| 122 AUTHOR_TIME_PATTERN.match(line).group(1)) | 132 AUTHOR_TIME_PATTERN.match(line).group(1)) |
| 123 # Sample: author-tz +0800. | 133 # Sample: author-tz +0800. |
| 124 elif AUTHOR_TIMEZONE_PATTERN.match(line): | 134 elif AUTHOR_TIMEZONE_PATTERN.match(line): |
| 125 time_zone = time_util.TimeZoneInfo( | 135 time_zone = time_util.TimeZoneInfo( |
| 126 AUTHOR_TIMEZONE_PATTERN.match(line).group(1)) | 136 AUTHOR_TIMEZONE_PATTERN.match(line).group(1)) |
| 127 commit_info[region_info['revision']]['author_time'] = ( | 137 commit_info[region_info.revision]['author_time'] = ( |
| 128 time_zone.LocalToUTC(datetime.fromtimestamp( | 138 time_zone.LocalToUTC(datetime.fromtimestamp( |
| 129 int(commit_info[region_info['revision']]['author_time'])))) | 139 int(commit_info[region_info.revision]['author_time'])))) |
| 130 | 140 |
| 131 if region_info: | 141 if region_info: |
| 132 blame.AddRegion( | 142 blame.AddRegion( |
| 133 Region(region_info['start'], | 143 Region(region_info.start, |
| 134 region_info['count'], | 144 region_info.count, |
| 135 region_info['revision'], | 145 region_info.revision, |
| 136 commit_info[region_info['revision']]['author_name'], | 146 commit_info[region_info.revision]['author_name'], |
| 137 commit_info[region_info['revision']]['author_email'], | 147 commit_info[region_info.revision]['author_email'], |
| 138 commit_info[region_info['revision']]['author_time'])) | 148 commit_info[region_info.revision]['author_time'])) |
| 139 | 149 |
| 140 return blame if blame else None | 150 return blame if blame else None |
| 141 | 151 |
| 142 | 152 |
| 143 def GetChangeType(initial): | 153 def GetChangeType(initial): |
| 144 """Gets Change type based on the initial character.""" | 154 """Gets Change type based on the initial character.""" |
| 145 return INITIAL_TO_CHANGE_TYPE.get(initial[0]) | 155 return INITIAL_TO_CHANGE_TYPE.get(initial[0]) |
| 146 | 156 |
| 147 | 157 |
| 148 def GetFileChangeInfo(change_type, path1, path2): | 158 def GetFileChangeInfo(change_type, path1, path2): |
| 149 """Set old/new path and old/new mode.""" | 159 """Set old/new path and old/new mode.""" |
| 150 if change_type.lower() == ChangeType.MODIFY: | 160 change_type = change_type.lower() |
| 151 return { | 161 if change_type == ChangeType.MODIFY: |
| 152 'change_type': change_type, | 162 return FileChangeInfo.Modify(path1) |
| 153 'old_path': path1, | |
| 154 'new_path': path1 | |
| 155 } | |
| 156 | 163 |
| 157 if change_type.lower() == ChangeType.ADD: | 164 if change_type == ChangeType.ADD: |
| 158 # Stay the same as gitile. | 165 return FileChangeInfo.Add(path1) |
| 159 return { | |
| 160 'change_type': change_type, | |
| 161 'old_path': None, | |
| 162 'new_path': path1 | |
| 163 } | |
| 164 | 166 |
| 165 if change_type.lower() == ChangeType.DELETE: | 167 if change_type == ChangeType.DELETE: |
| 166 return { | 168 return FileChangeInfo.Delete(path1) |
| 167 'change_type': change_type, | |
| 168 'old_path': path1, | |
| 169 'new_path': None | |
| 170 } | |
| 171 | 169 |
| 172 if (change_type.lower() == ChangeType.RENAME or | 170 if change_type == ChangeType.RENAME: |
| 173 change_type.lower() == ChangeType.COPY): | 171 return FileChangeInfo.Rename(path1, path2) |
| 174 return { | 172 |
| 175 'change_type': change_type, | 173 # TODO(http://crbug.com/659346): write coverage test for this branch |
| 176 'old_path': path1, | 174 if change_type.lower() == ChangeType.COPY: # pragma: no cover |
| 177 'new_path': path2 | 175 return FileChangeInfo.Copy(path1, path2) |
| 178 } | |
| 179 | 176 |
| 180 return None | 177 return None |
| 181 | 178 |
| 182 | 179 |
| 183 class GitChangeLogParser(GitParser): | 180 class GitChangeLogParser(GitParser): |
| 184 | 181 |
| 185 def __call__(self, output, repo_url): # pylint:disable=W | 182 def __call__(self, output, repo_url): # pylint:disable=W |
| 186 """Parses output of 'git log --pretty=format:<format>. | 183 """Parses output of 'git log --pretty=format:<format>. |
| 187 | 184 |
| 188 For example: | 185 For example: |
| (...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 390 + 'printPreviewSummaryFormatShort', | 387 + 'printPreviewSummaryFormatShort', |
| 391 + '<b>' + numSheets.toLocaleString() + '</b>', | 388 + '<b>' + numSheets.toLocaleString() + '</b>', |
| 392 + '<b>' + summaryLabel + '</b>'); | 389 + '<b>' + summaryLabel + '</b>'); |
| 393 label = loadTimeData.getStringF('printPreviewSummaryFormatShort', | 390 label = loadTimeData.getStringF('printPreviewSummaryFormatShort', |
| 394 - numSheets, summaryLabel); | 391 - numSheets, summaryLabel); |
| 395 + numSheets.toLocaleString(), | 392 + numSheets.toLocaleString(), |
| 396 + summaryLabel); | 393 + summaryLabel); |
| 397 } | 394 } |
| 398 """ | 395 """ |
| 399 return output if output else None | 396 return output if output else None |
| OLD | NEW |