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

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: more refactor 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') | tests/git_footers_test.py » ('J')
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
(...skipping 14 matching lines...) Expand all
25 def parse_footer(line): 25 def parse_footer(line):
26 match = FOOTER_PATTERN.match(line) 26 match = FOOTER_PATTERN.match(line)
27 if match: 27 if match:
28 return (match.group(1), match.group(2)) 28 return (match.group(1), match.group(2))
29 else: 29 else:
30 return None 30 return None
31 31
32 32
33 def parse_footers(message): 33 def parse_footers(message):
34 """Parses a git commit message into a multimap of footers.""" 34 """Parses a git commit message into a multimap of footers."""
35 _, _, parsed_footers = split_footers(message)
36 footer_map = defaultdict(list)
37 if parsed_footers:
38 # Read footers from bottom to top.
Sergiy Byelozyorov 2016/06/02 23:16:26 Why so?
tandrii(chromium) 2016/06/03 09:16:17 that's how git_footers always worked. But added a
Sergiy Byelozyorov 2016/06/03 12:43:07 Thanks.
39 for (k, v) in reversed(parsed_footers):
40 footer_map[normalize_name(k)].append(v.strip())
41 return footer_map
42
43
44 def split_footers(message):
45 """Returns (non_footer_lines, footer_lines, parsed footers).
46
47 Guarantees that:
48 (non_footer_lines + footer_lines) == message.splitlines().
49 parsed_footers correspond each line in footer_lines.
Sergiy Byelozyorov 2016/06/02 23:16:26 Document the structure of each entry in parsed_foo
tandrii(chromium) 2016/06/03 09:16:17 Done.
50 """
51 message_lines = list(message.splitlines())
35 footer_lines = [] 52 footer_lines = []
36 for line in reversed(message.splitlines()): 53 for line in reversed(message_lines):
37 if line == '' or line.isspace(): 54 if line == '' or line.isspace():
Yoshisato Yanagisawa 2016/06/03 07:00:00 Is non footer and footer are always split with emp
tandrii(chromium) 2016/06/03 09:16:16 Yes
38 break 55 break
39 footer_lines.append(line) 56 footer_lines.append(line)
57 else:
58 # The whole description was consisting of footers,
59 # which means those aren't footers.
60 footer_lines = []
40 61
62 footer_lines.reverse()
41 footers = map(parse_footer, footer_lines) 63 footers = map(parse_footer, footer_lines)
42 if not all(footers): 64 if not footer_lines or not all(footers):
43 return defaultdict(list) 65 return message_lines, [], []
44 66 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 67
51 68
52 def get_footer_svn_id(branch=None): 69 def get_footer_svn_id(branch=None):
53 if not branch: 70 if not branch:
54 branch = git.root() 71 branch = git.root()
55 svn_id = None 72 svn_id = None
56 message = git.run('log', '-1', '--format=%B', branch) 73 message = git.run('log', '-1', '--format=%B', branch)
57 footers = parse_footers(message) 74 footers = parse_footers(message)
58 git_svn_id = get_unique(footers, 'git-svn-id') 75 git_svn_id = get_unique(footers, 'git-svn-id')
59 if git_svn_id: 76 if git_svn_id:
60 match = GIT_SVN_ID_PATTERN.match(git_svn_id) 77 match = GIT_SVN_ID_PATTERN.match(git_svn_id)
61 if match: 78 if match:
62 svn_id = match.group(1) 79 svn_id = match.group(1)
63 return svn_id 80 return svn_id
64 81
65 82
66 def get_footer_change_id(message): 83 def get_footer_change_id(message):
67 """Returns a list of Gerrit's ChangeId from given commit message.""" 84 """Returns a list of Gerrit's ChangeId from given commit message."""
68 return parse_footers(message).get(normalize_name('Change-Id'), []) 85 return parse_footers(message).get(normalize_name('Change-Id'), [])
69 86
70
Sergiy Byelozyorov 2016/06/02 23:16:27 nit: keep the second line, these are top-level def
tandrii(chromium) 2016/06/03 09:16:17 Done.
71 def add_footer_change_id(message, change_id): 87 def add_footer_change_id(message, change_id):
72 """Returns message with Change-ID footer in it. 88 """Returns message with Change-ID footer in it.
73 89
74 Assumes that Change-Id is not yet in footers, which is then 90 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. 91 earliest footer line which is after all of these footers:
92 Bug|Issue|Test|Feature.
Sergiy Byelozyorov 2016/06/02 23:16:27 (optional) nit: Bug, Issue, Test, Feature
76 """ 93 """
77 assert 0 == len(get_footer_change_id(message)) 94 assert 'Change-Id' not in parse_footers(message)
Sergiy Byelozyorov 2016/06/02 23:16:26 (optional) Should we really use assert here? This
tandrii(chromium) 2016/06/03 09:16:17 True, but
Sergiy Byelozyorov 2016/06/03 12:43:07 Acknowledged.
78 change_id_line = 'Change-Id: %s' % change_id 95 return add_footer(message, 'Change-Id', change_id,
79 # This code does the same as parse_footers, but keeps track of line 96 after_keys=['Bug', 'Issue', 'Test', 'Feature'])
80 # numbers so that ChangeId is inserted in the right place. 97
81 lines = message.splitlines() 98 def add_footer(message, key, value, after_keys=None):
82 footer_lines = [] 99 """Returns a message with given footer appended.
83 for line in reversed(lines): 100
84 if line == '' or line.isspace(): 101 If after_keys is None (default), appends footer last.
85 break 102 Otherwise, after_keys must be iterable of footer keys, then the new footer
86 footer_lines.append(line) 103 would be inserted at the topmost position such there would be no footer lines
104 after it with key matching one of after_keys.
105 For example, given
106 message='Header.\n\nAdded: 2016\nBug: 123\nVerified-By: CQ'
107 after_keys=['Bug', 'Issue']
108 the new footer will be inserted between Bug and Verified-By existing footers.
109 """
110 assert key == normalize_name(key), 'Use normalized key'
Sergiy Byelozyorov 2016/06/02 23:16:26 ditto (also optional)
111 new_footer = '%s: %s' % (key, value)
112
113 top_lines, footer_lines, parsed_footers = split_footers(message)
114 if not footer_lines:
115 if not top_lines:
116 top_lines = ['', '']
Sergiy Byelozyorov 2016/06/02 23:16:27 Why two empty lines here?
tandrii(chromium) 2016/06/03 09:16:17 At the time of writing this, I was thinking of min
Sergiy Byelozyorov 2016/06/03 12:43:07 Acknowledged.
117 else:
118 top_lines.append('')
Sergiy Byelozyorov 2016/06/02 23:16:26 What if there was already an empty line in the end
tandrii(chromium) 2016/06/03 09:16:16 Good point! added test for that.
Sergiy Byelozyorov 2016/06/03 12:43:07 Acknowledged.
119 footer_lines = [new_footer]
120 elif not after_keys:
121 footer_lines.append(new_footer)
87 else: 122 else:
88 # The whole description was consisting of footers, 123 after_keys = set(map(normalize_name, after_keys))
89 # which means those aren't footers. 124 # Iterate from last to first footer till we find the footer keys above.
90 footer_lines = [] 125 for i, (key, _) in reversed(list(enumerate(parsed_footers))):
Sergiy Byelozyorov 2016/06/02 23:16:27 How about this? for i, key in enumerate(reverse
tandrii(chromium) 2016/06/03 09:16:16 meaning of `i` would be different.
Sergiy Byelozyorov 2016/06/03 12:43:07 Actually yes, I forgot to delete my comment after
91 # footers order is from end to start of the message. 126 if normalize_name(key) in after_keys:
92 footers = map(parse_footer, footer_lines) 127 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 128 break
102 else: 129 else:
103 insert_at = len(lines) - len(footers) 130 footer_lines.insert(0, new_footer)
104 lines.insert(insert_at, change_id_line) 131 return '\n'.join(top_lines + footer_lines)
105 return '\n'.join(lines)
106 132
107 133
108 def get_unique(footers, key): 134 def get_unique(footers, key):
109 key = normalize_name(key) 135 key = normalize_name(key)
110 values = footers[key] 136 values = footers[key]
111 assert len(values) <= 1, 'Multiple %s footers' % key 137 assert len(values) <= 1, 'Multiple %s footers' % key
112 if values: 138 if values:
113 return values[0] 139 return values[0]
114 else: 140 else:
115 return None 141 return None
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
207 print '%s: %s' % (k, v) 233 print '%s: %s' % (k, v)
208 return 0 234 return 0
209 235
210 236
211 if __name__ == '__main__': 237 if __name__ == '__main__':
212 try: 238 try:
213 sys.exit(main(sys.argv[1:])) 239 sys.exit(main(sys.argv[1:]))
214 except KeyboardInterrupt: 240 except KeyboardInterrupt:
215 sys.stderr.write('interrupted\n') 241 sys.stderr.write('interrupted\n')
216 sys.exit(1) 242 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | tests/git_footers_test.py » ('j') | tests/git_footers_test.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698