blob: 9f283470bc281765d402b036c28e74471147b61b [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass0d24de92012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass0d24de92012-01-14 15:12:45 +00004
Simon Glassd06e55a2020-10-29 21:46:17 -06005"""Handles parsing a stream of commits/emails from 'git log' or other source"""
6
Douglas Anderson833e4192019-09-27 09:23:56 -07007import datetime
Wu, Josh35ce2dc2015-04-03 10:51:17 +08008import math
Simon Glass0d24de92012-01-14 15:12:45 +00009import os
10import re
11import shutil
12import tempfile
13
Simon Glassbf776672020-04-17 18:09:04 -060014from patman import command
15from patman import commit
16from patman import gitutil
17from patman.series import Series
Simon Glass0d24de92012-01-14 15:12:45 +000018
19# Tags that we detect and remove
Simon Glass57699042020-10-29 21:46:18 -060020RE_REMOVE = re.compile(r'^BUG=|^TEST=|^BRANCH=|^Review URL:'
Simon Glassd06e55a2020-10-29 21:46:17 -060021 r'|Reviewed-on:|Commit-\w*:')
Simon Glass0d24de92012-01-14 15:12:45 +000022
23# Lines which are allowed after a TEST= line
Simon Glass57699042020-10-29 21:46:18 -060024RE_ALLOWED_AFTER_TEST = re.compile('^Signed-off-by:')
Simon Glass0d24de92012-01-14 15:12:45 +000025
Ilya Yanok05e5b732012-08-06 23:46:05 +000026# Signoffs
Simon Glass57699042020-10-29 21:46:18 -060027RE_SIGNOFF = re.compile('^Signed-off-by: *(.*)')
Ilya Yanok05e5b732012-08-06 23:46:05 +000028
Sean Anderson6949f702020-05-04 16:28:34 -040029# Cover letter tag
Simon Glass57699042020-10-29 21:46:18 -060030RE_COVER = re.compile('^Cover-([a-z-]*): *(.*)')
Simon Glassfe2f8d92013-03-20 16:43:00 +000031
Simon Glass0d24de92012-01-14 15:12:45 +000032# Patch series tag
Simon Glass57699042020-10-29 21:46:18 -060033RE_SERIES_TAG = re.compile('^Series-([a-z-]*): *(.*)')
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +010034
Douglas Anderson833e4192019-09-27 09:23:56 -070035# Change-Id will be used to generate the Message-Id and then be stripped
Simon Glass57699042020-10-29 21:46:18 -060036RE_CHANGE_ID = re.compile('^Change-Id: *(.*)')
Douglas Anderson833e4192019-09-27 09:23:56 -070037
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +010038# Commit series tag
Simon Glass57699042020-10-29 21:46:18 -060039RE_COMMIT_TAG = re.compile('^Commit-([a-z-]*): *(.*)')
Simon Glass0d24de92012-01-14 15:12:45 +000040
41# Commit tags that we want to collect and keep
Simon Glass57699042020-10-29 21:46:18 -060042RE_TAG = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc|Fixes): (.*)')
Simon Glass0d24de92012-01-14 15:12:45 +000043
44# The start of a new commit in the git log
Simon Glass57699042020-10-29 21:46:18 -060045RE_COMMIT = re.compile('^commit ([0-9a-f]*)$')
Simon Glass0d24de92012-01-14 15:12:45 +000046
47# We detect these since checkpatch doesn't always do it
Simon Glass57699042020-10-29 21:46:18 -060048RE_SPACE_BEFORE_TAB = re.compile('^[+].* \t')
Simon Glass0d24de92012-01-14 15:12:45 +000049
Sean Anderson0411fff2020-05-04 16:28:35 -040050# Match indented lines for changes
Simon Glass57699042020-10-29 21:46:18 -060051RE_LEADING_WHITESPACE = re.compile(r'^\s')
Sean Anderson0411fff2020-05-04 16:28:35 -040052
Simon Glass0d24de92012-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 Glasse3a816b2020-10-29 21:46:21 -060067 def __init__(self, series, is_log=False):
Simon Glass0d24de92012-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 Anderson6949f702020-05-04 16:28:34 -040070 self.lines_after_test = 0 # Number of lines found after TEST=
Simon Glass0d24de92012-01-14 15:12:45 +000071 self.warn = [] # List of warnings we have collected
72 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 Anderson6949f702020-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 Anderson0411fff2020-05-04 16:28:35 -040080 self.change_lines = [] # Lines of the current change
Simon Glass0d24de92012-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 Glass0d24de92012-01-14 15:12:45 +000083 self.signoff = [] # Contents of signoff line
84 self.commit = None # Current commit
85
Simon Glassb5cc3992020-10-29 21:46:23 -060086 def _add_warn(self, warn):
87 """Add a new warning to report to the user
88
89 Args:
90 warn (str): Warning to report
91 """
92 self.warn.append(warn)
93
Simon Glassd93720e2020-10-29 21:46:19 -060094 def _add_to_series(self, line, name, value):
Simon Glass0d24de92012-01-14 15:12:45 +000095 """Add a new Series-xxx tag.
96
97 When a Series-xxx tag is detected, we come here to record it, if we
98 are scanning a 'git log'.
99
100 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600101 line (str): Source line containing tag (useful for debug/error
102 messages)
103 name (str): Tag name (part after 'Series-')
104 value (str): Tag value (part after 'Series-xxx: ')
Simon Glass0d24de92012-01-14 15:12:45 +0000105 """
106 if name == 'notes':
107 self.in_section = name
108 self.skip_blank = False
109 if self.is_log:
110 self.series.AddTag(self.commit, line, name, value)
111
Simon Glasse3a816b2020-10-29 21:46:21 -0600112 def _add_to_commit(self, name):
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100113 """Add a new Commit-xxx tag.
114
115 When a Commit-xxx tag is detected, we come here to record it.
116
117 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600118 name (str): Tag name (part after 'Commit-')
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100119 """
120 if name == 'notes':
121 self.in_section = 'commit-' + name
122 self.skip_blank = False
123
Simon Glassd93720e2020-10-29 21:46:19 -0600124 def _add_commit_rtag(self, rtag_type, who):
Simon Glass7207e2b2020-07-05 21:41:57 -0600125 """Add a response tag to the current commit
126
127 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600128 rtag_type (str): rtag type (e.g. 'Reviewed-by')
129 who (str): Person who gave that rtag, e.g.
130 'Fred Bloggs <fred@bloggs.org>'
Simon Glass7207e2b2020-07-05 21:41:57 -0600131 """
132 self.commit.AddRtag(rtag_type, who)
133
Simon Glassd93720e2020-10-29 21:46:19 -0600134 def _close_commit(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000135 """Save the current commit into our commit list, and reset our state"""
136 if self.commit and self.is_log:
137 self.series.AddCommit(self.commit)
138 self.commit = None
Bin Meng0d577182016-06-26 23:24:30 -0700139 # If 'END' is missing in a 'Cover-letter' section, and that section
140 # happens to show up at the very end of the commit message, this is
141 # the chance for us to fix it up.
142 if self.in_section == 'cover' and self.is_log:
143 self.series.cover = self.section
144 self.in_section = None
145 self.skip_blank = True
146 self.section = []
Simon Glass0d24de92012-01-14 15:12:45 +0000147
Simon Glassd93720e2020-10-29 21:46:19 -0600148 def _parse_version(self, value, line):
Sean Anderson6949f702020-05-04 16:28:34 -0400149 """Parse a version from a *-changes tag
150
151 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600152 value (str): Tag value (part after 'xxx-changes: '
153 line (str): Source line containing tag
Sean Anderson6949f702020-05-04 16:28:34 -0400154
155 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600156 int: The version as an integer
157
158 Raises:
159 ValueError: the value cannot be converted
Sean Anderson6949f702020-05-04 16:28:34 -0400160 """
161 try:
162 return int(value)
Simon Glassdd147ed2020-10-29 21:46:20 -0600163 except ValueError:
Sean Anderson6949f702020-05-04 16:28:34 -0400164 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassd06e55a2020-10-29 21:46:17 -0600165 (self.commit.hash, line))
Sean Anderson6949f702020-05-04 16:28:34 -0400166
Simon Glassd93720e2020-10-29 21:46:19 -0600167 def _finalise_change(self):
168 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson0411fff2020-05-04 16:28:35 -0400169 if not self.change_lines:
170 return
171 change = '\n'.join(self.change_lines)
172
173 if self.in_change == 'Series':
174 self.series.AddChange(self.change_version, self.commit, change)
175 elif self.in_change == 'Cover':
176 self.series.AddChange(self.change_version, None, change)
177 elif self.in_change == 'Commit':
178 self.commit.AddChange(self.change_version, change)
179 self.change_lines = []
180
Simon Glassd93720e2020-10-29 21:46:19 -0600181 def process_line(self, line):
Simon Glass0d24de92012-01-14 15:12:45 +0000182 """Process a single line of a patch file or commit log
183
184 This process a line and returns a list of lines to output. The list
185 may be empty or may contain multiple output lines.
186
187 This is where all the complicated logic is located. The class's
188 state is used to move between different states and detect things
189 properly.
190
191 We can be in one of two modes:
192 self.is_log == True: This is 'git log' mode, where most output is
193 indented by 4 characters and we are scanning for tags
194
195 self.is_log == False: This is 'patch' mode, where we already have
196 all the tags, and are processing patches to remove junk we
197 don't want, and add things we think are required.
198
199 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600200 line (str): text line to process
Simon Glass0d24de92012-01-14 15:12:45 +0000201
202 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600203 list: list of output lines, or [] if nothing should be output
204
205 Raises:
206 ValueError: a fatal error occurred while parsing, e.g. an END
207 without a starting tag, or two commits with two change IDs
Simon Glass0d24de92012-01-14 15:12:45 +0000208 """
209 # Initially we have no output. Prepare the input line string
210 out = []
211 line = line.rstrip('\n')
Scott Wood4b89b812014-09-25 14:30:46 -0500212
Simon Glass57699042020-10-29 21:46:18 -0600213 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood4b89b812014-09-25 14:30:46 -0500214
Simon Glass0d24de92012-01-14 15:12:45 +0000215 if self.is_log:
216 if line[:4] == ' ':
217 line = line[4:]
218
219 # Handle state transition and skipping blank lines
Simon Glass57699042020-10-29 21:46:18 -0600220 series_tag_match = RE_SERIES_TAG.match(line)
221 change_id_match = RE_CHANGE_ID.match(line)
222 commit_tag_match = RE_COMMIT_TAG.match(line)
223 cover_match = RE_COVER.match(line)
224 signoff_match = RE_SIGNOFF.match(line)
225 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000226 tag_match = None
227 if self.state == STATE_PATCH_HEADER:
Simon Glass57699042020-10-29 21:46:18 -0600228 tag_match = RE_TAG.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000229 is_blank = not line.strip()
230 if is_blank:
231 if (self.state == STATE_MSG_HEADER
232 or self.state == STATE_PATCH_SUBJECT):
233 self.state += 1
234
235 # We don't have a subject in the text stream of patch files
236 # It has its own line with a Subject: tag
237 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
238 self.state += 1
239 elif commit_match:
240 self.state = STATE_MSG_HEADER
241
Bin Meng94fbd3e2016-06-26 23:24:32 -0700242 # If a tag is detected, or a new commit starts
Douglas Anderson833e4192019-09-27 09:23:56 -0700243 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson6949f702020-05-04 16:28:34 -0400244 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng57b6b192016-06-26 23:24:31 -0700245 # but we are already in a section, this means 'END' is missing
246 # for that section, fix it up.
Bin Meng13b98d92016-06-26 23:24:29 -0700247 if self.in_section:
Simon Glassb5cc3992020-10-29 21:46:23 -0600248 self._add_warn("Missing 'END' in section '%s'" % self.in_section)
Bin Meng13b98d92016-06-26 23:24:29 -0700249 if self.in_section == 'cover':
250 self.series.cover = self.section
251 elif self.in_section == 'notes':
252 if self.is_log:
253 self.series.notes += self.section
254 elif self.in_section == 'commit-notes':
255 if self.is_log:
256 self.commit.notes += self.section
257 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600258 # This should not happen
259 raise ValueError("Unknown section '%s'" % self.in_section)
Bin Meng13b98d92016-06-26 23:24:29 -0700260 self.in_section = None
261 self.skip_blank = True
262 self.section = []
Bin Meng57b6b192016-06-26 23:24:31 -0700263 # but we are already in a change list, that means a blank line
264 # is missing, fix it up.
265 if self.in_change:
Simon Glassb5cc3992020-10-29 21:46:23 -0600266 self._add_warn("Missing 'blank line' in section '%s-changes'" %
267 self.in_change)
Simon Glassd93720e2020-10-29 21:46:19 -0600268 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400269 self.in_change = None
270 self.change_version = 0
Bin Meng13b98d92016-06-26 23:24:29 -0700271
Simon Glass0d24de92012-01-14 15:12:45 +0000272 # If we are in a section, keep collecting lines until we see END
273 if self.in_section:
274 if line == 'END':
275 if self.in_section == 'cover':
276 self.series.cover = self.section
277 elif self.in_section == 'notes':
278 if self.is_log:
279 self.series.notes += self.section
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100280 elif self.in_section == 'commit-notes':
281 if self.is_log:
282 self.commit.notes += self.section
Simon Glass0d24de92012-01-14 15:12:45 +0000283 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600284 # This should not happen
285 raise ValueError("Unknown section '%s'" % self.in_section)
Simon Glass0d24de92012-01-14 15:12:45 +0000286 self.in_section = None
287 self.skip_blank = True
288 self.section = []
289 else:
290 self.section.append(line)
291
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200292 # If we are not in a section, it is an unexpected END
293 elif line == 'END':
Simon Glassd06e55a2020-10-29 21:46:17 -0600294 raise ValueError("'END' wihout section")
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200295
Simon Glass0d24de92012-01-14 15:12:45 +0000296 # Detect the commit subject
297 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
298 self.commit.subject = line
299
300 # Detect the tags we want to remove, and skip blank lines
Simon Glass57699042020-10-29 21:46:18 -0600301 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass0d24de92012-01-14 15:12:45 +0000302 self.skip_blank = True
303
304 # TEST= should be the last thing in the commit, so remove
305 # everything after it
306 if line.startswith('TEST='):
307 self.found_test = True
308 elif self.skip_blank and is_blank:
309 self.skip_blank = False
310
Sean Anderson6949f702020-05-04 16:28:34 -0400311 # Detect Cover-xxx tags
Bin Menge7df2182016-06-26 23:24:28 -0700312 elif cover_match:
Sean Anderson6949f702020-05-04 16:28:34 -0400313 name = cover_match.group(1)
314 value = cover_match.group(2)
315 if name == 'letter':
316 self.in_section = 'cover'
317 self.skip_blank = False
318 elif name == 'letter-cc':
Simon Glassd93720e2020-10-29 21:46:19 -0600319 self._add_to_series(line, 'cover-cc', value)
Sean Anderson6949f702020-05-04 16:28:34 -0400320 elif name == 'changes':
321 self.in_change = 'Cover'
Simon Glassd93720e2020-10-29 21:46:19 -0600322 self.change_version = self._parse_version(value, line)
Simon Glassfe2f8d92013-03-20 16:43:00 +0000323
Simon Glass0d24de92012-01-14 15:12:45 +0000324 # If we are in a change list, key collected lines until a blank one
325 elif self.in_change:
326 if is_blank:
327 # Blank line ends this change list
Simon Glassd93720e2020-10-29 21:46:19 -0600328 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400329 self.in_change = None
330 self.change_version = 0
Simon Glass102061b2014-04-20 10:50:14 -0600331 elif line == '---':
Simon Glassd93720e2020-10-29 21:46:19 -0600332 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400333 self.in_change = None
334 self.change_version = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600335 out = self.process_line(line)
Sean Anderson0411fff2020-05-04 16:28:35 -0400336 elif self.is_log:
337 if not leading_whitespace_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600338 self._finalise_change()
Sean Anderson0411fff2020-05-04 16:28:35 -0400339 self.change_lines.append(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000340 self.skip_blank = False
341
342 # Detect Series-xxx tags
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100343 elif series_tag_match:
344 name = series_tag_match.group(1)
345 value = series_tag_match.group(2)
Simon Glass0d24de92012-01-14 15:12:45 +0000346 if name == 'changes':
347 # value is the version number: e.g. 1, or 2
Sean Anderson6949f702020-05-04 16:28:34 -0400348 self.in_change = 'Series'
Simon Glassd93720e2020-10-29 21:46:19 -0600349 self.change_version = self._parse_version(value, line)
Simon Glass0d24de92012-01-14 15:12:45 +0000350 else:
Simon Glassd93720e2020-10-29 21:46:19 -0600351 self._add_to_series(line, name, value)
Simon Glass0d24de92012-01-14 15:12:45 +0000352 self.skip_blank = True
353
Douglas Anderson833e4192019-09-27 09:23:56 -0700354 # Detect Change-Id tags
355 elif change_id_match:
356 value = change_id_match.group(1)
357 if self.is_log:
358 if self.commit.change_id:
Simon Glassd06e55a2020-10-29 21:46:17 -0600359 raise ValueError(
360 "%s: Two Change-Ids: '%s' vs. '%s'" % self.commit.hash,
361 self.commit.change_id, value)
Douglas Anderson833e4192019-09-27 09:23:56 -0700362 self.commit.change_id = value
363 self.skip_blank = True
364
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100365 # Detect Commit-xxx tags
366 elif commit_tag_match:
367 name = commit_tag_match.group(1)
368 value = commit_tag_match.group(2)
369 if name == 'notes':
Simon Glasse3a816b2020-10-29 21:46:21 -0600370 self._add_to_commit(name)
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100371 self.skip_blank = True
Sean Anderson6949f702020-05-04 16:28:34 -0400372 elif name == 'changes':
373 self.in_change = 'Commit'
Simon Glassd93720e2020-10-29 21:46:19 -0600374 self.change_version = self._parse_version(value, line)
Patrick Delaunaye5ff9ab2020-07-02 19:52:54 +0200375 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600376 self._add_warn('Line %d: Ignoring Commit-%s' %
377 (self.linenum, name))
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100378
Simon Glass0d24de92012-01-14 15:12:45 +0000379 # Detect the start of a new commit
380 elif commit_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600381 self._close_commit()
Simon Glass0b5b4092014-10-15 02:27:00 -0600382 self.commit = commit.Commit(commit_match.group(1))
Simon Glass0d24de92012-01-14 15:12:45 +0000383
384 # Detect tags in the commit message
385 elif tag_match:
Simon Glass7207e2b2020-07-05 21:41:57 -0600386 rtag_type, who = tag_match.groups()
Simon Glassd93720e2020-10-29 21:46:19 -0600387 self._add_commit_rtag(rtag_type, who)
Simon Glass0d24de92012-01-14 15:12:45 +0000388 # Remove Tested-by self, since few will take much notice
Simon Glass7207e2b2020-07-05 21:41:57 -0600389 if (rtag_type == 'Tested-by' and
390 who.find(os.getenv('USER') + '@') != -1):
Simon Glassb5cc3992020-10-29 21:46:23 -0600391 self._add_warn("Ignoring %s" % line)
Simon Glass7207e2b2020-07-05 21:41:57 -0600392 elif rtag_type == 'Patch-cc':
393 self.commit.AddCc(who.split(','))
Simon Glass0d24de92012-01-14 15:12:45 +0000394 else:
Simon Glassd0c57192014-08-28 09:43:38 -0600395 out = [line]
Simon Glass0d24de92012-01-14 15:12:45 +0000396
Simon Glass102061b2014-04-20 10:50:14 -0600397 # Suppress duplicate signoffs
398 elif signoff_match:
Simon Glasse752edc2014-08-28 09:43:35 -0600399 if (self.is_log or not self.commit or
Simon Glassd06e55a2020-10-29 21:46:17 -0600400 self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
Simon Glass102061b2014-04-20 10:50:14 -0600401 out = [line]
402
Simon Glass0d24de92012-01-14 15:12:45 +0000403 # Well that means this is an ordinary line
404 else:
Simon Glass0d24de92012-01-14 15:12:45 +0000405 # Look for space before tab
Simon Glassdd147ed2020-10-29 21:46:20 -0600406 mat = RE_SPACE_BEFORE_TAB.match(line)
407 if mat:
Simon Glassb5cc3992020-10-29 21:46:23 -0600408 self._add_warn('Line %d/%d has space before tab' %
409 (self.linenum, mat.start()))
Simon Glass0d24de92012-01-14 15:12:45 +0000410
411 # OK, we have a valid non-blank line
412 out = [line]
413 self.linenum += 1
414 self.skip_blank = False
415 if self.state == STATE_DIFFS:
416 pass
417
418 # If this is the start of the diffs section, emit our tags and
419 # change log
420 elif line == '---':
421 self.state = STATE_DIFFS
422
Sean Anderson6949f702020-05-04 16:28:34 -0400423 # Output the tags (signoff first), then change list
Simon Glass0d24de92012-01-14 15:12:45 +0000424 out = []
Simon Glass0d24de92012-01-14 15:12:45 +0000425 log = self.series.MakeChangeLog(self.commit)
Simon Glasse752edc2014-08-28 09:43:35 -0600426 out += [line]
427 if self.commit:
428 out += self.commit.notes
429 out += [''] + log
Simon Glass0d24de92012-01-14 15:12:45 +0000430 elif self.found_test:
Simon Glass57699042020-10-29 21:46:18 -0600431 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass0d24de92012-01-14 15:12:45 +0000432 self.lines_after_test += 1
433
434 return out
435
Simon Glassd93720e2020-10-29 21:46:19 -0600436 def finalise(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000437 """Close out processing of this patch stream"""
Simon Glassd93720e2020-10-29 21:46:19 -0600438 self._finalise_change()
439 self._close_commit()
Simon Glass0d24de92012-01-14 15:12:45 +0000440 if self.lines_after_test:
Simon Glassb5cc3992020-10-29 21:46:23 -0600441 self._add_warn('Found %d lines after TEST=' % self.lines_after_test)
Simon Glass0d24de92012-01-14 15:12:45 +0000442
Simon Glassd93720e2020-10-29 21:46:19 -0600443 def _write_message_id(self, outfd):
Douglas Anderson833e4192019-09-27 09:23:56 -0700444 """Write the Message-Id into the output.
445
446 This is based on the Change-Id in the original patch, the version,
447 and the prefix.
448
449 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600450 outfd (io.IOBase): Output stream file object
Douglas Anderson833e4192019-09-27 09:23:56 -0700451 """
452 if not self.commit.change_id:
453 return
454
455 # If the count is -1 we're testing, so use a fixed time
456 if self.commit.count == -1:
457 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
458 else:
459 time_now = datetime.datetime.now()
460
461 # In theory there is email.utils.make_msgid() which would be nice
462 # to use, but it already produces something way too long and thus
463 # will produce ugly commit lines if someone throws this into
464 # a "Link:" tag in the final commit. So (sigh) roll our own.
465
466 # Start with the time; presumably we wouldn't send the same series
467 # with the same Change-Id at the exact same second.
468 parts = [time_now.strftime("%Y%m%d%H%M%S")]
469
470 # These seem like they would be nice to include.
471 if 'prefix' in self.series:
472 parts.append(self.series['prefix'])
473 if 'version' in self.series:
474 parts.append("v%s" % self.series['version'])
475
476 parts.append(str(self.commit.count + 1))
477
478 # The Change-Id must be last, right before the @
479 parts.append(self.commit.change_id)
480
481 # Join parts together with "." and write it out.
482 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
483
Simon Glassd93720e2020-10-29 21:46:19 -0600484 def process_stream(self, infd, outfd):
Simon Glass0d24de92012-01-14 15:12:45 +0000485 """Copy a stream from infd to outfd, filtering out unwanting things.
486
487 This is used to process patch files one at a time.
488
489 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600490 infd (io.IOBase): Input stream file object
491 outfd (io.IOBase): Output stream file object
Simon Glass0d24de92012-01-14 15:12:45 +0000492 """
493 # Extract the filename from each diff, for nice warnings
494 fname = None
495 last_fname = None
496 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson833e4192019-09-27 09:23:56 -0700497
Simon Glassd93720e2020-10-29 21:46:19 -0600498 self._write_message_id(outfd)
Douglas Anderson833e4192019-09-27 09:23:56 -0700499
Simon Glass0d24de92012-01-14 15:12:45 +0000500 while True:
501 line = infd.readline()
502 if not line:
503 break
Simon Glassd93720e2020-10-29 21:46:19 -0600504 out = self.process_line(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000505
506 # Try to detect blank lines at EOF
507 for line in out:
508 match = re_fname.match(line)
509 if match:
510 last_fname = fname
511 fname = match.group(1)
512 if line == '+':
513 self.blank_count += 1
514 else:
515 if self.blank_count and (line == '-- ' or match):
Simon Glassb5cc3992020-10-29 21:46:23 -0600516 self._add_warn("Found possible blank line(s) at end of file '%s'" %
517 last_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000518 outfd.write('+\n' * self.blank_count)
519 outfd.write(line + '\n')
520 self.blank_count = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600521 self.finalise()
Simon Glass0d24de92012-01-14 15:12:45 +0000522
523
Simon Glassd93720e2020-10-29 21:46:19 -0600524def get_metadata_for_list(commit_range, git_dir=None, count=None,
525 series=None, allow_overwrite=False):
Simon Glasse62f9052012-12-15 10:42:06 +0000526 """Reads out patch series metadata from the commits
527
528 This does a 'git log' on the relevant commits and pulls out the tags we
529 are interested in.
530
531 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600532 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
533 git_dir (str): Path to git repositiory (None to use default)
534 count (int): Number of commits to list, or None for no limit
535 series (Series): Object to add information into. By default a new series
Simon Glasse62f9052012-12-15 10:42:06 +0000536 is started.
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600537 allow_overwrite (bool): Allow tags to overwrite an existing tag
538
Simon Glasse62f9052012-12-15 10:42:06 +0000539 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600540 Series: Object containing information about the commits.
Simon Glasse62f9052012-12-15 10:42:06 +0000541 """
Simon Glass891b7a02014-09-05 19:00:19 -0600542 if not series:
543 series = Series()
Simon Glass950a2312014-09-05 19:00:23 -0600544 series.allow_overwrite = allow_overwrite
Simon Glass9ad96982016-03-06 19:45:33 -0700545 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
Simon Glasscda2a612014-08-09 15:33:10 -0600546 git_dir=git_dir)
547 stdout = command.RunPipe([params], capture=True).stdout
Simon Glassdd147ed2020-10-29 21:46:20 -0600548 pst = PatchStream(series, is_log=True)
Simon Glasse62f9052012-12-15 10:42:06 +0000549 for line in stdout.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600550 pst.process_line(line)
551 pst.finalise()
Simon Glasse62f9052012-12-15 10:42:06 +0000552 return series
553
Simon Glassd93720e2020-10-29 21:46:19 -0600554def get_metadata(branch, start, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000555 """Reads out patch series metadata from the commits
556
557 This does a 'git log' on the relevant commits and pulls out the tags we
558 are interested in.
559
560 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600561 branch (str): Branch to use (None for current branch)
562 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
563 count (int): Number of commits to list
564
565 Returns:
566 Series: Object containing information about the commits.
Simon Glass0d24de92012-01-14 15:12:45 +0000567 """
Simon Glassd93720e2020-10-29 21:46:19 -0600568 return get_metadata_for_list(
569 '%s~%d' % (branch if branch else 'HEAD', start), None, count)
Simon Glass0d24de92012-01-14 15:12:45 +0000570
Simon Glassd93720e2020-10-29 21:46:19 -0600571def get_metadata_for_test(text):
Simon Glass6e87ae12017-05-29 15:31:31 -0600572 """Process metadata from a file containing a git log. Used for tests
573
574 Args:
575 text:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600576
577 Returns:
578 Series: Object containing information about the commits.
Simon Glass6e87ae12017-05-29 15:31:31 -0600579 """
580 series = Series()
Simon Glassdd147ed2020-10-29 21:46:20 -0600581 pst = PatchStream(series, is_log=True)
Simon Glass6e87ae12017-05-29 15:31:31 -0600582 for line in text.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600583 pst.process_line(line)
584 pst.finalise()
Simon Glass6e87ae12017-05-29 15:31:31 -0600585 return series
586
Simon Glassdd147ed2020-10-29 21:46:20 -0600587def fix_patch(backup_dir, fname, series, cmt):
Simon Glass0d24de92012-01-14 15:12:45 +0000588 """Fix up a patch file, by adding/removing as required.
589
590 We remove our tags from the patch file, insert changes lists, etc.
591 The patch file is processed in place, and overwritten.
592
593 A backup file is put into backup_dir (if not None).
594
595 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600596 backup_dir (str): Path to directory to use to backup the file
597 fname (str): Filename to patch file to process
598 series (Series): Series information about this patch set
599 cmt (Commit): Commit object for this patch file
600
Simon Glass0d24de92012-01-14 15:12:45 +0000601 Return:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600602 list: A list of errors, each str, or [] if all ok.
Simon Glass0d24de92012-01-14 15:12:45 +0000603 """
604 handle, tmpname = tempfile.mkstemp()
Simon Glass272cd852019-10-31 07:42:51 -0600605 outfd = os.fdopen(handle, 'w', encoding='utf-8')
606 infd = open(fname, 'r', encoding='utf-8')
Simon Glassdd147ed2020-10-29 21:46:20 -0600607 pst = PatchStream(series)
608 pst.commit = cmt
609 pst.process_stream(infd, outfd)
Simon Glass0d24de92012-01-14 15:12:45 +0000610 infd.close()
611 outfd.close()
612
613 # Create a backup file if required
614 if backup_dir:
615 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
616 shutil.move(tmpname, fname)
Simon Glassdd147ed2020-10-29 21:46:20 -0600617 return pst.warn
Simon Glass0d24de92012-01-14 15:12:45 +0000618
Simon Glassd93720e2020-10-29 21:46:19 -0600619def fix_patches(series, fnames):
Simon Glass0d24de92012-01-14 15:12:45 +0000620 """Fix up a list of patches identified by filenames
621
622 The patch files are processed in place, and overwritten.
623
624 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600625 series (Series): The Series object
626 fnames (:type: list of str): List of patch files to process
Simon Glass0d24de92012-01-14 15:12:45 +0000627 """
628 # Current workflow creates patches, so we shouldn't need a backup
629 backup_dir = None #tempfile.mkdtemp('clean-patch')
630 count = 0
631 for fname in fnames:
Simon Glassdd147ed2020-10-29 21:46:20 -0600632 cmt = series.commits[count]
633 cmt.patch = fname
634 cmt.count = count
635 result = fix_patch(backup_dir, fname, series, cmt)
Simon Glass0d24de92012-01-14 15:12:45 +0000636 if result:
Paul Burtona920a172016-09-27 16:03:50 +0100637 print('%d warnings for %s:' % (len(result), fname))
Simon Glass0d24de92012-01-14 15:12:45 +0000638 for warn in result:
Paul Burtona920a172016-09-27 16:03:50 +0100639 print('\t', warn)
Simon Glass0d24de92012-01-14 15:12:45 +0000640 print
641 count += 1
Paul Burtona920a172016-09-27 16:03:50 +0100642 print('Cleaned %d patches' % count)
Simon Glass0d24de92012-01-14 15:12:45 +0000643
Simon Glassd93720e2020-10-29 21:46:19 -0600644def insert_cover_letter(fname, series, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000645 """Inserts a cover letter with the required info into patch 0
646
647 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600648 fname (str): Input / output filename of the cover letter file
649 series (Series): Series object
650 count (int): Number of patches in the series
Simon Glass0d24de92012-01-14 15:12:45 +0000651 """
Simon Glassdd147ed2020-10-29 21:46:20 -0600652 fil = open(fname, 'r')
653 lines = fil.readlines()
654 fil.close()
Simon Glass0d24de92012-01-14 15:12:45 +0000655
Simon Glassdd147ed2020-10-29 21:46:20 -0600656 fil = open(fname, 'w')
Simon Glass0d24de92012-01-14 15:12:45 +0000657 text = series.cover
658 prefix = series.GetPatchPrefix()
659 for line in lines:
660 if line.startswith('Subject:'):
Wu, Josh35ce2dc2015-04-03 10:51:17 +0800661 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
662 zero_repeat = int(math.log10(count)) + 1
663 zero = '0' * zero_repeat
664 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass0d24de92012-01-14 15:12:45 +0000665
666 # Insert our cover letter
667 elif line.startswith('*** BLURB HERE ***'):
668 # First the blurb test
669 line = '\n'.join(text[1:]) + '\n'
670 if series.get('notes'):
671 line += '\n'.join(series.notes) + '\n'
672
673 # Now the change list
674 out = series.MakeChangeLog(None)
675 line += '\n' + '\n'.join(out)
Simon Glassdd147ed2020-10-29 21:46:20 -0600676 fil.write(line)
677 fil.close()