blob: 1cb4d6ed0ddd45b9fd3bf0f72839945add4c7895 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass26132882012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass26132882012-01-14 15:12:45 +00004
Simon Glassc42b1d02020-10-29 21:46:17 -06005"""Handles parsing a stream of commits/emails from 'git log' or other source"""
6
Douglas Anderson52b5ee82019-09-27 09:23:56 -07007import datetime
Simon Glass06202d62020-10-29 21:46:27 -06008import io
Wu, Josh393aaa22015-04-03 10:51:17 +08009import math
Simon Glass26132882012-01-14 15:12:45 +000010import os
11import re
12import shutil
13import tempfile
14
Simon Glassa997ea52020-04-17 18:09:04 -060015from patman import command
16from patman import commit
17from patman import gitutil
18from patman.series import Series
Simon Glass26132882012-01-14 15:12:45 +000019
20# Tags that we detect and remove
Simon Glass06022262020-10-29 21:46:18 -060021RE_REMOVE = re.compile(r'^BUG=|^TEST=|^BRANCH=|^Review URL:'
Simon Glassc42b1d02020-10-29 21:46:17 -060022 r'|Reviewed-on:|Commit-\w*:')
Simon Glass26132882012-01-14 15:12:45 +000023
24# Lines which are allowed after a TEST= line
Simon Glass06022262020-10-29 21:46:18 -060025RE_ALLOWED_AFTER_TEST = re.compile('^Signed-off-by:')
Simon Glass26132882012-01-14 15:12:45 +000026
Ilya Yanok644c1842012-08-06 23:46:05 +000027# Signoffs
Simon Glass06022262020-10-29 21:46:18 -060028RE_SIGNOFF = re.compile('^Signed-off-by: *(.*)')
Ilya Yanok644c1842012-08-06 23:46:05 +000029
Sean Anderson48f46d62020-05-04 16:28:34 -040030# Cover letter tag
Simon Glass06022262020-10-29 21:46:18 -060031RE_COVER = re.compile('^Cover-([a-z-]*): *(.*)')
Simon Glassc72f3da2013-03-20 16:43:00 +000032
Simon Glass26132882012-01-14 15:12:45 +000033# Patch series tag
Simon Glass06022262020-10-29 21:46:18 -060034RE_SERIES_TAG = re.compile('^Series-([a-z-]*): *(.*)')
Albert ARIBAUDd880efd2013-11-12 11:14:41 +010035
Douglas Anderson52b5ee82019-09-27 09:23:56 -070036# Change-Id will be used to generate the Message-Id and then be stripped
Simon Glass06022262020-10-29 21:46:18 -060037RE_CHANGE_ID = re.compile('^Change-Id: *(.*)')
Douglas Anderson52b5ee82019-09-27 09:23:56 -070038
Albert ARIBAUDd880efd2013-11-12 11:14:41 +010039# Commit series tag
Simon Glass06022262020-10-29 21:46:18 -060040RE_COMMIT_TAG = re.compile('^Commit-([a-z-]*): *(.*)')
Simon Glass26132882012-01-14 15:12:45 +000041
42# Commit tags that we want to collect and keep
Simon Glass06022262020-10-29 21:46:18 -060043RE_TAG = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc|Fixes): (.*)')
Simon Glass26132882012-01-14 15:12:45 +000044
45# The start of a new commit in the git log
Simon Glass06022262020-10-29 21:46:18 -060046RE_COMMIT = re.compile('^commit ([0-9a-f]*)$')
Simon Glass26132882012-01-14 15:12:45 +000047
48# We detect these since checkpatch doesn't always do it
Simon Glass06022262020-10-29 21:46:18 -060049RE_SPACE_BEFORE_TAB = re.compile('^[+].* \t')
Simon Glass26132882012-01-14 15:12:45 +000050
Sean Anderson1a32f922020-05-04 16:28:35 -040051# Match indented lines for changes
Simon Glass06022262020-10-29 21:46:18 -060052RE_LEADING_WHITESPACE = re.compile(r'^\s')
Sean Anderson1a32f922020-05-04 16:28:35 -040053
Simon Glass26132882012-01-14 15:12:45 +000054# States we can be in - can we use range() and still have comments?
55STATE_MSG_HEADER = 0 # Still in the message header
56STATE_PATCH_SUBJECT = 1 # In patch subject (first line of log for a commit)
57STATE_PATCH_HEADER = 2 # In patch header (after the subject)
58STATE_DIFFS = 3 # In the diff part (past --- line)
59
60class PatchStream:
61 """Class for detecting/injecting tags in a patch or series of patches
62
63 We support processing the output of 'git log' to read out the tags we
64 are interested in. We can also process a patch file in order to remove
65 unwanted tags or inject additional ones. These correspond to the two
66 phases of processing.
67 """
Simon Glass5f6caa52020-10-29 21:46:21 -060068 def __init__(self, series, is_log=False):
Simon Glass26132882012-01-14 15:12:45 +000069 self.skip_blank = False # True to skip a single blank line
70 self.found_test = False # Found a TEST= line
Sean Anderson48f46d62020-05-04 16:28:34 -040071 self.lines_after_test = 0 # Number of lines found after TEST=
Simon Glass26132882012-01-14 15:12:45 +000072 self.linenum = 1 # Output line number we are up to
73 self.in_section = None # Name of start...END section we are in
74 self.notes = [] # Series notes
75 self.section = [] # The current section...END section
76 self.series = series # Info about the patch series
77 self.is_log = is_log # True if indent like git log
Sean Anderson48f46d62020-05-04 16:28:34 -040078 self.in_change = None # Name of the change list we are in
79 self.change_version = 0 # Non-zero if we are in a change list
Sean Anderson1a32f922020-05-04 16:28:35 -040080 self.change_lines = [] # Lines of the current change
Simon Glass26132882012-01-14 15:12:45 +000081 self.blank_count = 0 # Number of blank lines stored up
82 self.state = STATE_MSG_HEADER # What state are we in?
Simon Glass26132882012-01-14 15:12:45 +000083 self.commit = None # Current commit
84
Simon Glass06202d62020-10-29 21:46:27 -060085 @staticmethod
86 def process_text(text, is_comment=False):
87 """Process some text through this class using a default Commit/Series
88
89 Args:
90 text (str): Text to parse
91 is_comment (bool): True if this is a comment rather than a patch.
92 If True, PatchStream doesn't expect a patch subject at the
93 start, but jumps straight into the body
94
95 Returns:
96 PatchStream: object with results
97 """
98 pstrm = PatchStream(Series())
99 pstrm.commit = commit.Commit(None)
100 infd = io.StringIO(text)
101 outfd = io.StringIO()
102 if is_comment:
103 pstrm.state = STATE_PATCH_HEADER
104 pstrm.process_stream(infd, outfd)
105 return pstrm
106
Simon Glassb5800392020-10-29 21:46:23 -0600107 def _add_warn(self, warn):
Simon Glass6d00f6c2020-10-29 21:46:24 -0600108 """Add a new warning to report to the user about the current commit
109
110 The new warning is added to the current commit if not already present.
Simon Glassb5800392020-10-29 21:46:23 -0600111
112 Args:
113 warn (str): Warning to report
Simon Glass6d00f6c2020-10-29 21:46:24 -0600114
115 Raises:
116 ValueError: Warning is generated with no commit associated
Simon Glassb5800392020-10-29 21:46:23 -0600117 """
Simon Glass6d00f6c2020-10-29 21:46:24 -0600118 if not self.commit:
119 raise ValueError('Warning outside commit: %s' % warn)
120 if warn not in self.commit.warn:
121 self.commit.warn.append(warn)
Simon Glassb5800392020-10-29 21:46:23 -0600122
Simon Glass93f61c02020-10-29 21:46:19 -0600123 def _add_to_series(self, line, name, value):
Simon Glass26132882012-01-14 15:12:45 +0000124 """Add a new Series-xxx tag.
125
126 When a Series-xxx tag is detected, we come here to record it, if we
127 are scanning a 'git log'.
128
129 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600130 line (str): Source line containing tag (useful for debug/error
131 messages)
132 name (str): Tag name (part after 'Series-')
133 value (str): Tag value (part after 'Series-xxx: ')
Simon Glass26132882012-01-14 15:12:45 +0000134 """
135 if name == 'notes':
136 self.in_section = name
137 self.skip_blank = False
138 if self.is_log:
Simon Glass8093a8f2020-10-29 21:46:25 -0600139 warn = self.series.AddTag(self.commit, line, name, value)
140 if warn:
141 self.commit.warn.append(warn)
Simon Glass26132882012-01-14 15:12:45 +0000142
Simon Glass5f6caa52020-10-29 21:46:21 -0600143 def _add_to_commit(self, name):
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100144 """Add a new Commit-xxx tag.
145
146 When a Commit-xxx tag is detected, we come here to record it.
147
148 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600149 name (str): Tag name (part after 'Commit-')
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100150 """
151 if name == 'notes':
152 self.in_section = 'commit-' + name
153 self.skip_blank = False
154
Simon Glass93f61c02020-10-29 21:46:19 -0600155 def _add_commit_rtag(self, rtag_type, who):
Simon Glass08e91be2020-07-05 21:41:57 -0600156 """Add a response tag to the current commit
157
158 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600159 rtag_type (str): rtag type (e.g. 'Reviewed-by')
160 who (str): Person who gave that rtag, e.g.
161 'Fred Bloggs <fred@bloggs.org>'
Simon Glass08e91be2020-07-05 21:41:57 -0600162 """
163 self.commit.AddRtag(rtag_type, who)
164
Simon Glass93f61c02020-10-29 21:46:19 -0600165 def _close_commit(self):
Simon Glass26132882012-01-14 15:12:45 +0000166 """Save the current commit into our commit list, and reset our state"""
167 if self.commit and self.is_log:
168 self.series.AddCommit(self.commit)
169 self.commit = None
Bin Mengd23121c2016-06-26 23:24:30 -0700170 # If 'END' is missing in a 'Cover-letter' section, and that section
171 # happens to show up at the very end of the commit message, this is
172 # the chance for us to fix it up.
173 if self.in_section == 'cover' and self.is_log:
174 self.series.cover = self.section
175 self.in_section = None
176 self.skip_blank = True
177 self.section = []
Simon Glass26132882012-01-14 15:12:45 +0000178
Simon Glass93f61c02020-10-29 21:46:19 -0600179 def _parse_version(self, value, line):
Sean Anderson48f46d62020-05-04 16:28:34 -0400180 """Parse a version from a *-changes tag
181
182 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600183 value (str): Tag value (part after 'xxx-changes: '
184 line (str): Source line containing tag
Sean Anderson48f46d62020-05-04 16:28:34 -0400185
186 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600187 int: The version as an integer
188
189 Raises:
190 ValueError: the value cannot be converted
Sean Anderson48f46d62020-05-04 16:28:34 -0400191 """
192 try:
193 return int(value)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600194 except ValueError:
Sean Anderson48f46d62020-05-04 16:28:34 -0400195 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassc42b1d02020-10-29 21:46:17 -0600196 (self.commit.hash, line))
Sean Anderson48f46d62020-05-04 16:28:34 -0400197
Simon Glass93f61c02020-10-29 21:46:19 -0600198 def _finalise_change(self):
199 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson1a32f922020-05-04 16:28:35 -0400200 if not self.change_lines:
201 return
202 change = '\n'.join(self.change_lines)
203
204 if self.in_change == 'Series':
205 self.series.AddChange(self.change_version, self.commit, change)
206 elif self.in_change == 'Cover':
207 self.series.AddChange(self.change_version, None, change)
208 elif self.in_change == 'Commit':
209 self.commit.AddChange(self.change_version, change)
210 self.change_lines = []
211
Simon Glass93f61c02020-10-29 21:46:19 -0600212 def process_line(self, line):
Simon Glass26132882012-01-14 15:12:45 +0000213 """Process a single line of a patch file or commit log
214
215 This process a line and returns a list of lines to output. The list
216 may be empty or may contain multiple output lines.
217
218 This is where all the complicated logic is located. The class's
219 state is used to move between different states and detect things
220 properly.
221
222 We can be in one of two modes:
223 self.is_log == True: This is 'git log' mode, where most output is
224 indented by 4 characters and we are scanning for tags
225
226 self.is_log == False: This is 'patch' mode, where we already have
227 all the tags, and are processing patches to remove junk we
228 don't want, and add things we think are required.
229
230 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600231 line (str): text line to process
Simon Glass26132882012-01-14 15:12:45 +0000232
233 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600234 list: list of output lines, or [] if nothing should be output
235
236 Raises:
237 ValueError: a fatal error occurred while parsing, e.g. an END
238 without a starting tag, or two commits with two change IDs
Simon Glass26132882012-01-14 15:12:45 +0000239 """
240 # Initially we have no output. Prepare the input line string
241 out = []
242 line = line.rstrip('\n')
Scott Wood616a0ab2014-09-25 14:30:46 -0500243
Simon Glass06022262020-10-29 21:46:18 -0600244 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood616a0ab2014-09-25 14:30:46 -0500245
Simon Glass26132882012-01-14 15:12:45 +0000246 if self.is_log:
247 if line[:4] == ' ':
248 line = line[4:]
249
250 # Handle state transition and skipping blank lines
Simon Glass06022262020-10-29 21:46:18 -0600251 series_tag_match = RE_SERIES_TAG.match(line)
252 change_id_match = RE_CHANGE_ID.match(line)
253 commit_tag_match = RE_COMMIT_TAG.match(line)
254 cover_match = RE_COVER.match(line)
255 signoff_match = RE_SIGNOFF.match(line)
256 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glass26132882012-01-14 15:12:45 +0000257 tag_match = None
258 if self.state == STATE_PATCH_HEADER:
Simon Glass06022262020-10-29 21:46:18 -0600259 tag_match = RE_TAG.match(line)
Simon Glass26132882012-01-14 15:12:45 +0000260 is_blank = not line.strip()
261 if is_blank:
262 if (self.state == STATE_MSG_HEADER
263 or self.state == STATE_PATCH_SUBJECT):
264 self.state += 1
265
266 # We don't have a subject in the text stream of patch files
267 # It has its own line with a Subject: tag
268 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
269 self.state += 1
270 elif commit_match:
271 self.state = STATE_MSG_HEADER
272
Bin Meng9228cc22016-06-26 23:24:32 -0700273 # If a tag is detected, or a new commit starts
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700274 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson48f46d62020-05-04 16:28:34 -0400275 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng50ffc982016-06-26 23:24:31 -0700276 # but we are already in a section, this means 'END' is missing
277 # for that section, fix it up.
Bin Mengf83649f2016-06-26 23:24:29 -0700278 if self.in_section:
Simon Glassb5800392020-10-29 21:46:23 -0600279 self._add_warn("Missing 'END' in section '%s'" % self.in_section)
Bin Mengf83649f2016-06-26 23:24:29 -0700280 if self.in_section == 'cover':
281 self.series.cover = self.section
282 elif self.in_section == 'notes':
283 if self.is_log:
284 self.series.notes += self.section
285 elif self.in_section == 'commit-notes':
286 if self.is_log:
287 self.commit.notes += self.section
288 else:
Simon Glassb5800392020-10-29 21:46:23 -0600289 # This should not happen
290 raise ValueError("Unknown section '%s'" % self.in_section)
Bin Mengf83649f2016-06-26 23:24:29 -0700291 self.in_section = None
292 self.skip_blank = True
293 self.section = []
Bin Meng50ffc982016-06-26 23:24:31 -0700294 # but we are already in a change list, that means a blank line
295 # is missing, fix it up.
296 if self.in_change:
Simon Glassb5800392020-10-29 21:46:23 -0600297 self._add_warn("Missing 'blank line' in section '%s-changes'" %
298 self.in_change)
Simon Glass93f61c02020-10-29 21:46:19 -0600299 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400300 self.in_change = None
301 self.change_version = 0
Bin Mengf83649f2016-06-26 23:24:29 -0700302
Simon Glass26132882012-01-14 15:12:45 +0000303 # If we are in a section, keep collecting lines until we see END
304 if self.in_section:
305 if line == 'END':
306 if self.in_section == 'cover':
307 self.series.cover = self.section
308 elif self.in_section == 'notes':
309 if self.is_log:
310 self.series.notes += self.section
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100311 elif self.in_section == 'commit-notes':
312 if self.is_log:
313 self.commit.notes += self.section
Simon Glass26132882012-01-14 15:12:45 +0000314 else:
Simon Glassb5800392020-10-29 21:46:23 -0600315 # This should not happen
316 raise ValueError("Unknown section '%s'" % self.in_section)
Simon Glass26132882012-01-14 15:12:45 +0000317 self.in_section = None
318 self.skip_blank = True
319 self.section = []
320 else:
321 self.section.append(line)
322
Patrick Delaunay2870c1c2020-07-02 19:08:24 +0200323 # If we are not in a section, it is an unexpected END
324 elif line == 'END':
Simon Glassc42b1d02020-10-29 21:46:17 -0600325 raise ValueError("'END' wihout section")
Patrick Delaunay2870c1c2020-07-02 19:08:24 +0200326
Simon Glass26132882012-01-14 15:12:45 +0000327 # Detect the commit subject
328 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
329 self.commit.subject = line
330
331 # Detect the tags we want to remove, and skip blank lines
Simon Glass06022262020-10-29 21:46:18 -0600332 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass26132882012-01-14 15:12:45 +0000333 self.skip_blank = True
334
335 # TEST= should be the last thing in the commit, so remove
336 # everything after it
337 if line.startswith('TEST='):
338 self.found_test = True
339 elif self.skip_blank and is_blank:
340 self.skip_blank = False
341
Sean Anderson48f46d62020-05-04 16:28:34 -0400342 # Detect Cover-xxx tags
Bin Mengd0bc03c2016-06-26 23:24:28 -0700343 elif cover_match:
Sean Anderson48f46d62020-05-04 16:28:34 -0400344 name = cover_match.group(1)
345 value = cover_match.group(2)
346 if name == 'letter':
347 self.in_section = 'cover'
348 self.skip_blank = False
349 elif name == 'letter-cc':
Simon Glass93f61c02020-10-29 21:46:19 -0600350 self._add_to_series(line, 'cover-cc', value)
Sean Anderson48f46d62020-05-04 16:28:34 -0400351 elif name == 'changes':
352 self.in_change = 'Cover'
Simon Glass93f61c02020-10-29 21:46:19 -0600353 self.change_version = self._parse_version(value, line)
Simon Glassc72f3da2013-03-20 16:43:00 +0000354
Simon Glass26132882012-01-14 15:12:45 +0000355 # If we are in a change list, key collected lines until a blank one
356 elif self.in_change:
357 if is_blank:
358 # Blank line ends this change list
Simon Glass93f61c02020-10-29 21:46:19 -0600359 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400360 self.in_change = None
361 self.change_version = 0
Simon Glass46b34212014-04-20 10:50:14 -0600362 elif line == '---':
Simon Glass93f61c02020-10-29 21:46:19 -0600363 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400364 self.in_change = None
365 self.change_version = 0
Simon Glass93f61c02020-10-29 21:46:19 -0600366 out = self.process_line(line)
Sean Anderson1a32f922020-05-04 16:28:35 -0400367 elif self.is_log:
368 if not leading_whitespace_match:
Simon Glass93f61c02020-10-29 21:46:19 -0600369 self._finalise_change()
Sean Anderson1a32f922020-05-04 16:28:35 -0400370 self.change_lines.append(line)
Simon Glass26132882012-01-14 15:12:45 +0000371 self.skip_blank = False
372
373 # Detect Series-xxx tags
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100374 elif series_tag_match:
375 name = series_tag_match.group(1)
376 value = series_tag_match.group(2)
Simon Glass26132882012-01-14 15:12:45 +0000377 if name == 'changes':
378 # value is the version number: e.g. 1, or 2
Sean Anderson48f46d62020-05-04 16:28:34 -0400379 self.in_change = 'Series'
Simon Glass93f61c02020-10-29 21:46:19 -0600380 self.change_version = self._parse_version(value, line)
Simon Glass26132882012-01-14 15:12:45 +0000381 else:
Simon Glass93f61c02020-10-29 21:46:19 -0600382 self._add_to_series(line, name, value)
Simon Glass26132882012-01-14 15:12:45 +0000383 self.skip_blank = True
384
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700385 # Detect Change-Id tags
386 elif change_id_match:
387 value = change_id_match.group(1)
388 if self.is_log:
389 if self.commit.change_id:
Simon Glassc42b1d02020-10-29 21:46:17 -0600390 raise ValueError(
391 "%s: Two Change-Ids: '%s' vs. '%s'" % self.commit.hash,
392 self.commit.change_id, value)
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700393 self.commit.change_id = value
394 self.skip_blank = True
395
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100396 # Detect Commit-xxx tags
397 elif commit_tag_match:
398 name = commit_tag_match.group(1)
399 value = commit_tag_match.group(2)
400 if name == 'notes':
Simon Glass5f6caa52020-10-29 21:46:21 -0600401 self._add_to_commit(name)
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100402 self.skip_blank = True
Sean Anderson48f46d62020-05-04 16:28:34 -0400403 elif name == 'changes':
404 self.in_change = 'Commit'
Simon Glass93f61c02020-10-29 21:46:19 -0600405 self.change_version = self._parse_version(value, line)
Patrick Delaunayed73b312020-07-02 19:52:54 +0200406 else:
Simon Glassb5800392020-10-29 21:46:23 -0600407 self._add_warn('Line %d: Ignoring Commit-%s' %
408 (self.linenum, name))
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100409
Simon Glass26132882012-01-14 15:12:45 +0000410 # Detect the start of a new commit
411 elif commit_match:
Simon Glass93f61c02020-10-29 21:46:19 -0600412 self._close_commit()
Simon Glassbaec6842014-10-15 02:27:00 -0600413 self.commit = commit.Commit(commit_match.group(1))
Simon Glass26132882012-01-14 15:12:45 +0000414
415 # Detect tags in the commit message
416 elif tag_match:
Simon Glass08e91be2020-07-05 21:41:57 -0600417 rtag_type, who = tag_match.groups()
Simon Glass93f61c02020-10-29 21:46:19 -0600418 self._add_commit_rtag(rtag_type, who)
Simon Glass26132882012-01-14 15:12:45 +0000419 # Remove Tested-by self, since few will take much notice
Simon Glass08e91be2020-07-05 21:41:57 -0600420 if (rtag_type == 'Tested-by' and
421 who.find(os.getenv('USER') + '@') != -1):
Simon Glass3b762cc2020-10-29 21:46:28 -0600422 self._add_warn("Ignoring '%s'" % line)
Simon Glass08e91be2020-07-05 21:41:57 -0600423 elif rtag_type == 'Patch-cc':
424 self.commit.AddCc(who.split(','))
Simon Glass26132882012-01-14 15:12:45 +0000425 else:
Simon Glass27374d32014-08-28 09:43:38 -0600426 out = [line]
Simon Glass26132882012-01-14 15:12:45 +0000427
Simon Glass46b34212014-04-20 10:50:14 -0600428 # Suppress duplicate signoffs
429 elif signoff_match:
Simon Glassb0cd3412014-08-28 09:43:35 -0600430 if (self.is_log or not self.commit or
Simon Glassc42b1d02020-10-29 21:46:17 -0600431 self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
Simon Glass46b34212014-04-20 10:50:14 -0600432 out = [line]
433
Simon Glass26132882012-01-14 15:12:45 +0000434 # Well that means this is an ordinary line
435 else:
Simon Glass26132882012-01-14 15:12:45 +0000436 # Look for space before tab
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600437 mat = RE_SPACE_BEFORE_TAB.match(line)
438 if mat:
Simon Glassb5800392020-10-29 21:46:23 -0600439 self._add_warn('Line %d/%d has space before tab' %
440 (self.linenum, mat.start()))
Simon Glass26132882012-01-14 15:12:45 +0000441
442 # OK, we have a valid non-blank line
443 out = [line]
444 self.linenum += 1
445 self.skip_blank = False
446 if self.state == STATE_DIFFS:
447 pass
448
449 # If this is the start of the diffs section, emit our tags and
450 # change log
451 elif line == '---':
452 self.state = STATE_DIFFS
453
Sean Anderson48f46d62020-05-04 16:28:34 -0400454 # Output the tags (signoff first), then change list
Simon Glass26132882012-01-14 15:12:45 +0000455 out = []
Simon Glass26132882012-01-14 15:12:45 +0000456 log = self.series.MakeChangeLog(self.commit)
Simon Glassb0cd3412014-08-28 09:43:35 -0600457 out += [line]
458 if self.commit:
459 out += self.commit.notes
460 out += [''] + log
Simon Glass26132882012-01-14 15:12:45 +0000461 elif self.found_test:
Simon Glass06022262020-10-29 21:46:18 -0600462 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass26132882012-01-14 15:12:45 +0000463 self.lines_after_test += 1
464
465 return out
466
Simon Glass93f61c02020-10-29 21:46:19 -0600467 def finalise(self):
Simon Glass26132882012-01-14 15:12:45 +0000468 """Close out processing of this patch stream"""
Simon Glass93f61c02020-10-29 21:46:19 -0600469 self._finalise_change()
470 self._close_commit()
Simon Glass26132882012-01-14 15:12:45 +0000471 if self.lines_after_test:
Simon Glassb5800392020-10-29 21:46:23 -0600472 self._add_warn('Found %d lines after TEST=' % self.lines_after_test)
Simon Glass26132882012-01-14 15:12:45 +0000473
Simon Glass93f61c02020-10-29 21:46:19 -0600474 def _write_message_id(self, outfd):
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700475 """Write the Message-Id into the output.
476
477 This is based on the Change-Id in the original patch, the version,
478 and the prefix.
479
480 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600481 outfd (io.IOBase): Output stream file object
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700482 """
483 if not self.commit.change_id:
484 return
485
486 # If the count is -1 we're testing, so use a fixed time
487 if self.commit.count == -1:
488 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
489 else:
490 time_now = datetime.datetime.now()
491
492 # In theory there is email.utils.make_msgid() which would be nice
493 # to use, but it already produces something way too long and thus
494 # will produce ugly commit lines if someone throws this into
495 # a "Link:" tag in the final commit. So (sigh) roll our own.
496
497 # Start with the time; presumably we wouldn't send the same series
498 # with the same Change-Id at the exact same second.
499 parts = [time_now.strftime("%Y%m%d%H%M%S")]
500
501 # These seem like they would be nice to include.
502 if 'prefix' in self.series:
503 parts.append(self.series['prefix'])
504 if 'version' in self.series:
505 parts.append("v%s" % self.series['version'])
506
507 parts.append(str(self.commit.count + 1))
508
509 # The Change-Id must be last, right before the @
510 parts.append(self.commit.change_id)
511
512 # Join parts together with "." and write it out.
513 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
514
Simon Glass93f61c02020-10-29 21:46:19 -0600515 def process_stream(self, infd, outfd):
Simon Glass26132882012-01-14 15:12:45 +0000516 """Copy a stream from infd to outfd, filtering out unwanting things.
517
518 This is used to process patch files one at a time.
519
520 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600521 infd (io.IOBase): Input stream file object
522 outfd (io.IOBase): Output stream file object
Simon Glass26132882012-01-14 15:12:45 +0000523 """
524 # Extract the filename from each diff, for nice warnings
525 fname = None
526 last_fname = None
527 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700528
Simon Glass93f61c02020-10-29 21:46:19 -0600529 self._write_message_id(outfd)
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700530
Simon Glass26132882012-01-14 15:12:45 +0000531 while True:
532 line = infd.readline()
533 if not line:
534 break
Simon Glass93f61c02020-10-29 21:46:19 -0600535 out = self.process_line(line)
Simon Glass26132882012-01-14 15:12:45 +0000536
537 # Try to detect blank lines at EOF
538 for line in out:
539 match = re_fname.match(line)
540 if match:
541 last_fname = fname
542 fname = match.group(1)
543 if line == '+':
544 self.blank_count += 1
545 else:
546 if self.blank_count and (line == '-- ' or match):
Simon Glassb5800392020-10-29 21:46:23 -0600547 self._add_warn("Found possible blank line(s) at end of file '%s'" %
548 last_fname)
Simon Glass26132882012-01-14 15:12:45 +0000549 outfd.write('+\n' * self.blank_count)
550 outfd.write(line + '\n')
551 self.blank_count = 0
Simon Glass93f61c02020-10-29 21:46:19 -0600552 self.finalise()
Simon Glass26132882012-01-14 15:12:45 +0000553
554
Simon Glass93f61c02020-10-29 21:46:19 -0600555def get_metadata_for_list(commit_range, git_dir=None, count=None,
556 series=None, allow_overwrite=False):
Simon Glass26132882012-01-14 15:12:45 +0000557 """Reads out patch series metadata from the commits
558
559 This does a 'git log' on the relevant commits and pulls out the tags we
560 are interested in.
561
562 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600563 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
564 git_dir (str): Path to git repositiory (None to use default)
565 count (int): Number of commits to list, or None for no limit
566 series (Series): Object to add information into. By default a new series
Simon Glass680da3c2012-12-15 10:42:06 +0000567 is started.
Simon Glass06127f92020-10-29 21:46:22 -0600568 allow_overwrite (bool): Allow tags to overwrite an existing tag
569
Simon Glass680da3c2012-12-15 10:42:06 +0000570 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600571 Series: Object containing information about the commits.
Simon Glass26132882012-01-14 15:12:45 +0000572 """
Simon Glass4e5d6bc2014-09-05 19:00:19 -0600573 if not series:
574 series = Series()
Simon Glass359b55a62014-09-05 19:00:23 -0600575 series.allow_overwrite = allow_overwrite
Simon Glassfc372bc2016-03-06 19:45:33 -0700576 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
Simon Glassb9dbcb42014-08-09 15:33:10 -0600577 git_dir=git_dir)
578 stdout = command.RunPipe([params], capture=True).stdout
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600579 pst = PatchStream(series, is_log=True)
Simon Glass26132882012-01-14 15:12:45 +0000580 for line in stdout.splitlines():
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600581 pst.process_line(line)
582 pst.finalise()
Simon Glass26132882012-01-14 15:12:45 +0000583 return series
584
Simon Glass93f61c02020-10-29 21:46:19 -0600585def get_metadata(branch, start, count):
Simon Glass680da3c2012-12-15 10:42:06 +0000586 """Reads out patch series metadata from the commits
587
588 This does a 'git log' on the relevant commits and pulls out the tags we
589 are interested in.
590
591 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600592 branch (str): Branch to use (None for current branch)
593 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
594 count (int): Number of commits to list
595
596 Returns:
597 Series: Object containing information about the commits.
Simon Glass680da3c2012-12-15 10:42:06 +0000598 """
Simon Glass93f61c02020-10-29 21:46:19 -0600599 return get_metadata_for_list(
600 '%s~%d' % (branch if branch else 'HEAD', start), None, count)
Simon Glass680da3c2012-12-15 10:42:06 +0000601
Simon Glass93f61c02020-10-29 21:46:19 -0600602def get_metadata_for_test(text):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600603 """Process metadata from a file containing a git log. Used for tests
604
605 Args:
606 text:
Simon Glass06127f92020-10-29 21:46:22 -0600607
608 Returns:
609 Series: Object containing information about the commits.
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600610 """
611 series = Series()
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600612 pst = PatchStream(series, is_log=True)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600613 for line in text.splitlines():
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600614 pst.process_line(line)
615 pst.finalise()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600616 return series
617
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600618def fix_patch(backup_dir, fname, series, cmt):
Simon Glass26132882012-01-14 15:12:45 +0000619 """Fix up a patch file, by adding/removing as required.
620
621 We remove our tags from the patch file, insert changes lists, etc.
622 The patch file is processed in place, and overwritten.
623
624 A backup file is put into backup_dir (if not None).
625
626 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600627 backup_dir (str): Path to directory to use to backup the file
628 fname (str): Filename to patch file to process
629 series (Series): Series information about this patch set
630 cmt (Commit): Commit object for this patch file
631
Simon Glass26132882012-01-14 15:12:45 +0000632 Return:
Simon Glass06127f92020-10-29 21:46:22 -0600633 list: A list of errors, each str, or [] if all ok.
Simon Glass26132882012-01-14 15:12:45 +0000634 """
635 handle, tmpname = tempfile.mkstemp()
Simon Glassf544a2d2019-10-31 07:42:51 -0600636 outfd = os.fdopen(handle, 'w', encoding='utf-8')
637 infd = open(fname, 'r', encoding='utf-8')
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600638 pst = PatchStream(series)
639 pst.commit = cmt
640 pst.process_stream(infd, outfd)
Simon Glass26132882012-01-14 15:12:45 +0000641 infd.close()
642 outfd.close()
643
644 # Create a backup file if required
645 if backup_dir:
646 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
647 shutil.move(tmpname, fname)
Simon Glass6d00f6c2020-10-29 21:46:24 -0600648 return cmt.warn
Simon Glass26132882012-01-14 15:12:45 +0000649
Simon Glass93f61c02020-10-29 21:46:19 -0600650def fix_patches(series, fnames):
Simon Glass26132882012-01-14 15:12:45 +0000651 """Fix up a list of patches identified by filenames
652
653 The patch files are processed in place, and overwritten.
654
655 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600656 series (Series): The Series object
657 fnames (:type: list of str): List of patch files to process
Simon Glass26132882012-01-14 15:12:45 +0000658 """
659 # Current workflow creates patches, so we shouldn't need a backup
660 backup_dir = None #tempfile.mkdtemp('clean-patch')
661 count = 0
662 for fname in fnames:
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600663 cmt = series.commits[count]
664 cmt.patch = fname
665 cmt.count = count
666 result = fix_patch(backup_dir, fname, series, cmt)
Simon Glass26132882012-01-14 15:12:45 +0000667 if result:
Paul Burtonc3931342016-09-27 16:03:50 +0100668 print('%d warnings for %s:' % (len(result), fname))
Simon Glass26132882012-01-14 15:12:45 +0000669 for warn in result:
Paul Burtonc3931342016-09-27 16:03:50 +0100670 print('\t', warn)
Simon Glass26132882012-01-14 15:12:45 +0000671 print
672 count += 1
Paul Burtonc3931342016-09-27 16:03:50 +0100673 print('Cleaned %d patches' % count)
Simon Glass26132882012-01-14 15:12:45 +0000674
Simon Glass93f61c02020-10-29 21:46:19 -0600675def insert_cover_letter(fname, series, count):
Simon Glass26132882012-01-14 15:12:45 +0000676 """Inserts a cover letter with the required info into patch 0
677
678 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600679 fname (str): Input / output filename of the cover letter file
680 series (Series): Series object
681 count (int): Number of patches in the series
Simon Glass26132882012-01-14 15:12:45 +0000682 """
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600683 fil = open(fname, 'r')
684 lines = fil.readlines()
685 fil.close()
Simon Glass26132882012-01-14 15:12:45 +0000686
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600687 fil = open(fname, 'w')
Simon Glass26132882012-01-14 15:12:45 +0000688 text = series.cover
689 prefix = series.GetPatchPrefix()
690 for line in lines:
691 if line.startswith('Subject:'):
Wu, Josh393aaa22015-04-03 10:51:17 +0800692 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
693 zero_repeat = int(math.log10(count)) + 1
694 zero = '0' * zero_repeat
695 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass26132882012-01-14 15:12:45 +0000696
697 # Insert our cover letter
698 elif line.startswith('*** BLURB HERE ***'):
699 # First the blurb test
700 line = '\n'.join(text[1:]) + '\n'
701 if series.get('notes'):
702 line += '\n'.join(series.notes) + '\n'
703
704 # Now the change list
705 out = series.MakeChangeLog(None)
706 line += '\n' + '\n'.join(out)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600707 fil.write(line)
708 fil.close()