blob: 45040877f8ca115cf144a98968b246898f5b7398 [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
Simon Glassda8a2922020-10-29 21:46:37 -06007import collections
Douglas Anderson52b5ee82019-09-27 09:23:56 -07008import datetime
Simon Glass06202d62020-10-29 21:46:27 -06009import io
Wu, Josh393aaa22015-04-03 10:51:17 +080010import math
Simon Glass26132882012-01-14 15:12:45 +000011import os
12import re
Simon Glassda8a2922020-10-29 21:46:37 -060013import queue
Simon Glass26132882012-01-14 15:12:45 +000014import shutil
15import tempfile
16
Simon Glassa997ea52020-04-17 18:09:04 -060017from patman import commit
Simon Glassa997ea52020-04-17 18:09:04 -060018from patman.series import Series
Simon Glass131444f2023-02-23 18:18:04 -070019from u_boot_pylib import command
Simon Glassba1b3b92025-02-09 14:26:00 -070020from u_boot_pylib import gitutil
Simon Glass26132882012-01-14 15:12:45 +000021
22# Tags that we detect and remove
Simon Glass06022262020-10-29 21:46:18 -060023RE_REMOVE = re.compile(r'^BUG=|^TEST=|^BRANCH=|^Review URL:'
Simon Glassc42b1d02020-10-29 21:46:17 -060024 r'|Reviewed-on:|Commit-\w*:')
Simon Glass26132882012-01-14 15:12:45 +000025
26# Lines which are allowed after a TEST= line
Simon Glass06022262020-10-29 21:46:18 -060027RE_ALLOWED_AFTER_TEST = re.compile('^Signed-off-by:')
Simon Glass26132882012-01-14 15:12:45 +000028
Ilya Yanok644c1842012-08-06 23:46:05 +000029# Signoffs
Simon Glass06022262020-10-29 21:46:18 -060030RE_SIGNOFF = re.compile('^Signed-off-by: *(.*)')
Ilya Yanok644c1842012-08-06 23:46:05 +000031
Sean Anderson48f46d62020-05-04 16:28:34 -040032# Cover letter tag
Simon Glass06022262020-10-29 21:46:18 -060033RE_COVER = re.compile('^Cover-([a-z-]*): *(.*)')
Simon Glassc72f3da2013-03-20 16:43:00 +000034
Simon Glass26132882012-01-14 15:12:45 +000035# Patch series tag
Simon Glass06022262020-10-29 21:46:18 -060036RE_SERIES_TAG = re.compile('^Series-([a-z-]*): *(.*)')
Albert ARIBAUDd880efd2013-11-12 11:14:41 +010037
Douglas Anderson52b5ee82019-09-27 09:23:56 -070038# Change-Id will be used to generate the Message-Id and then be stripped
Simon Glass06022262020-10-29 21:46:18 -060039RE_CHANGE_ID = re.compile('^Change-Id: *(.*)')
Douglas Anderson52b5ee82019-09-27 09:23:56 -070040
Albert ARIBAUDd880efd2013-11-12 11:14:41 +010041# Commit series tag
Simon Glass06022262020-10-29 21:46:18 -060042RE_COMMIT_TAG = re.compile('^Commit-([a-z-]*): *(.*)')
Simon Glass26132882012-01-14 15:12:45 +000043
44# Commit tags that we want to collect and keep
Simon Glass06022262020-10-29 21:46:18 -060045RE_TAG = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc|Fixes): (.*)')
Simon Glass26132882012-01-14 15:12:45 +000046
47# The start of a new commit in the git log
Simon Glass06022262020-10-29 21:46:18 -060048RE_COMMIT = re.compile('^commit ([0-9a-f]*)$')
Simon Glass26132882012-01-14 15:12:45 +000049
50# We detect these since checkpatch doesn't always do it
Brian Norriseb88af22024-07-26 12:02:33 -070051RE_SPACE_BEFORE_TAB = re.compile(r'^[+].* \t')
Simon Glass26132882012-01-14 15:12:45 +000052
Sean Anderson1a32f922020-05-04 16:28:35 -040053# Match indented lines for changes
Simon Glass06022262020-10-29 21:46:18 -060054RE_LEADING_WHITESPACE = re.compile(r'^\s')
Sean Anderson1a32f922020-05-04 16:28:35 -040055
Simon Glassda8a2922020-10-29 21:46:37 -060056# Detect a 'diff' line
57RE_DIFF = re.compile(r'^>.*diff --git a/(.*) b/(.*)$')
58
59# Detect a context line, like '> @@ -153,8 +153,13 @@ CheckPatch
60RE_LINE = re.compile(r'>.*@@ \-(\d+),\d+ \+(\d+),\d+ @@ *(.*)')
61
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +020062# Detect line with invalid TAG
63RE_INV_TAG = re.compile('^Serie-([a-z-]*): *(.*)')
64
Simon Glass26132882012-01-14 15:12:45 +000065# States we can be in - can we use range() and still have comments?
66STATE_MSG_HEADER = 0 # Still in the message header
67STATE_PATCH_SUBJECT = 1 # In patch subject (first line of log for a commit)
68STATE_PATCH_HEADER = 2 # In patch header (after the subject)
69STATE_DIFFS = 3 # In the diff part (past --- line)
70
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -040071
Simon Glass26132882012-01-14 15:12:45 +000072class PatchStream:
73 """Class for detecting/injecting tags in a patch or series of patches
74
75 We support processing the output of 'git log' to read out the tags we
76 are interested in. We can also process a patch file in order to remove
77 unwanted tags or inject additional ones. These correspond to the two
78 phases of processing.
Simon Glassda1a6ec2025-03-28 07:02:20 -060079
80 Args:
81 keep_change_id (bool): Keep the Change-Id tag
82 insert_base_commit (bool): True to add the base commit to the end
Simon Glass26132882012-01-14 15:12:45 +000083 """
Simon Glassda1a6ec2025-03-28 07:02:20 -060084 def __init__(self, series, is_log=False, keep_change_id=False,
85 insert_base_commit=False):
Simon Glass26132882012-01-14 15:12:45 +000086 self.skip_blank = False # True to skip a single blank line
87 self.found_test = False # Found a TEST= line
Sean Anderson48f46d62020-05-04 16:28:34 -040088 self.lines_after_test = 0 # Number of lines found after TEST=
Simon Glass26132882012-01-14 15:12:45 +000089 self.linenum = 1 # Output line number we are up to
90 self.in_section = None # Name of start...END section we are in
91 self.notes = [] # Series notes
92 self.section = [] # The current section...END section
93 self.series = series # Info about the patch series
94 self.is_log = is_log # True if indent like git log
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -040095 self.keep_change_id = keep_change_id # True to keep Change-Id tags
Sean Anderson48f46d62020-05-04 16:28:34 -040096 self.in_change = None # Name of the change list we are in
97 self.change_version = 0 # Non-zero if we are in a change list
Sean Anderson1a32f922020-05-04 16:28:35 -040098 self.change_lines = [] # Lines of the current change
Simon Glass26132882012-01-14 15:12:45 +000099 self.blank_count = 0 # Number of blank lines stored up
100 self.state = STATE_MSG_HEADER # What state are we in?
Simon Glass26132882012-01-14 15:12:45 +0000101 self.commit = None # Current commit
Simon Glass2112d072020-10-29 21:46:38 -0600102 # List of unquoted test blocks, each a list of str lines
103 self.snippets = []
Simon Glassda8a2922020-10-29 21:46:37 -0600104 self.cur_diff = None # Last 'diff' line seen (str)
105 self.cur_line = None # Last context (@@) line seen (str)
Simon Glass2112d072020-10-29 21:46:38 -0600106 self.recent_diff = None # 'diff' line for current snippet (str)
107 self.recent_line = None # '@@' line for current snippet (str)
Simon Glassda8a2922020-10-29 21:46:37 -0600108 self.recent_quoted = collections.deque([], 5)
109 self.recent_unquoted = queue.Queue()
110 self.was_quoted = None
Simon Glassda1a6ec2025-03-28 07:02:20 -0600111 self.insert_base_commit = insert_base_commit
Simon Glassd09a2a52025-05-08 06:38:49 +0200112 self.lines = [] # All lines in a commit message
113 self.msg = None # Full commit message including subject
Simon Glass26132882012-01-14 15:12:45 +0000114
Simon Glass06202d62020-10-29 21:46:27 -0600115 @staticmethod
116 def process_text(text, is_comment=False):
117 """Process some text through this class using a default Commit/Series
118
119 Args:
120 text (str): Text to parse
121 is_comment (bool): True if this is a comment rather than a patch.
122 If True, PatchStream doesn't expect a patch subject at the
123 start, but jumps straight into the body
124
125 Returns:
126 PatchStream: object with results
127 """
128 pstrm = PatchStream(Series())
129 pstrm.commit = commit.Commit(None)
130 infd = io.StringIO(text)
131 outfd = io.StringIO()
132 if is_comment:
133 pstrm.state = STATE_PATCH_HEADER
134 pstrm.process_stream(infd, outfd)
135 return pstrm
136
Simon Glassb5800392020-10-29 21:46:23 -0600137 def _add_warn(self, warn):
Simon Glass6d00f6c2020-10-29 21:46:24 -0600138 """Add a new warning to report to the user about the current commit
139
140 The new warning is added to the current commit if not already present.
Simon Glassb5800392020-10-29 21:46:23 -0600141
142 Args:
143 warn (str): Warning to report
Simon Glass6d00f6c2020-10-29 21:46:24 -0600144
145 Raises:
146 ValueError: Warning is generated with no commit associated
Simon Glassb5800392020-10-29 21:46:23 -0600147 """
Simon Glass6d00f6c2020-10-29 21:46:24 -0600148 if not self.commit:
Simon Glassb3106562021-03-22 18:01:42 +1300149 print('Warning outside commit: %s' % warn)
150 elif warn not in self.commit.warn:
Simon Glass6d00f6c2020-10-29 21:46:24 -0600151 self.commit.warn.append(warn)
Simon Glassb5800392020-10-29 21:46:23 -0600152
Simon Glass93f61c02020-10-29 21:46:19 -0600153 def _add_to_series(self, line, name, value):
Simon Glass26132882012-01-14 15:12:45 +0000154 """Add a new Series-xxx tag.
155
156 When a Series-xxx tag is detected, we come here to record it, if we
157 are scanning a 'git log'.
158
159 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600160 line (str): Source line containing tag (useful for debug/error
161 messages)
162 name (str): Tag name (part after 'Series-')
163 value (str): Tag value (part after 'Series-xxx: ')
Simon Glass26132882012-01-14 15:12:45 +0000164 """
165 if name == 'notes':
166 self.in_section = name
167 self.skip_blank = False
168 if self.is_log:
Simon Glass8093a8f2020-10-29 21:46:25 -0600169 warn = self.series.AddTag(self.commit, line, name, value)
170 if warn:
171 self.commit.warn.append(warn)
Simon Glass26132882012-01-14 15:12:45 +0000172
Simon Glass5f6caa52020-10-29 21:46:21 -0600173 def _add_to_commit(self, name):
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100174 """Add a new Commit-xxx tag.
175
176 When a Commit-xxx tag is detected, we come here to record it.
177
178 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600179 name (str): Tag name (part after 'Commit-')
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100180 """
181 if name == 'notes':
182 self.in_section = 'commit-' + name
183 self.skip_blank = False
184
Simon Glass93f61c02020-10-29 21:46:19 -0600185 def _add_commit_rtag(self, rtag_type, who):
Simon Glass08e91be2020-07-05 21:41:57 -0600186 """Add a response tag to the current commit
187
188 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600189 rtag_type (str): rtag type (e.g. 'Reviewed-by')
190 who (str): Person who gave that rtag, e.g.
191 'Fred Bloggs <fred@bloggs.org>'
Simon Glass08e91be2020-07-05 21:41:57 -0600192 """
Simon Glass530ac272022-01-29 14:14:07 -0700193 self.commit.add_rtag(rtag_type, who)
Simon Glass08e91be2020-07-05 21:41:57 -0600194
Simon Glassd09a2a52025-05-08 06:38:49 +0200195 def _close_commit(self, skip_last_line):
196 """Save the current commit into our commit list, and reset our state
197
198 Args:
199 skip_last_line (bool): True to omit the final line of self.lines
200 when building the commit message. This is normally the blank
201 line between two commits, except at the end of the log, where
202 there is no blank line
203 """
Simon Glass26132882012-01-14 15:12:45 +0000204 if self.commit and self.is_log:
Simon Glassd09a2a52025-05-08 06:38:49 +0200205 # Skip the blank line before the subject
206 lines = self.lines[:-1] if skip_last_line else self.lines
207 self.commit.msg = '\n'.join(lines[1:]) + '\n'
Simon Glass26132882012-01-14 15:12:45 +0000208 self.series.AddCommit(self.commit)
209 self.commit = None
Simon Glassd09a2a52025-05-08 06:38:49 +0200210 self.lines = []
Bin Mengd23121c2016-06-26 23:24:30 -0700211 # If 'END' is missing in a 'Cover-letter' section, and that section
212 # happens to show up at the very end of the commit message, this is
213 # the chance for us to fix it up.
214 if self.in_section == 'cover' and self.is_log:
215 self.series.cover = self.section
216 self.in_section = None
217 self.skip_blank = True
218 self.section = []
Simon Glass26132882012-01-14 15:12:45 +0000219
Simon Glassda8a2922020-10-29 21:46:37 -0600220 self.cur_diff = None
221 self.recent_diff = None
222 self.recent_line = None
223
Simon Glass93f61c02020-10-29 21:46:19 -0600224 def _parse_version(self, value, line):
Sean Anderson48f46d62020-05-04 16:28:34 -0400225 """Parse a version from a *-changes tag
226
227 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600228 value (str): Tag value (part after 'xxx-changes: '
229 line (str): Source line containing tag
Sean Anderson48f46d62020-05-04 16:28:34 -0400230
231 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600232 int: The version as an integer
233
234 Raises:
235 ValueError: the value cannot be converted
Sean Anderson48f46d62020-05-04 16:28:34 -0400236 """
237 try:
238 return int(value)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600239 except ValueError:
Sean Anderson48f46d62020-05-04 16:28:34 -0400240 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassc42b1d02020-10-29 21:46:17 -0600241 (self.commit.hash, line))
Sean Anderson48f46d62020-05-04 16:28:34 -0400242
Simon Glass93f61c02020-10-29 21:46:19 -0600243 def _finalise_change(self):
244 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson1a32f922020-05-04 16:28:35 -0400245 if not self.change_lines:
246 return
247 change = '\n'.join(self.change_lines)
248
249 if self.in_change == 'Series':
250 self.series.AddChange(self.change_version, self.commit, change)
251 elif self.in_change == 'Cover':
252 self.series.AddChange(self.change_version, None, change)
253 elif self.in_change == 'Commit':
Simon Glass530ac272022-01-29 14:14:07 -0700254 self.commit.add_change(self.change_version, change)
Sean Anderson1a32f922020-05-04 16:28:35 -0400255 self.change_lines = []
256
Simon Glassda8a2922020-10-29 21:46:37 -0600257 def _finalise_snippet(self):
258 """Finish off a snippet and add it to the list
259
260 This is called when we get to the end of a snippet, i.e. the we enter
261 the next block of quoted text:
262
263 This is a comment from someone.
264
265 Something else
266
267 > Now we have some code <----- end of snippet
268 > more code
269
270 Now a comment about the above code
271
272 This adds the snippet to our list
273 """
274 quoted_lines = []
275 while self.recent_quoted:
276 quoted_lines.append(self.recent_quoted.popleft())
277 unquoted_lines = []
278 valid = False
279 while not self.recent_unquoted.empty():
280 text = self.recent_unquoted.get()
281 if not (text.startswith('On ') and text.endswith('wrote:')):
282 unquoted_lines.append(text)
283 if text:
284 valid = True
285 if valid:
286 lines = []
287 if self.recent_diff:
288 lines.append('> File: %s' % self.recent_diff)
289 if self.recent_line:
290 out = '> Line: %s / %s' % self.recent_line[:2]
291 if self.recent_line[2]:
292 out += ': %s' % self.recent_line[2]
293 lines.append(out)
294 lines += quoted_lines + unquoted_lines
295 if lines:
296 self.snippets.append(lines)
297
Simon Glass93f61c02020-10-29 21:46:19 -0600298 def process_line(self, line):
Simon Glass26132882012-01-14 15:12:45 +0000299 """Process a single line of a patch file or commit log
300
301 This process a line and returns a list of lines to output. The list
302 may be empty or may contain multiple output lines.
303
304 This is where all the complicated logic is located. The class's
305 state is used to move between different states and detect things
306 properly.
307
308 We can be in one of two modes:
309 self.is_log == True: This is 'git log' mode, where most output is
310 indented by 4 characters and we are scanning for tags
311
312 self.is_log == False: This is 'patch' mode, where we already have
313 all the tags, and are processing patches to remove junk we
314 don't want, and add things we think are required.
315
316 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600317 line (str): text line to process
Simon Glass26132882012-01-14 15:12:45 +0000318
319 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600320 list: list of output lines, or [] if nothing should be output
321
322 Raises:
323 ValueError: a fatal error occurred while parsing, e.g. an END
324 without a starting tag, or two commits with two change IDs
Simon Glass26132882012-01-14 15:12:45 +0000325 """
326 # Initially we have no output. Prepare the input line string
327 out = []
328 line = line.rstrip('\n')
Scott Wood616a0ab2014-09-25 14:30:46 -0500329
Simon Glass06022262020-10-29 21:46:18 -0600330 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood616a0ab2014-09-25 14:30:46 -0500331
Simon Glass26132882012-01-14 15:12:45 +0000332 if self.is_log:
333 if line[:4] == ' ':
334 line = line[4:]
335
336 # Handle state transition and skipping blank lines
Simon Glass06022262020-10-29 21:46:18 -0600337 series_tag_match = RE_SERIES_TAG.match(line)
338 change_id_match = RE_CHANGE_ID.match(line)
339 commit_tag_match = RE_COMMIT_TAG.match(line)
340 cover_match = RE_COVER.match(line)
341 signoff_match = RE_SIGNOFF.match(line)
342 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glassda8a2922020-10-29 21:46:37 -0600343 diff_match = RE_DIFF.match(line)
344 line_match = RE_LINE.match(line)
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200345 invalid_match = RE_INV_TAG.match(line)
Simon Glass26132882012-01-14 15:12:45 +0000346 tag_match = None
347 if self.state == STATE_PATCH_HEADER:
Simon Glass06022262020-10-29 21:46:18 -0600348 tag_match = RE_TAG.match(line)
Simon Glass26132882012-01-14 15:12:45 +0000349 is_blank = not line.strip()
350 if is_blank:
351 if (self.state == STATE_MSG_HEADER
352 or self.state == STATE_PATCH_SUBJECT):
353 self.state += 1
354
355 # We don't have a subject in the text stream of patch files
356 # It has its own line with a Subject: tag
357 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
358 self.state += 1
359 elif commit_match:
360 self.state = STATE_MSG_HEADER
Simon Glassd09a2a52025-05-08 06:38:49 +0200361 if self.state != STATE_MSG_HEADER:
362 self.lines.append(line)
Simon Glass26132882012-01-14 15:12:45 +0000363
Bin Meng9228cc22016-06-26 23:24:32 -0700364 # If a tag is detected, or a new commit starts
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700365 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson48f46d62020-05-04 16:28:34 -0400366 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng50ffc982016-06-26 23:24:31 -0700367 # but we are already in a section, this means 'END' is missing
368 # for that section, fix it up.
Bin Mengf83649f2016-06-26 23:24:29 -0700369 if self.in_section:
Simon Glassb5800392020-10-29 21:46:23 -0600370 self._add_warn("Missing 'END' in section '%s'" % self.in_section)
Bin Mengf83649f2016-06-26 23:24:29 -0700371 if self.in_section == 'cover':
372 self.series.cover = self.section
373 elif self.in_section == 'notes':
374 if self.is_log:
375 self.series.notes += self.section
376 elif self.in_section == 'commit-notes':
377 if self.is_log:
378 self.commit.notes += self.section
379 else:
Simon Glassb5800392020-10-29 21:46:23 -0600380 # This should not happen
381 raise ValueError("Unknown section '%s'" % self.in_section)
Bin Mengf83649f2016-06-26 23:24:29 -0700382 self.in_section = None
383 self.skip_blank = True
384 self.section = []
Bin Meng50ffc982016-06-26 23:24:31 -0700385 # but we are already in a change list, that means a blank line
386 # is missing, fix it up.
387 if self.in_change:
Simon Glassb5800392020-10-29 21:46:23 -0600388 self._add_warn("Missing 'blank line' in section '%s-changes'" %
389 self.in_change)
Simon Glass93f61c02020-10-29 21:46:19 -0600390 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400391 self.in_change = None
392 self.change_version = 0
Bin Mengf83649f2016-06-26 23:24:29 -0700393
Simon Glass26132882012-01-14 15:12:45 +0000394 # If we are in a section, keep collecting lines until we see END
395 if self.in_section:
396 if line == 'END':
397 if self.in_section == 'cover':
398 self.series.cover = self.section
399 elif self.in_section == 'notes':
400 if self.is_log:
401 self.series.notes += self.section
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100402 elif self.in_section == 'commit-notes':
403 if self.is_log:
404 self.commit.notes += self.section
Simon Glass26132882012-01-14 15:12:45 +0000405 else:
Simon Glassb5800392020-10-29 21:46:23 -0600406 # This should not happen
407 raise ValueError("Unknown section '%s'" % self.in_section)
Simon Glass26132882012-01-14 15:12:45 +0000408 self.in_section = None
409 self.skip_blank = True
410 self.section = []
411 else:
412 self.section.append(line)
413
Patrick Delaunay2870c1c2020-07-02 19:08:24 +0200414 # If we are not in a section, it is an unexpected END
415 elif line == 'END':
Simon Glassc42b1d02020-10-29 21:46:17 -0600416 raise ValueError("'END' wihout section")
Patrick Delaunay2870c1c2020-07-02 19:08:24 +0200417
Simon Glass26132882012-01-14 15:12:45 +0000418 # Detect the commit subject
419 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
420 self.commit.subject = line
421
422 # Detect the tags we want to remove, and skip blank lines
Simon Glass06022262020-10-29 21:46:18 -0600423 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass26132882012-01-14 15:12:45 +0000424 self.skip_blank = True
425
426 # TEST= should be the last thing in the commit, so remove
427 # everything after it
428 if line.startswith('TEST='):
429 self.found_test = True
430 elif self.skip_blank and is_blank:
431 self.skip_blank = False
432
Sean Anderson48f46d62020-05-04 16:28:34 -0400433 # Detect Cover-xxx tags
Bin Mengd0bc03c2016-06-26 23:24:28 -0700434 elif cover_match:
Sean Anderson48f46d62020-05-04 16:28:34 -0400435 name = cover_match.group(1)
436 value = cover_match.group(2)
437 if name == 'letter':
438 self.in_section = 'cover'
439 self.skip_blank = False
440 elif name == 'letter-cc':
Simon Glass93f61c02020-10-29 21:46:19 -0600441 self._add_to_series(line, 'cover-cc', value)
Sean Anderson48f46d62020-05-04 16:28:34 -0400442 elif name == 'changes':
443 self.in_change = 'Cover'
Simon Glass93f61c02020-10-29 21:46:19 -0600444 self.change_version = self._parse_version(value, line)
Simon Glassc72f3da2013-03-20 16:43:00 +0000445
Simon Glass26132882012-01-14 15:12:45 +0000446 # If we are in a change list, key collected lines until a blank one
447 elif self.in_change:
448 if is_blank:
449 # Blank line ends this change list
Simon Glass93f61c02020-10-29 21:46:19 -0600450 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400451 self.in_change = None
452 self.change_version = 0
Simon Glass46b34212014-04-20 10:50:14 -0600453 elif line == '---':
Simon Glass93f61c02020-10-29 21:46:19 -0600454 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400455 self.in_change = None
456 self.change_version = 0
Simon Glass93f61c02020-10-29 21:46:19 -0600457 out = self.process_line(line)
Sean Anderson1a32f922020-05-04 16:28:35 -0400458 elif self.is_log:
459 if not leading_whitespace_match:
Simon Glass93f61c02020-10-29 21:46:19 -0600460 self._finalise_change()
Sean Anderson1a32f922020-05-04 16:28:35 -0400461 self.change_lines.append(line)
Simon Glass26132882012-01-14 15:12:45 +0000462 self.skip_blank = False
463
464 # Detect Series-xxx tags
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100465 elif series_tag_match:
466 name = series_tag_match.group(1)
467 value = series_tag_match.group(2)
Simon Glass26132882012-01-14 15:12:45 +0000468 if name == 'changes':
469 # value is the version number: e.g. 1, or 2
Sean Anderson48f46d62020-05-04 16:28:34 -0400470 self.in_change = 'Series'
Simon Glass93f61c02020-10-29 21:46:19 -0600471 self.change_version = self._parse_version(value, line)
Simon Glass26132882012-01-14 15:12:45 +0000472 else:
Simon Glass93f61c02020-10-29 21:46:19 -0600473 self._add_to_series(line, name, value)
Simon Glass26132882012-01-14 15:12:45 +0000474 self.skip_blank = True
475
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700476 # Detect Change-Id tags
477 elif change_id_match:
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -0400478 if self.keep_change_id:
479 out = [line]
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700480 value = change_id_match.group(1)
481 if self.is_log:
482 if self.commit.change_id:
Simon Glassc42b1d02020-10-29 21:46:17 -0600483 raise ValueError(
Simon Glass718f1a82020-11-03 13:54:11 -0700484 "%s: Two Change-Ids: '%s' vs. '%s'" %
485 (self.commit.hash, self.commit.change_id, value))
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700486 self.commit.change_id = value
487 self.skip_blank = True
488
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100489 # Detect Commit-xxx tags
490 elif commit_tag_match:
491 name = commit_tag_match.group(1)
492 value = commit_tag_match.group(2)
493 if name == 'notes':
Simon Glass5f6caa52020-10-29 21:46:21 -0600494 self._add_to_commit(name)
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100495 self.skip_blank = True
Sean Anderson48f46d62020-05-04 16:28:34 -0400496 elif name == 'changes':
497 self.in_change = 'Commit'
Simon Glass93f61c02020-10-29 21:46:19 -0600498 self.change_version = self._parse_version(value, line)
Sean Anderson25978092024-04-18 22:36:31 -0400499 elif name == 'cc':
500 self.commit.add_cc(value.split(','))
Sean Andersone45678c2024-04-18 22:36:32 -0400501 elif name == 'added-in':
502 version = self._parse_version(value, line)
503 self.commit.add_change(version, '- New')
504 self.series.AddChange(version, None, '- %s' %
505 self.commit.subject)
Patrick Delaunayed73b312020-07-02 19:52:54 +0200506 else:
Simon Glassb5800392020-10-29 21:46:23 -0600507 self._add_warn('Line %d: Ignoring Commit-%s' %
508 (self.linenum, name))
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100509
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200510 # Detect invalid tags
511 elif invalid_match:
512 raise ValueError("Line %d: Invalid tag = '%s'" %
513 (self.linenum, line))
514
Simon Glass26132882012-01-14 15:12:45 +0000515 # Detect the start of a new commit
516 elif commit_match:
Simon Glassd09a2a52025-05-08 06:38:49 +0200517 self._close_commit(True)
Simon Glassbaec6842014-10-15 02:27:00 -0600518 self.commit = commit.Commit(commit_match.group(1))
Simon Glass26132882012-01-14 15:12:45 +0000519
520 # Detect tags in the commit message
521 elif tag_match:
Simon Glass08e91be2020-07-05 21:41:57 -0600522 rtag_type, who = tag_match.groups()
Simon Glass93f61c02020-10-29 21:46:19 -0600523 self._add_commit_rtag(rtag_type, who)
Simon Glass26132882012-01-14 15:12:45 +0000524 # Remove Tested-by self, since few will take much notice
Simon Glass08e91be2020-07-05 21:41:57 -0600525 if (rtag_type == 'Tested-by' and
526 who.find(os.getenv('USER') + '@') != -1):
Simon Glass3b762cc2020-10-29 21:46:28 -0600527 self._add_warn("Ignoring '%s'" % line)
Simon Glass08e91be2020-07-05 21:41:57 -0600528 elif rtag_type == 'Patch-cc':
Simon Glass530ac272022-01-29 14:14:07 -0700529 self.commit.add_cc(who.split(','))
Simon Glass26132882012-01-14 15:12:45 +0000530 else:
Simon Glass27374d32014-08-28 09:43:38 -0600531 out = [line]
Simon Glass26132882012-01-14 15:12:45 +0000532
Simon Glass46b34212014-04-20 10:50:14 -0600533 # Suppress duplicate signoffs
534 elif signoff_match:
Simon Glassb0cd3412014-08-28 09:43:35 -0600535 if (self.is_log or not self.commit or
Simon Glass530ac272022-01-29 14:14:07 -0700536 self.commit.check_duplicate_signoff(signoff_match.group(1))):
Simon Glass46b34212014-04-20 10:50:14 -0600537 out = [line]
538
Simon Glass26132882012-01-14 15:12:45 +0000539 # Well that means this is an ordinary line
540 else:
Simon Glass26132882012-01-14 15:12:45 +0000541 # Look for space before tab
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600542 mat = RE_SPACE_BEFORE_TAB.match(line)
543 if mat:
Simon Glassb5800392020-10-29 21:46:23 -0600544 self._add_warn('Line %d/%d has space before tab' %
545 (self.linenum, mat.start()))
Simon Glass26132882012-01-14 15:12:45 +0000546
547 # OK, we have a valid non-blank line
548 out = [line]
549 self.linenum += 1
550 self.skip_blank = False
Simon Glassda8a2922020-10-29 21:46:37 -0600551
552 if diff_match:
553 self.cur_diff = diff_match.group(1)
554
555 # If this is quoted, keep recent lines
556 if not diff_match and self.linenum > 1 and line:
557 if line.startswith('>'):
558 if not self.was_quoted:
559 self._finalise_snippet()
560 self.recent_line = None
561 if not line_match:
562 self.recent_quoted.append(line)
563 self.was_quoted = True
564 self.recent_diff = self.cur_diff
565 else:
566 self.recent_unquoted.put(line)
567 self.was_quoted = False
568
569 if line_match:
570 self.recent_line = line_match.groups()
571
Simon Glass26132882012-01-14 15:12:45 +0000572 if self.state == STATE_DIFFS:
573 pass
574
575 # If this is the start of the diffs section, emit our tags and
576 # change log
577 elif line == '---':
578 self.state = STATE_DIFFS
579
Sean Anderson48f46d62020-05-04 16:28:34 -0400580 # Output the tags (signoff first), then change list
Simon Glass26132882012-01-14 15:12:45 +0000581 out = []
Simon Glass26132882012-01-14 15:12:45 +0000582 log = self.series.MakeChangeLog(self.commit)
Simon Glassb0cd3412014-08-28 09:43:35 -0600583 out += [line]
584 if self.commit:
585 out += self.commit.notes
586 out += [''] + log
Simon Glass26132882012-01-14 15:12:45 +0000587 elif self.found_test:
Simon Glass06022262020-10-29 21:46:18 -0600588 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass26132882012-01-14 15:12:45 +0000589 self.lines_after_test += 1
590
591 return out
592
Simon Glass93f61c02020-10-29 21:46:19 -0600593 def finalise(self):
Simon Glass26132882012-01-14 15:12:45 +0000594 """Close out processing of this patch stream"""
Simon Glassda8a2922020-10-29 21:46:37 -0600595 self._finalise_snippet()
Simon Glass93f61c02020-10-29 21:46:19 -0600596 self._finalise_change()
Simon Glassd09a2a52025-05-08 06:38:49 +0200597 self._close_commit(False)
Simon Glass26132882012-01-14 15:12:45 +0000598 if self.lines_after_test:
Simon Glassb5800392020-10-29 21:46:23 -0600599 self._add_warn('Found %d lines after TEST=' % self.lines_after_test)
Simon Glass26132882012-01-14 15:12:45 +0000600
Simon Glass93f61c02020-10-29 21:46:19 -0600601 def _write_message_id(self, outfd):
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700602 """Write the Message-Id into the output.
603
604 This is based on the Change-Id in the original patch, the version,
605 and the prefix.
606
607 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600608 outfd (io.IOBase): Output stream file object
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700609 """
610 if not self.commit.change_id:
611 return
612
613 # If the count is -1 we're testing, so use a fixed time
614 if self.commit.count == -1:
615 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
616 else:
617 time_now = datetime.datetime.now()
618
619 # In theory there is email.utils.make_msgid() which would be nice
620 # to use, but it already produces something way too long and thus
621 # will produce ugly commit lines if someone throws this into
622 # a "Link:" tag in the final commit. So (sigh) roll our own.
623
624 # Start with the time; presumably we wouldn't send the same series
625 # with the same Change-Id at the exact same second.
626 parts = [time_now.strftime("%Y%m%d%H%M%S")]
627
628 # These seem like they would be nice to include.
629 if 'prefix' in self.series:
630 parts.append(self.series['prefix'])
Sean Andersondc1cd132021-10-22 19:07:04 -0400631 if 'postfix' in self.series:
Simon Glass547cba62022-02-11 13:23:18 -0700632 parts.append(self.series['postfix'])
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700633 if 'version' in self.series:
634 parts.append("v%s" % self.series['version'])
635
636 parts.append(str(self.commit.count + 1))
637
638 # The Change-Id must be last, right before the @
639 parts.append(self.commit.change_id)
640
641 # Join parts together with "." and write it out.
642 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
643
Simon Glass93f61c02020-10-29 21:46:19 -0600644 def process_stream(self, infd, outfd):
Simon Glass26132882012-01-14 15:12:45 +0000645 """Copy a stream from infd to outfd, filtering out unwanting things.
646
647 This is used to process patch files one at a time.
648
649 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600650 infd (io.IOBase): Input stream file object
651 outfd (io.IOBase): Output stream file object
Simon Glass26132882012-01-14 15:12:45 +0000652 """
653 # Extract the filename from each diff, for nice warnings
654 fname = None
655 last_fname = None
656 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700657
Simon Glass93f61c02020-10-29 21:46:19 -0600658 self._write_message_id(outfd)
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700659
Simon Glass26132882012-01-14 15:12:45 +0000660 while True:
661 line = infd.readline()
662 if not line:
663 break
Simon Glass93f61c02020-10-29 21:46:19 -0600664 out = self.process_line(line)
Simon Glass26132882012-01-14 15:12:45 +0000665
666 # Try to detect blank lines at EOF
667 for line in out:
668 match = re_fname.match(line)
669 if match:
670 last_fname = fname
671 fname = match.group(1)
672 if line == '+':
673 self.blank_count += 1
674 else:
675 if self.blank_count and (line == '-- ' or match):
Simon Glassb5800392020-10-29 21:46:23 -0600676 self._add_warn("Found possible blank line(s) at end of file '%s'" %
677 last_fname)
Simon Glass26132882012-01-14 15:12:45 +0000678 outfd.write('+\n' * self.blank_count)
679 outfd.write(line + '\n')
680 self.blank_count = 0
Simon Glass93f61c02020-10-29 21:46:19 -0600681 self.finalise()
Simon Glassda1a6ec2025-03-28 07:02:20 -0600682 if self.insert_base_commit:
683 if self.series.base_commit:
684 print(f'base-commit: {self.series.base_commit.hash}',
685 file=outfd)
686 if self.series.branch:
687 print(f'branch: {self.series.branch}', file=outfd)
688
Simon Glass26132882012-01-14 15:12:45 +0000689
Simon Glassd0a0a582020-10-29 21:46:36 -0600690def insert_tags(msg, tags_to_emit):
691 """Add extra tags to a commit message
692
693 The tags are added after an existing block of tags if found, otherwise at
694 the end.
695
696 Args:
697 msg (str): Commit message
698 tags_to_emit (list): List of tags to emit, each a str
699
700 Returns:
701 (str) new message
702 """
703 out = []
704 done = False
705 emit_tags = False
Simon Glass6a222e62021-08-01 16:02:39 -0600706 emit_blank = False
Simon Glassd0a0a582020-10-29 21:46:36 -0600707 for line in msg.splitlines():
708 if not done:
709 signoff_match = RE_SIGNOFF.match(line)
710 tag_match = RE_TAG.match(line)
711 if tag_match or signoff_match:
712 emit_tags = True
713 if emit_tags and not tag_match and not signoff_match:
714 out += tags_to_emit
715 emit_tags = False
716 done = True
Simon Glass6a222e62021-08-01 16:02:39 -0600717 emit_blank = not (signoff_match or tag_match)
718 else:
719 emit_blank = line
Simon Glassd0a0a582020-10-29 21:46:36 -0600720 out.append(line)
721 if not done:
Simon Glass6a222e62021-08-01 16:02:39 -0600722 if emit_blank:
723 out.append('')
Simon Glassd0a0a582020-10-29 21:46:36 -0600724 out += tags_to_emit
725 return '\n'.join(out)
726
727def get_list(commit_range, git_dir=None, count=None):
728 """Get a log of a list of comments
729
730 This returns the output of 'git log' for the selected commits
731
732 Args:
733 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
734 git_dir (str): Path to git repositiory (None to use default)
735 count (int): Number of commits to list, or None for no limit
736
737 Returns
738 str: String containing the contents of the git log
739 """
Simon Glass761648b2022-01-29 14:14:11 -0700740 params = gitutil.log_cmd(commit_range, reverse=True, count=count,
Simon Glassd0a0a582020-10-29 21:46:36 -0600741 git_dir=git_dir)
Simon Glass51f55182025-02-03 09:26:45 -0700742 return command.run_one(*params, capture=True).stdout
Simon Glass26132882012-01-14 15:12:45 +0000743
Simon Glass93f61c02020-10-29 21:46:19 -0600744def get_metadata_for_list(commit_range, git_dir=None, count=None,
745 series=None, allow_overwrite=False):
Simon Glass26132882012-01-14 15:12:45 +0000746 """Reads out patch series metadata from the commits
747
748 This does a 'git log' on the relevant commits and pulls out the tags we
749 are interested in.
750
751 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600752 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
753 git_dir (str): Path to git repositiory (None to use default)
754 count (int): Number of commits to list, or None for no limit
755 series (Series): Object to add information into. By default a new series
Simon Glass680da3c2012-12-15 10:42:06 +0000756 is started.
Simon Glass06127f92020-10-29 21:46:22 -0600757 allow_overwrite (bool): Allow tags to overwrite an existing tag
758
Simon Glass680da3c2012-12-15 10:42:06 +0000759 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600760 Series: Object containing information about the commits.
Simon Glass26132882012-01-14 15:12:45 +0000761 """
Simon Glass4e5d6bc2014-09-05 19:00:19 -0600762 if not series:
763 series = Series()
Simon Glass359b55a62014-09-05 19:00:23 -0600764 series.allow_overwrite = allow_overwrite
Simon Glassd0a0a582020-10-29 21:46:36 -0600765 stdout = get_list(commit_range, git_dir, count)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600766 pst = PatchStream(series, is_log=True)
Simon Glass26132882012-01-14 15:12:45 +0000767 for line in stdout.splitlines():
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600768 pst.process_line(line)
769 pst.finalise()
Simon Glass26132882012-01-14 15:12:45 +0000770 return series
771
Simon Glass9b0406e2025-05-07 18:06:52 +0200772def get_metadata(branch, start, count, git_dir=None):
Simon Glass680da3c2012-12-15 10:42:06 +0000773 """Reads out patch series metadata from the commits
774
775 This does a 'git log' on the relevant commits and pulls out the tags we
776 are interested in.
777
778 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600779 branch (str): Branch to use (None for current branch)
780 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
781 count (int): Number of commits to list
782
783 Returns:
784 Series: Object containing information about the commits.
Simon Glass680da3c2012-12-15 10:42:06 +0000785 """
Simon Glass414f1e02025-02-27 12:27:30 -0700786 top = f"{branch if branch else 'HEAD'}~{start}"
Simon Glass9b0406e2025-05-07 18:06:52 +0200787 series = get_metadata_for_list(top, git_dir, count)
788 series.base_commit = commit.Commit(
789 gitutil.get_hash(f'{top}~{count}', git_dir))
Simon Glass414f1e02025-02-27 12:27:30 -0700790 series.branch = branch or gitutil.get_branch()
791 series.top = top
792 return series
Simon Glass680da3c2012-12-15 10:42:06 +0000793
Simon Glass93f61c02020-10-29 21:46:19 -0600794def get_metadata_for_test(text):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600795 """Process metadata from a file containing a git log. Used for tests
796
797 Args:
798 text:
Simon Glass06127f92020-10-29 21:46:22 -0600799
800 Returns:
801 Series: Object containing information about the commits.
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600802 """
803 series = Series()
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600804 pst = PatchStream(series, is_log=True)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600805 for line in text.splitlines():
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600806 pst.process_line(line)
807 pst.finalise()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600808 return series
809
Simon Glassda1a6ec2025-03-28 07:02:20 -0600810def fix_patch(backup_dir, fname, series, cmt, keep_change_id=False,
Simon Glass5750cc22025-05-07 18:02:47 +0200811 insert_base_commit=False, cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000812 """Fix up a patch file, by adding/removing as required.
813
814 We remove our tags from the patch file, insert changes lists, etc.
815 The patch file is processed in place, and overwritten.
816
817 A backup file is put into backup_dir (if not None).
818
819 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600820 backup_dir (str): Path to directory to use to backup the file
821 fname (str): Filename to patch file to process
822 series (Series): Series information about this patch set
823 cmt (Commit): Commit object for this patch file
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -0400824 keep_change_id (bool): Keep the Change-Id tag.
Simon Glassda1a6ec2025-03-28 07:02:20 -0600825 insert_base_commit (bool): True to add the base commit to the end
Simon Glass5750cc22025-05-07 18:02:47 +0200826 cwd (str): Directory containing filename, or None for current
Simon Glass06127f92020-10-29 21:46:22 -0600827
Simon Glass26132882012-01-14 15:12:45 +0000828 Return:
Simon Glass06127f92020-10-29 21:46:22 -0600829 list: A list of errors, each str, or [] if all ok.
Simon Glass26132882012-01-14 15:12:45 +0000830 """
Simon Glass5750cc22025-05-07 18:02:47 +0200831 fname = os.path.join(cwd or '', fname)
Simon Glass26132882012-01-14 15:12:45 +0000832 handle, tmpname = tempfile.mkstemp()
Simon Glassf544a2d2019-10-31 07:42:51 -0600833 outfd = os.fdopen(handle, 'w', encoding='utf-8')
834 infd = open(fname, 'r', encoding='utf-8')
Simon Glassda1a6ec2025-03-28 07:02:20 -0600835 pst = PatchStream(series, keep_change_id=keep_change_id,
836 insert_base_commit=insert_base_commit)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600837 pst.commit = cmt
838 pst.process_stream(infd, outfd)
Simon Glass26132882012-01-14 15:12:45 +0000839 infd.close()
840 outfd.close()
841
842 # Create a backup file if required
843 if backup_dir:
844 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
845 shutil.move(tmpname, fname)
Simon Glass6d00f6c2020-10-29 21:46:24 -0600846 return cmt.warn
Simon Glass26132882012-01-14 15:12:45 +0000847
Simon Glass5750cc22025-05-07 18:02:47 +0200848def fix_patches(series, fnames, keep_change_id=False, insert_base_commit=False,
849 cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000850 """Fix up a list of patches identified by filenames
851
852 The patch files are processed in place, and overwritten.
853
854 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600855 series (Series): The Series object
856 fnames (:type: list of str): List of patch files to process
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -0400857 keep_change_id (bool): Keep the Change-Id tag.
Simon Glassda1a6ec2025-03-28 07:02:20 -0600858 insert_base_commit (bool): True to add the base commit to the end
Simon Glass5750cc22025-05-07 18:02:47 +0200859 cwd (str): Directory containing the patch files, or None for current
Simon Glass26132882012-01-14 15:12:45 +0000860 """
861 # Current workflow creates patches, so we shouldn't need a backup
862 backup_dir = None #tempfile.mkdtemp('clean-patch')
863 count = 0
864 for fname in fnames:
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600865 cmt = series.commits[count]
866 cmt.patch = fname
867 cmt.count = count
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -0400868 result = fix_patch(backup_dir, fname, series, cmt,
Simon Glassda1a6ec2025-03-28 07:02:20 -0600869 keep_change_id=keep_change_id,
Simon Glass5750cc22025-05-07 18:02:47 +0200870 insert_base_commit=insert_base_commit, cwd=cwd)
Simon Glass26132882012-01-14 15:12:45 +0000871 if result:
Simon Glass891316b2020-10-29 21:46:30 -0600872 print('%d warning%s for %s:' %
873 (len(result), 's' if len(result) > 1 else '', fname))
Simon Glass26132882012-01-14 15:12:45 +0000874 for warn in result:
Simon Glass891316b2020-10-29 21:46:30 -0600875 print('\t%s' % warn)
876 print()
Simon Glass26132882012-01-14 15:12:45 +0000877 count += 1
Simon Glass891316b2020-10-29 21:46:30 -0600878 print('Cleaned %d patch%s' % (count, 'es' if count > 1 else ''))
Simon Glass26132882012-01-14 15:12:45 +0000879
Simon Glass5750cc22025-05-07 18:02:47 +0200880def insert_cover_letter(fname, series, count, cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000881 """Inserts a cover letter with the required info into patch 0
882
883 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600884 fname (str): Input / output filename of the cover letter file
885 series (Series): Series object
886 count (int): Number of patches in the series
Simon Glass5750cc22025-05-07 18:02:47 +0200887 cwd (str): Directory containing filename, or None for current
Simon Glass26132882012-01-14 15:12:45 +0000888 """
Simon Glass5750cc22025-05-07 18:02:47 +0200889 fname = os.path.join(cwd or '', fname)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600890 fil = open(fname, 'r')
891 lines = fil.readlines()
892 fil.close()
Simon Glass26132882012-01-14 15:12:45 +0000893
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600894 fil = open(fname, 'w')
Simon Glass26132882012-01-14 15:12:45 +0000895 text = series.cover
896 prefix = series.GetPatchPrefix()
897 for line in lines:
898 if line.startswith('Subject:'):
Wu, Josh393aaa22015-04-03 10:51:17 +0800899 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
900 zero_repeat = int(math.log10(count)) + 1
901 zero = '0' * zero_repeat
902 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass26132882012-01-14 15:12:45 +0000903
904 # Insert our cover letter
905 elif line.startswith('*** BLURB HERE ***'):
906 # First the blurb test
907 line = '\n'.join(text[1:]) + '\n'
908 if series.get('notes'):
909 line += '\n'.join(series.notes) + '\n'
910
911 # Now the change list
912 out = series.MakeChangeLog(None)
913 line += '\n' + '\n'.join(out)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600914 fil.write(line)
Simon Glass414f1e02025-02-27 12:27:30 -0700915
916 # Insert the base commit and branch
917 if series.base_commit:
918 print(f'base-commit: {series.base_commit.hash}', file=fil)
919 if series.branch:
920 print(f'branch: {series.branch}', file=fil)
921
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600922 fil.close()