| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import argparse | 6 import argparse |
| 7 import json | 7 import json |
| 8 import re | 8 import re |
| 9 import sys | 9 import sys |
| 10 | 10 |
| 11 from collections import defaultdict | 11 from collections import defaultdict |
| 12 | 12 |
| 13 import git_common as git | 13 import git_common as git |
| 14 | 14 |
| 15 | 15 |
| 16 FOOTER_PATTERN = re.compile(r'^\s*([\w-]+): (.*)$') | 16 FOOTER_PATTERN = re.compile(r'^\s*([\w-]+): (.*)$') |
| 17 CHROME_COMMIT_POSITION_PATTERN = re.compile(r'^([\w/\-\.]+)@{#(\d+)}$') | 17 CHROME_COMMIT_POSITION_PATTERN = re.compile(r'^([\w/\-\.]+)@{#(\d+)}$') |
| 18 GIT_SVN_ID_PATTERN = re.compile('^([^\s@]+)@(\d+)') | |
| 19 | 18 |
| 20 | 19 |
| 21 def normalize_name(header): | 20 def normalize_name(header): |
| 22 return '-'.join([ word.title() for word in header.strip().split('-') ]) | 21 return '-'.join([ word.title() for word in header.strip().split('-') ]) |
| 23 | 22 |
| 24 | 23 |
| 25 def parse_footer(line): | 24 def parse_footer(line): |
| 26 """Returns footer's (key, value) if footer is valid, else None.""" | 25 """Returns footer's (key, value) if footer is valid, else None.""" |
| 27 match = FOOTER_PATTERN.match(line) | 26 match = FOOTER_PATTERN.match(line) |
| 28 if match: | 27 if match: |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 61 # which means those aren't footers. | 60 # which means those aren't footers. |
| 62 footer_lines = [] | 61 footer_lines = [] |
| 63 | 62 |
| 64 footer_lines.reverse() | 63 footer_lines.reverse() |
| 65 footers = map(parse_footer, footer_lines) | 64 footers = map(parse_footer, footer_lines) |
| 66 if not footer_lines or not all(footers): | 65 if not footer_lines or not all(footers): |
| 67 return message_lines, [], [] | 66 return message_lines, [], [] |
| 68 return message_lines[:-len(footer_lines)], footer_lines, footers | 67 return message_lines[:-len(footer_lines)], footer_lines, footers |
| 69 | 68 |
| 70 | 69 |
| 71 def get_footer_svn_id(branch=None): | |
| 72 if not branch: | |
| 73 branch = git.root() | |
| 74 svn_id = None | |
| 75 message = git.run('log', '-1', '--format=%B', branch) | |
| 76 footers = parse_footers(message) | |
| 77 git_svn_id = get_unique(footers, 'git-svn-id') | |
| 78 if git_svn_id: | |
| 79 match = GIT_SVN_ID_PATTERN.match(git_svn_id) | |
| 80 if match: | |
| 81 svn_id = match.group(1) | |
| 82 return svn_id | |
| 83 | |
| 84 | |
| 85 def get_footer_change_id(message): | 70 def get_footer_change_id(message): |
| 86 """Returns a list of Gerrit's ChangeId from given commit message.""" | 71 """Returns a list of Gerrit's ChangeId from given commit message.""" |
| 87 return parse_footers(message).get(normalize_name('Change-Id'), []) | 72 return parse_footers(message).get(normalize_name('Change-Id'), []) |
| 88 | 73 |
| 89 | 74 |
| 90 def add_footer_change_id(message, change_id): | 75 def add_footer_change_id(message, change_id): |
| 91 """Returns message with Change-ID footer in it. | 76 """Returns message with Change-ID footer in it. |
| 92 | 77 |
| 93 Assumes that Change-Id is not yet in footers, which is then inserted at | 78 Assumes that Change-Id is not yet in footers, which is then inserted at |
| 94 earliest footer line which is after all of these footers: | 79 earliest footer line which is after all of these footers: |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 143 | 128 |
| 144 | 129 |
| 145 def get_position(footers): | 130 def get_position(footers): |
| 146 """Get the commit position from the footers multimap using a heuristic. | 131 """Get the commit position from the footers multimap using a heuristic. |
| 147 | 132 |
| 148 Returns: | 133 Returns: |
| 149 A tuple of the branch and the position on that branch. For example, | 134 A tuple of the branch and the position on that branch. For example, |
| 150 | 135 |
| 151 Cr-Commit-Position: refs/heads/master@{#292272} | 136 Cr-Commit-Position: refs/heads/master@{#292272} |
| 152 | 137 |
| 153 would give the return value ('refs/heads/master', 292272). If | 138 would give the return value ('refs/heads/master', 292272). |
| 154 Cr-Commit-Position is not defined, we try to infer the ref and position | |
| 155 from git-svn-id. The position number can be None if it was not inferrable. | |
| 156 """ | 139 """ |
| 157 | 140 |
| 158 position = get_unique(footers, 'Cr-Commit-Position') | 141 position = get_unique(footers, 'Cr-Commit-Position') |
| 159 if position: | 142 if position: |
| 160 match = CHROME_COMMIT_POSITION_PATTERN.match(position) | 143 match = CHROME_COMMIT_POSITION_PATTERN.match(position) |
| 161 assert match, 'Invalid Cr-Commit-Position value: %s' % position | 144 assert match, 'Invalid Cr-Commit-Position value: %s' % position |
| 162 return (match.group(1), match.group(2)) | 145 return (match.group(1), match.group(2)) |
| 163 | 146 |
| 164 svn_commit = get_unique(footers, 'git-svn-id') | |
| 165 if svn_commit: | |
| 166 match = GIT_SVN_ID_PATTERN.match(svn_commit) | |
| 167 assert match, 'Invalid git-svn-id value: %s' % svn_commit | |
| 168 # V8 has different semantics than Chromium. | |
| 169 if re.match(r'.*https?://v8\.googlecode\.com/svn/trunk', | |
| 170 match.group(1)): | |
| 171 return ('refs/heads/candidates', match.group(2)) | |
| 172 if re.match(r'.*https?://v8\.googlecode\.com/svn/branches/bleeding_edge', | |
| 173 match.group(1)): | |
| 174 return ('refs/heads/master', match.group(2)) | |
| 175 | |
| 176 # Assume that any trunk svn revision will match the commit-position | |
| 177 # semantics. | |
| 178 if re.match('.*/trunk.*$', match.group(1)): | |
| 179 return ('refs/heads/master', match.group(2)) | |
| 180 | |
| 181 # But for now only support faking branch-heads for chrome. | |
| 182 branch_match = re.match('.*/chrome/branches/([\w/-]+)/src$', match.group(1)) | |
| 183 if branch_match: | |
| 184 # svn commit numbers do not map to branches. | |
| 185 return ('refs/branch-heads/%s' % branch_match.group(1), None) | |
| 186 | |
| 187 raise ValueError('Unable to infer commit position from footers') | 147 raise ValueError('Unable to infer commit position from footers') |
| 188 | 148 |
| 189 | 149 |
| 190 def main(args): | 150 def main(args): |
| 191 parser = argparse.ArgumentParser( | 151 parser = argparse.ArgumentParser( |
| 192 formatter_class=argparse.ArgumentDefaultsHelpFormatter | 152 formatter_class=argparse.ArgumentDefaultsHelpFormatter |
| 193 ) | 153 ) |
| 194 parser.add_argument('ref', nargs='?', help="Git ref to retrieve footers from." | 154 parser.add_argument('ref', nargs='?', help="Git ref to retrieve footers from." |
| 195 " Omit to parse stdin.") | 155 " Omit to parse stdin.") |
| 196 | 156 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 234 print '%s: %s' % (k, v) | 194 print '%s: %s' % (k, v) |
| 235 return 0 | 195 return 0 |
| 236 | 196 |
| 237 | 197 |
| 238 if __name__ == '__main__': | 198 if __name__ == '__main__': |
| 239 try: | 199 try: |
| 240 sys.exit(main(sys.argv[1:])) | 200 sys.exit(main(sys.argv[1:])) |
| 241 except KeyboardInterrupt: | 201 except KeyboardInterrupt: |
| 242 sys.stderr.write('interrupted\n') | 202 sys.stderr.write('interrupted\n') |
| 243 sys.exit(1) | 203 sys.exit(1) |
| OLD | NEW |