blob: b3a25d59d1146cc6b6e8b47ec3220b31017ba901 [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
554
Simon Glassd93720e2020-10-29 21:46:19 -0600555def get_metadata_for_list(commit_range, git_dir=None, count=None,
556 series=None, allow_overwrite=False):
Simon Glasse62f9052012-12-15 10:42:06 +0000557 """Reads out patch series metadata from the commits
558
559 This does a 'git log' on the relevant commits and pulls out the tags we
560 are interested in.
561
562 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600563 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
564 git_dir (str): Path to git repositiory (None to use default)
565 count (int): Number of commits to list, or None for no limit
566 series (Series): Object to add information into. By default a new series
Simon Glasse62f9052012-12-15 10:42:06 +0000567 is started.
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600568 allow_overwrite (bool): Allow tags to overwrite an existing tag
569
Simon Glasse62f9052012-12-15 10:42:06 +0000570 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600571 Series: Object containing information about the commits.
Simon Glasse62f9052012-12-15 10:42:06 +0000572 """
Simon Glass891b7a02014-09-05 19:00:19 -0600573 if not series:
574 series = Series()
Simon Glass950a2312014-09-05 19:00:23 -0600575 series.allow_overwrite = allow_overwrite
Simon Glass9ad96982016-03-06 19:45:33 -0700576 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
Simon Glasscda2a612014-08-09 15:33:10 -0600577 git_dir=git_dir)
578 stdout = command.RunPipe([params], capture=True).stdout
Simon Glassdd147ed2020-10-29 21:46:20 -0600579 pst = PatchStream(series, is_log=True)
Simon Glasse62f9052012-12-15 10:42:06 +0000580 for line in stdout.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600581 pst.process_line(line)
582 pst.finalise()
Simon Glasse62f9052012-12-15 10:42:06 +0000583 return series
584
Simon Glassd93720e2020-10-29 21:46:19 -0600585def get_metadata(branch, start, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000586 """Reads out patch series metadata from the commits
587
588 This does a 'git log' on the relevant commits and pulls out the tags we
589 are interested in.
590
591 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600592 branch (str): Branch to use (None for current branch)
593 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
594 count (int): Number of commits to list
595
596 Returns:
597 Series: Object containing information about the commits.
Simon Glass0d24de92012-01-14 15:12:45 +0000598 """
Simon Glassd93720e2020-10-29 21:46:19 -0600599 return get_metadata_for_list(
600 '%s~%d' % (branch if branch else 'HEAD', start), None, count)
Simon Glass0d24de92012-01-14 15:12:45 +0000601
Simon Glassd93720e2020-10-29 21:46:19 -0600602def get_metadata_for_test(text):
Simon Glass6e87ae12017-05-29 15:31:31 -0600603 """Process metadata from a file containing a git log. Used for tests
604
605 Args:
606 text:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600607
608 Returns:
609 Series: Object containing information about the commits.
Simon Glass6e87ae12017-05-29 15:31:31 -0600610 """
611 series = Series()
Simon Glassdd147ed2020-10-29 21:46:20 -0600612 pst = PatchStream(series, is_log=True)
Simon Glass6e87ae12017-05-29 15:31:31 -0600613 for line in text.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600614 pst.process_line(line)
615 pst.finalise()
Simon Glass6e87ae12017-05-29 15:31:31 -0600616 return series
617
Simon Glassdd147ed2020-10-29 21:46:20 -0600618def fix_patch(backup_dir, fname, series, cmt):
Simon Glass0d24de92012-01-14 15:12:45 +0000619 """Fix up a patch file, by adding/removing as required.
620
621 We remove our tags from the patch file, insert changes lists, etc.
622 The patch file is processed in place, and overwritten.
623
624 A backup file is put into backup_dir (if not None).
625
626 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600627 backup_dir (str): Path to directory to use to backup the file
628 fname (str): Filename to patch file to process
629 series (Series): Series information about this patch set
630 cmt (Commit): Commit object for this patch file
631
Simon Glass0d24de92012-01-14 15:12:45 +0000632 Return:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600633 list: A list of errors, each str, or [] if all ok.
Simon Glass0d24de92012-01-14 15:12:45 +0000634 """
635 handle, tmpname = tempfile.mkstemp()
Simon Glass272cd852019-10-31 07:42:51 -0600636 outfd = os.fdopen(handle, 'w', encoding='utf-8')
637 infd = open(fname, 'r', encoding='utf-8')
Simon Glassdd147ed2020-10-29 21:46:20 -0600638 pst = PatchStream(series)
639 pst.commit = cmt
640 pst.process_stream(infd, outfd)
Simon Glass0d24de92012-01-14 15:12:45 +0000641 infd.close()
642 outfd.close()
643
644 # Create a backup file if required
645 if backup_dir:
646 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
647 shutil.move(tmpname, fname)
Simon Glass313ef5f2020-10-29 21:46:24 -0600648 return cmt.warn
Simon Glass0d24de92012-01-14 15:12:45 +0000649
Simon Glassd93720e2020-10-29 21:46:19 -0600650def fix_patches(series, fnames):
Simon Glass0d24de92012-01-14 15:12:45 +0000651 """Fix up a list of patches identified by filenames
652
653 The patch files are processed in place, and overwritten.
654
655 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600656 series (Series): The Series object
657 fnames (:type: list of str): List of patch files to process
Simon Glass0d24de92012-01-14 15:12:45 +0000658 """
659 # Current workflow creates patches, so we shouldn't need a backup
660 backup_dir = None #tempfile.mkdtemp('clean-patch')
661 count = 0
662 for fname in fnames:
Simon Glassdd147ed2020-10-29 21:46:20 -0600663 cmt = series.commits[count]
664 cmt.patch = fname
665 cmt.count = count
666 result = fix_patch(backup_dir, fname, series, cmt)
Simon Glass0d24de92012-01-14 15:12:45 +0000667 if result:
Simon Glass9994baa2020-10-29 21:46:30 -0600668 print('%d warning%s for %s:' %
669 (len(result), 's' if len(result) > 1 else '', fname))
Simon Glass0d24de92012-01-14 15:12:45 +0000670 for warn in result:
Simon Glass9994baa2020-10-29 21:46:30 -0600671 print('\t%s' % warn)
672 print()
Simon Glass0d24de92012-01-14 15:12:45 +0000673 count += 1
Simon Glass9994baa2020-10-29 21:46:30 -0600674 print('Cleaned %d patch%s' % (count, 'es' if count > 1 else ''))
Simon Glass0d24de92012-01-14 15:12:45 +0000675
Simon Glassd93720e2020-10-29 21:46:19 -0600676def insert_cover_letter(fname, series, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000677 """Inserts a cover letter with the required info into patch 0
678
679 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600680 fname (str): Input / output filename of the cover letter file
681 series (Series): Series object
682 count (int): Number of patches in the series
Simon Glass0d24de92012-01-14 15:12:45 +0000683 """
Simon Glassdd147ed2020-10-29 21:46:20 -0600684 fil = open(fname, 'r')
685 lines = fil.readlines()
686 fil.close()
Simon Glass0d24de92012-01-14 15:12:45 +0000687
Simon Glassdd147ed2020-10-29 21:46:20 -0600688 fil = open(fname, 'w')
Simon Glass0d24de92012-01-14 15:12:45 +0000689 text = series.cover
690 prefix = series.GetPatchPrefix()
691 for line in lines:
692 if line.startswith('Subject:'):
Wu, Josh35ce2dc2015-04-03 10:51:17 +0800693 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
694 zero_repeat = int(math.log10(count)) + 1
695 zero = '0' * zero_repeat
696 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass0d24de92012-01-14 15:12:45 +0000697
698 # Insert our cover letter
699 elif line.startswith('*** BLURB HERE ***'):
700 # First the blurb test
701 line = '\n'.join(text[1:]) + '\n'
702 if series.get('notes'):
703 line += '\n'.join(series.notes) + '\n'
704
705 # Now the change list
706 out = series.MakeChangeLog(None)
707 line += '\n' + '\n'.join(out)
Simon Glassdd147ed2020-10-29 21:46:20 -0600708 fil.write(line)
709 fil.close()