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

Side by Side Diff: git_footers.py

Issue 2028303006: Refactor git_footers for later use in git cl. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@footer
Patch Set: nit Created 4 years, 6 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
« no previous file with comments | « no previous file | tests/git_footers_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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+)') 18 GIT_SVN_ID_PATTERN = re.compile('^([^\s@]+)@(\d+)')
19 19
20 20
21 def normalize_name(header): 21 def normalize_name(header):
22 return '-'.join([ word.title() for word in header.strip().split('-') ]) 22 return '-'.join([ word.title() for word in header.strip().split('-') ])
23 23
24 24
25 def parse_footer(line): 25 def parse_footer(line):
26 """Returns footer's (key, value) if footer is valid, else None."""
26 match = FOOTER_PATTERN.match(line) 27 match = FOOTER_PATTERN.match(line)
27 if match: 28 if match:
28 return (match.group(1), match.group(2)) 29 return (match.group(1), match.group(2))
29 else: 30 else:
30 return None 31 return None
31 32
32 33
33 def parse_footers(message): 34 def parse_footers(message):
34 """Parses a git commit message into a multimap of footers.""" 35 """Parses a git commit message into a multimap of footers."""
36 _, _, parsed_footers = split_footers(message)
37 footer_map = defaultdict(list)
38 if parsed_footers:
39 # Read footers from bottom to top, because latter takes precedense,
40 # and we want it to be first in the multimap value.
41 for (k, v) in reversed(parsed_footers):
42 footer_map[normalize_name(k)].append(v.strip())
43 return footer_map
44
45
46 def split_footers(message):
47 """Returns (non_footer_lines, footer_lines, parsed footers).
48
49 Guarantees that:
50 (non_footer_lines + footer_lines) == message.splitlines().
51 parsed_footers is parse_footer applied on each line of footer_lines.
52 """
53 message_lines = list(message.splitlines())
35 footer_lines = [] 54 footer_lines = []
36 for line in reversed(message.splitlines()): 55 for line in reversed(message_lines):
37 if line == '' or line.isspace(): 56 if line == '' or line.isspace():
38 break 57 break
39 footer_lines.append(line) 58 footer_lines.append(line)
59 else:
60 # The whole description was consisting of footers,
61 # which means those aren't footers.
62 footer_lines = []
40 63
64 footer_lines.reverse()
41 footers = map(parse_footer, footer_lines) 65 footers = map(parse_footer, footer_lines)
42 if not all(footers): 66 if not footer_lines or not all(footers):
43 return defaultdict(list) 67 return message_lines, [], []
44 68 return message_lines[:-len(footer_lines)], footer_lines, footers
45 footer_map = defaultdict(list)
46 for (k, v) in footers:
47 footer_map[normalize_name(k)].append(v.strip())
48
49 return footer_map
50 69
51 70
52 def get_footer_svn_id(branch=None): 71 def get_footer_svn_id(branch=None):
53 if not branch: 72 if not branch:
54 branch = git.root() 73 branch = git.root()
55 svn_id = None 74 svn_id = None
56 message = git.run('log', '-1', '--format=%B', branch) 75 message = git.run('log', '-1', '--format=%B', branch)
57 footers = parse_footers(message) 76 footers = parse_footers(message)
58 git_svn_id = get_unique(footers, 'git-svn-id') 77 git_svn_id = get_unique(footers, 'git-svn-id')
59 if git_svn_id: 78 if git_svn_id:
60 match = GIT_SVN_ID_PATTERN.match(git_svn_id) 79 match = GIT_SVN_ID_PATTERN.match(git_svn_id)
61 if match: 80 if match:
62 svn_id = match.group(1) 81 svn_id = match.group(1)
63 return svn_id 82 return svn_id
64 83
65 84
66 def get_footer_change_id(message): 85 def get_footer_change_id(message):
67 """Returns a list of Gerrit's ChangeId from given commit message.""" 86 """Returns a list of Gerrit's ChangeId from given commit message."""
68 return parse_footers(message).get(normalize_name('Change-Id'), []) 87 return parse_footers(message).get(normalize_name('Change-Id'), [])
69 88
70 89
71 def add_footer_change_id(message, change_id): 90 def add_footer_change_id(message, change_id):
72 """Returns message with Change-ID footer in it. 91 """Returns message with Change-ID footer in it.
73 92
74 Assumes that Change-Id is not yet in footers, which is then 93 Assumes that Change-Id is not yet in footers, which is then inserted at
75 inserted after any of these footers: Bug|Issue|Test|Feature. 94 earliest footer line which is after all of these footers:
95 Bug|Issue|Test|Feature.
76 """ 96 """
77 assert 0 == len(get_footer_change_id(message)) 97 assert 'Change-Id' not in parse_footers(message)
78 change_id_line = 'Change-Id: %s' % change_id 98 return add_footer(message, 'Change-Id', change_id,
79 # This code does the same as parse_footers, but keeps track of line 99 after_keys=['Bug', 'Issue', 'Test', 'Feature'])
80 # numbers so that ChangeId is inserted in the right place. 100
81 lines = message.splitlines() 101 def add_footer(message, key, value, after_keys=None):
82 footer_lines = [] 102 """Returns a message with given footer appended.
83 for line in reversed(lines): 103
84 if line == '' or line.isspace(): 104 If after_keys is None (default), appends footer last.
85 break 105 Otherwise, after_keys must be iterable of footer keys, then the new footer
86 footer_lines.append(line) 106 would be inserted at the topmost position such there would be no footer lines
107 after it with key matching one of after_keys.
108 For example, given
109 message='Header.\n\nAdded: 2016\nBug: 123\nVerified-By: CQ'
110 after_keys=['Bug', 'Issue']
111 the new footer will be inserted between Bug and Verified-By existing footers.
112 """
113 assert key == normalize_name(key), 'Use normalized key'
114 new_footer = '%s: %s' % (key, value)
115
116 top_lines, footer_lines, parsed_footers = split_footers(message)
117 if not footer_lines:
118 if not top_lines or top_lines[-1] != '':
119 top_lines.append('')
120 footer_lines = [new_footer]
121 elif not after_keys:
122 footer_lines.append(new_footer)
87 else: 123 else:
88 # The whole description was consisting of footers, 124 after_keys = set(map(normalize_name, after_keys))
89 # which means those aren't footers. 125 # Iterate from last to first footer till we find the footer keys above.
90 footer_lines = [] 126 for i, (key, _) in reversed(list(enumerate(parsed_footers))):
91 # footers order is from end to start of the message. 127 if normalize_name(key) in after_keys:
92 footers = map(parse_footer, footer_lines) 128 footer_lines.insert(i + 1, new_footer)
93 if not footers or not all(footers):
94 lines.append('')
95 lines.append(change_id_line)
96 else:
97 after = set(map(normalize_name, ['Bug', 'Issue', 'Test', 'Feature']))
98 for i, (key, _) in enumerate(footers):
99 if normalize_name(key) in after:
100 insert_at = len(lines) - i
101 break 129 break
102 else: 130 else:
103 insert_at = len(lines) - len(footers) 131 footer_lines.insert(0, new_footer)
104 lines.insert(insert_at, change_id_line) 132 return '\n'.join(top_lines + footer_lines)
105 return '\n'.join(lines)
106 133
107 134
108 def get_unique(footers, key): 135 def get_unique(footers, key):
109 key = normalize_name(key) 136 key = normalize_name(key)
110 values = footers[key] 137 values = footers[key]
111 assert len(values) <= 1, 'Multiple %s footers' % key 138 assert len(values) <= 1, 'Multiple %s footers' % key
112 if values: 139 if values:
113 return values[0] 140 return values[0]
114 else: 141 else:
115 return None 142 return None
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
207 print '%s: %s' % (k, v) 234 print '%s: %s' % (k, v)
208 return 0 235 return 0
209 236
210 237
211 if __name__ == '__main__': 238 if __name__ == '__main__':
212 try: 239 try:
213 sys.exit(main(sys.argv[1:])) 240 sys.exit(main(sys.argv[1:]))
214 except KeyboardInterrupt: 241 except KeyboardInterrupt:
215 sys.stderr.write('interrupted\n') 242 sys.stderr.write('interrupted\n')
216 sys.exit(1) 243 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | tests/git_footers_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698