blob: a4bf08b7d81c31f7d0b2e55d97f5914e8d084318 [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
Simon Glass74570512020-10-29 21:46:27 -06008import io
Wu, Josh35ce2dc2015-04-03 10:51:17 +08009import math
Simon Glass0d24de92012-01-14 15:12:45 +000010import os
11import re
12import shutil
13import tempfile
14
Simon Glassbf776672020-04-17 18:09:04 -060015from patman import command
16from patman import commit
17from patman import gitutil
18from patman.series import Series
Simon Glass0d24de92012-01-14 15:12:45 +000019
20# Tags that we detect and remove
Simon Glass57699042020-10-29 21:46:18 -060021RE_REMOVE = re.compile(r'^BUG=|^TEST=|^BRANCH=|^Review URL:'
Simon Glassd06e55a2020-10-29 21:46:17 -060022 r'|Reviewed-on:|Commit-\w*:')
Simon Glass0d24de92012-01-14 15:12:45 +000023
24# Lines which are allowed after a TEST= line
Simon Glass57699042020-10-29 21:46:18 -060025RE_ALLOWED_AFTER_TEST = re.compile('^Signed-off-by:')
Simon Glass0d24de92012-01-14 15:12:45 +000026
Ilya Yanok05e5b732012-08-06 23:46:05 +000027# Signoffs
Simon Glass57699042020-10-29 21:46:18 -060028RE_SIGNOFF = re.compile('^Signed-off-by: *(.*)')
Ilya Yanok05e5b732012-08-06 23:46:05 +000029
Sean Anderson6949f702020-05-04 16:28:34 -040030# Cover letter tag
Simon Glass57699042020-10-29 21:46:18 -060031RE_COVER = re.compile('^Cover-([a-z-]*): *(.*)')
Simon Glassfe2f8d92013-03-20 16:43:00 +000032
Simon Glass0d24de92012-01-14 15:12:45 +000033# Patch series tag
Simon Glass57699042020-10-29 21:46:18 -060034RE_SERIES_TAG = re.compile('^Series-([a-z-]*): *(.*)')
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +010035
Douglas Anderson833e4192019-09-27 09:23:56 -070036# Change-Id will be used to generate the Message-Id and then be stripped
Simon Glass57699042020-10-29 21:46:18 -060037RE_CHANGE_ID = re.compile('^Change-Id: *(.*)')
Douglas Anderson833e4192019-09-27 09:23:56 -070038
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +010039# Commit series tag
Simon Glass57699042020-10-29 21:46:18 -060040RE_COMMIT_TAG = re.compile('^Commit-([a-z-]*): *(.*)')
Simon Glass0d24de92012-01-14 15:12:45 +000041
42# Commit tags that we want to collect and keep
Simon Glass57699042020-10-29 21:46:18 -060043RE_TAG = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc|Fixes): (.*)')
Simon Glass0d24de92012-01-14 15:12:45 +000044
45# The start of a new commit in the git log
Simon Glass57699042020-10-29 21:46:18 -060046RE_COMMIT = re.compile('^commit ([0-9a-f]*)$')
Simon Glass0d24de92012-01-14 15:12:45 +000047
48# We detect these since checkpatch doesn't always do it
Simon Glass57699042020-10-29 21:46:18 -060049RE_SPACE_BEFORE_TAB = re.compile('^[+].* \t')
Simon Glass0d24de92012-01-14 15:12:45 +000050
Sean Anderson0411fff2020-05-04 16:28:35 -040051# Match indented lines for changes
Simon Glass57699042020-10-29 21:46:18 -060052RE_LEADING_WHITESPACE = re.compile(r'^\s')
Sean Anderson0411fff2020-05-04 16:28:35 -040053
Simon Glass0d24de92012-01-14 15:12:45 +000054# States we can be in - can we use range() and still have comments?
55STATE_MSG_HEADER = 0 # Still in the message header
56STATE_PATCH_SUBJECT = 1 # In patch subject (first line of log for a commit)
57STATE_PATCH_HEADER = 2 # In patch header (after the subject)
58STATE_DIFFS = 3 # In the diff part (past --- line)
59
60class PatchStream:
61 """Class for detecting/injecting tags in a patch or series of patches
62
63 We support processing the output of 'git log' to read out the tags we
64 are interested in. We can also process a patch file in order to remove
65 unwanted tags or inject additional ones. These correspond to the two
66 phases of processing.
67 """
Simon Glasse3a816b2020-10-29 21:46:21 -060068 def __init__(self, series, is_log=False):
Simon Glass0d24de92012-01-14 15:12:45 +000069 self.skip_blank = False # True to skip a single blank line
70 self.found_test = False # Found a TEST= line
Sean Anderson6949f702020-05-04 16:28:34 -040071 self.lines_after_test = 0 # Number of lines found after TEST=
Simon Glass0d24de92012-01-14 15:12:45 +000072 self.linenum = 1 # Output line number we are up to
73 self.in_section = None # Name of start...END section we are in
74 self.notes = [] # Series notes
75 self.section = [] # The current section...END section
76 self.series = series # Info about the patch series
77 self.is_log = is_log # True if indent like git log
Sean 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.commit = None # Current commit
84
Simon Glass74570512020-10-29 21:46:27 -060085 @staticmethod
86 def process_text(text, is_comment=False):
87 """Process some text through this class using a default Commit/Series
88
89 Args:
90 text (str): Text to parse
91 is_comment (bool): True if this is a comment rather than a patch.
92 If True, PatchStream doesn't expect a patch subject at the
93 start, but jumps straight into the body
94
95 Returns:
96 PatchStream: object with results
97 """
98 pstrm = PatchStream(Series())
99 pstrm.commit = commit.Commit(None)
100 infd = io.StringIO(text)
101 outfd = io.StringIO()
102 if is_comment:
103 pstrm.state = STATE_PATCH_HEADER
104 pstrm.process_stream(infd, outfd)
105 return pstrm
106
Simon Glassb5cc3992020-10-29 21:46:23 -0600107 def _add_warn(self, warn):
Simon Glass313ef5f2020-10-29 21:46:24 -0600108 """Add a new warning to report to the user about the current commit
109
110 The new warning is added to the current commit if not already present.
Simon Glassb5cc3992020-10-29 21:46:23 -0600111
112 Args:
113 warn (str): Warning to report
Simon Glass313ef5f2020-10-29 21:46:24 -0600114
115 Raises:
116 ValueError: Warning is generated with no commit associated
Simon Glassb5cc3992020-10-29 21:46:23 -0600117 """
Simon Glass313ef5f2020-10-29 21:46:24 -0600118 if not self.commit:
119 raise ValueError('Warning outside commit: %s' % warn)
120 if warn not in self.commit.warn:
121 self.commit.warn.append(warn)
Simon Glassb5cc3992020-10-29 21:46:23 -0600122
Simon Glassd93720e2020-10-29 21:46:19 -0600123 def _add_to_series(self, line, name, value):
Simon Glass0d24de92012-01-14 15:12:45 +0000124 """Add a new Series-xxx tag.
125
126 When a Series-xxx tag is detected, we come here to record it, if we
127 are scanning a 'git log'.
128
129 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600130 line (str): Source line containing tag (useful for debug/error
131 messages)
132 name (str): Tag name (part after 'Series-')
133 value (str): Tag value (part after 'Series-xxx: ')
Simon Glass0d24de92012-01-14 15:12:45 +0000134 """
135 if name == 'notes':
136 self.in_section = name
137 self.skip_blank = False
138 if self.is_log:
Simon Glassdffa42c2020-10-29 21:46:25 -0600139 warn = self.series.AddTag(self.commit, line, name, value)
140 if warn:
141 self.commit.warn.append(warn)
Simon Glass0d24de92012-01-14 15:12:45 +0000142
Simon Glasse3a816b2020-10-29 21:46:21 -0600143 def _add_to_commit(self, name):
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100144 """Add a new Commit-xxx tag.
145
146 When a Commit-xxx tag is detected, we come here to record it.
147
148 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600149 name (str): Tag name (part after 'Commit-')
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100150 """
151 if name == 'notes':
152 self.in_section = 'commit-' + name
153 self.skip_blank = False
154
Simon Glassd93720e2020-10-29 21:46:19 -0600155 def _add_commit_rtag(self, rtag_type, who):
Simon Glass7207e2b2020-07-05 21:41:57 -0600156 """Add a response tag to the current commit
157
158 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600159 rtag_type (str): rtag type (e.g. 'Reviewed-by')
160 who (str): Person who gave that rtag, e.g.
161 'Fred Bloggs <fred@bloggs.org>'
Simon Glass7207e2b2020-07-05 21:41:57 -0600162 """
163 self.commit.AddRtag(rtag_type, who)
164
Simon Glassd93720e2020-10-29 21:46:19 -0600165 def _close_commit(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000166 """Save the current commit into our commit list, and reset our state"""
167 if self.commit and self.is_log:
168 self.series.AddCommit(self.commit)
169 self.commit = None
Bin Meng0d577182016-06-26 23:24:30 -0700170 # If 'END' is missing in a 'Cover-letter' section, and that section
171 # happens to show up at the very end of the commit message, this is
172 # the chance for us to fix it up.
173 if self.in_section == 'cover' and self.is_log:
174 self.series.cover = self.section
175 self.in_section = None
176 self.skip_blank = True
177 self.section = []
Simon Glass0d24de92012-01-14 15:12:45 +0000178
Simon Glassd93720e2020-10-29 21:46:19 -0600179 def _parse_version(self, value, line):
Sean Anderson6949f702020-05-04 16:28:34 -0400180 """Parse a version from a *-changes tag
181
182 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600183 value (str): Tag value (part after 'xxx-changes: '
184 line (str): Source line containing tag
Sean Anderson6949f702020-05-04 16:28:34 -0400185
186 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600187 int: The version as an integer
188
189 Raises:
190 ValueError: the value cannot be converted
Sean Anderson6949f702020-05-04 16:28:34 -0400191 """
192 try:
193 return int(value)
Simon Glassdd147ed2020-10-29 21:46:20 -0600194 except ValueError:
Sean Anderson6949f702020-05-04 16:28:34 -0400195 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassd06e55a2020-10-29 21:46:17 -0600196 (self.commit.hash, line))
Sean Anderson6949f702020-05-04 16:28:34 -0400197
Simon Glassd93720e2020-10-29 21:46:19 -0600198 def _finalise_change(self):
199 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson0411fff2020-05-04 16:28:35 -0400200 if not self.change_lines:
201 return
202 change = '\n'.join(self.change_lines)
203
204 if self.in_change == 'Series':
205 self.series.AddChange(self.change_version, self.commit, change)
206 elif self.in_change == 'Cover':
207 self.series.AddChange(self.change_version, None, change)
208 elif self.in_change == 'Commit':
209 self.commit.AddChange(self.change_version, change)
210 self.change_lines = []
211
Simon Glassd93720e2020-10-29 21:46:19 -0600212 def process_line(self, line):
Simon Glass0d24de92012-01-14 15:12:45 +0000213 """Process a single line of a patch file or commit log
214
215 This process a line and returns a list of lines to output. The list
216 may be empty or may contain multiple output lines.
217
218 This is where all the complicated logic is located. The class's
219 state is used to move between different states and detect things
220 properly.
221
222 We can be in one of two modes:
223 self.is_log == True: This is 'git log' mode, where most output is
224 indented by 4 characters and we are scanning for tags
225
226 self.is_log == False: This is 'patch' mode, where we already have
227 all the tags, and are processing patches to remove junk we
228 don't want, and add things we think are required.
229
230 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600231 line (str): text line to process
Simon Glass0d24de92012-01-14 15:12:45 +0000232
233 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600234 list: list of output lines, or [] if nothing should be output
235
236 Raises:
237 ValueError: a fatal error occurred while parsing, e.g. an END
238 without a starting tag, or two commits with two change IDs
Simon Glass0d24de92012-01-14 15:12:45 +0000239 """
240 # Initially we have no output. Prepare the input line string
241 out = []
242 line = line.rstrip('\n')
Scott Wood4b89b812014-09-25 14:30:46 -0500243
Simon Glass57699042020-10-29 21:46:18 -0600244 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood4b89b812014-09-25 14:30:46 -0500245
Simon Glass0d24de92012-01-14 15:12:45 +0000246 if self.is_log:
247 if line[:4] == ' ':
248 line = line[4:]
249
250 # Handle state transition and skipping blank lines
Simon Glass57699042020-10-29 21:46:18 -0600251 series_tag_match = RE_SERIES_TAG.match(line)
252 change_id_match = RE_CHANGE_ID.match(line)
253 commit_tag_match = RE_COMMIT_TAG.match(line)
254 cover_match = RE_COVER.match(line)
255 signoff_match = RE_SIGNOFF.match(line)
256 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000257 tag_match = None
258 if self.state == STATE_PATCH_HEADER:
Simon Glass57699042020-10-29 21:46:18 -0600259 tag_match = RE_TAG.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000260 is_blank = not line.strip()
261 if is_blank:
262 if (self.state == STATE_MSG_HEADER
263 or self.state == STATE_PATCH_SUBJECT):
264 self.state += 1
265
266 # We don't have a subject in the text stream of patch files
267 # It has its own line with a Subject: tag
268 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
269 self.state += 1
270 elif commit_match:
271 self.state = STATE_MSG_HEADER
272
Bin Meng94fbd3e2016-06-26 23:24:32 -0700273 # If a tag is detected, or a new commit starts
Douglas Anderson833e4192019-09-27 09:23:56 -0700274 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson6949f702020-05-04 16:28:34 -0400275 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng57b6b192016-06-26 23:24:31 -0700276 # but we are already in a section, this means 'END' is missing
277 # for that section, fix it up.
Bin Meng13b98d92016-06-26 23:24:29 -0700278 if self.in_section:
Simon Glassb5cc3992020-10-29 21:46:23 -0600279 self._add_warn("Missing 'END' in section '%s'" % self.in_section)
Bin Meng13b98d92016-06-26 23:24:29 -0700280 if self.in_section == 'cover':
281 self.series.cover = self.section
282 elif self.in_section == 'notes':
283 if self.is_log:
284 self.series.notes += self.section
285 elif self.in_section == 'commit-notes':
286 if self.is_log:
287 self.commit.notes += self.section
288 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600289 # This should not happen
290 raise ValueError("Unknown section '%s'" % self.in_section)
Bin Meng13b98d92016-06-26 23:24:29 -0700291 self.in_section = None
292 self.skip_blank = True
293 self.section = []
Bin Meng57b6b192016-06-26 23:24:31 -0700294 # but we are already in a change list, that means a blank line
295 # is missing, fix it up.
296 if self.in_change:
Simon Glassb5cc3992020-10-29 21:46:23 -0600297 self._add_warn("Missing 'blank line' in section '%s-changes'" %
298 self.in_change)
Simon Glassd93720e2020-10-29 21:46:19 -0600299 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400300 self.in_change = None
301 self.change_version = 0
Bin Meng13b98d92016-06-26 23:24:29 -0700302
Simon Glass0d24de92012-01-14 15:12:45 +0000303 # If we are in a section, keep collecting lines until we see END
304 if self.in_section:
305 if line == 'END':
306 if self.in_section == 'cover':
307 self.series.cover = self.section
308 elif self.in_section == 'notes':
309 if self.is_log:
310 self.series.notes += self.section
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100311 elif self.in_section == 'commit-notes':
312 if self.is_log:
313 self.commit.notes += self.section
Simon Glass0d24de92012-01-14 15:12:45 +0000314 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600315 # This should not happen
316 raise ValueError("Unknown section '%s'" % self.in_section)
Simon Glass0d24de92012-01-14 15:12:45 +0000317 self.in_section = None
318 self.skip_blank = True
319 self.section = []
320 else:
321 self.section.append(line)
322
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200323 # If we are not in a section, it is an unexpected END
324 elif line == 'END':
Simon Glassd06e55a2020-10-29 21:46:17 -0600325 raise ValueError("'END' wihout section")
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200326
Simon Glass0d24de92012-01-14 15:12:45 +0000327 # Detect the commit subject
328 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
329 self.commit.subject = line
330
331 # Detect the tags we want to remove, and skip blank lines
Simon Glass57699042020-10-29 21:46:18 -0600332 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass0d24de92012-01-14 15:12:45 +0000333 self.skip_blank = True
334
335 # TEST= should be the last thing in the commit, so remove
336 # everything after it
337 if line.startswith('TEST='):
338 self.found_test = True
339 elif self.skip_blank and is_blank:
340 self.skip_blank = False
341
Sean Anderson6949f702020-05-04 16:28:34 -0400342 # Detect Cover-xxx tags
Bin Menge7df2182016-06-26 23:24:28 -0700343 elif cover_match:
Sean Anderson6949f702020-05-04 16:28:34 -0400344 name = cover_match.group(1)
345 value = cover_match.group(2)
346 if name == 'letter':
347 self.in_section = 'cover'
348 self.skip_blank = False
349 elif name == 'letter-cc':
Simon Glassd93720e2020-10-29 21:46:19 -0600350 self._add_to_series(line, 'cover-cc', value)
Sean Anderson6949f702020-05-04 16:28:34 -0400351 elif name == 'changes':
352 self.in_change = 'Cover'
Simon Glassd93720e2020-10-29 21:46:19 -0600353 self.change_version = self._parse_version(value, line)
Simon Glassfe2f8d92013-03-20 16:43:00 +0000354
Simon Glass0d24de92012-01-14 15:12:45 +0000355 # If we are in a change list, key collected lines until a blank one
356 elif self.in_change:
357 if is_blank:
358 # Blank line ends this change list
Simon Glassd93720e2020-10-29 21:46:19 -0600359 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400360 self.in_change = None
361 self.change_version = 0
Simon Glass102061b2014-04-20 10:50:14 -0600362 elif line == '---':
Simon Glassd93720e2020-10-29 21:46:19 -0600363 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400364 self.in_change = None
365 self.change_version = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600366 out = self.process_line(line)
Sean Anderson0411fff2020-05-04 16:28:35 -0400367 elif self.is_log:
368 if not leading_whitespace_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600369 self._finalise_change()
Sean Anderson0411fff2020-05-04 16:28:35 -0400370 self.change_lines.append(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000371 self.skip_blank = False
372
373 # Detect Series-xxx tags
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100374 elif series_tag_match:
375 name = series_tag_match.group(1)
376 value = series_tag_match.group(2)
Simon Glass0d24de92012-01-14 15:12:45 +0000377 if name == 'changes':
378 # value is the version number: e.g. 1, or 2
Sean Anderson6949f702020-05-04 16:28:34 -0400379 self.in_change = 'Series'
Simon Glassd93720e2020-10-29 21:46:19 -0600380 self.change_version = self._parse_version(value, line)
Simon Glass0d24de92012-01-14 15:12:45 +0000381 else:
Simon Glassd93720e2020-10-29 21:46:19 -0600382 self._add_to_series(line, name, value)
Simon Glass0d24de92012-01-14 15:12:45 +0000383 self.skip_blank = True
384
Douglas Anderson833e4192019-09-27 09:23:56 -0700385 # Detect Change-Id tags
386 elif change_id_match:
387 value = change_id_match.group(1)
388 if self.is_log:
389 if self.commit.change_id:
Simon Glassd06e55a2020-10-29 21:46:17 -0600390 raise ValueError(
391 "%s: Two Change-Ids: '%s' vs. '%s'" % self.commit.hash,
392 self.commit.change_id, value)
Douglas Anderson833e4192019-09-27 09:23:56 -0700393 self.commit.change_id = value
394 self.skip_blank = True
395
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100396 # Detect Commit-xxx tags
397 elif commit_tag_match:
398 name = commit_tag_match.group(1)
399 value = commit_tag_match.group(2)
400 if name == 'notes':
Simon Glasse3a816b2020-10-29 21:46:21 -0600401 self._add_to_commit(name)
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100402 self.skip_blank = True
Sean Anderson6949f702020-05-04 16:28:34 -0400403 elif name == 'changes':
404 self.in_change = 'Commit'
Simon Glassd93720e2020-10-29 21:46:19 -0600405 self.change_version = self._parse_version(value, line)
Patrick Delaunaye5ff9ab2020-07-02 19:52:54 +0200406 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600407 self._add_warn('Line %d: Ignoring Commit-%s' %
408 (self.linenum, name))
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100409
Simon Glass0d24de92012-01-14 15:12:45 +0000410 # Detect the start of a new commit
411 elif commit_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600412 self._close_commit()
Simon Glass0b5b4092014-10-15 02:27:00 -0600413 self.commit = commit.Commit(commit_match.group(1))
Simon Glass0d24de92012-01-14 15:12:45 +0000414
415 # Detect tags in the commit message
416 elif tag_match:
Simon Glass7207e2b2020-07-05 21:41:57 -0600417 rtag_type, who = tag_match.groups()
Simon Glassd93720e2020-10-29 21:46:19 -0600418 self._add_commit_rtag(rtag_type, who)
Simon Glass0d24de92012-01-14 15:12:45 +0000419 # Remove Tested-by self, since few will take much notice
Simon Glass7207e2b2020-07-05 21:41:57 -0600420 if (rtag_type == 'Tested-by' and
421 who.find(os.getenv('USER') + '@') != -1):
Simon Glass4af99872020-10-29 21:46:28 -0600422 self._add_warn("Ignoring '%s'" % line)
Simon Glass7207e2b2020-07-05 21:41:57 -0600423 elif rtag_type == 'Patch-cc':
424 self.commit.AddCc(who.split(','))
Simon Glass0d24de92012-01-14 15:12:45 +0000425 else:
Simon Glassd0c57192014-08-28 09:43:38 -0600426 out = [line]
Simon Glass0d24de92012-01-14 15:12:45 +0000427
Simon Glass102061b2014-04-20 10:50:14 -0600428 # Suppress duplicate signoffs
429 elif signoff_match:
Simon Glasse752edc2014-08-28 09:43:35 -0600430 if (self.is_log or not self.commit or
Simon Glassd06e55a2020-10-29 21:46:17 -0600431 self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
Simon Glass102061b2014-04-20 10:50:14 -0600432 out = [line]
433
Simon Glass0d24de92012-01-14 15:12:45 +0000434 # Well that means this is an ordinary line
435 else:
Simon Glass0d24de92012-01-14 15:12:45 +0000436 # Look for space before tab
Simon Glassdd147ed2020-10-29 21:46:20 -0600437 mat = RE_SPACE_BEFORE_TAB.match(line)
438 if mat:
Simon Glassb5cc3992020-10-29 21:46:23 -0600439 self._add_warn('Line %d/%d has space before tab' %
440 (self.linenum, mat.start()))
Simon Glass0d24de92012-01-14 15:12:45 +0000441
442 # OK, we have a valid non-blank line
443 out = [line]
444 self.linenum += 1
445 self.skip_blank = False
446 if self.state == STATE_DIFFS:
447 pass
448
449 # If this is the start of the diffs section, emit our tags and
450 # change log
451 elif line == '---':
452 self.state = STATE_DIFFS
453
Sean Anderson6949f702020-05-04 16:28:34 -0400454 # Output the tags (signoff first), then change list
Simon Glass0d24de92012-01-14 15:12:45 +0000455 out = []
Simon Glass0d24de92012-01-14 15:12:45 +0000456 log = self.series.MakeChangeLog(self.commit)
Simon Glasse752edc2014-08-28 09:43:35 -0600457 out += [line]
458 if self.commit:
459 out += self.commit.notes
460 out += [''] + log
Simon Glass0d24de92012-01-14 15:12:45 +0000461 elif self.found_test:
Simon Glass57699042020-10-29 21:46:18 -0600462 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass0d24de92012-01-14 15:12:45 +0000463 self.lines_after_test += 1
464
465 return out
466
Simon Glassd93720e2020-10-29 21:46:19 -0600467 def finalise(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000468 """Close out processing of this patch stream"""
Simon Glassd93720e2020-10-29 21:46:19 -0600469 self._finalise_change()
470 self._close_commit()
Simon Glass0d24de92012-01-14 15:12:45 +0000471 if self.lines_after_test:
Simon Glassb5cc3992020-10-29 21:46:23 -0600472 self._add_warn('Found %d lines after TEST=' % self.lines_after_test)
Simon Glass0d24de92012-01-14 15:12:45 +0000473
Simon Glassd93720e2020-10-29 21:46:19 -0600474 def _write_message_id(self, outfd):
Douglas Anderson833e4192019-09-27 09:23:56 -0700475 """Write the Message-Id into the output.
476
477 This is based on the Change-Id in the original patch, the version,
478 and the prefix.
479
480 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600481 outfd (io.IOBase): Output stream file object
Douglas Anderson833e4192019-09-27 09:23:56 -0700482 """
483 if not self.commit.change_id:
484 return
485
486 # If the count is -1 we're testing, so use a fixed time
487 if self.commit.count == -1:
488 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
489 else:
490 time_now = datetime.datetime.now()
491
492 # In theory there is email.utils.make_msgid() which would be nice
493 # to use, but it already produces something way too long and thus
494 # will produce ugly commit lines if someone throws this into
495 # a "Link:" tag in the final commit. So (sigh) roll our own.
496
497 # Start with the time; presumably we wouldn't send the same series
498 # with the same Change-Id at the exact same second.
499 parts = [time_now.strftime("%Y%m%d%H%M%S")]
500
501 # These seem like they would be nice to include.
502 if 'prefix' in self.series:
503 parts.append(self.series['prefix'])
504 if 'version' in self.series:
505 parts.append("v%s" % self.series['version'])
506
507 parts.append(str(self.commit.count + 1))
508
509 # The Change-Id must be last, right before the @
510 parts.append(self.commit.change_id)
511
512 # Join parts together with "." and write it out.
513 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
514
Simon Glassd93720e2020-10-29 21:46:19 -0600515 def process_stream(self, infd, outfd):
Simon Glass0d24de92012-01-14 15:12:45 +0000516 """Copy a stream from infd to outfd, filtering out unwanting things.
517
518 This is used to process patch files one at a time.
519
520 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600521 infd (io.IOBase): Input stream file object
522 outfd (io.IOBase): Output stream file object
Simon Glass0d24de92012-01-14 15:12:45 +0000523 """
524 # Extract the filename from each diff, for nice warnings
525 fname = None
526 last_fname = None
527 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson833e4192019-09-27 09:23:56 -0700528
Simon Glassd93720e2020-10-29 21:46:19 -0600529 self._write_message_id(outfd)
Douglas Anderson833e4192019-09-27 09:23:56 -0700530
Simon Glass0d24de92012-01-14 15:12:45 +0000531 while True:
532 line = infd.readline()
533 if not line:
534 break
Simon Glassd93720e2020-10-29 21:46:19 -0600535 out = self.process_line(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000536
537 # Try to detect blank lines at EOF
538 for line in out:
539 match = re_fname.match(line)
540 if match:
541 last_fname = fname
542 fname = match.group(1)
543 if line == '+':
544 self.blank_count += 1
545 else:
546 if self.blank_count and (line == '-- ' or match):
Simon Glassb5cc3992020-10-29 21:46:23 -0600547 self._add_warn("Found possible blank line(s) at end of file '%s'" %
548 last_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000549 outfd.write('+\n' * self.blank_count)
550 outfd.write(line + '\n')
551 self.blank_count = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600552 self.finalise()
Simon Glass0d24de92012-01-14 15:12:45 +0000553
Simon Glass8f9ba3a2020-10-29 21:46:36 -0600554def insert_tags(msg, tags_to_emit):
555 """Add extra tags to a commit message
556
557 The tags are added after an existing block of tags if found, otherwise at
558 the end.
559
560 Args:
561 msg (str): Commit message
562 tags_to_emit (list): List of tags to emit, each a str
563
564 Returns:
565 (str) new message
566 """
567 out = []
568 done = False
569 emit_tags = False
570 for line in msg.splitlines():
571 if not done:
572 signoff_match = RE_SIGNOFF.match(line)
573 tag_match = RE_TAG.match(line)
574 if tag_match or signoff_match:
575 emit_tags = True
576 if emit_tags and not tag_match and not signoff_match:
577 out += tags_to_emit
578 emit_tags = False
579 done = True
580 out.append(line)
581 if not done:
582 out.append('')
583 out += tags_to_emit
584 return '\n'.join(out)
585
586def get_list(commit_range, git_dir=None, count=None):
587 """Get a log of a list of comments
588
589 This returns the output of 'git log' for the selected commits
590
591 Args:
592 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
593 git_dir (str): Path to git repositiory (None to use default)
594 count (int): Number of commits to list, or None for no limit
595
596 Returns
597 str: String containing the contents of the git log
598 """
599 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
600 git_dir=git_dir)
601 return command.RunPipe([params], capture=True).stdout
Simon Glass0d24de92012-01-14 15:12:45 +0000602
Simon Glassd93720e2020-10-29 21:46:19 -0600603def get_metadata_for_list(commit_range, git_dir=None, count=None,
604 series=None, allow_overwrite=False):
Simon Glasse62f9052012-12-15 10:42:06 +0000605 """Reads out patch series metadata from the commits
606
607 This does a 'git log' on the relevant commits and pulls out the tags we
608 are interested in.
609
610 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600611 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
612 git_dir (str): Path to git repositiory (None to use default)
613 count (int): Number of commits to list, or None for no limit
614 series (Series): Object to add information into. By default a new series
Simon Glasse62f9052012-12-15 10:42:06 +0000615 is started.
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600616 allow_overwrite (bool): Allow tags to overwrite an existing tag
617
Simon Glasse62f9052012-12-15 10:42:06 +0000618 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600619 Series: Object containing information about the commits.
Simon Glasse62f9052012-12-15 10:42:06 +0000620 """
Simon Glass891b7a02014-09-05 19:00:19 -0600621 if not series:
622 series = Series()
Simon Glass950a2312014-09-05 19:00:23 -0600623 series.allow_overwrite = allow_overwrite
Simon Glass8f9ba3a2020-10-29 21:46:36 -0600624 stdout = get_list(commit_range, git_dir, count)
Simon Glassdd147ed2020-10-29 21:46:20 -0600625 pst = PatchStream(series, is_log=True)
Simon Glasse62f9052012-12-15 10:42:06 +0000626 for line in stdout.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600627 pst.process_line(line)
628 pst.finalise()
Simon Glasse62f9052012-12-15 10:42:06 +0000629 return series
630
Simon Glassd93720e2020-10-29 21:46:19 -0600631def get_metadata(branch, start, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000632 """Reads out patch series metadata from the commits
633
634 This does a 'git log' on the relevant commits and pulls out the tags we
635 are interested in.
636
637 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600638 branch (str): Branch to use (None for current branch)
639 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
640 count (int): Number of commits to list
641
642 Returns:
643 Series: Object containing information about the commits.
Simon Glass0d24de92012-01-14 15:12:45 +0000644 """
Simon Glassd93720e2020-10-29 21:46:19 -0600645 return get_metadata_for_list(
646 '%s~%d' % (branch if branch else 'HEAD', start), None, count)
Simon Glass0d24de92012-01-14 15:12:45 +0000647
Simon Glassd93720e2020-10-29 21:46:19 -0600648def get_metadata_for_test(text):
Simon Glass6e87ae12017-05-29 15:31:31 -0600649 """Process metadata from a file containing a git log. Used for tests
650
651 Args:
652 text:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600653
654 Returns:
655 Series: Object containing information about the commits.
Simon Glass6e87ae12017-05-29 15:31:31 -0600656 """
657 series = Series()
Simon Glassdd147ed2020-10-29 21:46:20 -0600658 pst = PatchStream(series, is_log=True)
Simon Glass6e87ae12017-05-29 15:31:31 -0600659 for line in text.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600660 pst.process_line(line)
661 pst.finalise()
Simon Glass6e87ae12017-05-29 15:31:31 -0600662 return series
663
Simon Glassdd147ed2020-10-29 21:46:20 -0600664def fix_patch(backup_dir, fname, series, cmt):
Simon Glass0d24de92012-01-14 15:12:45 +0000665 """Fix up a patch file, by adding/removing as required.
666
667 We remove our tags from the patch file, insert changes lists, etc.
668 The patch file is processed in place, and overwritten.
669
670 A backup file is put into backup_dir (if not None).
671
672 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600673 backup_dir (str): Path to directory to use to backup the file
674 fname (str): Filename to patch file to process
675 series (Series): Series information about this patch set
676 cmt (Commit): Commit object for this patch file
677
Simon Glass0d24de92012-01-14 15:12:45 +0000678 Return:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600679 list: A list of errors, each str, or [] if all ok.
Simon Glass0d24de92012-01-14 15:12:45 +0000680 """
681 handle, tmpname = tempfile.mkstemp()
Simon Glass272cd852019-10-31 07:42:51 -0600682 outfd = os.fdopen(handle, 'w', encoding='utf-8')
683 infd = open(fname, 'r', encoding='utf-8')
Simon Glassdd147ed2020-10-29 21:46:20 -0600684 pst = PatchStream(series)
685 pst.commit = cmt
686 pst.process_stream(infd, outfd)
Simon Glass0d24de92012-01-14 15:12:45 +0000687 infd.close()
688 outfd.close()
689
690 # Create a backup file if required
691 if backup_dir:
692 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
693 shutil.move(tmpname, fname)
Simon Glass313ef5f2020-10-29 21:46:24 -0600694 return cmt.warn
Simon Glass0d24de92012-01-14 15:12:45 +0000695
Simon Glassd93720e2020-10-29 21:46:19 -0600696def fix_patches(series, fnames):
Simon Glass0d24de92012-01-14 15:12:45 +0000697 """Fix up a list of patches identified by filenames
698
699 The patch files are processed in place, and overwritten.
700
701 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600702 series (Series): The Series object
703 fnames (:type: list of str): List of patch files to process
Simon Glass0d24de92012-01-14 15:12:45 +0000704 """
705 # Current workflow creates patches, so we shouldn't need a backup
706 backup_dir = None #tempfile.mkdtemp('clean-patch')
707 count = 0
708 for fname in fnames:
Simon Glassdd147ed2020-10-29 21:46:20 -0600709 cmt = series.commits[count]
710 cmt.patch = fname
711 cmt.count = count
712 result = fix_patch(backup_dir, fname, series, cmt)
Simon Glass0d24de92012-01-14 15:12:45 +0000713 if result:
Simon Glass9994baa2020-10-29 21:46:30 -0600714 print('%d warning%s for %s:' %
715 (len(result), 's' if len(result) > 1 else '', fname))
Simon Glass0d24de92012-01-14 15:12:45 +0000716 for warn in result:
Simon Glass9994baa2020-10-29 21:46:30 -0600717 print('\t%s' % warn)
718 print()
Simon Glass0d24de92012-01-14 15:12:45 +0000719 count += 1
Simon Glass9994baa2020-10-29 21:46:30 -0600720 print('Cleaned %d patch%s' % (count, 'es' if count > 1 else ''))
Simon Glass0d24de92012-01-14 15:12:45 +0000721
Simon Glassd93720e2020-10-29 21:46:19 -0600722def insert_cover_letter(fname, series, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000723 """Inserts a cover letter with the required info into patch 0
724
725 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600726 fname (str): Input / output filename of the cover letter file
727 series (Series): Series object
728 count (int): Number of patches in the series
Simon Glass0d24de92012-01-14 15:12:45 +0000729 """
Simon Glassdd147ed2020-10-29 21:46:20 -0600730 fil = open(fname, 'r')
731 lines = fil.readlines()
732 fil.close()
Simon Glass0d24de92012-01-14 15:12:45 +0000733
Simon Glassdd147ed2020-10-29 21:46:20 -0600734 fil = open(fname, 'w')
Simon Glass0d24de92012-01-14 15:12:45 +0000735 text = series.cover
736 prefix = series.GetPatchPrefix()
737 for line in lines:
738 if line.startswith('Subject:'):
Wu, Josh35ce2dc2015-04-03 10:51:17 +0800739 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
740 zero_repeat = int(math.log10(count)) + 1
741 zero = '0' * zero_repeat
742 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass0d24de92012-01-14 15:12:45 +0000743
744 # Insert our cover letter
745 elif line.startswith('*** BLURB HERE ***'):
746 # First the blurb test
747 line = '\n'.join(text[1:]) + '\n'
748 if series.get('notes'):
749 line += '\n'.join(series.notes) + '\n'
750
751 # Now the change list
752 out = series.MakeChangeLog(None)
753 line += '\n' + '\n'.join(out)
Simon Glassdd147ed2020-10-29 21:46:20 -0600754 fil.write(line)
755 fil.close()