blob: 3ec06a6b5f5801638beca914f0ed632af74b1c39 [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 Glass26132882012-01-14 15:12:45 +0000112
Simon Glass06202d62020-10-29 21:46:27 -0600113 @staticmethod
114 def process_text(text, is_comment=False):
115 """Process some text through this class using a default Commit/Series
116
117 Args:
118 text (str): Text to parse
119 is_comment (bool): True if this is a comment rather than a patch.
120 If True, PatchStream doesn't expect a patch subject at the
121 start, but jumps straight into the body
122
123 Returns:
124 PatchStream: object with results
125 """
126 pstrm = PatchStream(Series())
127 pstrm.commit = commit.Commit(None)
128 infd = io.StringIO(text)
129 outfd = io.StringIO()
130 if is_comment:
131 pstrm.state = STATE_PATCH_HEADER
132 pstrm.process_stream(infd, outfd)
133 return pstrm
134
Simon Glassb5800392020-10-29 21:46:23 -0600135 def _add_warn(self, warn):
Simon Glass6d00f6c2020-10-29 21:46:24 -0600136 """Add a new warning to report to the user about the current commit
137
138 The new warning is added to the current commit if not already present.
Simon Glassb5800392020-10-29 21:46:23 -0600139
140 Args:
141 warn (str): Warning to report
Simon Glass6d00f6c2020-10-29 21:46:24 -0600142
143 Raises:
144 ValueError: Warning is generated with no commit associated
Simon Glassb5800392020-10-29 21:46:23 -0600145 """
Simon Glass6d00f6c2020-10-29 21:46:24 -0600146 if not self.commit:
Simon Glassb3106562021-03-22 18:01:42 +1300147 print('Warning outside commit: %s' % warn)
148 elif warn not in self.commit.warn:
Simon Glass6d00f6c2020-10-29 21:46:24 -0600149 self.commit.warn.append(warn)
Simon Glassb5800392020-10-29 21:46:23 -0600150
Simon Glass93f61c02020-10-29 21:46:19 -0600151 def _add_to_series(self, line, name, value):
Simon Glass26132882012-01-14 15:12:45 +0000152 """Add a new Series-xxx tag.
153
154 When a Series-xxx tag is detected, we come here to record it, if we
155 are scanning a 'git log'.
156
157 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600158 line (str): Source line containing tag (useful for debug/error
159 messages)
160 name (str): Tag name (part after 'Series-')
161 value (str): Tag value (part after 'Series-xxx: ')
Simon Glass26132882012-01-14 15:12:45 +0000162 """
163 if name == 'notes':
164 self.in_section = name
165 self.skip_blank = False
166 if self.is_log:
Simon Glass8093a8f2020-10-29 21:46:25 -0600167 warn = self.series.AddTag(self.commit, line, name, value)
168 if warn:
169 self.commit.warn.append(warn)
Simon Glass26132882012-01-14 15:12:45 +0000170
Simon Glass5f6caa52020-10-29 21:46:21 -0600171 def _add_to_commit(self, name):
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100172 """Add a new Commit-xxx tag.
173
174 When a Commit-xxx tag is detected, we come here to record it.
175
176 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600177 name (str): Tag name (part after 'Commit-')
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100178 """
179 if name == 'notes':
180 self.in_section = 'commit-' + name
181 self.skip_blank = False
182
Simon Glass93f61c02020-10-29 21:46:19 -0600183 def _add_commit_rtag(self, rtag_type, who):
Simon Glass08e91be2020-07-05 21:41:57 -0600184 """Add a response tag to the current commit
185
186 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600187 rtag_type (str): rtag type (e.g. 'Reviewed-by')
188 who (str): Person who gave that rtag, e.g.
189 'Fred Bloggs <fred@bloggs.org>'
Simon Glass08e91be2020-07-05 21:41:57 -0600190 """
Simon Glass530ac272022-01-29 14:14:07 -0700191 self.commit.add_rtag(rtag_type, who)
Simon Glass08e91be2020-07-05 21:41:57 -0600192
Simon Glass93f61c02020-10-29 21:46:19 -0600193 def _close_commit(self):
Simon Glass26132882012-01-14 15:12:45 +0000194 """Save the current commit into our commit list, and reset our state"""
195 if self.commit and self.is_log:
196 self.series.AddCommit(self.commit)
197 self.commit = None
Bin Mengd23121c2016-06-26 23:24:30 -0700198 # If 'END' is missing in a 'Cover-letter' section, and that section
199 # happens to show up at the very end of the commit message, this is
200 # the chance for us to fix it up.
201 if self.in_section == 'cover' and self.is_log:
202 self.series.cover = self.section
203 self.in_section = None
204 self.skip_blank = True
205 self.section = []
Simon Glass26132882012-01-14 15:12:45 +0000206
Simon Glassda8a2922020-10-29 21:46:37 -0600207 self.cur_diff = None
208 self.recent_diff = None
209 self.recent_line = None
210
Simon Glass93f61c02020-10-29 21:46:19 -0600211 def _parse_version(self, value, line):
Sean Anderson48f46d62020-05-04 16:28:34 -0400212 """Parse a version from a *-changes tag
213
214 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600215 value (str): Tag value (part after 'xxx-changes: '
216 line (str): Source line containing tag
Sean Anderson48f46d62020-05-04 16:28:34 -0400217
218 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600219 int: The version as an integer
220
221 Raises:
222 ValueError: the value cannot be converted
Sean Anderson48f46d62020-05-04 16:28:34 -0400223 """
224 try:
225 return int(value)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600226 except ValueError:
Sean Anderson48f46d62020-05-04 16:28:34 -0400227 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassc42b1d02020-10-29 21:46:17 -0600228 (self.commit.hash, line))
Sean Anderson48f46d62020-05-04 16:28:34 -0400229
Simon Glass93f61c02020-10-29 21:46:19 -0600230 def _finalise_change(self):
231 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson1a32f922020-05-04 16:28:35 -0400232 if not self.change_lines:
233 return
234 change = '\n'.join(self.change_lines)
235
236 if self.in_change == 'Series':
237 self.series.AddChange(self.change_version, self.commit, change)
238 elif self.in_change == 'Cover':
239 self.series.AddChange(self.change_version, None, change)
240 elif self.in_change == 'Commit':
Simon Glass530ac272022-01-29 14:14:07 -0700241 self.commit.add_change(self.change_version, change)
Sean Anderson1a32f922020-05-04 16:28:35 -0400242 self.change_lines = []
243
Simon Glassda8a2922020-10-29 21:46:37 -0600244 def _finalise_snippet(self):
245 """Finish off a snippet and add it to the list
246
247 This is called when we get to the end of a snippet, i.e. the we enter
248 the next block of quoted text:
249
250 This is a comment from someone.
251
252 Something else
253
254 > Now we have some code <----- end of snippet
255 > more code
256
257 Now a comment about the above code
258
259 This adds the snippet to our list
260 """
261 quoted_lines = []
262 while self.recent_quoted:
263 quoted_lines.append(self.recent_quoted.popleft())
264 unquoted_lines = []
265 valid = False
266 while not self.recent_unquoted.empty():
267 text = self.recent_unquoted.get()
268 if not (text.startswith('On ') and text.endswith('wrote:')):
269 unquoted_lines.append(text)
270 if text:
271 valid = True
272 if valid:
273 lines = []
274 if self.recent_diff:
275 lines.append('> File: %s' % self.recent_diff)
276 if self.recent_line:
277 out = '> Line: %s / %s' % self.recent_line[:2]
278 if self.recent_line[2]:
279 out += ': %s' % self.recent_line[2]
280 lines.append(out)
281 lines += quoted_lines + unquoted_lines
282 if lines:
283 self.snippets.append(lines)
284
Simon Glass93f61c02020-10-29 21:46:19 -0600285 def process_line(self, line):
Simon Glass26132882012-01-14 15:12:45 +0000286 """Process a single line of a patch file or commit log
287
288 This process a line and returns a list of lines to output. The list
289 may be empty or may contain multiple output lines.
290
291 This is where all the complicated logic is located. The class's
292 state is used to move between different states and detect things
293 properly.
294
295 We can be in one of two modes:
296 self.is_log == True: This is 'git log' mode, where most output is
297 indented by 4 characters and we are scanning for tags
298
299 self.is_log == False: This is 'patch' mode, where we already have
300 all the tags, and are processing patches to remove junk we
301 don't want, and add things we think are required.
302
303 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600304 line (str): text line to process
Simon Glass26132882012-01-14 15:12:45 +0000305
306 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600307 list: list of output lines, or [] if nothing should be output
308
309 Raises:
310 ValueError: a fatal error occurred while parsing, e.g. an END
311 without a starting tag, or two commits with two change IDs
Simon Glass26132882012-01-14 15:12:45 +0000312 """
313 # Initially we have no output. Prepare the input line string
314 out = []
315 line = line.rstrip('\n')
Scott Wood616a0ab2014-09-25 14:30:46 -0500316
Simon Glass06022262020-10-29 21:46:18 -0600317 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood616a0ab2014-09-25 14:30:46 -0500318
Simon Glass26132882012-01-14 15:12:45 +0000319 if self.is_log:
320 if line[:4] == ' ':
321 line = line[4:]
322
323 # Handle state transition and skipping blank lines
Simon Glass06022262020-10-29 21:46:18 -0600324 series_tag_match = RE_SERIES_TAG.match(line)
325 change_id_match = RE_CHANGE_ID.match(line)
326 commit_tag_match = RE_COMMIT_TAG.match(line)
327 cover_match = RE_COVER.match(line)
328 signoff_match = RE_SIGNOFF.match(line)
329 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glassda8a2922020-10-29 21:46:37 -0600330 diff_match = RE_DIFF.match(line)
331 line_match = RE_LINE.match(line)
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200332 invalid_match = RE_INV_TAG.match(line)
Simon Glass26132882012-01-14 15:12:45 +0000333 tag_match = None
334 if self.state == STATE_PATCH_HEADER:
Simon Glass06022262020-10-29 21:46:18 -0600335 tag_match = RE_TAG.match(line)
Simon Glass26132882012-01-14 15:12:45 +0000336 is_blank = not line.strip()
337 if is_blank:
338 if (self.state == STATE_MSG_HEADER
339 or self.state == STATE_PATCH_SUBJECT):
340 self.state += 1
341
342 # We don't have a subject in the text stream of patch files
343 # It has its own line with a Subject: tag
344 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
345 self.state += 1
346 elif commit_match:
347 self.state = STATE_MSG_HEADER
348
Bin Meng9228cc22016-06-26 23:24:32 -0700349 # If a tag is detected, or a new commit starts
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700350 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson48f46d62020-05-04 16:28:34 -0400351 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng50ffc982016-06-26 23:24:31 -0700352 # but we are already in a section, this means 'END' is missing
353 # for that section, fix it up.
Bin Mengf83649f2016-06-26 23:24:29 -0700354 if self.in_section:
Simon Glassb5800392020-10-29 21:46:23 -0600355 self._add_warn("Missing 'END' in section '%s'" % self.in_section)
Bin Mengf83649f2016-06-26 23:24:29 -0700356 if self.in_section == 'cover':
357 self.series.cover = self.section
358 elif self.in_section == 'notes':
359 if self.is_log:
360 self.series.notes += self.section
361 elif self.in_section == 'commit-notes':
362 if self.is_log:
363 self.commit.notes += self.section
364 else:
Simon Glassb5800392020-10-29 21:46:23 -0600365 # This should not happen
366 raise ValueError("Unknown section '%s'" % self.in_section)
Bin Mengf83649f2016-06-26 23:24:29 -0700367 self.in_section = None
368 self.skip_blank = True
369 self.section = []
Bin Meng50ffc982016-06-26 23:24:31 -0700370 # but we are already in a change list, that means a blank line
371 # is missing, fix it up.
372 if self.in_change:
Simon Glassb5800392020-10-29 21:46:23 -0600373 self._add_warn("Missing 'blank line' in section '%s-changes'" %
374 self.in_change)
Simon Glass93f61c02020-10-29 21:46:19 -0600375 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400376 self.in_change = None
377 self.change_version = 0
Bin Mengf83649f2016-06-26 23:24:29 -0700378
Simon Glass26132882012-01-14 15:12:45 +0000379 # If we are in a section, keep collecting lines until we see END
380 if self.in_section:
381 if line == 'END':
382 if self.in_section == 'cover':
383 self.series.cover = self.section
384 elif self.in_section == 'notes':
385 if self.is_log:
386 self.series.notes += self.section
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100387 elif self.in_section == 'commit-notes':
388 if self.is_log:
389 self.commit.notes += self.section
Simon Glass26132882012-01-14 15:12:45 +0000390 else:
Simon Glassb5800392020-10-29 21:46:23 -0600391 # This should not happen
392 raise ValueError("Unknown section '%s'" % self.in_section)
Simon Glass26132882012-01-14 15:12:45 +0000393 self.in_section = None
394 self.skip_blank = True
395 self.section = []
396 else:
397 self.section.append(line)
398
Patrick Delaunay2870c1c2020-07-02 19:08:24 +0200399 # If we are not in a section, it is an unexpected END
400 elif line == 'END':
Simon Glassc42b1d02020-10-29 21:46:17 -0600401 raise ValueError("'END' wihout section")
Patrick Delaunay2870c1c2020-07-02 19:08:24 +0200402
Simon Glass26132882012-01-14 15:12:45 +0000403 # Detect the commit subject
404 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
405 self.commit.subject = line
406
407 # Detect the tags we want to remove, and skip blank lines
Simon Glass06022262020-10-29 21:46:18 -0600408 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass26132882012-01-14 15:12:45 +0000409 self.skip_blank = True
410
411 # TEST= should be the last thing in the commit, so remove
412 # everything after it
413 if line.startswith('TEST='):
414 self.found_test = True
415 elif self.skip_blank and is_blank:
416 self.skip_blank = False
417
Sean Anderson48f46d62020-05-04 16:28:34 -0400418 # Detect Cover-xxx tags
Bin Mengd0bc03c2016-06-26 23:24:28 -0700419 elif cover_match:
Sean Anderson48f46d62020-05-04 16:28:34 -0400420 name = cover_match.group(1)
421 value = cover_match.group(2)
422 if name == 'letter':
423 self.in_section = 'cover'
424 self.skip_blank = False
425 elif name == 'letter-cc':
Simon Glass93f61c02020-10-29 21:46:19 -0600426 self._add_to_series(line, 'cover-cc', value)
Sean Anderson48f46d62020-05-04 16:28:34 -0400427 elif name == 'changes':
428 self.in_change = 'Cover'
Simon Glass93f61c02020-10-29 21:46:19 -0600429 self.change_version = self._parse_version(value, line)
Simon Glassc72f3da2013-03-20 16:43:00 +0000430
Simon Glass26132882012-01-14 15:12:45 +0000431 # If we are in a change list, key collected lines until a blank one
432 elif self.in_change:
433 if is_blank:
434 # Blank line ends this change list
Simon Glass93f61c02020-10-29 21:46:19 -0600435 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400436 self.in_change = None
437 self.change_version = 0
Simon Glass46b34212014-04-20 10:50:14 -0600438 elif line == '---':
Simon Glass93f61c02020-10-29 21:46:19 -0600439 self._finalise_change()
Sean Anderson48f46d62020-05-04 16:28:34 -0400440 self.in_change = None
441 self.change_version = 0
Simon Glass93f61c02020-10-29 21:46:19 -0600442 out = self.process_line(line)
Sean Anderson1a32f922020-05-04 16:28:35 -0400443 elif self.is_log:
444 if not leading_whitespace_match:
Simon Glass93f61c02020-10-29 21:46:19 -0600445 self._finalise_change()
Sean Anderson1a32f922020-05-04 16:28:35 -0400446 self.change_lines.append(line)
Simon Glass26132882012-01-14 15:12:45 +0000447 self.skip_blank = False
448
449 # Detect Series-xxx tags
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100450 elif series_tag_match:
451 name = series_tag_match.group(1)
452 value = series_tag_match.group(2)
Simon Glass26132882012-01-14 15:12:45 +0000453 if name == 'changes':
454 # value is the version number: e.g. 1, or 2
Sean Anderson48f46d62020-05-04 16:28:34 -0400455 self.in_change = 'Series'
Simon Glass93f61c02020-10-29 21:46:19 -0600456 self.change_version = self._parse_version(value, line)
Simon Glass26132882012-01-14 15:12:45 +0000457 else:
Simon Glass93f61c02020-10-29 21:46:19 -0600458 self._add_to_series(line, name, value)
Simon Glass26132882012-01-14 15:12:45 +0000459 self.skip_blank = True
460
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700461 # Detect Change-Id tags
462 elif change_id_match:
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -0400463 if self.keep_change_id:
464 out = [line]
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700465 value = change_id_match.group(1)
466 if self.is_log:
467 if self.commit.change_id:
Simon Glassc42b1d02020-10-29 21:46:17 -0600468 raise ValueError(
Simon Glass718f1a82020-11-03 13:54:11 -0700469 "%s: Two Change-Ids: '%s' vs. '%s'" %
470 (self.commit.hash, self.commit.change_id, value))
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700471 self.commit.change_id = value
472 self.skip_blank = True
473
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100474 # Detect Commit-xxx tags
475 elif commit_tag_match:
476 name = commit_tag_match.group(1)
477 value = commit_tag_match.group(2)
478 if name == 'notes':
Simon Glass5f6caa52020-10-29 21:46:21 -0600479 self._add_to_commit(name)
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100480 self.skip_blank = True
Sean Anderson48f46d62020-05-04 16:28:34 -0400481 elif name == 'changes':
482 self.in_change = 'Commit'
Simon Glass93f61c02020-10-29 21:46:19 -0600483 self.change_version = self._parse_version(value, line)
Sean Anderson25978092024-04-18 22:36:31 -0400484 elif name == 'cc':
485 self.commit.add_cc(value.split(','))
Sean Andersone45678c2024-04-18 22:36:32 -0400486 elif name == 'added-in':
487 version = self._parse_version(value, line)
488 self.commit.add_change(version, '- New')
489 self.series.AddChange(version, None, '- %s' %
490 self.commit.subject)
Patrick Delaunayed73b312020-07-02 19:52:54 +0200491 else:
Simon Glassb5800392020-10-29 21:46:23 -0600492 self._add_warn('Line %d: Ignoring Commit-%s' %
493 (self.linenum, name))
Albert ARIBAUDd880efd2013-11-12 11:14:41 +0100494
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200495 # Detect invalid tags
496 elif invalid_match:
497 raise ValueError("Line %d: Invalid tag = '%s'" %
498 (self.linenum, line))
499
Simon Glass26132882012-01-14 15:12:45 +0000500 # Detect the start of a new commit
501 elif commit_match:
Simon Glass93f61c02020-10-29 21:46:19 -0600502 self._close_commit()
Simon Glassbaec6842014-10-15 02:27:00 -0600503 self.commit = commit.Commit(commit_match.group(1))
Simon Glass26132882012-01-14 15:12:45 +0000504
505 # Detect tags in the commit message
506 elif tag_match:
Simon Glass08e91be2020-07-05 21:41:57 -0600507 rtag_type, who = tag_match.groups()
Simon Glass93f61c02020-10-29 21:46:19 -0600508 self._add_commit_rtag(rtag_type, who)
Simon Glass26132882012-01-14 15:12:45 +0000509 # Remove Tested-by self, since few will take much notice
Simon Glass08e91be2020-07-05 21:41:57 -0600510 if (rtag_type == 'Tested-by' and
511 who.find(os.getenv('USER') + '@') != -1):
Simon Glass3b762cc2020-10-29 21:46:28 -0600512 self._add_warn("Ignoring '%s'" % line)
Simon Glass08e91be2020-07-05 21:41:57 -0600513 elif rtag_type == 'Patch-cc':
Simon Glass530ac272022-01-29 14:14:07 -0700514 self.commit.add_cc(who.split(','))
Simon Glass26132882012-01-14 15:12:45 +0000515 else:
Simon Glass27374d32014-08-28 09:43:38 -0600516 out = [line]
Simon Glass26132882012-01-14 15:12:45 +0000517
Simon Glass46b34212014-04-20 10:50:14 -0600518 # Suppress duplicate signoffs
519 elif signoff_match:
Simon Glassb0cd3412014-08-28 09:43:35 -0600520 if (self.is_log or not self.commit or
Simon Glass530ac272022-01-29 14:14:07 -0700521 self.commit.check_duplicate_signoff(signoff_match.group(1))):
Simon Glass46b34212014-04-20 10:50:14 -0600522 out = [line]
523
Simon Glass26132882012-01-14 15:12:45 +0000524 # Well that means this is an ordinary line
525 else:
Simon Glass26132882012-01-14 15:12:45 +0000526 # Look for space before tab
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600527 mat = RE_SPACE_BEFORE_TAB.match(line)
528 if mat:
Simon Glassb5800392020-10-29 21:46:23 -0600529 self._add_warn('Line %d/%d has space before tab' %
530 (self.linenum, mat.start()))
Simon Glass26132882012-01-14 15:12:45 +0000531
532 # OK, we have a valid non-blank line
533 out = [line]
534 self.linenum += 1
535 self.skip_blank = False
Simon Glassda8a2922020-10-29 21:46:37 -0600536
537 if diff_match:
538 self.cur_diff = diff_match.group(1)
539
540 # If this is quoted, keep recent lines
541 if not diff_match and self.linenum > 1 and line:
542 if line.startswith('>'):
543 if not self.was_quoted:
544 self._finalise_snippet()
545 self.recent_line = None
546 if not line_match:
547 self.recent_quoted.append(line)
548 self.was_quoted = True
549 self.recent_diff = self.cur_diff
550 else:
551 self.recent_unquoted.put(line)
552 self.was_quoted = False
553
554 if line_match:
555 self.recent_line = line_match.groups()
556
Simon Glass26132882012-01-14 15:12:45 +0000557 if self.state == STATE_DIFFS:
558 pass
559
560 # If this is the start of the diffs section, emit our tags and
561 # change log
562 elif line == '---':
563 self.state = STATE_DIFFS
564
Sean Anderson48f46d62020-05-04 16:28:34 -0400565 # Output the tags (signoff first), then change list
Simon Glass26132882012-01-14 15:12:45 +0000566 out = []
Simon Glass26132882012-01-14 15:12:45 +0000567 log = self.series.MakeChangeLog(self.commit)
Simon Glassb0cd3412014-08-28 09:43:35 -0600568 out += [line]
569 if self.commit:
570 out += self.commit.notes
571 out += [''] + log
Simon Glass26132882012-01-14 15:12:45 +0000572 elif self.found_test:
Simon Glass06022262020-10-29 21:46:18 -0600573 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass26132882012-01-14 15:12:45 +0000574 self.lines_after_test += 1
575
576 return out
577
Simon Glass93f61c02020-10-29 21:46:19 -0600578 def finalise(self):
Simon Glass26132882012-01-14 15:12:45 +0000579 """Close out processing of this patch stream"""
Simon Glassda8a2922020-10-29 21:46:37 -0600580 self._finalise_snippet()
Simon Glass93f61c02020-10-29 21:46:19 -0600581 self._finalise_change()
582 self._close_commit()
Simon Glass26132882012-01-14 15:12:45 +0000583 if self.lines_after_test:
Simon Glassb5800392020-10-29 21:46:23 -0600584 self._add_warn('Found %d lines after TEST=' % self.lines_after_test)
Simon Glass26132882012-01-14 15:12:45 +0000585
Simon Glass93f61c02020-10-29 21:46:19 -0600586 def _write_message_id(self, outfd):
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700587 """Write the Message-Id into the output.
588
589 This is based on the Change-Id in the original patch, the version,
590 and the prefix.
591
592 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600593 outfd (io.IOBase): Output stream file object
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700594 """
595 if not self.commit.change_id:
596 return
597
598 # If the count is -1 we're testing, so use a fixed time
599 if self.commit.count == -1:
600 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
601 else:
602 time_now = datetime.datetime.now()
603
604 # In theory there is email.utils.make_msgid() which would be nice
605 # to use, but it already produces something way too long and thus
606 # will produce ugly commit lines if someone throws this into
607 # a "Link:" tag in the final commit. So (sigh) roll our own.
608
609 # Start with the time; presumably we wouldn't send the same series
610 # with the same Change-Id at the exact same second.
611 parts = [time_now.strftime("%Y%m%d%H%M%S")]
612
613 # These seem like they would be nice to include.
614 if 'prefix' in self.series:
615 parts.append(self.series['prefix'])
Sean Andersondc1cd132021-10-22 19:07:04 -0400616 if 'postfix' in self.series:
Simon Glass547cba62022-02-11 13:23:18 -0700617 parts.append(self.series['postfix'])
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700618 if 'version' in self.series:
619 parts.append("v%s" % self.series['version'])
620
621 parts.append(str(self.commit.count + 1))
622
623 # The Change-Id must be last, right before the @
624 parts.append(self.commit.change_id)
625
626 # Join parts together with "." and write it out.
627 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
628
Simon Glass93f61c02020-10-29 21:46:19 -0600629 def process_stream(self, infd, outfd):
Simon Glass26132882012-01-14 15:12:45 +0000630 """Copy a stream from infd to outfd, filtering out unwanting things.
631
632 This is used to process patch files one at a time.
633
634 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600635 infd (io.IOBase): Input stream file object
636 outfd (io.IOBase): Output stream file object
Simon Glass26132882012-01-14 15:12:45 +0000637 """
638 # Extract the filename from each diff, for nice warnings
639 fname = None
640 last_fname = None
641 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700642
Simon Glass93f61c02020-10-29 21:46:19 -0600643 self._write_message_id(outfd)
Douglas Anderson52b5ee82019-09-27 09:23:56 -0700644
Simon Glass26132882012-01-14 15:12:45 +0000645 while True:
646 line = infd.readline()
647 if not line:
648 break
Simon Glass93f61c02020-10-29 21:46:19 -0600649 out = self.process_line(line)
Simon Glass26132882012-01-14 15:12:45 +0000650
651 # Try to detect blank lines at EOF
652 for line in out:
653 match = re_fname.match(line)
654 if match:
655 last_fname = fname
656 fname = match.group(1)
657 if line == '+':
658 self.blank_count += 1
659 else:
660 if self.blank_count and (line == '-- ' or match):
Simon Glassb5800392020-10-29 21:46:23 -0600661 self._add_warn("Found possible blank line(s) at end of file '%s'" %
662 last_fname)
Simon Glass26132882012-01-14 15:12:45 +0000663 outfd.write('+\n' * self.blank_count)
664 outfd.write(line + '\n')
665 self.blank_count = 0
Simon Glass93f61c02020-10-29 21:46:19 -0600666 self.finalise()
Simon Glassda1a6ec2025-03-28 07:02:20 -0600667 if self.insert_base_commit:
668 if self.series.base_commit:
669 print(f'base-commit: {self.series.base_commit.hash}',
670 file=outfd)
671 if self.series.branch:
672 print(f'branch: {self.series.branch}', file=outfd)
673
Simon Glass26132882012-01-14 15:12:45 +0000674
Simon Glassd0a0a582020-10-29 21:46:36 -0600675def insert_tags(msg, tags_to_emit):
676 """Add extra tags to a commit message
677
678 The tags are added after an existing block of tags if found, otherwise at
679 the end.
680
681 Args:
682 msg (str): Commit message
683 tags_to_emit (list): List of tags to emit, each a str
684
685 Returns:
686 (str) new message
687 """
688 out = []
689 done = False
690 emit_tags = False
Simon Glass6a222e62021-08-01 16:02:39 -0600691 emit_blank = False
Simon Glassd0a0a582020-10-29 21:46:36 -0600692 for line in msg.splitlines():
693 if not done:
694 signoff_match = RE_SIGNOFF.match(line)
695 tag_match = RE_TAG.match(line)
696 if tag_match or signoff_match:
697 emit_tags = True
698 if emit_tags and not tag_match and not signoff_match:
699 out += tags_to_emit
700 emit_tags = False
701 done = True
Simon Glass6a222e62021-08-01 16:02:39 -0600702 emit_blank = not (signoff_match or tag_match)
703 else:
704 emit_blank = line
Simon Glassd0a0a582020-10-29 21:46:36 -0600705 out.append(line)
706 if not done:
Simon Glass6a222e62021-08-01 16:02:39 -0600707 if emit_blank:
708 out.append('')
Simon Glassd0a0a582020-10-29 21:46:36 -0600709 out += tags_to_emit
710 return '\n'.join(out)
711
712def get_list(commit_range, git_dir=None, count=None):
713 """Get a log of a list of comments
714
715 This returns the output of 'git log' for the selected commits
716
717 Args:
718 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
719 git_dir (str): Path to git repositiory (None to use default)
720 count (int): Number of commits to list, or None for no limit
721
722 Returns
723 str: String containing the contents of the git log
724 """
Simon Glass761648b2022-01-29 14:14:11 -0700725 params = gitutil.log_cmd(commit_range, reverse=True, count=count,
Simon Glassd0a0a582020-10-29 21:46:36 -0600726 git_dir=git_dir)
Simon Glass51f55182025-02-03 09:26:45 -0700727 return command.run_one(*params, capture=True).stdout
Simon Glass26132882012-01-14 15:12:45 +0000728
Simon Glass93f61c02020-10-29 21:46:19 -0600729def get_metadata_for_list(commit_range, git_dir=None, count=None,
730 series=None, allow_overwrite=False):
Simon Glass26132882012-01-14 15:12:45 +0000731 """Reads out patch series metadata from the commits
732
733 This does a 'git log' on the relevant commits and pulls out the tags we
734 are interested in.
735
736 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600737 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
738 git_dir (str): Path to git repositiory (None to use default)
739 count (int): Number of commits to list, or None for no limit
740 series (Series): Object to add information into. By default a new series
Simon Glass680da3c2012-12-15 10:42:06 +0000741 is started.
Simon Glass06127f92020-10-29 21:46:22 -0600742 allow_overwrite (bool): Allow tags to overwrite an existing tag
743
Simon Glass680da3c2012-12-15 10:42:06 +0000744 Returns:
Simon Glass06127f92020-10-29 21:46:22 -0600745 Series: Object containing information about the commits.
Simon Glass26132882012-01-14 15:12:45 +0000746 """
Simon Glass4e5d6bc2014-09-05 19:00:19 -0600747 if not series:
748 series = Series()
Simon Glass359b55a62014-09-05 19:00:23 -0600749 series.allow_overwrite = allow_overwrite
Simon Glassd0a0a582020-10-29 21:46:36 -0600750 stdout = get_list(commit_range, git_dir, count)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600751 pst = PatchStream(series, is_log=True)
Simon Glass26132882012-01-14 15:12:45 +0000752 for line in stdout.splitlines():
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600753 pst.process_line(line)
754 pst.finalise()
Simon Glass26132882012-01-14 15:12:45 +0000755 return series
756
Simon Glass93f61c02020-10-29 21:46:19 -0600757def get_metadata(branch, start, count):
Simon Glass680da3c2012-12-15 10:42:06 +0000758 """Reads out patch series metadata from the commits
759
760 This does a 'git log' on the relevant commits and pulls out the tags we
761 are interested in.
762
763 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600764 branch (str): Branch to use (None for current branch)
765 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
766 count (int): Number of commits to list
767
768 Returns:
769 Series: Object containing information about the commits.
Simon Glass680da3c2012-12-15 10:42:06 +0000770 """
Simon Glass414f1e02025-02-27 12:27:30 -0700771 top = f"{branch if branch else 'HEAD'}~{start}"
772 series = get_metadata_for_list(top, None, count)
773 series.base_commit = commit.Commit(gitutil.get_hash(f'{top}~{count}'))
774 series.branch = branch or gitutil.get_branch()
775 series.top = top
776 return series
Simon Glass680da3c2012-12-15 10:42:06 +0000777
Simon Glass93f61c02020-10-29 21:46:19 -0600778def get_metadata_for_test(text):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600779 """Process metadata from a file containing a git log. Used for tests
780
781 Args:
782 text:
Simon Glass06127f92020-10-29 21:46:22 -0600783
784 Returns:
785 Series: Object containing information about the commits.
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600786 """
787 series = Series()
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600788 pst = PatchStream(series, is_log=True)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600789 for line in text.splitlines():
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600790 pst.process_line(line)
791 pst.finalise()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600792 return series
793
Simon Glassda1a6ec2025-03-28 07:02:20 -0600794def fix_patch(backup_dir, fname, series, cmt, keep_change_id=False,
Simon Glass5750cc22025-05-07 18:02:47 +0200795 insert_base_commit=False, cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000796 """Fix up a patch file, by adding/removing as required.
797
798 We remove our tags from the patch file, insert changes lists, etc.
799 The patch file is processed in place, and overwritten.
800
801 A backup file is put into backup_dir (if not None).
802
803 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600804 backup_dir (str): Path to directory to use to backup the file
805 fname (str): Filename to patch file to process
806 series (Series): Series information about this patch set
807 cmt (Commit): Commit object for this patch file
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -0400808 keep_change_id (bool): Keep the Change-Id tag.
Simon Glassda1a6ec2025-03-28 07:02:20 -0600809 insert_base_commit (bool): True to add the base commit to the end
Simon Glass5750cc22025-05-07 18:02:47 +0200810 cwd (str): Directory containing filename, or None for current
Simon Glass06127f92020-10-29 21:46:22 -0600811
Simon Glass26132882012-01-14 15:12:45 +0000812 Return:
Simon Glass06127f92020-10-29 21:46:22 -0600813 list: A list of errors, each str, or [] if all ok.
Simon Glass26132882012-01-14 15:12:45 +0000814 """
Simon Glass5750cc22025-05-07 18:02:47 +0200815 fname = os.path.join(cwd or '', fname)
Simon Glass26132882012-01-14 15:12:45 +0000816 handle, tmpname = tempfile.mkstemp()
Simon Glassf544a2d2019-10-31 07:42:51 -0600817 outfd = os.fdopen(handle, 'w', encoding='utf-8')
818 infd = open(fname, 'r', encoding='utf-8')
Simon Glassda1a6ec2025-03-28 07:02:20 -0600819 pst = PatchStream(series, keep_change_id=keep_change_id,
820 insert_base_commit=insert_base_commit)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600821 pst.commit = cmt
822 pst.process_stream(infd, outfd)
Simon Glass26132882012-01-14 15:12:45 +0000823 infd.close()
824 outfd.close()
825
826 # Create a backup file if required
827 if backup_dir:
828 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
829 shutil.move(tmpname, fname)
Simon Glass6d00f6c2020-10-29 21:46:24 -0600830 return cmt.warn
Simon Glass26132882012-01-14 15:12:45 +0000831
Simon Glass5750cc22025-05-07 18:02:47 +0200832def fix_patches(series, fnames, keep_change_id=False, insert_base_commit=False,
833 cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000834 """Fix up a list of patches identified by filenames
835
836 The patch files are processed in place, and overwritten.
837
838 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600839 series (Series): The Series object
840 fnames (:type: list of str): List of patch files to process
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -0400841 keep_change_id (bool): Keep the Change-Id tag.
Simon Glassda1a6ec2025-03-28 07:02:20 -0600842 insert_base_commit (bool): True to add the base commit to the end
Simon Glass5750cc22025-05-07 18:02:47 +0200843 cwd (str): Directory containing the patch files, or None for current
Simon Glass26132882012-01-14 15:12:45 +0000844 """
845 # Current workflow creates patches, so we shouldn't need a backup
846 backup_dir = None #tempfile.mkdtemp('clean-patch')
847 count = 0
848 for fname in fnames:
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600849 cmt = series.commits[count]
850 cmt.patch = fname
851 cmt.count = count
Maxim Cournoyer12f99fd2023-10-12 23:06:24 -0400852 result = fix_patch(backup_dir, fname, series, cmt,
Simon Glassda1a6ec2025-03-28 07:02:20 -0600853 keep_change_id=keep_change_id,
Simon Glass5750cc22025-05-07 18:02:47 +0200854 insert_base_commit=insert_base_commit, cwd=cwd)
Simon Glass26132882012-01-14 15:12:45 +0000855 if result:
Simon Glass891316b2020-10-29 21:46:30 -0600856 print('%d warning%s for %s:' %
857 (len(result), 's' if len(result) > 1 else '', fname))
Simon Glass26132882012-01-14 15:12:45 +0000858 for warn in result:
Simon Glass891316b2020-10-29 21:46:30 -0600859 print('\t%s' % warn)
860 print()
Simon Glass26132882012-01-14 15:12:45 +0000861 count += 1
Simon Glass891316b2020-10-29 21:46:30 -0600862 print('Cleaned %d patch%s' % (count, 'es' if count > 1 else ''))
Simon Glass26132882012-01-14 15:12:45 +0000863
Simon Glass5750cc22025-05-07 18:02:47 +0200864def insert_cover_letter(fname, series, count, cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000865 """Inserts a cover letter with the required info into patch 0
866
867 Args:
Simon Glass06127f92020-10-29 21:46:22 -0600868 fname (str): Input / output filename of the cover letter file
869 series (Series): Series object
870 count (int): Number of patches in the series
Simon Glass5750cc22025-05-07 18:02:47 +0200871 cwd (str): Directory containing filename, or None for current
Simon Glass26132882012-01-14 15:12:45 +0000872 """
Simon Glass5750cc22025-05-07 18:02:47 +0200873 fname = os.path.join(cwd or '', fname)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600874 fil = open(fname, 'r')
875 lines = fil.readlines()
876 fil.close()
Simon Glass26132882012-01-14 15:12:45 +0000877
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600878 fil = open(fname, 'w')
Simon Glass26132882012-01-14 15:12:45 +0000879 text = series.cover
880 prefix = series.GetPatchPrefix()
881 for line in lines:
882 if line.startswith('Subject:'):
Wu, Josh393aaa22015-04-03 10:51:17 +0800883 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
884 zero_repeat = int(math.log10(count)) + 1
885 zero = '0' * zero_repeat
886 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass26132882012-01-14 15:12:45 +0000887
888 # Insert our cover letter
889 elif line.startswith('*** BLURB HERE ***'):
890 # First the blurb test
891 line = '\n'.join(text[1:]) + '\n'
892 if series.get('notes'):
893 line += '\n'.join(series.notes) + '\n'
894
895 # Now the change list
896 out = series.MakeChangeLog(None)
897 line += '\n' + '\n'.join(out)
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600898 fil.write(line)
Simon Glass414f1e02025-02-27 12:27:30 -0700899
900 # Insert the base commit and branch
901 if series.base_commit:
902 print(f'base-commit: {series.base_commit.hash}', file=fil)
903 if series.branch:
904 print(f'branch: {series.branch}', file=fil)
905
Simon Glassf9b6e0f2020-10-29 21:46:20 -0600906 fil.close()