blob: b2cb2debeeef7f61d4031b6e6e11ff18aa20cf7c [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.warn = [] # List of warnings we have collected
72 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.signoff = [] # Contents of signoff line
84 self.commit = None # Current commit
85
Simon Glassd93720e2020-10-29 21:46:19 -060086 def _add_to_series(self, line, name, value):
Simon Glass0d24de92012-01-14 15:12:45 +000087 """Add a new Series-xxx tag.
88
89 When a Series-xxx tag is detected, we come here to record it, if we
90 are scanning a 'git log'.
91
92 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -060093 line (str): Source line containing tag (useful for debug/error
94 messages)
95 name (str): Tag name (part after 'Series-')
96 value (str): Tag value (part after 'Series-xxx: ')
Simon Glass0d24de92012-01-14 15:12:45 +000097 """
98 if name == 'notes':
99 self.in_section = name
100 self.skip_blank = False
101 if self.is_log:
102 self.series.AddTag(self.commit, line, name, value)
103
Simon Glasse3a816b2020-10-29 21:46:21 -0600104 def _add_to_commit(self, name):
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100105 """Add a new Commit-xxx tag.
106
107 When a Commit-xxx tag is detected, we come here to record it.
108
109 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600110 name (str): Tag name (part after 'Commit-')
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100111 """
112 if name == 'notes':
113 self.in_section = 'commit-' + name
114 self.skip_blank = False
115
Simon Glassd93720e2020-10-29 21:46:19 -0600116 def _add_commit_rtag(self, rtag_type, who):
Simon Glass7207e2b2020-07-05 21:41:57 -0600117 """Add a response tag to the current commit
118
119 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600120 rtag_type (str): rtag type (e.g. 'Reviewed-by')
121 who (str): Person who gave that rtag, e.g.
122 'Fred Bloggs <fred@bloggs.org>'
Simon Glass7207e2b2020-07-05 21:41:57 -0600123 """
124 self.commit.AddRtag(rtag_type, who)
125
Simon Glassd93720e2020-10-29 21:46:19 -0600126 def _close_commit(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000127 """Save the current commit into our commit list, and reset our state"""
128 if self.commit and self.is_log:
129 self.series.AddCommit(self.commit)
130 self.commit = None
Bin Meng0d577182016-06-26 23:24:30 -0700131 # If 'END' is missing in a 'Cover-letter' section, and that section
132 # happens to show up at the very end of the commit message, this is
133 # the chance for us to fix it up.
134 if self.in_section == 'cover' and self.is_log:
135 self.series.cover = self.section
136 self.in_section = None
137 self.skip_blank = True
138 self.section = []
Simon Glass0d24de92012-01-14 15:12:45 +0000139
Simon Glassd93720e2020-10-29 21:46:19 -0600140 def _parse_version(self, value, line):
Sean Anderson6949f702020-05-04 16:28:34 -0400141 """Parse a version from a *-changes tag
142
143 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600144 value (str): Tag value (part after 'xxx-changes: '
145 line (str): Source line containing tag
Sean Anderson6949f702020-05-04 16:28:34 -0400146
147 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600148 int: The version as an integer
149
150 Raises:
151 ValueError: the value cannot be converted
Sean Anderson6949f702020-05-04 16:28:34 -0400152 """
153 try:
154 return int(value)
Simon Glassdd147ed2020-10-29 21:46:20 -0600155 except ValueError:
Sean Anderson6949f702020-05-04 16:28:34 -0400156 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassd06e55a2020-10-29 21:46:17 -0600157 (self.commit.hash, line))
Sean Anderson6949f702020-05-04 16:28:34 -0400158
Simon Glassd93720e2020-10-29 21:46:19 -0600159 def _finalise_change(self):
160 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson0411fff2020-05-04 16:28:35 -0400161 if not self.change_lines:
162 return
163 change = '\n'.join(self.change_lines)
164
165 if self.in_change == 'Series':
166 self.series.AddChange(self.change_version, self.commit, change)
167 elif self.in_change == 'Cover':
168 self.series.AddChange(self.change_version, None, change)
169 elif self.in_change == 'Commit':
170 self.commit.AddChange(self.change_version, change)
171 self.change_lines = []
172
Simon Glassd93720e2020-10-29 21:46:19 -0600173 def process_line(self, line):
Simon Glass0d24de92012-01-14 15:12:45 +0000174 """Process a single line of a patch file or commit log
175
176 This process a line and returns a list of lines to output. The list
177 may be empty or may contain multiple output lines.
178
179 This is where all the complicated logic is located. The class's
180 state is used to move between different states and detect things
181 properly.
182
183 We can be in one of two modes:
184 self.is_log == True: This is 'git log' mode, where most output is
185 indented by 4 characters and we are scanning for tags
186
187 self.is_log == False: This is 'patch' mode, where we already have
188 all the tags, and are processing patches to remove junk we
189 don't want, and add things we think are required.
190
191 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600192 line (str): text line to process
Simon Glass0d24de92012-01-14 15:12:45 +0000193
194 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600195 list: list of output lines, or [] if nothing should be output
196
197 Raises:
198 ValueError: a fatal error occurred while parsing, e.g. an END
199 without a starting tag, or two commits with two change IDs
Simon Glass0d24de92012-01-14 15:12:45 +0000200 """
201 # Initially we have no output. Prepare the input line string
202 out = []
203 line = line.rstrip('\n')
Scott Wood4b89b812014-09-25 14:30:46 -0500204
Simon Glass57699042020-10-29 21:46:18 -0600205 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood4b89b812014-09-25 14:30:46 -0500206
Simon Glass0d24de92012-01-14 15:12:45 +0000207 if self.is_log:
208 if line[:4] == ' ':
209 line = line[4:]
210
211 # Handle state transition and skipping blank lines
Simon Glass57699042020-10-29 21:46:18 -0600212 series_tag_match = RE_SERIES_TAG.match(line)
213 change_id_match = RE_CHANGE_ID.match(line)
214 commit_tag_match = RE_COMMIT_TAG.match(line)
215 cover_match = RE_COVER.match(line)
216 signoff_match = RE_SIGNOFF.match(line)
217 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000218 tag_match = None
219 if self.state == STATE_PATCH_HEADER:
Simon Glass57699042020-10-29 21:46:18 -0600220 tag_match = RE_TAG.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000221 is_blank = not line.strip()
222 if is_blank:
223 if (self.state == STATE_MSG_HEADER
224 or self.state == STATE_PATCH_SUBJECT):
225 self.state += 1
226
227 # We don't have a subject in the text stream of patch files
228 # It has its own line with a Subject: tag
229 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
230 self.state += 1
231 elif commit_match:
232 self.state = STATE_MSG_HEADER
233
Bin Meng94fbd3e2016-06-26 23:24:32 -0700234 # If a tag is detected, or a new commit starts
Douglas Anderson833e4192019-09-27 09:23:56 -0700235 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson6949f702020-05-04 16:28:34 -0400236 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng57b6b192016-06-26 23:24:31 -0700237 # but we are already in a section, this means 'END' is missing
238 # for that section, fix it up.
Bin Meng13b98d92016-06-26 23:24:29 -0700239 if self.in_section:
240 self.warn.append("Missing 'END' in section '%s'" % self.in_section)
241 if self.in_section == 'cover':
242 self.series.cover = self.section
243 elif self.in_section == 'notes':
244 if self.is_log:
245 self.series.notes += self.section
246 elif self.in_section == 'commit-notes':
247 if self.is_log:
248 self.commit.notes += self.section
249 else:
250 self.warn.append("Unknown section '%s'" % self.in_section)
251 self.in_section = None
252 self.skip_blank = True
253 self.section = []
Bin Meng57b6b192016-06-26 23:24:31 -0700254 # but we are already in a change list, that means a blank line
255 # is missing, fix it up.
256 if self.in_change:
Sean Anderson6949f702020-05-04 16:28:34 -0400257 self.warn.append("Missing 'blank line' in section '%s-changes'" % self.in_change)
Simon Glassd93720e2020-10-29 21:46:19 -0600258 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400259 self.in_change = None
260 self.change_version = 0
Bin Meng13b98d92016-06-26 23:24:29 -0700261
Simon Glass0d24de92012-01-14 15:12:45 +0000262 # If we are in a section, keep collecting lines until we see END
263 if self.in_section:
264 if line == 'END':
265 if self.in_section == 'cover':
266 self.series.cover = self.section
267 elif self.in_section == 'notes':
268 if self.is_log:
269 self.series.notes += self.section
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100270 elif self.in_section == 'commit-notes':
271 if self.is_log:
272 self.commit.notes += self.section
Simon Glass0d24de92012-01-14 15:12:45 +0000273 else:
274 self.warn.append("Unknown section '%s'" % self.in_section)
275 self.in_section = None
276 self.skip_blank = True
277 self.section = []
278 else:
279 self.section.append(line)
280
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200281 # If we are not in a section, it is an unexpected END
282 elif line == 'END':
Simon Glassd06e55a2020-10-29 21:46:17 -0600283 raise ValueError("'END' wihout section")
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200284
Simon Glass0d24de92012-01-14 15:12:45 +0000285 # Detect the commit subject
286 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
287 self.commit.subject = line
288
289 # Detect the tags we want to remove, and skip blank lines
Simon Glass57699042020-10-29 21:46:18 -0600290 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass0d24de92012-01-14 15:12:45 +0000291 self.skip_blank = True
292
293 # TEST= should be the last thing in the commit, so remove
294 # everything after it
295 if line.startswith('TEST='):
296 self.found_test = True
297 elif self.skip_blank and is_blank:
298 self.skip_blank = False
299
Sean Anderson6949f702020-05-04 16:28:34 -0400300 # Detect Cover-xxx tags
Bin Menge7df2182016-06-26 23:24:28 -0700301 elif cover_match:
Sean Anderson6949f702020-05-04 16:28:34 -0400302 name = cover_match.group(1)
303 value = cover_match.group(2)
304 if name == 'letter':
305 self.in_section = 'cover'
306 self.skip_blank = False
307 elif name == 'letter-cc':
Simon Glassd93720e2020-10-29 21:46:19 -0600308 self._add_to_series(line, 'cover-cc', value)
Sean Anderson6949f702020-05-04 16:28:34 -0400309 elif name == 'changes':
310 self.in_change = 'Cover'
Simon Glassd93720e2020-10-29 21:46:19 -0600311 self.change_version = self._parse_version(value, line)
Simon Glassfe2f8d92013-03-20 16:43:00 +0000312
Simon Glass0d24de92012-01-14 15:12:45 +0000313 # If we are in a change list, key collected lines until a blank one
314 elif self.in_change:
315 if is_blank:
316 # Blank line ends this change list
Simon Glassd93720e2020-10-29 21:46:19 -0600317 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400318 self.in_change = None
319 self.change_version = 0
Simon Glass102061b2014-04-20 10:50:14 -0600320 elif line == '---':
Simon Glassd93720e2020-10-29 21:46:19 -0600321 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400322 self.in_change = None
323 self.change_version = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600324 out = self.process_line(line)
Sean Anderson0411fff2020-05-04 16:28:35 -0400325 elif self.is_log:
326 if not leading_whitespace_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600327 self._finalise_change()
Sean Anderson0411fff2020-05-04 16:28:35 -0400328 self.change_lines.append(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000329 self.skip_blank = False
330
331 # Detect Series-xxx tags
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100332 elif series_tag_match:
333 name = series_tag_match.group(1)
334 value = series_tag_match.group(2)
Simon Glass0d24de92012-01-14 15:12:45 +0000335 if name == 'changes':
336 # value is the version number: e.g. 1, or 2
Sean Anderson6949f702020-05-04 16:28:34 -0400337 self.in_change = 'Series'
Simon Glassd93720e2020-10-29 21:46:19 -0600338 self.change_version = self._parse_version(value, line)
Simon Glass0d24de92012-01-14 15:12:45 +0000339 else:
Simon Glassd93720e2020-10-29 21:46:19 -0600340 self._add_to_series(line, name, value)
Simon Glass0d24de92012-01-14 15:12:45 +0000341 self.skip_blank = True
342
Douglas Anderson833e4192019-09-27 09:23:56 -0700343 # Detect Change-Id tags
344 elif change_id_match:
345 value = change_id_match.group(1)
346 if self.is_log:
347 if self.commit.change_id:
Simon Glassd06e55a2020-10-29 21:46:17 -0600348 raise ValueError(
349 "%s: Two Change-Ids: '%s' vs. '%s'" % self.commit.hash,
350 self.commit.change_id, value)
Douglas Anderson833e4192019-09-27 09:23:56 -0700351 self.commit.change_id = value
352 self.skip_blank = True
353
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100354 # Detect Commit-xxx tags
355 elif commit_tag_match:
356 name = commit_tag_match.group(1)
357 value = commit_tag_match.group(2)
358 if name == 'notes':
Simon Glasse3a816b2020-10-29 21:46:21 -0600359 self._add_to_commit(name)
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100360 self.skip_blank = True
Sean Anderson6949f702020-05-04 16:28:34 -0400361 elif name == 'changes':
362 self.in_change = 'Commit'
Simon Glassd93720e2020-10-29 21:46:19 -0600363 self.change_version = self._parse_version(value, line)
Patrick Delaunaye5ff9ab2020-07-02 19:52:54 +0200364 else:
365 self.warn.append('Line %d: Ignoring Commit-%s' %
Simon Glassd06e55a2020-10-29 21:46:17 -0600366 (self.linenum, name))
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100367
Simon Glass0d24de92012-01-14 15:12:45 +0000368 # Detect the start of a new commit
369 elif commit_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600370 self._close_commit()
Simon Glass0b5b4092014-10-15 02:27:00 -0600371 self.commit = commit.Commit(commit_match.group(1))
Simon Glass0d24de92012-01-14 15:12:45 +0000372
373 # Detect tags in the commit message
374 elif tag_match:
Simon Glass7207e2b2020-07-05 21:41:57 -0600375 rtag_type, who = tag_match.groups()
Simon Glassd93720e2020-10-29 21:46:19 -0600376 self._add_commit_rtag(rtag_type, who)
Simon Glass0d24de92012-01-14 15:12:45 +0000377 # Remove Tested-by self, since few will take much notice
Simon Glass7207e2b2020-07-05 21:41:57 -0600378 if (rtag_type == 'Tested-by' and
379 who.find(os.getenv('USER') + '@') != -1):
Simon Glass0d24de92012-01-14 15:12:45 +0000380 self.warn.append("Ignoring %s" % line)
Simon Glass7207e2b2020-07-05 21:41:57 -0600381 elif rtag_type == 'Patch-cc':
382 self.commit.AddCc(who.split(','))
Simon Glass0d24de92012-01-14 15:12:45 +0000383 else:
Simon Glassd0c57192014-08-28 09:43:38 -0600384 out = [line]
Simon Glass0d24de92012-01-14 15:12:45 +0000385
Simon Glass102061b2014-04-20 10:50:14 -0600386 # Suppress duplicate signoffs
387 elif signoff_match:
Simon Glasse752edc2014-08-28 09:43:35 -0600388 if (self.is_log or not self.commit or
Simon Glassd06e55a2020-10-29 21:46:17 -0600389 self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
Simon Glass102061b2014-04-20 10:50:14 -0600390 out = [line]
391
Simon Glass0d24de92012-01-14 15:12:45 +0000392 # Well that means this is an ordinary line
393 else:
Simon Glass0d24de92012-01-14 15:12:45 +0000394 # Look for space before tab
Simon Glassdd147ed2020-10-29 21:46:20 -0600395 mat = RE_SPACE_BEFORE_TAB.match(line)
396 if mat:
Simon Glass0d24de92012-01-14 15:12:45 +0000397 self.warn.append('Line %d/%d has space before tab' %
Simon Glassdd147ed2020-10-29 21:46:20 -0600398 (self.linenum, mat.start()))
Simon Glass0d24de92012-01-14 15:12:45 +0000399
400 # OK, we have a valid non-blank line
401 out = [line]
402 self.linenum += 1
403 self.skip_blank = False
404 if self.state == STATE_DIFFS:
405 pass
406
407 # If this is the start of the diffs section, emit our tags and
408 # change log
409 elif line == '---':
410 self.state = STATE_DIFFS
411
Sean Anderson6949f702020-05-04 16:28:34 -0400412 # Output the tags (signoff first), then change list
Simon Glass0d24de92012-01-14 15:12:45 +0000413 out = []
Simon Glass0d24de92012-01-14 15:12:45 +0000414 log = self.series.MakeChangeLog(self.commit)
Simon Glasse752edc2014-08-28 09:43:35 -0600415 out += [line]
416 if self.commit:
417 out += self.commit.notes
418 out += [''] + log
Simon Glass0d24de92012-01-14 15:12:45 +0000419 elif self.found_test:
Simon Glass57699042020-10-29 21:46:18 -0600420 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass0d24de92012-01-14 15:12:45 +0000421 self.lines_after_test += 1
422
423 return out
424
Simon Glassd93720e2020-10-29 21:46:19 -0600425 def finalise(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000426 """Close out processing of this patch stream"""
Simon Glassd93720e2020-10-29 21:46:19 -0600427 self._finalise_change()
428 self._close_commit()
Simon Glass0d24de92012-01-14 15:12:45 +0000429 if self.lines_after_test:
430 self.warn.append('Found %d lines after TEST=' %
Simon Glassd06e55a2020-10-29 21:46:17 -0600431 self.lines_after_test)
Simon Glass0d24de92012-01-14 15:12:45 +0000432
Simon Glassd93720e2020-10-29 21:46:19 -0600433 def _write_message_id(self, outfd):
Douglas Anderson833e4192019-09-27 09:23:56 -0700434 """Write the Message-Id into the output.
435
436 This is based on the Change-Id in the original patch, the version,
437 and the prefix.
438
439 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600440 outfd (io.IOBase): Output stream file object
Douglas Anderson833e4192019-09-27 09:23:56 -0700441 """
442 if not self.commit.change_id:
443 return
444
445 # If the count is -1 we're testing, so use a fixed time
446 if self.commit.count == -1:
447 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
448 else:
449 time_now = datetime.datetime.now()
450
451 # In theory there is email.utils.make_msgid() which would be nice
452 # to use, but it already produces something way too long and thus
453 # will produce ugly commit lines if someone throws this into
454 # a "Link:" tag in the final commit. So (sigh) roll our own.
455
456 # Start with the time; presumably we wouldn't send the same series
457 # with the same Change-Id at the exact same second.
458 parts = [time_now.strftime("%Y%m%d%H%M%S")]
459
460 # These seem like they would be nice to include.
461 if 'prefix' in self.series:
462 parts.append(self.series['prefix'])
463 if 'version' in self.series:
464 parts.append("v%s" % self.series['version'])
465
466 parts.append(str(self.commit.count + 1))
467
468 # The Change-Id must be last, right before the @
469 parts.append(self.commit.change_id)
470
471 # Join parts together with "." and write it out.
472 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
473
Simon Glassd93720e2020-10-29 21:46:19 -0600474 def process_stream(self, infd, outfd):
Simon Glass0d24de92012-01-14 15:12:45 +0000475 """Copy a stream from infd to outfd, filtering out unwanting things.
476
477 This is used to process patch files one at a time.
478
479 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600480 infd (io.IOBase): Input stream file object
481 outfd (io.IOBase): Output stream file object
Simon Glass0d24de92012-01-14 15:12:45 +0000482 """
483 # Extract the filename from each diff, for nice warnings
484 fname = None
485 last_fname = None
486 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson833e4192019-09-27 09:23:56 -0700487
Simon Glassd93720e2020-10-29 21:46:19 -0600488 self._write_message_id(outfd)
Douglas Anderson833e4192019-09-27 09:23:56 -0700489
Simon Glass0d24de92012-01-14 15:12:45 +0000490 while True:
491 line = infd.readline()
492 if not line:
493 break
Simon Glassd93720e2020-10-29 21:46:19 -0600494 out = self.process_line(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000495
496 # Try to detect blank lines at EOF
497 for line in out:
498 match = re_fname.match(line)
499 if match:
500 last_fname = fname
501 fname = match.group(1)
502 if line == '+':
503 self.blank_count += 1
504 else:
505 if self.blank_count and (line == '-- ' or match):
506 self.warn.append("Found possible blank line(s) at "
Simon Glassd06e55a2020-10-29 21:46:17 -0600507 "end of file '%s'" % last_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000508 outfd.write('+\n' * self.blank_count)
509 outfd.write(line + '\n')
510 self.blank_count = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600511 self.finalise()
Simon Glass0d24de92012-01-14 15:12:45 +0000512
513
Simon Glassd93720e2020-10-29 21:46:19 -0600514def get_metadata_for_list(commit_range, git_dir=None, count=None,
515 series=None, allow_overwrite=False):
Simon Glasse62f9052012-12-15 10:42:06 +0000516 """Reads out patch series metadata from the commits
517
518 This does a 'git log' on the relevant commits and pulls out the tags we
519 are interested in.
520
521 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600522 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
523 git_dir (str): Path to git repositiory (None to use default)
524 count (int): Number of commits to list, or None for no limit
525 series (Series): Object to add information into. By default a new series
Simon Glasse62f9052012-12-15 10:42:06 +0000526 is started.
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600527 allow_overwrite (bool): Allow tags to overwrite an existing tag
528
Simon Glasse62f9052012-12-15 10:42:06 +0000529 Returns:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600530 Series: Object containing information about the commits.
Simon Glasse62f9052012-12-15 10:42:06 +0000531 """
Simon Glass891b7a02014-09-05 19:00:19 -0600532 if not series:
533 series = Series()
Simon Glass950a2312014-09-05 19:00:23 -0600534 series.allow_overwrite = allow_overwrite
Simon Glass9ad96982016-03-06 19:45:33 -0700535 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
Simon Glasscda2a612014-08-09 15:33:10 -0600536 git_dir=git_dir)
537 stdout = command.RunPipe([params], capture=True).stdout
Simon Glassdd147ed2020-10-29 21:46:20 -0600538 pst = PatchStream(series, is_log=True)
Simon Glasse62f9052012-12-15 10:42:06 +0000539 for line in stdout.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600540 pst.process_line(line)
541 pst.finalise()
Simon Glasse62f9052012-12-15 10:42:06 +0000542 return series
543
Simon Glassd93720e2020-10-29 21:46:19 -0600544def get_metadata(branch, start, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000545 """Reads out patch series metadata from the commits
546
547 This does a 'git log' on the relevant commits and pulls out the tags we
548 are interested in.
549
550 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600551 branch (str): Branch to use (None for current branch)
552 start (int): Commit to start from: 0=branch HEAD, 1=next one, etc.
553 count (int): Number of commits to list
554
555 Returns:
556 Series: Object containing information about the commits.
Simon Glass0d24de92012-01-14 15:12:45 +0000557 """
Simon Glassd93720e2020-10-29 21:46:19 -0600558 return get_metadata_for_list(
559 '%s~%d' % (branch if branch else 'HEAD', start), None, count)
Simon Glass0d24de92012-01-14 15:12:45 +0000560
Simon Glassd93720e2020-10-29 21:46:19 -0600561def get_metadata_for_test(text):
Simon Glass6e87ae12017-05-29 15:31:31 -0600562 """Process metadata from a file containing a git log. Used for tests
563
564 Args:
565 text:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600566
567 Returns:
568 Series: Object containing information about the commits.
Simon Glass6e87ae12017-05-29 15:31:31 -0600569 """
570 series = Series()
Simon Glassdd147ed2020-10-29 21:46:20 -0600571 pst = PatchStream(series, is_log=True)
Simon Glass6e87ae12017-05-29 15:31:31 -0600572 for line in text.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600573 pst.process_line(line)
574 pst.finalise()
Simon Glass6e87ae12017-05-29 15:31:31 -0600575 return series
576
Simon Glassdd147ed2020-10-29 21:46:20 -0600577def fix_patch(backup_dir, fname, series, cmt):
Simon Glass0d24de92012-01-14 15:12:45 +0000578 """Fix up a patch file, by adding/removing as required.
579
580 We remove our tags from the patch file, insert changes lists, etc.
581 The patch file is processed in place, and overwritten.
582
583 A backup file is put into backup_dir (if not None).
584
585 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600586 backup_dir (str): Path to directory to use to backup the file
587 fname (str): Filename to patch file to process
588 series (Series): Series information about this patch set
589 cmt (Commit): Commit object for this patch file
590
Simon Glass0d24de92012-01-14 15:12:45 +0000591 Return:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600592 list: A list of errors, each str, or [] if all ok.
Simon Glass0d24de92012-01-14 15:12:45 +0000593 """
594 handle, tmpname = tempfile.mkstemp()
Simon Glass272cd852019-10-31 07:42:51 -0600595 outfd = os.fdopen(handle, 'w', encoding='utf-8')
596 infd = open(fname, 'r', encoding='utf-8')
Simon Glassdd147ed2020-10-29 21:46:20 -0600597 pst = PatchStream(series)
598 pst.commit = cmt
599 pst.process_stream(infd, outfd)
Simon Glass0d24de92012-01-14 15:12:45 +0000600 infd.close()
601 outfd.close()
602
603 # Create a backup file if required
604 if backup_dir:
605 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
606 shutil.move(tmpname, fname)
Simon Glassdd147ed2020-10-29 21:46:20 -0600607 return pst.warn
Simon Glass0d24de92012-01-14 15:12:45 +0000608
Simon Glassd93720e2020-10-29 21:46:19 -0600609def fix_patches(series, fnames):
Simon Glass0d24de92012-01-14 15:12:45 +0000610 """Fix up a list of patches identified by filenames
611
612 The patch files are processed in place, and overwritten.
613
614 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600615 series (Series): The Series object
616 fnames (:type: list of str): List of patch files to process
Simon Glass0d24de92012-01-14 15:12:45 +0000617 """
618 # Current workflow creates patches, so we shouldn't need a backup
619 backup_dir = None #tempfile.mkdtemp('clean-patch')
620 count = 0
621 for fname in fnames:
Simon Glassdd147ed2020-10-29 21:46:20 -0600622 cmt = series.commits[count]
623 cmt.patch = fname
624 cmt.count = count
625 result = fix_patch(backup_dir, fname, series, cmt)
Simon Glass0d24de92012-01-14 15:12:45 +0000626 if result:
Paul Burtona920a172016-09-27 16:03:50 +0100627 print('%d warnings for %s:' % (len(result), fname))
Simon Glass0d24de92012-01-14 15:12:45 +0000628 for warn in result:
Paul Burtona920a172016-09-27 16:03:50 +0100629 print('\t', warn)
Simon Glass0d24de92012-01-14 15:12:45 +0000630 print
631 count += 1
Paul Burtona920a172016-09-27 16:03:50 +0100632 print('Cleaned %d patches' % count)
Simon Glass0d24de92012-01-14 15:12:45 +0000633
Simon Glassd93720e2020-10-29 21:46:19 -0600634def insert_cover_letter(fname, series, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000635 """Inserts a cover letter with the required info into patch 0
636
637 Args:
Simon Glass1cb1c0f2020-10-29 21:46:22 -0600638 fname (str): Input / output filename of the cover letter file
639 series (Series): Series object
640 count (int): Number of patches in the series
Simon Glass0d24de92012-01-14 15:12:45 +0000641 """
Simon Glassdd147ed2020-10-29 21:46:20 -0600642 fil = open(fname, 'r')
643 lines = fil.readlines()
644 fil.close()
Simon Glass0d24de92012-01-14 15:12:45 +0000645
Simon Glassdd147ed2020-10-29 21:46:20 -0600646 fil = open(fname, 'w')
Simon Glass0d24de92012-01-14 15:12:45 +0000647 text = series.cover
648 prefix = series.GetPatchPrefix()
649 for line in lines:
650 if line.startswith('Subject:'):
Wu, Josh35ce2dc2015-04-03 10:51:17 +0800651 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
652 zero_repeat = int(math.log10(count)) + 1
653 zero = '0' * zero_repeat
654 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass0d24de92012-01-14 15:12:45 +0000655
656 # Insert our cover letter
657 elif line.startswith('*** BLURB HERE ***'):
658 # First the blurb test
659 line = '\n'.join(text[1:]) + '\n'
660 if series.get('notes'):
661 line += '\n'.join(series.notes) + '\n'
662
663 # Now the change list
664 out = series.MakeChangeLog(None)
665 line += '\n' + '\n'.join(out)
Simon Glassdd147ed2020-10-29 21:46:20 -0600666 fil.write(line)
667 fil.close()