blob: c5402dd896a65a632405a7ed6ee46961c89a8626 [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:
93 line: Source line containing tag (useful for debug/error messages)
94 name: Tag name (part after 'Series-')
95 value: Tag value (part after 'Series-xxx: ')
96 """
97 if name == 'notes':
98 self.in_section = name
99 self.skip_blank = False
100 if self.is_log:
101 self.series.AddTag(self.commit, line, name, value)
102
Simon Glasse3a816b2020-10-29 21:46:21 -0600103 def _add_to_commit(self, name):
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100104 """Add a new Commit-xxx tag.
105
106 When a Commit-xxx tag is detected, we come here to record it.
107
108 Args:
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100109 name: Tag name (part after 'Commit-')
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100110 """
111 if name == 'notes':
112 self.in_section = 'commit-' + name
113 self.skip_blank = False
114
Simon Glassd93720e2020-10-29 21:46:19 -0600115 def _add_commit_rtag(self, rtag_type, who):
Simon Glass7207e2b2020-07-05 21:41:57 -0600116 """Add a response tag to the current commit
117
118 Args:
119 key: rtag type (e.g. 'Reviewed-by')
120 who: Person who gave that rtag, e.g. 'Fred Bloggs <fred@bloggs.org>'
121 """
122 self.commit.AddRtag(rtag_type, who)
123
Simon Glassd93720e2020-10-29 21:46:19 -0600124 def _close_commit(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000125 """Save the current commit into our commit list, and reset our state"""
126 if self.commit and self.is_log:
127 self.series.AddCommit(self.commit)
128 self.commit = None
Bin Meng0d577182016-06-26 23:24:30 -0700129 # If 'END' is missing in a 'Cover-letter' section, and that section
130 # happens to show up at the very end of the commit message, this is
131 # the chance for us to fix it up.
132 if self.in_section == 'cover' and self.is_log:
133 self.series.cover = self.section
134 self.in_section = None
135 self.skip_blank = True
136 self.section = []
Simon Glass0d24de92012-01-14 15:12:45 +0000137
Simon Glassd93720e2020-10-29 21:46:19 -0600138 def _parse_version(self, value, line):
Sean Anderson6949f702020-05-04 16:28:34 -0400139 """Parse a version from a *-changes tag
140
141 Args:
142 value: Tag value (part after 'xxx-changes: '
143 line: Source line containing tag
144
145 Returns:
146 The version as an integer
147 """
148 try:
149 return int(value)
Simon Glassdd147ed2020-10-29 21:46:20 -0600150 except ValueError:
Sean Anderson6949f702020-05-04 16:28:34 -0400151 raise ValueError("%s: Cannot decode version info '%s'" %
Simon Glassd06e55a2020-10-29 21:46:17 -0600152 (self.commit.hash, line))
Sean Anderson6949f702020-05-04 16:28:34 -0400153
Simon Glassd93720e2020-10-29 21:46:19 -0600154 def _finalise_change(self):
155 """_finalise a (multi-line) change and add it to the series or commit"""
Sean Anderson0411fff2020-05-04 16:28:35 -0400156 if not self.change_lines:
157 return
158 change = '\n'.join(self.change_lines)
159
160 if self.in_change == 'Series':
161 self.series.AddChange(self.change_version, self.commit, change)
162 elif self.in_change == 'Cover':
163 self.series.AddChange(self.change_version, None, change)
164 elif self.in_change == 'Commit':
165 self.commit.AddChange(self.change_version, change)
166 self.change_lines = []
167
Simon Glassd93720e2020-10-29 21:46:19 -0600168 def process_line(self, line):
Simon Glass0d24de92012-01-14 15:12:45 +0000169 """Process a single line of a patch file or commit log
170
171 This process a line and returns a list of lines to output. The list
172 may be empty or may contain multiple output lines.
173
174 This is where all the complicated logic is located. The class's
175 state is used to move between different states and detect things
176 properly.
177
178 We can be in one of two modes:
179 self.is_log == True: This is 'git log' mode, where most output is
180 indented by 4 characters and we are scanning for tags
181
182 self.is_log == False: This is 'patch' mode, where we already have
183 all the tags, and are processing patches to remove junk we
184 don't want, and add things we think are required.
185
186 Args:
187 line: text line to process
188
189 Returns:
190 list of output lines, or [] if nothing should be output
191 """
192 # Initially we have no output. Prepare the input line string
193 out = []
194 line = line.rstrip('\n')
Scott Wood4b89b812014-09-25 14:30:46 -0500195
Simon Glass57699042020-10-29 21:46:18 -0600196 commit_match = RE_COMMIT.match(line) if self.is_log else None
Scott Wood4b89b812014-09-25 14:30:46 -0500197
Simon Glass0d24de92012-01-14 15:12:45 +0000198 if self.is_log:
199 if line[:4] == ' ':
200 line = line[4:]
201
202 # Handle state transition and skipping blank lines
Simon Glass57699042020-10-29 21:46:18 -0600203 series_tag_match = RE_SERIES_TAG.match(line)
204 change_id_match = RE_CHANGE_ID.match(line)
205 commit_tag_match = RE_COMMIT_TAG.match(line)
206 cover_match = RE_COVER.match(line)
207 signoff_match = RE_SIGNOFF.match(line)
208 leading_whitespace_match = RE_LEADING_WHITESPACE.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000209 tag_match = None
210 if self.state == STATE_PATCH_HEADER:
Simon Glass57699042020-10-29 21:46:18 -0600211 tag_match = RE_TAG.match(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000212 is_blank = not line.strip()
213 if is_blank:
214 if (self.state == STATE_MSG_HEADER
215 or self.state == STATE_PATCH_SUBJECT):
216 self.state += 1
217
218 # We don't have a subject in the text stream of patch files
219 # It has its own line with a Subject: tag
220 if not self.is_log and self.state == STATE_PATCH_SUBJECT:
221 self.state += 1
222 elif commit_match:
223 self.state = STATE_MSG_HEADER
224
Bin Meng94fbd3e2016-06-26 23:24:32 -0700225 # If a tag is detected, or a new commit starts
Douglas Anderson833e4192019-09-27 09:23:56 -0700226 if series_tag_match or commit_tag_match or change_id_match or \
Sean Anderson6949f702020-05-04 16:28:34 -0400227 cover_match or signoff_match or self.state == STATE_MSG_HEADER:
Bin Meng57b6b192016-06-26 23:24:31 -0700228 # but we are already in a section, this means 'END' is missing
229 # for that section, fix it up.
Bin Meng13b98d92016-06-26 23:24:29 -0700230 if self.in_section:
231 self.warn.append("Missing 'END' in section '%s'" % self.in_section)
232 if self.in_section == 'cover':
233 self.series.cover = self.section
234 elif self.in_section == 'notes':
235 if self.is_log:
236 self.series.notes += self.section
237 elif self.in_section == 'commit-notes':
238 if self.is_log:
239 self.commit.notes += self.section
240 else:
241 self.warn.append("Unknown section '%s'" % self.in_section)
242 self.in_section = None
243 self.skip_blank = True
244 self.section = []
Bin Meng57b6b192016-06-26 23:24:31 -0700245 # but we are already in a change list, that means a blank line
246 # is missing, fix it up.
247 if self.in_change:
Sean Anderson6949f702020-05-04 16:28:34 -0400248 self.warn.append("Missing 'blank line' in section '%s-changes'" % self.in_change)
Simon Glassd93720e2020-10-29 21:46:19 -0600249 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400250 self.in_change = None
251 self.change_version = 0
Bin Meng13b98d92016-06-26 23:24:29 -0700252
Simon Glass0d24de92012-01-14 15:12:45 +0000253 # If we are in a section, keep collecting lines until we see END
254 if self.in_section:
255 if line == 'END':
256 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
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100261 elif self.in_section == 'commit-notes':
262 if self.is_log:
263 self.commit.notes += self.section
Simon Glass0d24de92012-01-14 15:12:45 +0000264 else:
265 self.warn.append("Unknown section '%s'" % self.in_section)
266 self.in_section = None
267 self.skip_blank = True
268 self.section = []
269 else:
270 self.section.append(line)
271
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200272 # If we are not in a section, it is an unexpected END
273 elif line == 'END':
Simon Glassd06e55a2020-10-29 21:46:17 -0600274 raise ValueError("'END' wihout section")
Patrick Delaunay7058dd02020-07-02 19:08:24 +0200275
Simon Glass0d24de92012-01-14 15:12:45 +0000276 # Detect the commit subject
277 elif not is_blank and self.state == STATE_PATCH_SUBJECT:
278 self.commit.subject = line
279
280 # Detect the tags we want to remove, and skip blank lines
Simon Glass57699042020-10-29 21:46:18 -0600281 elif RE_REMOVE.match(line) and not commit_tag_match:
Simon Glass0d24de92012-01-14 15:12:45 +0000282 self.skip_blank = True
283
284 # TEST= should be the last thing in the commit, so remove
285 # everything after it
286 if line.startswith('TEST='):
287 self.found_test = True
288 elif self.skip_blank and is_blank:
289 self.skip_blank = False
290
Sean Anderson6949f702020-05-04 16:28:34 -0400291 # Detect Cover-xxx tags
Bin Menge7df2182016-06-26 23:24:28 -0700292 elif cover_match:
Sean Anderson6949f702020-05-04 16:28:34 -0400293 name = cover_match.group(1)
294 value = cover_match.group(2)
295 if name == 'letter':
296 self.in_section = 'cover'
297 self.skip_blank = False
298 elif name == 'letter-cc':
Simon Glassd93720e2020-10-29 21:46:19 -0600299 self._add_to_series(line, 'cover-cc', value)
Sean Anderson6949f702020-05-04 16:28:34 -0400300 elif name == 'changes':
301 self.in_change = 'Cover'
Simon Glassd93720e2020-10-29 21:46:19 -0600302 self.change_version = self._parse_version(value, line)
Simon Glassfe2f8d92013-03-20 16:43:00 +0000303
Simon Glass0d24de92012-01-14 15:12:45 +0000304 # If we are in a change list, key collected lines until a blank one
305 elif self.in_change:
306 if is_blank:
307 # Blank line ends this change list
Simon Glassd93720e2020-10-29 21:46:19 -0600308 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400309 self.in_change = None
310 self.change_version = 0
Simon Glass102061b2014-04-20 10:50:14 -0600311 elif line == '---':
Simon Glassd93720e2020-10-29 21:46:19 -0600312 self._finalise_change()
Sean Anderson6949f702020-05-04 16:28:34 -0400313 self.in_change = None
314 self.change_version = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600315 out = self.process_line(line)
Sean Anderson0411fff2020-05-04 16:28:35 -0400316 elif self.is_log:
317 if not leading_whitespace_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600318 self._finalise_change()
Sean Anderson0411fff2020-05-04 16:28:35 -0400319 self.change_lines.append(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000320 self.skip_blank = False
321
322 # Detect Series-xxx tags
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100323 elif series_tag_match:
324 name = series_tag_match.group(1)
325 value = series_tag_match.group(2)
Simon Glass0d24de92012-01-14 15:12:45 +0000326 if name == 'changes':
327 # value is the version number: e.g. 1, or 2
Sean Anderson6949f702020-05-04 16:28:34 -0400328 self.in_change = 'Series'
Simon Glassd93720e2020-10-29 21:46:19 -0600329 self.change_version = self._parse_version(value, line)
Simon Glass0d24de92012-01-14 15:12:45 +0000330 else:
Simon Glassd93720e2020-10-29 21:46:19 -0600331 self._add_to_series(line, name, value)
Simon Glass0d24de92012-01-14 15:12:45 +0000332 self.skip_blank = True
333
Douglas Anderson833e4192019-09-27 09:23:56 -0700334 # Detect Change-Id tags
335 elif change_id_match:
336 value = change_id_match.group(1)
337 if self.is_log:
338 if self.commit.change_id:
Simon Glassd06e55a2020-10-29 21:46:17 -0600339 raise ValueError(
340 "%s: Two Change-Ids: '%s' vs. '%s'" % self.commit.hash,
341 self.commit.change_id, value)
Douglas Anderson833e4192019-09-27 09:23:56 -0700342 self.commit.change_id = value
343 self.skip_blank = True
344
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100345 # Detect Commit-xxx tags
346 elif commit_tag_match:
347 name = commit_tag_match.group(1)
348 value = commit_tag_match.group(2)
349 if name == 'notes':
Simon Glasse3a816b2020-10-29 21:46:21 -0600350 self._add_to_commit(name)
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100351 self.skip_blank = True
Sean Anderson6949f702020-05-04 16:28:34 -0400352 elif name == 'changes':
353 self.in_change = 'Commit'
Simon Glassd93720e2020-10-29 21:46:19 -0600354 self.change_version = self._parse_version(value, line)
Patrick Delaunaye5ff9ab2020-07-02 19:52:54 +0200355 else:
356 self.warn.append('Line %d: Ignoring Commit-%s' %
Simon Glassd06e55a2020-10-29 21:46:17 -0600357 (self.linenum, name))
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100358
Simon Glass0d24de92012-01-14 15:12:45 +0000359 # Detect the start of a new commit
360 elif commit_match:
Simon Glassd93720e2020-10-29 21:46:19 -0600361 self._close_commit()
Simon Glass0b5b4092014-10-15 02:27:00 -0600362 self.commit = commit.Commit(commit_match.group(1))
Simon Glass0d24de92012-01-14 15:12:45 +0000363
364 # Detect tags in the commit message
365 elif tag_match:
Simon Glass7207e2b2020-07-05 21:41:57 -0600366 rtag_type, who = tag_match.groups()
Simon Glassd93720e2020-10-29 21:46:19 -0600367 self._add_commit_rtag(rtag_type, who)
Simon Glass0d24de92012-01-14 15:12:45 +0000368 # Remove Tested-by self, since few will take much notice
Simon Glass7207e2b2020-07-05 21:41:57 -0600369 if (rtag_type == 'Tested-by' and
370 who.find(os.getenv('USER') + '@') != -1):
Simon Glass0d24de92012-01-14 15:12:45 +0000371 self.warn.append("Ignoring %s" % line)
Simon Glass7207e2b2020-07-05 21:41:57 -0600372 elif rtag_type == 'Patch-cc':
373 self.commit.AddCc(who.split(','))
Simon Glass0d24de92012-01-14 15:12:45 +0000374 else:
Simon Glassd0c57192014-08-28 09:43:38 -0600375 out = [line]
Simon Glass0d24de92012-01-14 15:12:45 +0000376
Simon Glass102061b2014-04-20 10:50:14 -0600377 # Suppress duplicate signoffs
378 elif signoff_match:
Simon Glasse752edc2014-08-28 09:43:35 -0600379 if (self.is_log or not self.commit or
Simon Glassd06e55a2020-10-29 21:46:17 -0600380 self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
Simon Glass102061b2014-04-20 10:50:14 -0600381 out = [line]
382
Simon Glass0d24de92012-01-14 15:12:45 +0000383 # Well that means this is an ordinary line
384 else:
Simon Glass0d24de92012-01-14 15:12:45 +0000385 # Look for space before tab
Simon Glassdd147ed2020-10-29 21:46:20 -0600386 mat = RE_SPACE_BEFORE_TAB.match(line)
387 if mat:
Simon Glass0d24de92012-01-14 15:12:45 +0000388 self.warn.append('Line %d/%d has space before tab' %
Simon Glassdd147ed2020-10-29 21:46:20 -0600389 (self.linenum, mat.start()))
Simon Glass0d24de92012-01-14 15:12:45 +0000390
391 # OK, we have a valid non-blank line
392 out = [line]
393 self.linenum += 1
394 self.skip_blank = False
395 if self.state == STATE_DIFFS:
396 pass
397
398 # If this is the start of the diffs section, emit our tags and
399 # change log
400 elif line == '---':
401 self.state = STATE_DIFFS
402
Sean Anderson6949f702020-05-04 16:28:34 -0400403 # Output the tags (signoff first), then change list
Simon Glass0d24de92012-01-14 15:12:45 +0000404 out = []
Simon Glass0d24de92012-01-14 15:12:45 +0000405 log = self.series.MakeChangeLog(self.commit)
Simon Glasse752edc2014-08-28 09:43:35 -0600406 out += [line]
407 if self.commit:
408 out += self.commit.notes
409 out += [''] + log
Simon Glass0d24de92012-01-14 15:12:45 +0000410 elif self.found_test:
Simon Glass57699042020-10-29 21:46:18 -0600411 if not RE_ALLOWED_AFTER_TEST.match(line):
Simon Glass0d24de92012-01-14 15:12:45 +0000412 self.lines_after_test += 1
413
414 return out
415
Simon Glassd93720e2020-10-29 21:46:19 -0600416 def finalise(self):
Simon Glass0d24de92012-01-14 15:12:45 +0000417 """Close out processing of this patch stream"""
Simon Glassd93720e2020-10-29 21:46:19 -0600418 self._finalise_change()
419 self._close_commit()
Simon Glass0d24de92012-01-14 15:12:45 +0000420 if self.lines_after_test:
421 self.warn.append('Found %d lines after TEST=' %
Simon Glassd06e55a2020-10-29 21:46:17 -0600422 self.lines_after_test)
Simon Glass0d24de92012-01-14 15:12:45 +0000423
Simon Glassd93720e2020-10-29 21:46:19 -0600424 def _write_message_id(self, outfd):
Douglas Anderson833e4192019-09-27 09:23:56 -0700425 """Write the Message-Id into the output.
426
427 This is based on the Change-Id in the original patch, the version,
428 and the prefix.
429
430 Args:
431 outfd: Output stream file object
432 """
433 if not self.commit.change_id:
434 return
435
436 # If the count is -1 we're testing, so use a fixed time
437 if self.commit.count == -1:
438 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
439 else:
440 time_now = datetime.datetime.now()
441
442 # In theory there is email.utils.make_msgid() which would be nice
443 # to use, but it already produces something way too long and thus
444 # will produce ugly commit lines if someone throws this into
445 # a "Link:" tag in the final commit. So (sigh) roll our own.
446
447 # Start with the time; presumably we wouldn't send the same series
448 # with the same Change-Id at the exact same second.
449 parts = [time_now.strftime("%Y%m%d%H%M%S")]
450
451 # These seem like they would be nice to include.
452 if 'prefix' in self.series:
453 parts.append(self.series['prefix'])
454 if 'version' in self.series:
455 parts.append("v%s" % self.series['version'])
456
457 parts.append(str(self.commit.count + 1))
458
459 # The Change-Id must be last, right before the @
460 parts.append(self.commit.change_id)
461
462 # Join parts together with "." and write it out.
463 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
464
Simon Glassd93720e2020-10-29 21:46:19 -0600465 def process_stream(self, infd, outfd):
Simon Glass0d24de92012-01-14 15:12:45 +0000466 """Copy a stream from infd to outfd, filtering out unwanting things.
467
468 This is used to process patch files one at a time.
469
470 Args:
471 infd: Input stream file object
472 outfd: Output stream file object
473 """
474 # Extract the filename from each diff, for nice warnings
475 fname = None
476 last_fname = None
477 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson833e4192019-09-27 09:23:56 -0700478
Simon Glassd93720e2020-10-29 21:46:19 -0600479 self._write_message_id(outfd)
Douglas Anderson833e4192019-09-27 09:23:56 -0700480
Simon Glass0d24de92012-01-14 15:12:45 +0000481 while True:
482 line = infd.readline()
483 if not line:
484 break
Simon Glassd93720e2020-10-29 21:46:19 -0600485 out = self.process_line(line)
Simon Glass0d24de92012-01-14 15:12:45 +0000486
487 # Try to detect blank lines at EOF
488 for line in out:
489 match = re_fname.match(line)
490 if match:
491 last_fname = fname
492 fname = match.group(1)
493 if line == '+':
494 self.blank_count += 1
495 else:
496 if self.blank_count and (line == '-- ' or match):
497 self.warn.append("Found possible blank line(s) at "
Simon Glassd06e55a2020-10-29 21:46:17 -0600498 "end of file '%s'" % last_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000499 outfd.write('+\n' * self.blank_count)
500 outfd.write(line + '\n')
501 self.blank_count = 0
Simon Glassd93720e2020-10-29 21:46:19 -0600502 self.finalise()
Simon Glass0d24de92012-01-14 15:12:45 +0000503
504
Simon Glassd93720e2020-10-29 21:46:19 -0600505def get_metadata_for_list(commit_range, git_dir=None, count=None,
506 series=None, allow_overwrite=False):
Simon Glasse62f9052012-12-15 10:42:06 +0000507 """Reads out patch series metadata from the commits
508
509 This does a 'git log' on the relevant commits and pulls out the tags we
510 are interested in.
511
512 Args:
513 commit_range: Range of commits to count (e.g. 'HEAD..base')
514 git_dir: Path to git repositiory (None to use default)
515 count: Number of commits to list, or None for no limit
516 series: Series object to add information into. By default a new series
517 is started.
Simon Glass950a2312014-09-05 19:00:23 -0600518 allow_overwrite: Allow tags to overwrite an existing tag
Simon Glasse62f9052012-12-15 10:42:06 +0000519 Returns:
520 A Series object containing information about the commits.
521 """
Simon Glass891b7a02014-09-05 19:00:19 -0600522 if not series:
523 series = Series()
Simon Glass950a2312014-09-05 19:00:23 -0600524 series.allow_overwrite = allow_overwrite
Simon Glass9ad96982016-03-06 19:45:33 -0700525 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
Simon Glasscda2a612014-08-09 15:33:10 -0600526 git_dir=git_dir)
527 stdout = command.RunPipe([params], capture=True).stdout
Simon Glassdd147ed2020-10-29 21:46:20 -0600528 pst = PatchStream(series, is_log=True)
Simon Glasse62f9052012-12-15 10:42:06 +0000529 for line in stdout.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600530 pst.process_line(line)
531 pst.finalise()
Simon Glasse62f9052012-12-15 10:42:06 +0000532 return series
533
Simon Glassd93720e2020-10-29 21:46:19 -0600534def get_metadata(branch, start, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000535 """Reads out patch series metadata from the commits
536
537 This does a 'git log' on the relevant commits and pulls out the tags we
538 are interested in.
539
540 Args:
Simon Glass262130f2020-07-05 21:41:51 -0600541 branch: Branch to use (None for current branch)
542 start: Commit to start from: 0=branch HEAD, 1=next one, etc.
Simon Glass0d24de92012-01-14 15:12:45 +0000543 count: Number of commits to list
544 """
Simon Glassd93720e2020-10-29 21:46:19 -0600545 return get_metadata_for_list(
546 '%s~%d' % (branch if branch else 'HEAD', start), None, count)
Simon Glass0d24de92012-01-14 15:12:45 +0000547
Simon Glassd93720e2020-10-29 21:46:19 -0600548def get_metadata_for_test(text):
Simon Glass6e87ae12017-05-29 15:31:31 -0600549 """Process metadata from a file containing a git log. Used for tests
550
551 Args:
552 text:
553 """
554 series = Series()
Simon Glassdd147ed2020-10-29 21:46:20 -0600555 pst = PatchStream(series, is_log=True)
Simon Glass6e87ae12017-05-29 15:31:31 -0600556 for line in text.splitlines():
Simon Glassdd147ed2020-10-29 21:46:20 -0600557 pst.process_line(line)
558 pst.finalise()
Simon Glass6e87ae12017-05-29 15:31:31 -0600559 return series
560
Simon Glassdd147ed2020-10-29 21:46:20 -0600561def fix_patch(backup_dir, fname, series, cmt):
Simon Glass0d24de92012-01-14 15:12:45 +0000562 """Fix up a patch file, by adding/removing as required.
563
564 We remove our tags from the patch file, insert changes lists, etc.
565 The patch file is processed in place, and overwritten.
566
567 A backup file is put into backup_dir (if not None).
568
569 Args:
570 fname: Filename to patch file to process
571 series: Series information about this patch set
Simon Glassdd147ed2020-10-29 21:46:20 -0600572 cmt: Commit object for this patch file
Simon Glass0d24de92012-01-14 15:12:45 +0000573 Return:
574 A list of errors, or [] if all ok.
575 """
576 handle, tmpname = tempfile.mkstemp()
Simon Glass272cd852019-10-31 07:42:51 -0600577 outfd = os.fdopen(handle, 'w', encoding='utf-8')
578 infd = open(fname, 'r', encoding='utf-8')
Simon Glassdd147ed2020-10-29 21:46:20 -0600579 pst = PatchStream(series)
580 pst.commit = cmt
581 pst.process_stream(infd, outfd)
Simon Glass0d24de92012-01-14 15:12:45 +0000582 infd.close()
583 outfd.close()
584
585 # Create a backup file if required
586 if backup_dir:
587 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
588 shutil.move(tmpname, fname)
Simon Glassdd147ed2020-10-29 21:46:20 -0600589 return pst.warn
Simon Glass0d24de92012-01-14 15:12:45 +0000590
Simon Glassd93720e2020-10-29 21:46:19 -0600591def fix_patches(series, fnames):
Simon Glass0d24de92012-01-14 15:12:45 +0000592 """Fix up a list of patches identified by filenames
593
594 The patch files are processed in place, and overwritten.
595
596 Args:
597 series: The series object
598 fnames: List of patch files to process
599 """
600 # Current workflow creates patches, so we shouldn't need a backup
601 backup_dir = None #tempfile.mkdtemp('clean-patch')
602 count = 0
603 for fname in fnames:
Simon Glassdd147ed2020-10-29 21:46:20 -0600604 cmt = series.commits[count]
605 cmt.patch = fname
606 cmt.count = count
607 result = fix_patch(backup_dir, fname, series, cmt)
Simon Glass0d24de92012-01-14 15:12:45 +0000608 if result:
Paul Burtona920a172016-09-27 16:03:50 +0100609 print('%d warnings for %s:' % (len(result), fname))
Simon Glass0d24de92012-01-14 15:12:45 +0000610 for warn in result:
Paul Burtona920a172016-09-27 16:03:50 +0100611 print('\t', warn)
Simon Glass0d24de92012-01-14 15:12:45 +0000612 print
613 count += 1
Paul Burtona920a172016-09-27 16:03:50 +0100614 print('Cleaned %d patches' % count)
Simon Glass0d24de92012-01-14 15:12:45 +0000615
Simon Glassd93720e2020-10-29 21:46:19 -0600616def insert_cover_letter(fname, series, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000617 """Inserts a cover letter with the required info into patch 0
618
619 Args:
620 fname: Input / output filename of the cover letter file
621 series: Series object
622 count: Number of patches in the series
623 """
Simon Glassdd147ed2020-10-29 21:46:20 -0600624 fil = open(fname, 'r')
625 lines = fil.readlines()
626 fil.close()
Simon Glass0d24de92012-01-14 15:12:45 +0000627
Simon Glassdd147ed2020-10-29 21:46:20 -0600628 fil = open(fname, 'w')
Simon Glass0d24de92012-01-14 15:12:45 +0000629 text = series.cover
630 prefix = series.GetPatchPrefix()
631 for line in lines:
632 if line.startswith('Subject:'):
Wu, Josh35ce2dc2015-04-03 10:51:17 +0800633 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
634 zero_repeat = int(math.log10(count)) + 1
635 zero = '0' * zero_repeat
636 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass0d24de92012-01-14 15:12:45 +0000637
638 # Insert our cover letter
639 elif line.startswith('*** BLURB HERE ***'):
640 # First the blurb test
641 line = '\n'.join(text[1:]) + '\n'
642 if series.get('notes'):
643 line += '\n'.join(series.notes) + '\n'
644
645 # Now the change list
646 out = series.MakeChangeLog(None)
647 line += '\n' + '\n'.join(out)
Simon Glassdd147ed2020-10-29 21:46:20 -0600648 fil.write(line)
649 fil.close()