blob: 880d7ddc7f27a0f6179acb0e889e89558e973714 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass0d24de92012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass0d24de92012-01-14 15:12:45 +00004
Simon Glassd06e55a2020-10-29 21:46:17 -06005"""Handles parsing a stream of commits/emails from 'git log' or other source"""
6
Douglas Anderson833e4192019-09-27 09:23:56 -07007import datetime
Wu, Josh35ce2dc2015-04-03 10:51:17 +08008import math
Simon Glass0d24de92012-01-14 15:12:45 +00009import os
10import re
11import shutil
12import tempfile
13
Simon Glassbf776672020-04-17 18:09:04 -060014from patman import command
15from patman import commit
16from patman import gitutil
17from patman.series import Series
Simon Glass0d24de92012-01-14 15:12:45 +000018
19# Tags that we detect and remove
Simon Glass57699042020-10-29 21:46:18 -060020RE_REMOVE = re.compile(r'^BUG=|^TEST=|^BRANCH=|^Review URL:'
Simon Glassd06e55a2020-10-29 21:46:17 -060021 r'|Reviewed-on:|Commit-\w*:')
Simon Glass0d24de92012-01-14 15:12:45 +000022
23# Lines which are allowed after a TEST= line
Simon Glass57699042020-10-29 21:46:18 -060024RE_ALLOWED_AFTER_TEST = re.compile('^Signed-off-by:')
Simon Glass0d24de92012-01-14 15:12:45 +000025
Ilya Yanok05e5b732012-08-06 23:46:05 +000026# Signoffs
Simon Glass57699042020-10-29 21:46:18 -060027RE_SIGNOFF = re.compile('^Signed-off-by: *(.*)')
Ilya Yanok05e5b732012-08-06 23:46:05 +000028
Sean Anderson6949f702020-05-04 16:28:34 -040029# Cover letter tag
Simon Glass57699042020-10-29 21:46:18 -060030RE_COVER = re.compile('^Cover-([a-z-]*): *(.*)')
Simon Glassfe2f8d92013-03-20 16:43:00 +000031
Simon Glass0d24de92012-01-14 15:12:45 +000032# Patch series tag
Simon Glass57699042020-10-29 21:46:18 -060033RE_SERIES_TAG = re.compile('^Series-([a-z-]*): *(.*)')
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +010034
Douglas Anderson833e4192019-09-27 09:23:56 -070035# Change-Id will be used to generate the Message-Id and then be stripped
Simon Glass57699042020-10-29 21:46:18 -060036RE_CHANGE_ID = re.compile('^Change-Id: *(.*)')
Douglas Anderson833e4192019-09-27 09:23:56 -070037
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +010038# Commit series tag
Simon Glass57699042020-10-29 21:46:18 -060039RE_COMMIT_TAG = re.compile('^Commit-([a-z-]*): *(.*)')
Simon Glass0d24de92012-01-14 15:12:45 +000040
41# Commit tags that we want to collect and keep
Simon Glass57699042020-10-29 21:46:18 -060042RE_TAG = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc|Fixes): (.*)')
Simon Glass0d24de92012-01-14 15:12:45 +000043
44# The start of a new commit in the git log
Simon Glass57699042020-10-29 21:46:18 -060045RE_COMMIT = re.compile('^commit ([0-9a-f]*)$')
Simon Glass0d24de92012-01-14 15:12:45 +000046
47# We detect these since checkpatch doesn't always do it
Simon Glass57699042020-10-29 21:46:18 -060048RE_SPACE_BEFORE_TAB = re.compile('^[+].* \t')
Simon Glass0d24de92012-01-14 15:12:45 +000049
Sean Anderson0411fff2020-05-04 16:28:35 -040050# Match indented lines for changes
Simon Glass57699042020-10-29 21:46:18 -060051RE_LEADING_WHITESPACE = re.compile(r'^\s')
Sean Anderson0411fff2020-05-04 16:28:35 -040052
Simon Glass0d24de92012-01-14 15:12:45 +000053# States we can be in - can we use range() and still have comments?
54STATE_MSG_HEADER = 0 # Still in the message header
55STATE_PATCH_SUBJECT = 1 # In patch subject (first line of log for a commit)
56STATE_PATCH_HEADER = 2 # In patch header (after the subject)
57STATE_DIFFS = 3 # In the diff part (past --- line)
58
59class PatchStream:
60 """Class for detecting/injecting tags in a patch or series of patches
61
62 We support processing the output of 'git log' to read out the tags we
63 are interested in. We can also process a patch file in order to remove
64 unwanted tags or inject additional ones. These correspond to the two
65 phases of processing.
66 """
Simon Glasse3a816b2020-10-29 21:46:21 -060067 def __init__(self, series, is_log=False):
Simon Glass0d24de92012-01-14 15:12:45 +000068 self.skip_blank = False # True to skip a single blank line
69 self.found_test = False # Found a TEST= line
Sean Anderson6949f702020-05-04 16:28:34 -040070 self.lines_after_test = 0 # Number of lines found after TEST=
Simon Glass0d24de92012-01-14 15:12:45 +000071 self.linenum = 1 # Output line number we are up to
72 self.in_section = None # Name of start...END section we are in
73 self.notes = [] # Series notes
74 self.section = [] # The current section...END section
75 self.series = series # Info about the patch series
76 self.is_log = is_log # True if indent like git log
Sean Anderson6949f702020-05-04 16:28:34 -040077 self.in_change = None # Name of the change list we are in
78 self.change_version = 0 # Non-zero if we are in a change list
Sean Anderson0411fff2020-05-04 16:28:35 -040079 self.change_lines = [] # Lines of the current change
Simon Glass0d24de92012-01-14 15:12:45 +000080 self.blank_count = 0 # Number of blank lines stored up
81 self.state = STATE_MSG_HEADER # What state are we in?
Simon Glass0d24de92012-01-14 15:12:45 +000082 self.signoff = [] # Contents of signoff line
83 self.commit = None # Current commit
84
Simon Glassb5cc3992020-10-29 21:46:23 -060085 def _add_warn(self, warn):
Simon Glass313ef5f2020-10-29 21:46:24 -060086 """Add a new warning to report to the user about the current commit
87
88 The new warning is added to the current commit if not already present.
Simon Glassb5cc3992020-10-29 21:46:23 -060089
90 Args:
91 warn (str): Warning to report
Simon Glass313ef5f2020-10-29 21:46:24 -060092
93 Raises:
94 ValueError: Warning is generated with no commit associated
Simon Glassb5cc3992020-10-29 21:46:23 -060095 """
Simon Glass313ef5f2020-10-29 21:46:24 -060096 if not self.commit:
97 raise ValueError('Warning outside commit: %s' % warn)
98 if warn not in self.commit.warn:
99 self.commit.warn.append(warn)
Simon Glassb5cc3992020-10-29 21:46:23 -0600100
Simon Glassd93720e2020-10-29 21:46:19 -0600101 def _add_to_series(self, line, name, value):
Simon Glass0d24de92012-01-14 15:12:45 +0000102 """Add a new Series-xxx tag.
103
104 When a Series-xxx tag is detected, we come here to record it, if we
105 are scanning a 'git log'.
106
107 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600108 line (str): Source line containing tag (useful for debug/error
109 messages)
110 name (str): Tag name (part after 'Series-')
111 value (str): Tag value (part after 'Series-xxx: ')
Simon Glass0d24de92012-01-14 15:12:45 +0000112 """
113 if name == 'notes':
114 self.in_section = name
115 self.skip_blank = False
116 if self.is_log:
117 self.series.AddTag(self.commit, line, name, value)
118
Simon Glasse3a816b2020-10-29 21:46:21 -0600119 def _add_to_commit(self, name):
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100120 """Add a new Commit-xxx tag.
121
122 When a Commit-xxx tag is detected, we come here to record it.
123
124 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600125 name (str): Tag name (part after 'Commit-')
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100126 """
127 if name == 'notes':
128 self.in_section = 'commit-' + name
129 self.skip_blank = False
130
Simon Glassd93720e2020-10-29 21:46:19 -0600131 def _add_commit_rtag(self, rtag_type, who):
Simon Glass7207e2b2020-07-05 21:41:57 -0600132 """Add a response tag to the current commit
133
134 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600135 rtag_type (str): rtag type (e.g. 'Reviewed-by')
136 who (str): Person who gave that rtag, e.g.
137 'Fred Bloggs <fred@bloggs.org>'
Simon Glass7207e2b2020-07-05 21:41:57 -0600138 """
139 self.commit.AddRtag(rtag_type, who)
140
Simon Glassd93720e2020-10-29 21:46:19 -0600141 def _close_commit(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000142 """Save the current commit into our commit list, and reset our state"""
143 if self.commit and self.is_log:
144 self.series.AddCommit(self.commit)
145 self.commit = None
Bin Meng0d577182016-06-26 23:24:30 -0700146 # If 'END' is missing in a 'Cover-letter' section, and that section
147 # happens to show up at the very end of the commit message, this is
148 # the chance for us to fix it up.
149 if self.in_section == 'cover' and self.is_log:
150 self.series.cover = self.section
151 self.in_section = None
152 self.skip_blank = True
153 self.section = []
Simon Glass0d24de92012-01-14 15:12:45 +0000154
Simon Glassd93720e2020-10-29 21:46:19 -0600155 def _parse_version(self, value, line):
Sean Anderson6949f702020-05-04 16:28:34 -0400156 """Parse a version from a *-changes tag
157
158 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600159 value (str): Tag value (part after 'xxx-changes: '
160 line (str): Source line containing tag
Sean Anderson6949f702020-05-04 16:28:34 -0400161
162 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600163 int: The version as an integer
164
165 Raises:
166 ValueError: the value cannot be converted
Sean Anderson6949f702020-05-04 16:28:34 -0400167 """
168 try:
169 return int(value)
Simon Glassdd147ed2020-10-29 21:46:20 -0600170 except ValueError:
Sean Anderson6949f702020-05-04 16:28:34 -0400171 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassd06e55a2020-10-29 21:46:17 -0600172 (self.commit.hash, line))
Sean Anderson6949f702020-05-04 16:28:34 -0400173
Simon Glassd93720e2020-10-29 21:46:19 -0600174 def _finalise_change(self):
175 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson0411fff2020-05-04 16:28:35 -0400176 if not self.change_lines:
177 return
178 change = '\n'.join(self.change_lines)
179
180 if self.in_change == 'Series':
181 self.series.AddChange(self.change_version, self.commit, change)
182 elif self.in_change == 'Cover':
183 self.series.AddChange(self.change_version, None, change)
184 elif self.in_change == 'Commit':
185 self.commit.AddChange(self.change_version, change)
186 self.change_lines = []
187
Simon Glassd93720e2020-10-29 21:46:19 -0600188 def process_line(self, line):
Simon Glass0d24de92012-01-14 15:12:45 +0000189 """Process a single line of a patch file or commit log
190
191 This process a line and returns a list of lines to output. The list
192 may be empty or may contain multiple output lines.
193
194 This is where all the complicated logic is located. The class's
195 state is used to move between different states and detect things
196 properly.
197
198 We can be in one of two modes:
199 self.is_log == True: This is 'git log' mode, where most output is
200 indented by 4 characters and we are scanning for tags
201
202 self.is_log == False: This is 'patch' mode, where we already have
203 all the tags, and are processing patches to remove junk we
204 don't want, and add things we think are required.
205
206 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600207 line (str): text line to process
Simon Glass0d24de92012-01-14 15:12:45 +0000208
209 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600210 list: list of output lines, or [] if nothing should be output
211
212 Raises:
213 ValueError: a fatal error occurred while parsing, e.g. an END
214 without a starting tag, or two commits with two change IDs
Simon Glass0d24de92012-01-14 15:12:45 +0000215 """
216 # Initially we have no output. Prepare the input line string
217 out = []
218 line = line.rstrip('\n')
Scott Wood4b89b812014-09-25 14:30:46 -0500219
Simon Glass57699042020-10-29 21:46:18 -0600220 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood4b89b812014-09-25 14:30:46 -0500221
Simon Glass0d24de92012-01-14 15:12:45 +0000222 if self.is_log:
223 if line[:4] == ' ':
224 line = line[4:]
225
226 # Handle state transition and skipping blank lines
Simon Glass57699042020-10-29 21:46:18 -0600227 series_tag_match = RE_SERIES_TAG.match(line)
228 change_id_match = RE_CHANGE_ID.match(line)
229 commit_tag_match = RE_COMMIT_TAG.match(line)
230 cover_match = RE_COVER.match(line)
231 signoff_match = RE_SIGNOFF.match(line)
232 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000233 tag_match = None
234 if self.state == STATE_PATCH_HEADER:
Simon Glass57699042020-10-29 21:46:18 -0600235 tag_match = RE_TAG.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000236 is_blank = not line.strip()
237 if is_blank:
238 if (self.state == STATE_MSG_HEADER
239 or self.state == STATE_PATCH_SUBJECT):
240 self.state += 1
241
242 # We don't have a subject in the text stream of patch files
243 # It has its own line with a Subject: tag
244 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
245 self.state += 1
246 elif commit_match:
247 self.state = STATE_MSG_HEADER
248
Bin Meng94fbd3e2016-06-26 23:24:32 -0700249 # If a tag is detected, or a new commit starts
Douglas Anderson833e4192019-09-27 09:23:56 -0700250 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson6949f702020-05-04 16:28:34 -0400251 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng57b6b192016-06-26 23:24:31 -0700252 # but we are already in a section, this means 'END' is missing
253 # for that section, fix it up.
Bin Meng13b98d92016-06-26 23:24:29 -0700254 if self.in_section:
Simon Glassb5cc3992020-10-29 21:46:23 -0600255 self._add_warn("Missing 'END' in section '%s'" % self.in_section)
Bin Meng13b98d92016-06-26 23:24:29 -0700256 if self.in_section == 'cover':
257 self.series.cover = self.section
258 elif self.in_section == 'notes':
259 if self.is_log:
260 self.series.notes += self.section
261 elif self.in_section == 'commit-notes':
262 if self.is_log:
263 self.commit.notes += self.section
264 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600265 # This should not happen
266 raise ValueError("Unknown section '%s'" % self.in_section)
Bin Meng13b98d92016-06-26 23:24:29 -0700267 self.in_section = None
268 self.skip_blank = True
269 self.section = []
Bin Meng57b6b192016-06-26 23:24:31 -0700270 # but we are already in a change list, that means a blank line
271 # is missing, fix it up.
272 if self.in_change:
Simon Glassb5cc3992020-10-29 21:46:23 -0600273 self._add_warn("Missing 'blank line' in section '%s-changes'" %
274 self.in_change)
Simon Glassd93720e2020-10-29 21:46:19 -0600275 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400276 self.in_change = None
277 self.change_version = 0
Bin Meng13b98d92016-06-26 23:24:29 -0700278
Simon Glass0d24de92012-01-14 15:12:45 +0000279 # If we are in a section, keep collecting lines until we see END
280 if self.in_section:
281 if line == 'END':
282 if self.in_section == 'cover':
283 self.series.cover = self.section
284 elif self.in_section == 'notes':
285 if self.is_log:
286 self.series.notes += self.section
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100287 elif self.in_section == 'commit-notes':
288 if self.is_log:
289 self.commit.notes += self.section
Simon Glass0d24de92012-01-14 15:12:45 +0000290 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600291 # This should not happen
292 raise ValueError("Unknown section '%s'" % self.in_section)
Simon Glass0d24de92012-01-14 15:12:45 +0000293 self.in_section = None
294 self.skip_blank = True
295 self.section = []
296 else:
297 self.section.append(line)
298
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200299 # If we are not in a section, it is an unexpected END
300 elif line == 'END':
Simon Glassd06e55a2020-10-29 21:46:17 -0600301 raise ValueError("'END' wihout section")
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200302
Simon Glass0d24de92012-01-14 15:12:45 +0000303 # Detect the commit subject
304 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
305 self.commit.subject = line
306
307 # Detect the tags we want to remove, and skip blank lines
Simon Glass57699042020-10-29 21:46:18 -0600308 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass0d24de92012-01-14 15:12:45 +0000309 self.skip_blank = True
310
311 # TEST= should be the last thing in the commit, so remove
312 # everything after it
313 if line.startswith('TEST='):
314 self.found_test = True
315 elif self.skip_blank and is_blank:
316 self.skip_blank = False
317
Sean Anderson6949f702020-05-04 16:28:34 -0400318 # Detect Cover-xxx tags
Bin Menge7df2182016-06-26 23:24:28 -0700319 elif cover_match:
Sean Anderson6949f702020-05-04 16:28:34 -0400320 name = cover_match.group(1)
321 value = cover_match.group(2)
322 if name == 'letter':
323 self.in_section = 'cover'
324 self.skip_blank = False
325 elif name == 'letter-cc':
Simon Glassd93720e2020-10-29 21:46:19 -0600326 self._add_to_series(line, 'cover-cc', value)
Sean Anderson6949f702020-05-04 16:28:34 -0400327 elif name == 'changes':
328 self.in_change = 'Cover'
Simon Glassd93720e2020-10-29 21:46:19 -0600329 self.change_version = self._parse_version(value, line)
Simon Glassfe2f8d92013-03-20 16:43:00 +0000330
Simon Glass0d24de92012-01-14 15:12:45 +0000331 # If we are in a change list, key collected lines until a blank one
332 elif self.in_change:
333 if is_blank:
334 # Blank line ends this change list
Simon Glassd93720e2020-10-29 21:46:19 -0600335 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400336 self.in_change = None
337 self.change_version = 0
Simon Glass102061b2014-04-20 10:50:14 -0600338 elif line == '---':
Simon Glassd93720e2020-10-29 21:46:19 -0600339 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400340 self.in_change = None
341 self.change_version = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600342 out = self.process_line(line)
Sean Anderson0411fff2020-05-04 16:28:35 -0400343 elif self.is_log:
344 if not leading_whitespace_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600345 self._finalise_change()
Sean Anderson0411fff2020-05-04 16:28:35 -0400346 self.change_lines.append(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000347 self.skip_blank = False
348
349 # Detect Series-xxx tags
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100350 elif series_tag_match:
351 name = series_tag_match.group(1)
352 value = series_tag_match.group(2)
Simon Glass0d24de92012-01-14 15:12:45 +0000353 if name == 'changes':
354 # value is the version number: e.g. 1, or 2
Sean Anderson6949f702020-05-04 16:28:34 -0400355 self.in_change = 'Series'
Simon Glassd93720e2020-10-29 21:46:19 -0600356 self.change_version = self._parse_version(value, line)
Simon Glass0d24de92012-01-14 15:12:45 +0000357 else:
Simon Glassd93720e2020-10-29 21:46:19 -0600358 self._add_to_series(line, name, value)
Simon Glass0d24de92012-01-14 15:12:45 +0000359 self.skip_blank = True
360
Douglas Anderson833e4192019-09-27 09:23:56 -0700361 # Detect Change-Id tags
362 elif change_id_match:
363 value = change_id_match.group(1)
364 if self.is_log:
365 if self.commit.change_id:
Simon Glassd06e55a2020-10-29 21:46:17 -0600366 raise ValueError(
367 "%s: Two Change-Ids: '%s' vs. '%s'" % self.commit.hash,
368 self.commit.change_id, value)
Douglas Anderson833e4192019-09-27 09:23:56 -0700369 self.commit.change_id = value
370 self.skip_blank = True
371
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100372 # Detect Commit-xxx tags
373 elif commit_tag_match:
374 name = commit_tag_match.group(1)
375 value = commit_tag_match.group(2)
376 if name == 'notes':
Simon Glasse3a816b2020-10-29 21:46:21 -0600377 self._add_to_commit(name)
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100378 self.skip_blank = True
Sean Anderson6949f702020-05-04 16:28:34 -0400379 elif name == 'changes':
380 self.in_change = 'Commit'
Simon Glassd93720e2020-10-29 21:46:19 -0600381 self.change_version = self._parse_version(value, line)
Patrick Delaunaye5ff9ab2020-07-02 19:52:54 +0200382 else:
Simon Glassb5cc3992020-10-29 21:46:23 -0600383 self._add_warn('Line %d: Ignoring Commit-%s' %
384 (self.linenum, name))
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100385
Simon Glass0d24de92012-01-14 15:12:45 +0000386 # Detect the start of a new commit
387 elif commit_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600388 self._close_commit()
Simon Glass0b5b4092014-10-15 02:27:00 -0600389 self.commit = commit.Commit(commit_match.group(1))
Simon Glass0d24de92012-01-14 15:12:45 +0000390
391 # Detect tags in the commit message
392 elif tag_match:
Simon Glass7207e2b2020-07-05 21:41:57 -0600393 rtag_type, who = tag_match.groups()
Simon Glassd93720e2020-10-29 21:46:19 -0600394 self._add_commit_rtag(rtag_type, who)
Simon Glass0d24de92012-01-14 15:12:45 +0000395 # Remove Tested-by self, since few will take much notice
Simon Glass7207e2b2020-07-05 21:41:57 -0600396 if (rtag_type == 'Tested-by' and
397 who.find(os.getenv('USER') + '@') != -1):
Simon Glassb5cc3992020-10-29 21:46:23 -0600398 self._add_warn("Ignoring %s" % line)
Simon Glass7207e2b2020-07-05 21:41:57 -0600399 elif rtag_type == 'Patch-cc':
400 self.commit.AddCc(who.split(','))
Simon Glass0d24de92012-01-14 15:12:45 +0000401 else:
Simon Glassd0c57192014-08-28 09:43:38 -0600402 out = [line]
Simon Glass0d24de92012-01-14 15:12:45 +0000403
Simon Glass102061b2014-04-20 10:50:14 -0600404 # Suppress duplicate signoffs
405 elif signoff_match:
Simon Glasse752edc2014-08-28 09:43:35 -0600406 if (self.is_log or not self.commit or
Simon Glassd06e55a2020-10-29 21:46:17 -0600407 self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
Simon Glass102061b2014-04-20 10:50:14 -0600408 out = [line]
409
Simon Glass0d24de92012-01-14 15:12:45 +0000410 # Well that means this is an ordinary line
411 else:
Simon Glass0d24de92012-01-14 15:12:45 +0000412 # Look for space before tab
Simon Glassdd147ed2020-10-29 21:46:20 -0600413 mat = RE_SPACE_BEFORE_TAB.match(line)
414 if mat:
Simon Glassb5cc3992020-10-29 21:46:23 -0600415 self._add_warn('Line %d/%d has space before tab' %
416 (self.linenum, mat.start()))
Simon Glass0d24de92012-01-14 15:12:45 +0000417
418 # OK, we have a valid non-blank line
419 out = [line]
420 self.linenum += 1
421 self.skip_blank = False
422 if self.state == STATE_DIFFS:
423 pass
424
425 # If this is the start of the diffs section, emit our tags and
426 # change log
427 elif line == '---':
428 self.state = STATE_DIFFS
429
Sean Anderson6949f702020-05-04 16:28:34 -0400430 # Output the tags (signoff first), then change list
Simon Glass0d24de92012-01-14 15:12:45 +0000431 out = []
Simon Glass0d24de92012-01-14 15:12:45 +0000432 log = self.series.MakeChangeLog(self.commit)
Simon Glasse752edc2014-08-28 09:43:35 -0600433 out += [line]
434 if self.commit:
435 out += self.commit.notes
436 out += [''] + log
Simon Glass0d24de92012-01-14 15:12:45 +0000437 elif self.found_test:
Simon Glass57699042020-10-29 21:46:18 -0600438 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass0d24de92012-01-14 15:12:45 +0000439 self.lines_after_test += 1
440
441 return out
442
Simon Glassd93720e2020-10-29 21:46:19 -0600443 def finalise(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000444 """Close out processing of this patch stream"""
Simon Glassd93720e2020-10-29 21:46:19 -0600445 self._finalise_change()
446 self._close_commit()
Simon Glass0d24de92012-01-14 15:12:45 +0000447 if self.lines_after_test:
Simon Glassb5cc3992020-10-29 21:46:23 -0600448 self._add_warn('Found %d lines after TEST=' % self.lines_after_test)
Simon Glass0d24de92012-01-14 15:12:45 +0000449
Simon Glassd93720e2020-10-29 21:46:19 -0600450 def _write_message_id(self, outfd):
Douglas Anderson833e4192019-09-27 09:23:56 -0700451 """Write the Message-Id into the output.
452
453 This is based on the Change-Id in the original patch, the version,
454 and the prefix.
455
456 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600457 outfd (io.IOBase): Output stream file object
Douglas Anderson833e4192019-09-27 09:23:56 -0700458 """
459 if not self.commit.change_id:
460 return
461
462 # If the count is -1 we're testing, so use a fixed time
463 if self.commit.count == -1:
464 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
465 else:
466 time_now = datetime.datetime.now()
467
468 # In theory there is email.utils.make_msgid() which would be nice
469 # to use, but it already produces something way too long and thus
470 # will produce ugly commit lines if someone throws this into
471 # a "Link:" tag in the final commit. So (sigh) roll our own.
472
473 # Start with the time; presumably we wouldn't send the same series
474 # with the same Change-Id at the exact same second.
475 parts = [time_now.strftime("%Y%m%d%H%M%S")]
476
477 # These seem like they would be nice to include.
478 if 'prefix' in self.series:
479 parts.append(self.series['prefix'])
480 if 'version' in self.series:
481 parts.append("v%s" % self.series['version'])
482
483 parts.append(str(self.commit.count + 1))
484
485 # The Change-Id must be last, right before the @
486 parts.append(self.commit.change_id)
487
488 # Join parts together with "." and write it out.
489 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
490
Simon Glassd93720e2020-10-29 21:46:19 -0600491 def process_stream(self, infd, outfd):
Simon Glass0d24de92012-01-14 15:12:45 +0000492 """Copy a stream from infd to outfd, filtering out unwanting things.
493
494 This is used to process patch files one at a time.
495
496 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600497 infd (io.IOBase): Input stream file object
498 outfd (io.IOBase): Output stream file object
Simon Glass0d24de92012-01-14 15:12:45 +0000499 """
500 # Extract the filename from each diff, for nice warnings
501 fname = None
502 last_fname = None
503 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson833e4192019-09-27 09:23:56 -0700504
Simon Glassd93720e2020-10-29 21:46:19 -0600505 self._write_message_id(outfd)
Douglas Anderson833e4192019-09-27 09:23:56 -0700506
Simon Glass0d24de92012-01-14 15:12:45 +0000507 while True:
508 line = infd.readline()
509 if not line:
510 break
Simon Glassd93720e2020-10-29 21:46:19 -0600511 out = self.process_line(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000512
513 # Try to detect blank lines at EOF
514 for line in out:
515 match = re_fname.match(line)
516 if match:
517 last_fname = fname
518 fname = match.group(1)
519 if line == '+':
520 self.blank_count += 1
521 else:
522 if self.blank_count and (line == '-- ' or match):
Simon Glassb5cc3992020-10-29 21:46:23 -0600523 self._add_warn("Found possible blank line(s) at end of file '%s'" %
524 last_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000525 outfd.write('+\n' * self.blank_count)
526 outfd.write(line + '\n')
527 self.blank_count = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600528 self.finalise()
Simon Glass0d24de92012-01-14 15:12:45 +0000529
530
Simon Glassd93720e2020-10-29 21:46:19 -0600531def get_metadata_for_list(commit_range, git_dir=None, count=None,
532 series=None, allow_overwrite=False):
Simon Glasse62f9052012-12-15 10:42:06 +0000533 """Reads out patch series metadata from the commits
534
535 This does a 'git log' on the relevant commits and pulls out the tags we
536 are interested in.
537
538 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600539 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
540 git_dir (str): Path to git repositiory (None to use default)
541 count (int): Number of commits to list, or None for no limit
542 series (Series): Object to add information into. By default a new series
Simon Glasse62f9052012-12-15 10:42:06 +0000543 is started.
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600544 allow_overwrite (bool): Allow tags to overwrite an existing tag
545
Simon Glasse62f9052012-12-15 10:42:06 +0000546 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600547 Series: Object containing information about the commits.
Simon Glasse62f9052012-12-15 10:42:06 +0000548 """
Simon Glass891b7a02014-09-05 19:00:19 -0600549 if not series:
550 series = Series()
Simon Glass950a2312014-09-05 19:00:23 -0600551 series.allow_overwrite = allow_overwrite
Simon Glass9ad96982016-03-06 19:45:33 -0700552 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
Simon Glasscda2a612014-08-09 15:33:10 -0600553 git_dir=git_dir)
554 stdout = command.RunPipe([params], capture=True).stdout
Simon Glassdd147ed2020-10-29 21:46:20 -0600555 pst = PatchStream(series, is_log=True)
Simon Glasse62f9052012-12-15 10:42:06 +0000556 for line in stdout.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600557 pst.process_line(line)
558 pst.finalise()
Simon Glasse62f9052012-12-15 10:42:06 +0000559 return series
560
Simon Glassd93720e2020-10-29 21:46:19 -0600561def get_metadata(branch, start, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000562 """Reads out patch series metadata from the commits
563
564 This does a 'git log' on the relevant commits and pulls out the tags we
565 are interested in.
566
567 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600568 branch (str): Branch to use (None for current branch)
569 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
570 count (int): Number of commits to list
571
572 Returns:
573 Series: Object containing information about the commits.
Simon Glass0d24de92012-01-14 15:12:45 +0000574 """
Simon Glassd93720e2020-10-29 21:46:19 -0600575 return get_metadata_for_list(
576 '%s~%d' % (branch if branch else 'HEAD', start), None, count)
Simon Glass0d24de92012-01-14 15:12:45 +0000577
Simon Glassd93720e2020-10-29 21:46:19 -0600578def get_metadata_for_test(text):
Simon Glass6e87ae12017-05-29 15:31:31 -0600579 """Process metadata from a file containing a git log. Used for tests
580
581 Args:
582 text:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600583
584 Returns:
585 Series: Object containing information about the commits.
Simon Glass6e87ae12017-05-29 15:31:31 -0600586 """
587 series = Series()
Simon Glassdd147ed2020-10-29 21:46:20 -0600588 pst = PatchStream(series, is_log=True)
Simon Glass6e87ae12017-05-29 15:31:31 -0600589 for line in text.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600590 pst.process_line(line)
591 pst.finalise()
Simon Glass6e87ae12017-05-29 15:31:31 -0600592 return series
593
Simon Glassdd147ed2020-10-29 21:46:20 -0600594def fix_patch(backup_dir, fname, series, cmt):
Simon Glass0d24de92012-01-14 15:12:45 +0000595 """Fix up a patch file, by adding/removing as required.
596
597 We remove our tags from the patch file, insert changes lists, etc.
598 The patch file is processed in place, and overwritten.
599
600 A backup file is put into backup_dir (if not None).
601
602 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600603 backup_dir (str): Path to directory to use to backup the file
604 fname (str): Filename to patch file to process
605 series (Series): Series information about this patch set
606 cmt (Commit): Commit object for this patch file
607
Simon Glass0d24de92012-01-14 15:12:45 +0000608 Return:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600609 list: A list of errors, each str, or [] if all ok.
Simon Glass0d24de92012-01-14 15:12:45 +0000610 """
611 handle, tmpname = tempfile.mkstemp()
Simon Glass272cd852019-10-31 07:42:51 -0600612 outfd = os.fdopen(handle, 'w', encoding='utf-8')
613 infd = open(fname, 'r', encoding='utf-8')
Simon Glassdd147ed2020-10-29 21:46:20 -0600614 pst = PatchStream(series)
615 pst.commit = cmt
616 pst.process_stream(infd, outfd)
Simon Glass0d24de92012-01-14 15:12:45 +0000617 infd.close()
618 outfd.close()
619
620 # Create a backup file if required
621 if backup_dir:
622 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
623 shutil.move(tmpname, fname)
Simon Glass313ef5f2020-10-29 21:46:24 -0600624 return cmt.warn
Simon Glass0d24de92012-01-14 15:12:45 +0000625
Simon Glassd93720e2020-10-29 21:46:19 -0600626def fix_patches(series, fnames):
Simon Glass0d24de92012-01-14 15:12:45 +0000627 """Fix up a list of patches identified by filenames
628
629 The patch files are processed in place, and overwritten.
630
631 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600632 series (Series): The Series object
633 fnames (:type: list of str): List of patch files to process
Simon Glass0d24de92012-01-14 15:12:45 +0000634 """
635 # Current workflow creates patches, so we shouldn't need a backup
636 backup_dir = None #tempfile.mkdtemp('clean-patch')
637 count = 0
638 for fname in fnames:
Simon Glassdd147ed2020-10-29 21:46:20 -0600639 cmt = series.commits[count]
640 cmt.patch = fname
641 cmt.count = count
642 result = fix_patch(backup_dir, fname, series, cmt)
Simon Glass0d24de92012-01-14 15:12:45 +0000643 if result:
Paul Burtona920a172016-09-27 16:03:50 +0100644 print('%d warnings for %s:' % (len(result), fname))
Simon Glass0d24de92012-01-14 15:12:45 +0000645 for warn in result:
Paul Burtona920a172016-09-27 16:03:50 +0100646 print('\t', warn)
Simon Glass0d24de92012-01-14 15:12:45 +0000647 print
648 count += 1
Paul Burtona920a172016-09-27 16:03:50 +0100649 print('Cleaned %d patches' % count)
Simon Glass0d24de92012-01-14 15:12:45 +0000650
Simon Glassd93720e2020-10-29 21:46:19 -0600651def insert_cover_letter(fname, series, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000652 """Inserts a cover letter with the required info into patch 0
653
654 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600655 fname (str): Input / output filename of the cover letter file
656 series (Series): Series object
657 count (int): Number of patches in the series
Simon Glass0d24de92012-01-14 15:12:45 +0000658 """
Simon Glassdd147ed2020-10-29 21:46:20 -0600659 fil = open(fname, 'r')
660 lines = fil.readlines()
661 fil.close()
Simon Glass0d24de92012-01-14 15:12:45 +0000662
Simon Glassdd147ed2020-10-29 21:46:20 -0600663 fil = open(fname, 'w')
Simon Glass0d24de92012-01-14 15:12:45 +0000664 text = series.cover
665 prefix = series.GetPatchPrefix()
666 for line in lines:
667 if line.startswith('Subject:'):
Wu, Josh35ce2dc2015-04-03 10:51:17 +0800668 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
669 zero_repeat = int(math.log10(count)) + 1
670 zero = '0' * zero_repeat
671 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass0d24de92012-01-14 15:12:45 +0000672
673 # Insert our cover letter
674 elif line.startswith('*** BLURB HERE ***'):
675 # First the blurb test
676 line = '\n'.join(text[1:]) + '\n'
677 if series.get('notes'):
678 line += '\n'.join(series.notes) + '\n'
679
680 # Now the change list
681 out = series.MakeChangeLog(None)
682 line += '\n' + '\n'.join(out)
Simon Glassdd147ed2020-10-29 21:46:20 -0600683 fil.write(line)
684 fil.close()