blob: 24040d43d625d67ea1b3fc02e7826f7e01718298 [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
Wu, Josh393aaa22015-04-03 10:51:17 +08008import math
Simon Glass26132882012-01-14 15:12:45 +00009import os
10import re
11import shutil
12import tempfile
13
Simon Glassa997ea52020-04-17 18:09:04 -060014from patman import command
15from patman import commit
16from patman import gitutil
17from patman.series import Series
Simon Glass26132882012-01-14 15:12:45 +000018
19# Tags that we detect and remove
Simon Glass06022262020-10-29 21:46:18 -060020RE_REMOVE = re.compile(r'^BUG=|^TEST=|^BRANCH=|^Review URL:'
Simon Glassc42b1d02020-10-29 21:46:17 -060021 r'|Reviewed-on:|Commit-\w*:')
Simon Glass26132882012-01-14 15:12:45 +000022
23# Lines which are allowed after a TEST= line
Simon Glass06022262020-10-29 21:46:18 -060024RE_ALLOWED_AFTER_TEST = re.compile('^Signed-off-by:')
Simon Glass26132882012-01-14 15:12:45 +000025
Ilya Yanok644c1842012-08-06 23:46:05 +000026# Signoffs
Simon Glass06022262020-10-29 21:46:18 -060027RE_SIGNOFF = re.compile('^Signed-off-by: *(.*)')
Ilya Yanok644c1842012-08-06 23:46:05 +000028
Sean Anderson48f46d62020-05-04 16:28:34 -040029# Cover letter tag
Simon Glass06022262020-10-29 21:46:18 -060030RE_COVER = re.compile('^Cover-([a-z-]*): *(.*)')
Simon Glassc72f3da2013-03-20 16:43:00 +000031
Simon Glass26132882012-01-14 15:12:45 +000032# Patch series tag
Simon Glass06022262020-10-29 21:46:18 -060033RE_SERIES_TAG = re.compile('^Series-([a-z-]*): *(.*)')
Albert ARIBAUDd880efd2013-11-12 11:14:41 +010034
Douglas Anderson52b5ee82019-09-27 09:23:56 -070035# Change-Id will be used to generate the Message-Id and then be stripped
Simon Glass06022262020-10-29 21:46:18 -060036RE_CHANGE_ID = re.compile('^Change-Id: *(.*)')
Douglas Anderson52b5ee82019-09-27 09:23:56 -070037
Albert ARIBAUDd880efd2013-11-12 11:14:41 +010038# Commit series tag
Simon Glass06022262020-10-29 21:46:18 -060039RE_COMMIT_TAG = re.compile('^Commit-([a-z-]*): *(.*)')
Simon Glass26132882012-01-14 15:12:45 +000040
41# Commit tags that we want to collect and keep
Simon Glass06022262020-10-29 21:46:18 -060042RE_TAG = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc|Fixes): (.*)')
Simon Glass26132882012-01-14 15:12:45 +000043
44# The start of a new commit in the git log
Simon Glass06022262020-10-29 21:46:18 -060045RE_COMMIT = re.compile('^commit ([0-9a-f]*)$')
Simon Glass26132882012-01-14 15:12:45 +000046
47# We detect these since checkpatch doesn't always do it
Simon Glass06022262020-10-29 21:46:18 -060048RE_SPACE_BEFORE_TAB = re.compile('^[+].* \t')
Simon Glass26132882012-01-14 15:12:45 +000049
Sean Anderson1a32f922020-05-04 16:28:35 -040050# Match indented lines for changes
Simon Glass06022262020-10-29 21:46:18 -060051RE_LEADING_WHITESPACE = re.compile(r'^\s')
Sean Anderson1a32f922020-05-04 16:28:35 -040052
Simon Glass26132882012-01-14 15:12:45 +000053# States we can be in - can we use range() and still have comments?
54STATE_MSG_HEADER = 0 # Still in the message header
55STATE_PATCH_SUBJECT = 1 # In patch subject (first line of log for a commit)
56STATE_PATCH_HEADER = 2 # In patch header (after the subject)
57STATE_DIFFS = 3 # In the diff part (past --- line)
58
59class PatchStream:
60 """Class for detecting/injecting tags in a patch or series of patches
61
62 We support processing the output of 'git log' to read out the tags we
63 are interested in. We can also process a patch file in order to remove
64 unwanted tags or inject additional ones. These correspond to the two
65 phases of processing.
66 """
Simon Glass5f6caa52020-10-29 21:46:21 -060067 def __init__(self, series, is_log=False):
Simon Glass26132882012-01-14 15:12:45 +000068 self.skip_blank = False # True to skip a single blank line
69 self.found_test = False # Found a TEST= line
Sean Anderson48f46d62020-05-04 16:28:34 -040070 self.lines_after_test = 0 # Number of lines found after TEST=
Simon Glass26132882012-01-14 15:12:45 +000071 self.linenum = 1 # Output line number we are up to
72 self.in_section = None # Name of start...END section we are in
73 self.notes = [] # Series notes
74 self.section = [] # The current section...END section
75 self.series = series # Info about the patch series
76 self.is_log = is_log # True if indent like git log
Sean Anderson48f46d62020-05-04 16:28:34 -040077 self.in_change = None # Name of the change list we are in
78 self.change_version = 0 # Non-zero if we are in a change list
Sean Anderson1a32f922020-05-04 16:28:35 -040079 self.change_lines = [] # Lines of the current change
Simon Glass26132882012-01-14 15:12:45 +000080 self.blank_count = 0 # Number of blank lines stored up
81 self.state = STATE_MSG_HEADER # What state are we in?
Simon Glass26132882012-01-14 15:12:45 +000082 self.signoff = [] # Contents of signoff line
83 self.commit = None # Current commit
84
Simon Glassb5800392020-10-29 21:46:23 -060085 def _add_warn(self, warn):
Simon Glass6d00f6c2020-10-29 21:46:24 -060086 """Add a new warning to report to the user about the current commit
87
88 The new warning is added to the current commit if not already present.
Simon Glassb5800392020-10-29 21:46:23 -060089
90 Args:
91 warn (str): Warning to report
Simon Glass6d00f6c2020-10-29 21:46:24 -060092
93 Raises:
94 ValueError: Warning is generated with no commit associated
Simon Glassb5800392020-10-29 21:46:23 -060095 """
Simon Glass6d00f6c2020-10-29 21:46:24 -060096 if not self.commit:
97 raise ValueError('Warning outside commit: %s' % warn)
98 if warn not in self.commit.warn:
99 self.commit.warn.append(warn)
Simon Glassb5800392020-10-29 21:46:23 -0600100
Simon Glass93f61c02020-10-29 21:46:19 -0600101 def _add_to_series(self, line, name, value):
Simon Glass26132882012-01-14 15:12:45 +0000102 """Add a new Series-xxx tag.
103
104 When a Series-xxx tag is detected, we come here to record it, if we
105 are scanning a 'git log'.
106
107 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600108 line (str): Source line containing tag (useful for debug/error
109 messages)
110 name (str): Tag name (part after 'Series-')
111 value (str): Tag value (part after 'Series-xxx: ')
Simon Glass26132882012-01-14 15:12:45 +0000112 """
113 if name == 'notes':
114 self.in_section = name
115 self.skip_blank = False
116 if self.is_log:
Simon Glass8093a8f2020-10-29 21:46:25 -0600117 warn = self.series.AddTag(self.commit, line, name, value)
118 if warn:
119 self.commit.warn.append(warn)
Simon Glass26132882012-01-14 15:12:45 +0000120
Simon Glass5f6caa52020-10-29 21:46:21 -0600121 def _add_to_commit(self, name):
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100122 """Add a new Commit-xxx tag.
123
124 When a Commit-xxx tag is detected, we come here to record it.
125
126 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600127 name (str): Tag name (part after 'Commit-')
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100128 """
129 if name == 'notes':
130 self.in_section = 'commit-' + name
131 self.skip_blank = False
132
Simon Glass93f61c02020-10-29 21:46:19 -0600133 def _add_commit_rtag(self, rtag_type, who):
Simon Glass08e91be2020-07-05 21:41:57 -0600134 """Add a response tag to the current commit
135
136 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600137 rtag_type (str): rtag type (e.g. 'Reviewed-by')
138 who (str): Person who gave that rtag, e.g.
139 'Fred Bloggs <fred@bloggs.org>'
Simon Glass08e91be2020-07-05 21:41:57 -0600140 """
141 self.commit.AddRtag(rtag_type, who)
142
Simon Glass93f61c02020-10-29 21:46:19 -0600143 def _close_commit(self):
Simon Glass26132882012-01-14 15:12:45 +0000144 """Save the current commit into our commit list, and reset our state"""
145 if self.commit and self.is_log:
146 self.series.AddCommit(self.commit)
147 self.commit = None
Bin Mengd23121c2016-06-26 23:24:30 -0700148 # If 'END' is missing in a 'Cover-letter' section, and that section
149 # happens to show up at the very end of the commit message, this is
150 # the chance for us to fix it up.
151 if self.in_section == 'cover' and self.is_log:
152 self.series.cover = self.section
153 self.in_section = None
154 self.skip_blank = True
155 self.section = []
Simon Glass26132882012-01-14 15:12:45 +0000156
Simon Glass93f61c02020-10-29 21:46:19 -0600157 def _parse_version(self, value, line):
Sean Anderson48f46d62020-05-04 16:28:34 -0400158 """Parse a version from a *-changes tag
159
160 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600161 value (str): Tag value (part after 'xxx-changes: '
162 line (str): Source line containing tag
Sean Anderson48f46d62020-05-04 16:28:34 -0400163
164 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600165 int: The version as an integer
166
167 Raises:
168 ValueError: the value cannot be converted
Sean Anderson48f46d62020-05-04 16:28:34 -0400169 """
170 try:
171 return int(value)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600172 except ValueError:
Sean Anderson48f46d62020-05-04 16:28:34 -0400173 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassc42b1d02020-10-29 21:46:17 -0600174 (self.commit.hash, line))
Sean Anderson48f46d62020-05-04 16:28:34 -0400175
Simon Glass93f61c02020-10-29 21:46:19 -0600176 def _finalise_change(self):
177 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson1a32f922020-05-04 16:28:35 -0400178 if not self.change_lines:
179 return
180 change = '\n'.join(self.change_lines)
181
182 if self.in_change == 'Series':
183 self.series.AddChange(self.change_version, self.commit, change)
184 elif self.in_change == 'Cover':
185 self.series.AddChange(self.change_version, None, change)
186 elif self.in_change == 'Commit':
187 self.commit.AddChange(self.change_version, change)
188 self.change_lines = []
189
Simon Glass93f61c02020-10-29 21:46:19 -0600190 def process_line(self, line):
Simon Glass26132882012-01-14 15:12:45 +0000191 """Process a single line of a patch file or commit log
192
193 This process a line and returns a list of lines to output. The list
194 may be empty or may contain multiple output lines.
195
196 This is where all the complicated logic is located. The class's
197 state is used to move between different states and detect things
198 properly.
199
200 We can be in one of two modes:
201 self.is_log == True: This is 'git log' mode, where most output is
202 indented by 4 characters and we are scanning for tags
203
204 self.is_log == False: This is 'patch' mode, where we already have
205 all the tags, and are processing patches to remove junk we
206 don't want, and add things we think are required.
207
208 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600209 line (str): text line to process
Simon Glass26132882012-01-14 15:12:45 +0000210
211 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600212 list: list of output lines, or [] if nothing should be output
213
214 Raises:
215 ValueError: a fatal error occurred while parsing, e.g. an END
216 without a starting tag, or two commits with two change IDs
Simon Glass26132882012-01-14 15:12:45 +0000217 """
218 # Initially we have no output. Prepare the input line string
219 out = []
220 line = line.rstrip('\n')
Scott Wood616a0ab2014-09-25 14:30:46 -0500221
Simon Glass06022262020-10-29 21:46:18 -0600222 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood616a0ab2014-09-25 14:30:46 -0500223
Simon Glass26132882012-01-14 15:12:45 +0000224 if self.is_log:
225 if line[:4] == ' ':
226 line = line[4:]
227
228 # Handle state transition and skipping blank lines
Simon Glass06022262020-10-29 21:46:18 -0600229 series_tag_match = RE_SERIES_TAG.match(line)
230 change_id_match = RE_CHANGE_ID.match(line)
231 commit_tag_match = RE_COMMIT_TAG.match(line)
232 cover_match = RE_COVER.match(line)
233 signoff_match = RE_SIGNOFF.match(line)
234 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glass26132882012-01-14 15:12:45 +0000235 tag_match = None
236 if self.state == STATE_PATCH_HEADER:
Simon Glass06022262020-10-29 21:46:18 -0600237 tag_match = RE_TAG.match(line)
Simon Glass26132882012-01-14 15:12:45 +0000238 is_blank = not line.strip()
239 if is_blank:
240 if (self.state == STATE_MSG_HEADER
241 or self.state == STATE_PATCH_SUBJECT):
242 self.state += 1
243
244 # We don't have a subject in the text stream of patch files
245 # It has its own line with a Subject: tag
246 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
247 self.state += 1
248 elif commit_match:
249 self.state = STATE_MSG_HEADER
250
Bin Meng9228cc22016-06-26 23:24:32 -0700251 # If a tag is detected, or a new commit starts
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700252 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson48f46d62020-05-04 16:28:34 -0400253 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng50ffc982016-06-26 23:24:31 -0700254 # but we are already in a section, this means 'END' is missing
255 # for that section, fix it up.
Bin Mengf83649f2016-06-26 23:24:29 -0700256 if self.in_section:
Simon Glassb5800392020-10-29 21:46:23 -0600257 self._add_warn("Missing 'END' in section '%s'" % self.in_section)
Bin Mengf83649f2016-06-26 23:24:29 -0700258 if self.in_section == 'cover':
259 self.series.cover = self.section
260 elif self.in_section == 'notes':
261 if self.is_log:
262 self.series.notes += self.section
263 elif self.in_section == 'commit-notes':
264 if self.is_log:
265 self.commit.notes += self.section
266 else:
Simon Glassb5800392020-10-29 21:46:23 -0600267 # This should not happen
268 raise ValueError("Unknown section '%s'" % self.in_section)
Bin Mengf83649f2016-06-26 23:24:29 -0700269 self.in_section = None
270 self.skip_blank = True
271 self.section = []
Bin Meng50ffc982016-06-26 23:24:31 -0700272 # but we are already in a change list, that means a blank line
273 # is missing, fix it up.
274 if self.in_change:
Simon Glassb5800392020-10-29 21:46:23 -0600275 self._add_warn("Missing 'blank line' in section '%s-changes'" %
276 self.in_change)
Simon Glass93f61c02020-10-29 21:46:19 -0600277 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400278 self.in_change = None
279 self.change_version = 0
Bin Mengf83649f2016-06-26 23:24:29 -0700280
Simon Glass26132882012-01-14 15:12:45 +0000281 # If we are in a section, keep collecting lines until we see END
282 if self.in_section:
283 if line == 'END':
284 if self.in_section == 'cover':
285 self.series.cover = self.section
286 elif self.in_section == 'notes':
287 if self.is_log:
288 self.series.notes += self.section
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100289 elif self.in_section == 'commit-notes':
290 if self.is_log:
291 self.commit.notes += self.section
Simon Glass26132882012-01-14 15:12:45 +0000292 else:
Simon Glassb5800392020-10-29 21:46:23 -0600293 # This should not happen
294 raise ValueError("Unknown section '%s'" % self.in_section)
Simon Glass26132882012-01-14 15:12:45 +0000295 self.in_section = None
296 self.skip_blank = True
297 self.section = []
298 else:
299 self.section.append(line)
300
Patrick Delaunay2870c1c2020-07-02 19:08:24 +0200301 # If we are not in a section, it is an unexpected END
302 elif line == 'END':
Simon Glassc42b1d02020-10-29 21:46:17 -0600303 raise ValueError("'END' wihout section")
Patrick Delaunay2870c1c2020-07-02 19:08:24 +0200304
Simon Glass26132882012-01-14 15:12:45 +0000305 # Detect the commit subject
306 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
307 self.commit.subject = line
308
309 # Detect the tags we want to remove, and skip blank lines
Simon Glass06022262020-10-29 21:46:18 -0600310 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass26132882012-01-14 15:12:45 +0000311 self.skip_blank = True
312
313 # TEST= should be the last thing in the commit, so remove
314 # everything after it
315 if line.startswith('TEST='):
316 self.found_test = True
317 elif self.skip_blank and is_blank:
318 self.skip_blank = False
319
Sean Anderson48f46d62020-05-04 16:28:34 -0400320 # Detect Cover-xxx tags
Bin Mengd0bc03c2016-06-26 23:24:28 -0700321 elif cover_match:
Sean Anderson48f46d62020-05-04 16:28:34 -0400322 name = cover_match.group(1)
323 value = cover_match.group(2)
324 if name == 'letter':
325 self.in_section = 'cover'
326 self.skip_blank = False
327 elif name == 'letter-cc':
Simon Glass93f61c02020-10-29 21:46:19 -0600328 self._add_to_series(line, 'cover-cc', value)
Sean Anderson48f46d62020-05-04 16:28:34 -0400329 elif name == 'changes':
330 self.in_change = 'Cover'
Simon Glass93f61c02020-10-29 21:46:19 -0600331 self.change_version = self._parse_version(value, line)
Simon Glassc72f3da2013-03-20 16:43:00 +0000332
Simon Glass26132882012-01-14 15:12:45 +0000333 # If we are in a change list, key collected lines until a blank one
334 elif self.in_change:
335 if is_blank:
336 # Blank line ends this change list
Simon Glass93f61c02020-10-29 21:46:19 -0600337 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400338 self.in_change = None
339 self.change_version = 0
Simon Glass46b34212014-04-20 10:50:14 -0600340 elif line == '---':
Simon Glass93f61c02020-10-29 21:46:19 -0600341 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400342 self.in_change = None
343 self.change_version = 0
Simon Glass93f61c02020-10-29 21:46:19 -0600344 out = self.process_line(line)
Sean Anderson1a32f922020-05-04 16:28:35 -0400345 elif self.is_log:
346 if not leading_whitespace_match:
Simon Glass93f61c02020-10-29 21:46:19 -0600347 self._finalise_change()
Sean Anderson1a32f922020-05-04 16:28:35 -0400348 self.change_lines.append(line)
Simon Glass26132882012-01-14 15:12:45 +0000349 self.skip_blank = False
350
351 # Detect Series-xxx tags
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100352 elif series_tag_match:
353 name = series_tag_match.group(1)
354 value = series_tag_match.group(2)
Simon Glass26132882012-01-14 15:12:45 +0000355 if name == 'changes':
356 # value is the version number: e.g. 1, or 2
Sean Anderson48f46d62020-05-04 16:28:34 -0400357 self.in_change = 'Series'
Simon Glass93f61c02020-10-29 21:46:19 -0600358 self.change_version = self._parse_version(value, line)
Simon Glass26132882012-01-14 15:12:45 +0000359 else:
Simon Glass93f61c02020-10-29 21:46:19 -0600360 self._add_to_series(line, name, value)
Simon Glass26132882012-01-14 15:12:45 +0000361 self.skip_blank = True
362
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700363 # Detect Change-Id tags
364 elif change_id_match:
365 value = change_id_match.group(1)
366 if self.is_log:
367 if self.commit.change_id:
Simon Glassc42b1d02020-10-29 21:46:17 -0600368 raise ValueError(
369 "%s: Two Change-Ids: '%s' vs. '%s'" % self.commit.hash,
370 self.commit.change_id, value)
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700371 self.commit.change_id = value
372 self.skip_blank = True
373
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100374 # Detect Commit-xxx tags
375 elif commit_tag_match:
376 name = commit_tag_match.group(1)
377 value = commit_tag_match.group(2)
378 if name == 'notes':
Simon Glass5f6caa52020-10-29 21:46:21 -0600379 self._add_to_commit(name)
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100380 self.skip_blank = True
Sean Anderson48f46d62020-05-04 16:28:34 -0400381 elif name == 'changes':
382 self.in_change = 'Commit'
Simon Glass93f61c02020-10-29 21:46:19 -0600383 self.change_version = self._parse_version(value, line)
Patrick Delaunayed73b312020-07-02 19:52:54 +0200384 else:
Simon Glassb5800392020-10-29 21:46:23 -0600385 self._add_warn('Line %d: Ignoring Commit-%s' %
386 (self.linenum, name))
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100387
Simon Glass26132882012-01-14 15:12:45 +0000388 # Detect the start of a new commit
389 elif commit_match:
Simon Glass93f61c02020-10-29 21:46:19 -0600390 self._close_commit()
Simon Glassbaec6842014-10-15 02:27:00 -0600391 self.commit = commit.Commit(commit_match.group(1))
Simon Glass26132882012-01-14 15:12:45 +0000392
393 # Detect tags in the commit message
394 elif tag_match:
Simon Glass08e91be2020-07-05 21:41:57 -0600395 rtag_type, who = tag_match.groups()
Simon Glass93f61c02020-10-29 21:46:19 -0600396 self._add_commit_rtag(rtag_type, who)
Simon Glass26132882012-01-14 15:12:45 +0000397 # Remove Tested-by self, since few will take much notice
Simon Glass08e91be2020-07-05 21:41:57 -0600398 if (rtag_type == 'Tested-by' and
399 who.find(os.getenv('USER') + '@') != -1):
Simon Glassb5800392020-10-29 21:46:23 -0600400 self._add_warn("Ignoring %s" % line)
Simon Glass08e91be2020-07-05 21:41:57 -0600401 elif rtag_type == 'Patch-cc':
402 self.commit.AddCc(who.split(','))
Simon Glass26132882012-01-14 15:12:45 +0000403 else:
Simon Glass27374d32014-08-28 09:43:38 -0600404 out = [line]
Simon Glass26132882012-01-14 15:12:45 +0000405
Simon Glass46b34212014-04-20 10:50:14 -0600406 # Suppress duplicate signoffs
407 elif signoff_match:
Simon Glassb0cd3412014-08-28 09:43:35 -0600408 if (self.is_log or not self.commit or
Simon Glassc42b1d02020-10-29 21:46:17 -0600409 self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
Simon Glass46b34212014-04-20 10:50:14 -0600410 out = [line]
411
Simon Glass26132882012-01-14 15:12:45 +0000412 # Well that means this is an ordinary line
413 else:
Simon Glass26132882012-01-14 15:12:45 +0000414 # Look for space before tab
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600415 mat = RE_SPACE_BEFORE_TAB.match(line)
416 if mat:
Simon Glassb5800392020-10-29 21:46:23 -0600417 self._add_warn('Line %d/%d has space before tab' %
418 (self.linenum, mat.start()))
Simon Glass26132882012-01-14 15:12:45 +0000419
420 # OK, we have a valid non-blank line
421 out = [line]
422 self.linenum += 1
423 self.skip_blank = False
424 if self.state == STATE_DIFFS:
425 pass
426
427 # If this is the start of the diffs section, emit our tags and
428 # change log
429 elif line == '---':
430 self.state = STATE_DIFFS
431
Sean Anderson48f46d62020-05-04 16:28:34 -0400432 # Output the tags (signoff first), then change list
Simon Glass26132882012-01-14 15:12:45 +0000433 out = []
Simon Glass26132882012-01-14 15:12:45 +0000434 log = self.series.MakeChangeLog(self.commit)
Simon Glassb0cd3412014-08-28 09:43:35 -0600435 out += [line]
436 if self.commit:
437 out += self.commit.notes
438 out += [''] + log
Simon Glass26132882012-01-14 15:12:45 +0000439 elif self.found_test:
Simon Glass06022262020-10-29 21:46:18 -0600440 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass26132882012-01-14 15:12:45 +0000441 self.lines_after_test += 1
442
443 return out
444
Simon Glass93f61c02020-10-29 21:46:19 -0600445 def finalise(self):
Simon Glass26132882012-01-14 15:12:45 +0000446 """Close out processing of this patch stream"""
Simon Glass93f61c02020-10-29 21:46:19 -0600447 self._finalise_change()
448 self._close_commit()
Simon Glass26132882012-01-14 15:12:45 +0000449 if self.lines_after_test:
Simon Glassb5800392020-10-29 21:46:23 -0600450 self._add_warn('Found %d lines after TEST=' % self.lines_after_test)
Simon Glass26132882012-01-14 15:12:45 +0000451
Simon Glass93f61c02020-10-29 21:46:19 -0600452 def _write_message_id(self, outfd):
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700453 """Write the Message-Id into the output.
454
455 This is based on the Change-Id in the original patch, the version,
456 and the prefix.
457
458 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600459 outfd (io.IOBase): Output stream file object
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700460 """
461 if not self.commit.change_id:
462 return
463
464 # If the count is -1 we're testing, so use a fixed time
465 if self.commit.count == -1:
466 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
467 else:
468 time_now = datetime.datetime.now()
469
470 # In theory there is email.utils.make_msgid() which would be nice
471 # to use, but it already produces something way too long and thus
472 # will produce ugly commit lines if someone throws this into
473 # a "Link:" tag in the final commit. So (sigh) roll our own.
474
475 # Start with the time; presumably we wouldn't send the same series
476 # with the same Change-Id at the exact same second.
477 parts = [time_now.strftime("%Y%m%d%H%M%S")]
478
479 # These seem like they would be nice to include.
480 if 'prefix' in self.series:
481 parts.append(self.series['prefix'])
482 if 'version' in self.series:
483 parts.append("v%s" % self.series['version'])
484
485 parts.append(str(self.commit.count + 1))
486
487 # The Change-Id must be last, right before the @
488 parts.append(self.commit.change_id)
489
490 # Join parts together with "." and write it out.
491 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
492
Simon Glass93f61c02020-10-29 21:46:19 -0600493 def process_stream(self, infd, outfd):
Simon Glass26132882012-01-14 15:12:45 +0000494 """Copy a stream from infd to outfd, filtering out unwanting things.
495
496 This is used to process patch files one at a time.
497
498 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600499 infd (io.IOBase): Input stream file object
500 outfd (io.IOBase): Output stream file object
Simon Glass26132882012-01-14 15:12:45 +0000501 """
502 # Extract the filename from each diff, for nice warnings
503 fname = None
504 last_fname = None
505 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700506
Simon Glass93f61c02020-10-29 21:46:19 -0600507 self._write_message_id(outfd)
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700508
Simon Glass26132882012-01-14 15:12:45 +0000509 while True:
510 line = infd.readline()
511 if not line:
512 break
Simon Glass93f61c02020-10-29 21:46:19 -0600513 out = self.process_line(line)
Simon Glass26132882012-01-14 15:12:45 +0000514
515 # Try to detect blank lines at EOF
516 for line in out:
517 match = re_fname.match(line)
518 if match:
519 last_fname = fname
520 fname = match.group(1)
521 if line == '+':
522 self.blank_count += 1
523 else:
524 if self.blank_count and (line == '-- ' or match):
Simon Glassb5800392020-10-29 21:46:23 -0600525 self._add_warn("Found possible blank line(s) at end of file '%s'" %
526 last_fname)
Simon Glass26132882012-01-14 15:12:45 +0000527 outfd.write('+\n' * self.blank_count)
528 outfd.write(line + '\n')
529 self.blank_count = 0
Simon Glass93f61c02020-10-29 21:46:19 -0600530 self.finalise()
Simon Glass26132882012-01-14 15:12:45 +0000531
532
Simon Glass93f61c02020-10-29 21:46:19 -0600533def get_metadata_for_list(commit_range, git_dir=None, count=None,
534 series=None, allow_overwrite=False):
Simon Glass26132882012-01-14 15:12:45 +0000535 """Reads out patch series metadata from the commits
536
537 This does a 'git log' on the relevant commits and pulls out the tags we
538 are interested in.
539
540 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600541 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
542 git_dir (str): Path to git repositiory (None to use default)
543 count (int): Number of commits to list, or None for no limit
544 series (Series): Object to add information into. By default a new series
Simon Glass680da3c2012-12-15 10:42:06 +0000545 is started.
Simon Glass06127f92020-10-29 21:46:22 -0600546 allow_overwrite (bool): Allow tags to overwrite an existing tag
547
Simon Glass680da3c2012-12-15 10:42:06 +0000548 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600549 Series: Object containing information about the commits.
Simon Glass26132882012-01-14 15:12:45 +0000550 """
Simon Glass4e5d6bc2014-09-05 19:00:19 -0600551 if not series:
552 series = Series()
Simon Glass359b55a62014-09-05 19:00:23 -0600553 series.allow_overwrite = allow_overwrite
Simon Glassfc372bc2016-03-06 19:45:33 -0700554 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
Simon Glassb9dbcb42014-08-09 15:33:10 -0600555 git_dir=git_dir)
556 stdout = command.RunPipe([params], capture=True).stdout
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600557 pst = PatchStream(series, is_log=True)
Simon Glass26132882012-01-14 15:12:45 +0000558 for line in stdout.splitlines():
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600559 pst.process_line(line)
560 pst.finalise()
Simon Glass26132882012-01-14 15:12:45 +0000561 return series
562
Simon Glass93f61c02020-10-29 21:46:19 -0600563def get_metadata(branch, start, count):
Simon Glass680da3c2012-12-15 10:42:06 +0000564 """Reads out patch series metadata from the commits
565
566 This does a 'git log' on the relevant commits and pulls out the tags we
567 are interested in.
568
569 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600570 branch (str): Branch to use (None for current branch)
571 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
572 count (int): Number of commits to list
573
574 Returns:
575 Series: Object containing information about the commits.
Simon Glass680da3c2012-12-15 10:42:06 +0000576 """
Simon Glass93f61c02020-10-29 21:46:19 -0600577 return get_metadata_for_list(
578 '%s~%d' % (branch if branch else 'HEAD', start), None, count)
Simon Glass680da3c2012-12-15 10:42:06 +0000579
Simon Glass93f61c02020-10-29 21:46:19 -0600580def get_metadata_for_test(text):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600581 """Process metadata from a file containing a git log. Used for tests
582
583 Args:
584 text:
Simon Glass06127f92020-10-29 21:46:22 -0600585
586 Returns:
587 Series: Object containing information about the commits.
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600588 """
589 series = Series()
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600590 pst = PatchStream(series, is_log=True)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600591 for line in text.splitlines():
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600592 pst.process_line(line)
593 pst.finalise()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600594 return series
595
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600596def fix_patch(backup_dir, fname, series, cmt):
Simon Glass26132882012-01-14 15:12:45 +0000597 """Fix up a patch file, by adding/removing as required.
598
599 We remove our tags from the patch file, insert changes lists, etc.
600 The patch file is processed in place, and overwritten.
601
602 A backup file is put into backup_dir (if not None).
603
604 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600605 backup_dir (str): Path to directory to use to backup the file
606 fname (str): Filename to patch file to process
607 series (Series): Series information about this patch set
608 cmt (Commit): Commit object for this patch file
609
Simon Glass26132882012-01-14 15:12:45 +0000610 Return:
Simon Glass06127f92020-10-29 21:46:22 -0600611 list: A list of errors, each str, or [] if all ok.
Simon Glass26132882012-01-14 15:12:45 +0000612 """
613 handle, tmpname = tempfile.mkstemp()
Simon Glassf544a2d2019-10-31 07:42:51 -0600614 outfd = os.fdopen(handle, 'w', encoding='utf-8')
615 infd = open(fname, 'r', encoding='utf-8')
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600616 pst = PatchStream(series)
617 pst.commit = cmt
618 pst.process_stream(infd, outfd)
Simon Glass26132882012-01-14 15:12:45 +0000619 infd.close()
620 outfd.close()
621
622 # Create a backup file if required
623 if backup_dir:
624 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
625 shutil.move(tmpname, fname)
Simon Glass6d00f6c2020-10-29 21:46:24 -0600626 return cmt.warn
Simon Glass26132882012-01-14 15:12:45 +0000627
Simon Glass93f61c02020-10-29 21:46:19 -0600628def fix_patches(series, fnames):
Simon Glass26132882012-01-14 15:12:45 +0000629 """Fix up a list of patches identified by filenames
630
631 The patch files are processed in place, and overwritten.
632
633 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600634 series (Series): The Series object
635 fnames (:type: list of str): List of patch files to process
Simon Glass26132882012-01-14 15:12:45 +0000636 """
637 # Current workflow creates patches, so we shouldn't need a backup
638 backup_dir = None #tempfile.mkdtemp('clean-patch')
639 count = 0
640 for fname in fnames:
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600641 cmt = series.commits[count]
642 cmt.patch = fname
643 cmt.count = count
644 result = fix_patch(backup_dir, fname, series, cmt)
Simon Glass26132882012-01-14 15:12:45 +0000645 if result:
Paul Burtonc3931342016-09-27 16:03:50 +0100646 print('%d warnings for %s:' % (len(result), fname))
Simon Glass26132882012-01-14 15:12:45 +0000647 for warn in result:
Paul Burtonc3931342016-09-27 16:03:50 +0100648 print('\t', warn)
Simon Glass26132882012-01-14 15:12:45 +0000649 print
650 count += 1
Paul Burtonc3931342016-09-27 16:03:50 +0100651 print('Cleaned %d patches' % count)
Simon Glass26132882012-01-14 15:12:45 +0000652
Simon Glass93f61c02020-10-29 21:46:19 -0600653def insert_cover_letter(fname, series, count):
Simon Glass26132882012-01-14 15:12:45 +0000654 """Inserts a cover letter with the required info into patch 0
655
656 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600657 fname (str): Input / output filename of the cover letter file
658 series (Series): Series object
659 count (int): Number of patches in the series
Simon Glass26132882012-01-14 15:12:45 +0000660 """
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600661 fil = open(fname, 'r')
662 lines = fil.readlines()
663 fil.close()
Simon Glass26132882012-01-14 15:12:45 +0000664
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600665 fil = open(fname, 'w')
Simon Glass26132882012-01-14 15:12:45 +0000666 text = series.cover
667 prefix = series.GetPatchPrefix()
668 for line in lines:
669 if line.startswith('Subject:'):
Wu, Josh393aaa22015-04-03 10:51:17 +0800670 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
671 zero_repeat = int(math.log10(count)) + 1
672 zero = '0' * zero_repeat
673 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass26132882012-01-14 15:12:45 +0000674
675 # Insert our cover letter
676 elif line.startswith('*** BLURB HERE ***'):
677 # First the blurb test
678 line = '\n'.join(text[1:]) + '\n'
679 if series.get('notes'):
680 line += '\n'.join(series.notes) + '\n'
681
682 # Now the change list
683 out = series.MakeChangeLog(None)
684 line += '\n' + '\n'.join(out)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600685 fil.write(line)
686 fil.close()